Page MenuHome GnuPG

No OneTemporary

This file is larger than 256 KB, so syntax highlighting was skipped.
This document is not UTF8. It was detected as ISO-8859-1 (Latin 1) and converted to UTF8 for display.
diff --git a/NEWS b/NEWS
index cf096b66e..5eab68ef0 100644
--- a/NEWS
+++ b/NEWS
@@ -1,4016 +1,4199 @@
Noteworthy changes in version 2.3.0 (unreleased)
------------------------------------------------
+ Changes also found in 2.2.16:
+
+ * gpg,gpgsm: Fix deadlock on Windows due to a keybox sharing
+ violation. [#4505]
+
+ * gpg: Allow deletion of subkeys with --delete-key. This finally
+ makes the bang-suffix work as expected for that command. [#4457]
+
+ * gpg: Replace SHA-1 by SHA-256 in self-signatures when updating
+ them with --quick-set-expire or --quick-set-primary-uid. [#4508]
+
+ * gpg: Improve the photo image viewer selection. [#4334]
+
+ * gpg: Fix decryption with --use-embedded-filename. [#4500]
+
+ * gpg: Remove hints on using the --keyserver option. [#4512]
+
+ * gpg: Fix export of certain secret keys with comments. [#4490]
+
+ * gpg: Reject too long user-ids in --quick-gen-key. [#4532]
+
+ * gpg: Fix a double free in the best key selection code. [#4462]
+
+ * gpg: Fix the key generation dialog for switching back from EdDSA
+ to ECDSA.
+
+ * gpg: Use AES-192 with SHA-384 to comply with RFC-6637.
+
+ * gpg: Use only the addrspec from the Signer's UID subpacket to
+ mitigate a problem with another implementation.
+
+ * gpg: Skip invalid packets during a keyring listing and sync
+ diagnostics with the output.
+
+ * gpgsm: Avoid confusing diagnostic when signing with the default
+ key. [#4535]
+
+ * agent: Do not delete any secret key in --dry-run mode.
+
+ * agent: Fix failures on 64 bit big-endian boxes related to URIs in
+ a keyfile. [#4501]
+
+ * agent: Stop scdaemon after a reload with disable-scdaemon newly
+ configured. [#4326]
+
+ * dirmngr: Improve caching algorithm for WKD domains.
+
+ * dirmngr: Support other hash algorithms than SHA-1 for OCSP. [#3966]
+
+ * gpgconf: Make --homedir work for --launch. [#4496]
+
+ * gpgconf: Before --launch check for a valid config file. [#4497]
+
+ * wkd: Do not import more than 5 keys from one WKD address.
+
+ * wkd: Accept keys which are stored in armored format in the
+ directory.
+
+ * The installer for Windows now comes with signed binaries.
+
+ Release-info: https://dev.gnupg.org/T4509
+ See-also: gnupg-announce/2019q2/000438.html
+
+ Changes also found in 2.2.15:
+
+ * sm: Fix --logger-fd and --status-fd on Windows for non-standard
+ file descriptors.
+
+ * sm: Allow decryption even if expired keys are configured. [#4431]
+
+ * agent: Change command KEYINFO to print ssh fingerprints with other
+ hash algos.
+
+ * dirmngr: Fix build problems on Solaris due to the use of reserved
+ symbol names. [#4420]
+
+ * wkd: New commands --print-wkd-hash and --print-wkd-url for
+ gpg-wks-client.
+
+ Release-info: https://dev.gnupg.org/T4434
+ See-also: gnupg-announce/2019q1/000436.html
+
+ Changes also found in 2.2.14:
+
+ * gpg: Allow import of PGP desktop exported secret keys. Also avoid
+ importing secret keys if the secret keyblock is not valid. [#4392]
+
+ * gpg: Make invalid primary key algo obvious in key listings.
+
+ * sm: Do not mark a certificate in a key listing as de-vs compliant
+ if its use for a signature will not be possible.
+
+ * sm: Fix certificate creation with key on card.
+
+ * sm: Create rsa3072 bit certificates by default.
+
+ * sm: Print Yubikey attestation extensions with --dump-cert.
+
+ * agent: Fix cancellation handling for scdaemon.
+
+ * agent: Support --mode=ssh option for CLEAR_PASSPHRASE. [#4340]
+
+ * scd: Fix flushing of the CA-FPR DOs in app-openpgp.
+
+ * scd: Avoid a conflict error with the "undefined" app.
+
+ * dirmngr: Add CSRF protection exception for protonmail.
+
+ * dirmngr: Fix build problems with gcc 9 in libdns.
+
+ * gpgconf: New option --show-socket for use with --launch.
+
+ * gpgtar: Make option -C work for archive creation.
+
+ Release-info: https://dev.gnupg.org/T4412
+ See-also: gnupg-announce/2019q1/000435.html
+
+ Changes also found in 2.2.13:
+
+ * gpg: Implement key lookup via keygrip (using the & prefix).
+
+ * gpg: Allow generating Ed25519 key from existing key.
+
+ * gpg: Emit an ERROR status line if no key was found with -k.
+
+ * gpg: Stop early when trying to create a primary Elgamal key. [#4329]
+
+ * gpgsm: Print the card's key algorithms along with their keygrips
+ in interactive key generation.
+
+ * agent: Clear bogus pinentry cache in the error case. [#4348]
+
+ * scd: Support "acknowledge button" feature.
+
+ * scd: Fix for USB INTERRUPT transfer. [#4308]
+
+ * wks: Do no use compression for the the encrypted challenge and
+ response.
+
+ Release-info: https://dev.gnupg.org/T4290
+ See-also: gnupg-announce/2019q1/000434.html
+
+ Changes also found in 2.2.12:
+
+ * tools: New commands --install-key and --remove-key for
+ gpg-wks-client. This allows to prepare a Web Key Directory on a
+ local file system for later upload to a web server.
+
+ * gpg: New --list-option "show-only-fpr-mbox". This makes the use
+ of the new gpg-wks-client --install-key command easier on Windows.
+
+ * gpg: Improve processing speed when --skip-verify is used.
+
+ * gpg: Fix a bug where a LF was accidentally written to the console.
+
+ * gpg: --card-status now shows whether a card has the new KDF
+ feature enabled.
+
+ * agent: New runtime option --s2k-calibration=MSEC. New configure
+ option --with-agent-s2k-calibration=MSEC. [#3399]
+
+ * dirmngr: Try another keyserver from the pool on receiving a 502,
+ 503, or 504 error. [#4175]
+
+ * dirmngr: Avoid possible CSRF attacks via http redirects. A HTTP
+ query will not anymore follow a 3xx redirect unless the Location
+ header gives the same host. If the host is different only the
+ host and port is taken from the Location header and the original
+ path and query parts are kept.
+
+ * dirmngr: New command FLUSHCRL to flush all CRLS from disk and
+ memory. [#3967]
+
+ * New simplified Chinese translation (zh_CN).
+
+ Release-info: https://dev.gnupg.org/T4289
+ See-also: gnupg-announce/2018q4/000433.html
+
Changes also found in 2.2.11:
* gpgsm: Fix CRL loading when intermediate certicates are not yet
trusted.
* gpgsm: Fix an error message about the digest algo. [#4219]
* gpg: Fix a wrong warning due to new sign usage check introduced
with 2.2.9. [#4014]
* gpg: Print the "data source" even for an unsuccessful keyserver
query.
* gpg: Do not store the TOFU trust model in the trustdb. This
allows to enable or disable a TOFO model without triggering a
trustdb rebuild. [#4134]
* scd: Fix cases of "Bad PIN" after using "forcesig". [#4177]
* agent: Fix possible hang in the ssh handler. [#4221]
* dirmngr: Tack the unmodified mail address to a WKD request. See
commit a2bd4a64e5b057f291a60a9499f881dd47745e2f for details.
* dirmngr: Tweak diagnostic about missing LDAP server file.
* dirmngr: In verbose mode print the OCSP responder id.
* dirmngr: Fix parsing of the LDAP port. [#4230]
* wks: Add option --directory/-C to the server. Always build the
server on Unix systems.
* wks: Add option --with-colons to the client. Support sites which
use the policy file instead of the submission-address file.
* Fix EBADF when gpg et al. are called by broken CGI scripts.
* Fix some minor memory leaks and bugs.
Release-info: https://dev.gnupg.org/T4233
See-also: gnupg-announce/2018q4/000432.html
Changes also found in 2.2.10:
* gpg: Refresh expired keys originating from the WKD. [#2917]
* gpg: Use a 256 KiB limit for a WKD imported key.
* gpg: New option --known-notation. [#4060]
* scd: Add support for the Trustica Cryptoucan reader.
* agent: Speed up starting during on-demand launching. [#3490]
* dirmngr: Validate SRV records in WKD queries.
Release-info: https://dev.gnupg.org/T4112
See-also: gnupg-announce/2018q3/000428.html
Changes also found in 2.2.9:
* dirmngr: Fix recursive resolver mode and other bugs in the libdns
code. [#3374,#3803,#3610]
* dirmngr: When using libgpg-error 1.32 or later a GnuPG build with
NTBTLS support (e.g. the standard Windows installer) does not
anymore block for dozens of seconds before returning data.
* gpg: Fix bug in --show-keys which actually imported revocation
certificates. [#4017]
* gpg: Ignore too long user-ID and comment packets. [#4022]
* gpg: Fix crash due to bad German translation. Improved printf
format compile time check.
* gpg: Handle missing ISSUER sub packet gracefully in the presence of
the new ISSUER_FPR. [#4046]
* gpg: Allow decryption using several passphrases in most cases.
[#3795,#4050]
* gpg: Command --show-keys now enables the list options
show-unusable-uids, show-unusable-subkeys, show-notations and
show-policy-urls by default.
* gpg: Command --show-keys now prints revocation certificates. [#4018]
* gpg: Add revocation reason to the "rev" and "rvs" records of the
option --with-colons. [#1173]
* gpg: Export option export-clean does now remove certain expired
subkeys; export-minimal removes all expired subkeys. [#3622]
* gpg: New "usage" property for the drop-subkey filters. [#4019]
Release-info: https://dev.gnupg.org/T4036
See-also: gnupg-announce/2018q3/000427.html
Changes also found in 2.2.8:
* gpg: Decryption of messages not using the MDC mode will now lead
to a hard failure even if a legacy cipher algorithm was used. The
option --ignore-mdc-error can be used to turn this failure into a
warning. Take care: Never use that option unconditionally or
without a prior warning.
* gpg: The MDC encryption mode is now always used regardless of the
cipher algorithm or any preferences. For testing --rfc2440 can be
used to create a message without an MDC.
* gpg: Sanitize the diagnostic output of the original file name in
verbose mode. [#4012,CVE-2018-12020]
* gpg: Detect suspicious multiple plaintext packets in a more
reliable way. [#4000]
* gpg: Fix the duplicate key signature detection code. [#3994]
* gpg: The options --no-mdc-warn, --force-mdc, --no-force-mdc,
--disable-mdc and --no-disable-mdc have no more effect.
* gpg: New command --show-keys.
* agent: Add DBUS_SESSION_BUS_ADDRESS and a few other envvars to the
list of startup environment variables. [#3947]
See-also: gnupg-announce/2018q2/000425.html
Changes also found in 2.2.7:
* gpg: New option --no-symkey-cache to disable the passphrase cache
for symmetrical en- and decryption.
* gpg: The ERRSIG status now prints the fingerprint if that is part
of the signature.
* gpg: Relax emitting of FAILURE status lines
* gpg: Add a status flag to "sig" lines printed with --list-sigs.
* gpg: Fix "Too many open files" when using --multifile. [#3951]
* ssh: Return an error for unknown ssh-agent flags. [#3880]
* dirmngr: Fix a regression since 2.1.16 which caused corrupted CRL
caches under Windows. [#2448,#3923]
* dirmngr: Fix a CNAME problem with pools and TLS. Also use a fixed
mapping of keys.gnupg.net to sks-keyservers.net. [#3755]
* dirmngr: Try resurrecting dead hosts earlier (from 3 to 1.5 hours).
* dirmngr: Fallback to CRL if no default OCSP responder is configured.
* dirmngr: Implement CRL fetching via https. Here a redirection to
http is explicitly allowed.
* dirmngr: Make LDAP searching and CRL fetching work under Windows.
This stopped working with 2.1. [#3937]
* agent,dirmngr: New sub-command "getenv" for "getinfo" to ease
debugging.
See-also: gnupg-announce/2018q2/000424.html
Changes also found in 2.2.6:
* gpg,gpgsm: New option --request-origin to pretend requests coming
from a browser or a remote site.
* gpg: Fix race condition on trustdb.gpg updates due to too early
released lock. [#3839]
* gpg: Emit FAILURE status lines in almost all cases. [#3872]
* gpg: Implement --dry-run for --passwd to make checking a key's
passphrase straightforward.
* gpg: Make sure to only accept a certification capable key for key
signatures. [#3844]
* gpg: Better user interaction in --card-edit for the factory-reset
sub-command.
* gpg: Improve changing key attributes in --card-edit by adding an
explicit "key-attr" sub-command. [#3781]
* gpg: Print the keygrips in the --card-status.
* scd: Support KDF DO setup. [#3823]
* scd: Fix some issues with PC/SC on Windows. [#3825]
* scd: Fix suspend/resume handling in the CCID driver.
* agent: Evict cached passphrases also via a timer. [#3829]
* agent: Use separate passphrase caches depending on the request
origin. [#3858]
* ssh: Support signature flags. [#3880]
* dirmngr: Handle failures related to missing IPv6 support
gracefully. [#3331]
* Fix corner cases related to specified home directory with
drive letter on Windows. [#3720]
* Allow the use of UNC directory names as homedir. [#3818]
See-also: gnupg-announce/2018q2/000421.html
Changes also found in 2.2.5:
* gpg: Allow the use of the "cv25519" and "ed25519" short names in
addition to the canonical curve names in --batch --gen-key.
* gpg: Make sure to print all secret keys with option --list-only
and --decrypt. [#3718]
* gpg: Fix the use of future-default with --quick-add-key for
signing keys. [#3747]
* gpg: Select a secret key by checking availability under gpg-agent.
[#1967]
* gpg: Fix reversed prompt texts for --only-sign-text-ids. [#3787]
* gpg,gpgsm: Fix detection of bogus keybox blobs on 32 bit systems.
[#3770]
* gpgsm: Fix regression since 2.1 in --export-secret-key-raw which
got $d mod (q-1)$ wrong. Note that most tools automatically fixup
that parameter anyway.
* ssh: Fix a regression in getting the client'd PID on *BSD and
macOS.
* scd: Support the KDF Data Object of the OpenPGP card 3.3. [#3152]
* scd: Fix a regression in the internal CCID driver for certain card
readers. [#3508]
* scd: Fix a problem on NetBSD killing scdaemon on gpg-agent
shutdown. [#3778]
* dirmngr: Improve returned error description on failure of DNS
resolving. [#3756]
* wks: Implement command --install-key for gpg-wks-server.
* Add option STATIC=1 to the Speedo build system to allow a build
with statically linked versions of the core GnuPG libraries. Also
use --enable-wks-tools by default by Speedo builds for Unix.
See-also: gnupg-announce/2018q1/000420.html
Changes also found in 2.2.4:
* gpg: Change default preferences to prefer SHA512.
* gpg: Print a warning when more than 150 MiB are encrypted using a
cipher with 64 bit block size.
* gpg: Print a warning if the MDC feature has not been used for a
message.
* gpg: Fix regular expression of domain addresses in trust
signatures. [#2923]
* agent: New option --auto-expand-secmem to help with high numbers
of concurrent connections. Requires libgcrypt 1.8.2 for having
an effect. [#3530]
* dirmngr: Cache responses of WKD queries.
* gpgconf: Add option --status-fd.
* wks: Add commands --check and --remove-key to gpg-wks-server.
* Increase the backlog parameter of the daemons to 64 and add
option --listen-backlog.
* New configure option --enable-run-gnupg-user-socket to first try a
socket directory which is not removed by systemd at session end.
See-also: gnupg-announce/2017q4/000419.html
Changes also found in 2.2.3:
* gpgsm: Fix initial keybox creation on Windows. [#3507]
* dirmngr: Fix crash in case of a CRL loading error. [#3510]
* Fix the name of the Windows registry key. [Git#4f5afaf1fd]
* gpgtar: Fix wrong behaviour of --set-filename. [#3500]
* gpg: Silence AKL retrieval messages. [#3504]
* agent: Use clock or clock_gettime for calibration. [#3056]
* agent: Improve robustness of the shutdown pending
state. [Git#7ffedfab89]
See-also: gnupg-announce/2017q4/000417.html
Changes also found in 2.2.2:
* gpg: Avoid duplicate key imports by concurrently running gpg
processes. [#3446]
* gpg: Fix creating on-disk subkey with on-card primary key. [#3280]
* gpg: Fix validity retrieval for multiple keyrings. [Debian#878812]
* gpg: Fix --dry-run and import option show-only for secret keys.
* gpg: Print "sec" or "sbb" for secret keys with import option
import-show. [#3431]
* gpg: Make import less verbose. [#3397]
* gpg: Add alias "Key-Grip" for parameter "Keygrip" and new
parameter "Subkey-Grip" to unattended key generation. [#3478]
* gpg: Improve "factory-reset" command for OpenPGP cards. [#3286]
* gpg: Ease switching Gnuk tokens into ECC mode by using the magic
keysize value 25519.
* gpgsm: Fix --with-colon listing in crt records for fields > 12.
* gpgsm: Do not expect X.509 keyids to be unique. [#1644]
* agent: Fix stucked Pinentry when using --max-passphrase-days. [#3190]
* agent: New option --s2k-count. [#3276 (workaround)]
* dirmngr: Do not follow https-to-http redirects. [#3436]
* dirmngr: Reduce default LDAP timeout from 100 to 15 seconds. [#3487]
* gpgconf: Ignore non-installed components for commands
--apply-profile and --apply-defaults. [#3313]
* Add configure option --enable-werror. [#2423]
See-also: gnupg-announce/2017q4/000416.html
Changes also found in 2.2.1:
* gpg: Fix formatting of the user id in batch mode key generation
if only "name-email" is given.
* gpgv: Fix annoying "not suitable for" warnings.
* wks: Convey only the newest user id to the provider. This is the
case if different names are used with the same addr-spec.
* wks: Create a complying user id for provider policy mailbox-only.
* wks: Add workaround for posteo.de.
* scd: Fix the use of large ECC keys with an OpenPGP card.
* dirmngr: Use system provided root certificates if no specific HKP
certificates are configured. If build with GNUTLS, this was
already the case.
See-also: gnupg-announce/2017q3/000415.html
Release dates of 2.2.x versions:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Version 2.2.1 (2017-09-19)
Version 2.2.2 (2017-11-07)
Version 2.2.3 (2017-11-20)
Version 2.2.4 (2017-12-20)
Version 2.2.5 (2018-02-22)
Version 2.2.6 (2018-04-09)
Version 2.2.7 (2018-05-02)
Version 2.2.8 (2018-06-08)
Version 2.2.9 (2018-07-12)
Version 2.2.10 (2018-08-30)
Version 2.2.11 (2018-11-06)
+ Version 2.2.12 (2018-12-14)
+ Version 2.2.13 (2019-02-12)
+ Version 2.2.14 (2019-03-19)
+ Version 2.2.15 (2019-03-26)
+ Version 2.2.16 (2019-05-28)
Noteworthy changes in version 2.2.0 (2017-08-28)
------------------------------------------------
This is the new long term stable branch. This branch will only see
bug fixes and no new features.
* gpg: Reverted change in 2.1.23 so that --no-auto-key-retrieve is
again the default.
* Fixed a few minor bugs.
See-also: gnupg-announce/2017q3/000413.html
Noteworthy changes in version 2.1.23 (2017-08-09)
-------------------------------------------------
* gpg: "gpg" is now installed as "gpg" and not anymore as "gpg2".
If needed, the new configure option --enable-gpg-is-gpg2 can be
used to revert this.
* gpg: Options --auto-key-retrieve and --auto-key-locate "local,wkd"
are now used by default. Note: this enables keyserver and Web Key
Directory operators to notice when a signature from a locally
non-available key is being verified for the first time or when
you intend to encrypt to a mail address without having the key
locally. This new behaviour will eventually make key discovery
much easier and mostly automatic. Disable this by adding
no-auto-key-retrieve
auto-key-locate local
to your gpg.conf.
* agent: Option --no-grab is now the default. The new option --grab
allows to revert this.
* gpg: New import option "show-only".
* gpg: New option --disable-dirmngr to entirely disable network
access for gpg.
* gpg,gpgsm: Tweaked DE-VS compliance behaviour.
* New configure flag --enable-all-tests to run more extensive tests
during "make check".
* gpgsm: The keygrip is now always printed in colon mode as
documented in the man page.
* Fixed connection timeout problem under Windows.
See-also: gnupg-announce/2017q3/000412.html
Noteworthy changes in version 2.1.22 (2017-07-28)
-------------------------------------------------
* gpg: Extend command --quick-set-expire to allow for setting the
expiration time of subkeys.
* gpg: By default try to repair keys during import. New sub-option
no-repair-keys for --import-options.
* gpg,gpgsm: Improved checking and reporting of DE-VS compliance.
* gpg: New options --key-origin and --with-key-origin. Store the
time of the last key update from keyservers, WKD, or DANE.
* agent: New option --ssh-fingerprint-digest.
* dimngr: Lower timeouts on keyserver connection attempts and made
it configurable.
* dirmngr: Tor will now automatically be detected and used. The
option --no-use-tor disables Tor detection.
* dirmngr: Now detects a changed /etc/resolv.conf.
* agent,dirmngr: Initiate shutdown on removal of the GnuPG home
directory.
* gpg: Avoid caching passphrase for failed symmetric encryption.
* agent: Support for unprotected ssh keys.
* dirmngr: Fixed name resolving on systems using only v6
nameservers.
* dirmngr: Allow the use of TLS over http proxies.
* w32: Change directory of the daemons after startup.
* wks: New man pages for client and server.
* Many other bug fixes.
See-also: gnupg-announce/2017q3/000411.html
Noteworthy changes in version 2.1.21 (2017-05-15)
-------------------------------------------------
* gpg,gpgsm: Fix corruption of old style keyring.gpg files. This
bug was introduced with version 2.1.20. Note that the default
pubring.kbx format was not affected.
* gpg,dirmngr: Removed the skeleton config file support. The
system's standard methods for providing default configuration
files should be used instead.
* w32: The Windows installer now allows installation of GnuPG
without Administrator permissions.
* gpg: Fixed import filter property match bug.
* scd: Removed Linux support for Cardman 4040 PCMCIA reader.
* scd: Fixed some corner case bugs in resume/suspend handling.
* Many minor bug fixes and code cleanup.
See-also: gnupg-announce/2017q2/000405.html
Noteworthy changes in version 2.1.20 (2017-04-03)
-------------------------------------------------
* gpg: New properties 'expired', 'revoked', and 'disabled' for the
import and export filters.
* gpg: New command --quick-set-primary-uid.
* gpg: New compliance field for the --with-colon key listing.
* gpg: Changed the key parser to generalize the processing of local
meta data packets.
* gpg: Fixed assertion failure in the TOFU trust model.
* gpg: Fixed exporting of zero length user ID packets.
* scd: Improved support for multiple readers.
* scd: Fixed timeout handling for key generation.
* agent: New option --enable-extended-key-format.
* dirmngr: Do not add a keyserver to a new dirmngr.conf. Dirmngr
uses a default keyserver.
* dimngr: Do not treat TLS warning alerts as severe error when
building with GNUTLS.
* dirmngr: Actually take /etc/hosts in account.
* wks: Fixed client problems on Windows. Published keys are now set
to world-readable.
* tests: Fixed creation of temporary directories.
* A socket directory for a non standard GNUGHOME is now created on
the fly under /run/user. Thus "gpgconf --create-socketdir" is now
optional. The use of "gpgconf --remove-socketdir" to clean up
obsolete socket directories is however recommended to avoid
cluttering /run/user with useless directories.
* Fixed build problems on some platforms.
See-also: gnupg-announce/2017q2/000404.html
Noteworthy changes in version 2.1.19 (2017-03-01)
-------------------------------------------------
* gpg: Print a warning if Tor mode is requested but the Tor daemon
is not running.
* gpg: New status code DECRYPTION_KEY to print the actual private
key used for decryption.
* gpgv: New options --log-file and --debug.
* gpg-agent: Revamp the prompts to ask for card PINs.
* scd: Support for multiple card readers.
* scd: Removed option --debug-disable-ticker. Ticker is used
only when it is required to watch removal of device/card.
* scd: Improved detection of card inserting and removal.
* dirmngr: New option --disable-ipv4.
* dirmngr: New option --no-use-tor to explicitly disable the use of
Tor.
* dirmngr: The option --allow-version-check is now required even if
the option --use-tor is also used.
* dirmngr: Handle a missing nsswitch.conf gracefully.
* dirmngr: Avoid PTR lookups for keyserver pools. The are only done
for the debug command "keyserver --hosttable".
* dirmngr: Rework the internal certificate cache to support classes
of certificates. Load system provided certificates on startup.
Add options --tls, --no-crl, and --systrust to the "VALIDATE"
command.
* dirmngr: Add support for the ntbtls library.
* wks: Create mails with a "WKS-Phase" header. Fix detection of
Draft-2 mode.
* The Windows installer is now build with limited TLS support.
* Many other bug fixes and new regression tests.
See-also: gnupg-announce/2017q1/000402.html
Noteworthy changes in version 2.1.18 (2017-01-23)
-------------------------------------------------
* gpg: Remove bogus subkey signature while cleaning a key (with
export-clean, import-clean, or --edit-key's sub-command clean)
* gpg: Allow freezing the clock with --faked-system-time.
* gpg: New --export-option flag "backup", new --import-option flag
"restore".
* gpg-agent: Fixed long delay due to a regression in the progress
callback code.
* scd: Lots of code cleanup and internal changes.
* scd: Improved the internal CCID driver.
* dirmngr: Fixed problem with the DNS glue code (removal of the
trailing dot in domain names).
* dirmngr: Make sure that Tor is actually enabled after changing the
conf file and sending SIGHUP or "gpgconf --reload dirmngr".
* dirmngr: Fixed Tor access to IPv6 addresses. Note that current
versions of Tor may require that the flag "IPv6Traffic" is used
with the option "SocksPort" in torrc to actually allow IPv6
traffic.
* dirmngr: Fixed HKP for literally given IPv6 addresses.
* dirmngr: Enabled reverse DNS lookups via Tor.
* dirmngr: Added experimental SRV record lookup for WKD.
See commit 88dc3af3d4ae1afe1d5e136bc4c38bc4e7d4cd10 for details.
* dirmngr: For HKP use "pgpkey-hkps" and "pgpkey-hkp" in SRV record
lookups. Avoid SRV record lookup when a port is explicitly
specified. This fixes a regression from the 1.4 and 2.0 behavior.
* dirmngr: Gracefully handle a missing /etc/nsswitch.conf. Ignore
negation terms (e.g. "[!UNAVAIL=return]" instead of bailing out.
* dirmngr: Better debug output for flags "dns" and "network".
* dirmngr: On reload mark all known HKP servers alive.
* gpgconf: Allow keyword "all" for --launch, --kill, and --reload.
* tools: gpg-wks-client now ignores a missing policy file on the
server.
* Avoid unnecessary ambiguity error message in the option parsing.
* Further improvements of the regression test suite.
* Fixed building with --disable-libdns configure option.
* Fixed a crash running the tests on 32 bit architectures.
* Fixed spurious failures on BSD system in the spawn functions.
This affected for example gpg-wks-client and gpgconf.
See-also: gnupg-announce/2017q1/000401.html
Noteworthy changes in version 2.1.17 (2016-12-20)
-------------------------------------------------
* gpg: By default new keys expire after 2 years.
* gpg: New command --quick-set-expire to conveniently change the
expiration date of keys.
* gpg: Option and command names have been changed for easier
comprehension. The old names are still available as aliases.
* gpg: Improved the TOFU trust model.
* gpg: New option --default-new-key-algo.
* scd: Support OpenPGP card V3 for RSA.
* dirmngr: Support for the ADNS library has been removed. Instead
William Ahern's Libdns is now source included and used on all
platforms. This enables Tor support on all platforms. The new
option --standard-resolver can be used to disable this code at
runtime. In case of build problems the new configure option
--disable-libdns can be used to build without Libdns.
* dirmngr: Lazily launch ldap reaper thread.
* tools: New options --check and --status-fd for gpg-wks-client.
* The UTF-8 byte order mark is now skipped when reading conf files.
* Fixed many bugs and regressions.
* Major improvements to the test suite. For example it is possible
to run the external test suite of GPGME.
See-also: gnupg-announce/2016q4/000400.html
Noteworthy changes in version 2.1.16 (2016-11-18)
-------------------------------------------------
* gpg: New algorithm for selecting the best ranked public key when
using a mail address with -r, -R, or --locate-key.
* gpg: New option --with-tofu-info to print a new "tfs" record in
colon formatted key listings.
* gpg: New option --compliance as an alternative way to specify
options like --rfc2440, --rfc4880, et al.
* gpg: Many changes to the TOFU implementation.
* gpg: Improve usability of --quick-gen-key.
* gpg: In --verbose mode print a diagnostic when a pinentry is
launched.
* gpg: Remove code which warns for old versions of gnome-keyring.
* gpg: New option --override-session-key-fd.
* gpg: Option --output does now work with --verify.
* gpgv: New option --output to allow saving the verified data.
* gpgv: New option --enable-special-filenames.
* agent, dirmngr: New --supervised mode for use by systemd and alike.
* agent: By default listen on all available sockets using standard
names.
* agent: Invoke scdaemon with --homedir.
* dirmngr: On Linux now detects the removal of its own socket and
terminates.
* scd: Support ECC key generation.
* scd: Support more card readers.
* dirmngr: New option --allow-version-check to download a software
version database in the background.
* dirmngr: Use system provided CAs if no --hkp-cacert is given.
* dirmngr: Use a default keyserver if none is explicitly set
* gpgconf: New command --query-swdb to check software versions
against an copy of an online database.
* gpgconf: Print the socket directory with --list-dirs.
* tools: The WKS tools now support draft version -02.
* tools: Always build gpg-wks-client and install under libexec.
* tools: New option --supported for gpg-wks-client.
* The log-file option now accepts a value "socket://" to log to the
socket named "S.log" in the standard socket directory.
* Provide fake pinentries for use by tests cases of downstream
developers.
* Fixed many bugs and regressions.
* Many changes and improvements for the test suite.
See-also: gnupg-announce/2016q4/000398.html
Noteworthy changes in version 2.1.15 (2016-08-18)
-------------------------------------------------
* gpg: Remove the --tofu-db-format option and support for the split
TOFU database.
* gpg: Add option --sender to prepare for coming features.
* gpg: Add option --input-size-hint to help progress indicators.
* gpg: Extend the PROGRESS status line with the counted unit.
* gpg: Avoid publishing the GnuPG version by default with --armor.
* gpg: Properly ignore legacy keys in the keyring cache.
* gpg: Always print fingerprint records in --with-colons mode.
* gpg: Make sure that keygrips are printed for each subkey in
--with-colons mode.
* gpg: New import filter "drop-sig".
* gpgsm: Fix a bug in the machine-readable key listing.
* gpg,gpgsm: Block signals during keyring updates to limits the
effects of a Ctrl-C at the wrong time.
* g13: Add command --umount and other fixes for dm-crypt.
* agent: Fix regression in SIGTERM handling.
* agent: Cleanup of the ssh-agent code.
* agent: Allow import of overly long keys.
* scd: Fix problems with card removal.
* dirmngr: Remove all code for running as a system service.
* tools: Make gpg-wks-client conforming to the specs.
* tests: Improve the output of the new regression test tool.
* tests: Distribute the standalone test runner.
* tests: Run each test in a clean environment.
* Spelling and grammar fixes.
See-also: gnupg-announce/2016q3/000396.html
Noteworthy changes in version 2.1.14 (2016-07-14)
-------------------------------------------------
* gpg: Removed options --print-dane-records and --print-pka-records.
The new export options "export-pka" and "export-dane" can instead
be used with the export command.
* gpg: New options --import-filter and --export-filter.
* gpg: New import options "import-show" and "import-export".
* gpg: New option --no-keyring.
* gpg: New command --quick-revuid.
* gpg: New options -f/--recipient-file and -F/--hidden-recipient-file
to directly specify encryption keys.
* gpg: New option --mimemode to indicate that the content is a MIME
part. Does only enable --textmode right now.
* gpg: New option --rfc4880bis to allow experiments with proposed
changes to the current OpenPGP specs.
* gpg: Fix regression in the "fetch" sub-command of --card-edit.
* gpg: Fix regression since 2.1 in option --try-all-secrets.
* gpgv: Change default options for extra security.
* gpgsm: No more root certificates are installed by default.
* agent: "updatestartuptty" does now affect more environment
variables.
* scd: The option --homedir does now work with scdaemon.
* scd: Support some more GEMPlus card readers.
* gpgtar: Fix handling of '-' as file name.
* gpgtar: New commands --create and --extract.
* gpgconf: Tweak for --list-dirs to better support shell scripts.
* tools: Add programs gpg-wks-client and gpg-wks-server to implement
a Web Key Service. The configure option --enable-wks-tools is
required to build them; they should be considered Beta software.
* tests: Complete rework of the openpgp part of the test suite. The
test scripts have been changed from Bourne shell scripts to Scheme
programs. A customized scheme interpreter (gpgscm) is included.
This change was triggered by the need to run the test suite on
non-Unix platforms.
* The rendering of the man pages has been improved.
See-also: gnupg-announce/2016q3/000393.html
Noteworthy changes in version 2.1.13 (2016-06-16)
-------------------------------------------------
* gpg: New command --quick-addkey. Extend the --quick-gen-key
command.
* gpg: New --keyid-format "none" which is now also the default.
* gpg: New option --with-subkey-fingerprint.
* gpg: Include Signer's UID subpacket in signatures if the secret key
has been specified using a mail address and the new option
--disable-signer-uid is not used.
* gpg: Allow unattended deletion of a secret key.
* gpg: Allow export of non-passphrase protected secret keys.
* gpg: New status lines KEY_CONSIDERED and NOTATION_FLAGS.
* gpg: Change status line TOFU_STATS_LONG to use '~' as
a non-breaking-space character.
* gpg: Speedup key listings in Tofu mode.
* gpg: Make sure that the current and total values of a PROGRESS
status line are small enough.
* gpgsm: Allow the use of AES192 and SERPENT ciphers.
* dirmngr: Adjust WKD lookup to current specs.
* dirmngr: Fallback to LDAP v3 if v2 is is not supported.
* gpgconf: New commands --create-socketdir and --remove-socketdir,
new option --homedir.
* If a /run/user/$UID directory exists, that directory is now used
for IPC sockets instead of the GNUPGHOME directory. This fixes
problems with NFS and too long socket names and thus avoids the
need for redirection files.
* The Speedo build systems now uses the new versions.gnupg.org server
to retrieve the default package versions.
* Fix detection of libusb on FreeBSD.
* Speedup fd closing after a fork.
See-also: gnupg-announce/2016q2/000390.html
Noteworthy changes in version 2.1.12 (2016-05-04)
-------------------------------------------------
* gpg: New --edit-key sub-command "change-usage" for testing
purposes.
* gpg: Out of order key-signatures are now systematically detected
and fixed by --edit-key.
* gpg: Improved detection of non-armored messages.
* gpg: Removed the extra prompt needed to create Curve25519 keys.
* gpg: Improved user ID selection for --quick-sign-key.
* gpg: Use the root CAs provided by the system with --fetch-key.
* gpg: Add support for the experimental Web Key Directory key
location service.
* gpg: Improve formatting of Tofu messages and emit new Tofu specific
status lines.
* gpgsm: Add option --pinentry-mode to support a loopback pinentry.
* gpgsm: A new pubring.kbx is now created with the header blob so
that gpg can detect that the keybox format needs to be used.
* agent: Add read support for the new private key protection format
openpgp-s2k-ocb-aes.
* agent: Add read support for the new extended private key format.
* agent: Default to --allow-loopback-pinentry and add option
--no-allow-loopback-pinentry.
* scd: Changed to use the new libusb 1.0 API for the internal CCID
driver.
* dirmngr: The dirmngr-client does now auto-detect the PEM format.
* g13: Add experimental support for dm-crypt.
* w32: Tofu support is now available with the Speedo build method.
* w32: Removed the need for libiconv.dll.
* The man pages for gpg and gpgv are now installed under the correct
name (gpg2 or gpg - depending on a configure option).
* Lots of internal cleanups and bug fixes.
See-also: gnupg-announce/2016q2/000387.html
Noteworthy changes in version 2.1.11 (2016-01-26)
-------------------------------------------------
* gpg: New command --export-ssh-key to replace the gpgkey2ssh tool.
* gpg: Allow to generate mail address only keys with --gen-key.
* gpg: "--list-options show-usage" is now the default.
* gpg: Make lookup of DNS CERT records holding an URL work.
* gpg: Emit PROGRESS status lines during key generation.
* gpg: Don't check for ambiguous or non-matching key specification in
the config file or given to --encrypt-to. This feature will return
in 2.3.x.
* gpg: Lock keybox files while updating them.
* gpg: Solve rare error on Windows during keyring and Keybox updates.
* gpg: Fix possible keyring corruption. (bug#2193)
* gpg: Fix regression of "bkuptocard" sub-command in --edit-key and
remove "checkbkupkey" sub-command introduced with 2.1. (bug#2169)
* gpg: Fix internal error in gpgv when using default keyid-format.
* gpg: Fix --auto-key-retrieve to work with dirmngr.conf configured
keyservers. (bug#2147).
* agent: New option --pinentry-timeout.
* scd: Improve unplugging of USB readers under Windows.
* scd: Fix regression for generating RSA keys on card.
* dirmmgr: All configured keyservers are now searched.
* dirmngr: Install CA certificate for hkps.pool.sks-keyservers.net.
Use this certificate even if --hkp-cacert is not used.
* gpgtar: Add actual encryption code. gpgtar does now fully replace
gpg-zip.
* gpgtar: Fix filename encoding problem on Windows.
* Print a warning if a GnuPG component is using an older version of
gpg-agent, dirmngr, or scdaemon.
See-also: gnupg-announce/2016q1/000383.html
Noteworthy changes in version 2.1.10 (2015-12-04)
-------------------------------------------------
* gpg: New trust models "tofu" and "tofu+pgp".
* gpg: New command --tofu-policy. New options --tofu-default-policy
and --tofu-db-format.
* gpg: New option --weak-digest to specify hash algorithms which
should be considered weak.
* gpg: Allow the use of multiple --default-key options; take the last
available key.
* gpg: New option --encrypt-to-default-key.
* gpg: New option --unwrap to only strip the encryption layer.
* gpg: New option --only-sign-text-ids to exclude photo IDs from key
signing.
* gpg: Check for ambiguous or non-matching key specification in the
config file or given to --encrypt-to.
* gpg: Show the used card reader with --card-status.
* gpg: Print export statistics and an EXPORTED status line.
* gpg: Allow selecting subkeys by keyid in --edit-key.
* gpg: Allow updating the expiration time of multiple subkeys at
once.
* dirmngr: New option --use-tor. For full support this requires
libassuan version 2.4.2 and a patched version of libadns
(e.g. adns-1.4-g10-7 as used by the standard Windows installer).
* dirmngr: New option --nameserver to specify the nameserver used in
Tor mode.
* dirmngr: Keyservers may again be specified by IP address.
* dirmngr: Fixed problems in resolving keyserver pools.
* dirmngr: Fixed handling of premature termination of TLS streams so
that large numbers of keys can be refreshed via hkps.
* gpg: Fixed a regression in --locate-key [since 2.1.9].
* gpg: Fixed another bug for keyrings with legacy keys.
* gpgsm: Allow combinations of usage flags in --gen-key.
* Make tilde expansion work with most options.
* Many other cleanups and bug fixes.
See-also: gnupg-announce/2015q4/000381.html
Noteworthy changes in version 2.1.9 (2015-10-09)
------------------------------------------------
* gpg: Allow fetching keys via OpenPGP DANE (--auto-key-locate). New
option --print-dane-records. [Update: --print-dane-records replaced
in 2.1.4.]
* gpg: Fix for a problem with PGP-2 keys in a keyring.
* gpg: Fail with an error instead of a warning if a modern cipher
algorithm is used without a MDC.
* agent: New option --pinentry-invisible-char.
* agent: Always do a RSA signature verification after creation.
* agent: Fix a regression in ssh-add-ing Ed25519 keys.
* agent: Fix ssh fingerprint computation for nistp384 and EdDSA.
* agent: Fix crash during passphrase entry on some platforms.
* scd: Change timeout to fix problems with some 2.1 cards.
* dirmngr: Displayed name is now Key Acquirer.
* dirmngr: Add option --keyserver. Deprecate that option for gpg.
Install a dirmngr.conf file from a skeleton for new installations.
See-also: gnupg-announce/2015q4/000380.html
Noteworthy changes in version 2.1.8 (2015-09-10)
------------------------------------------------
* gpg: Sending very large keys to the keyservers works again.
* gpg: Validity strings in key listings are now again translatable.
* gpg: Emit FAILURE status lines to help GPGME.
* gpg: Does not anymore link to Libksba to reduce dependencies.
* gpgsm: Export of secret keys via Assuan is now possible.
* agent: Raise the maximum passphrase length from 100 to 255 bytes.
* agent: Fix regression using EdDSA keys with ssh.
* Does not anymore use a build timestamp by default.
* The fallback encoding for broken locale settings changed
from Latin-1 to UTF-8.
* Many code cleanups and improved internal documentation.
* Various minor bug fixes.
See-also: gnupg-announce/2015q3/000379.html
Noteworthy changes in version 2.1.7 (2015-08-11)
------------------------------------------------
* gpg: Support encryption with Curve25519 if Libgcrypt 1.7 is used.
* gpg: In the --edit-key menu: Removed the need for "toggle", changed
how secret keys are indicated, new commands "fpr *" and "grip".
* gpg: More fixes related to legacy keys in a keyring.
* gpgv: Does now also work with a "trustedkeys.kbx" file.
* scd: Support some feature from the OpenPGP card 3.0 specs.
* scd: Improved ECC support
* agent: New option --force for the DELETE_KEY command.
* w32: Look for the Pinentry at more places.
* Dropped deprecated gpgsm-gencert.sh
* Various other bug fixes.
See-also: gnupg-announce/2015q3/000371.html
Noteworthy changes in version 2.1.6 (2015-07-01)
------------------------------------------------
* agent: New option --verify for the PASSWD command.
* gpgsm: Add command option "offline" as an alternative to
--disable-dirmngr.
* gpg: Do not prompt multiple times for a password in pinentry
loopback mode.
* Allow the use of debug category names with --debug.
* Using gpg-agent and gpg/gpgsm with different locales will now show
the correct translations in Pinentry.
* gpg: Improve speed of --list-sigs and --check-sigs.
* gpg: Make --list-options show-sig-subpackets work again.
* gpg: Fix an export problem for old keyrings with PGP-2 keys.
* scd: Support PIN-pads on more readers.
* dirmngr: Properly cleanup zombie LDAP helper processes and avoid
hangs on dirmngr shutdown.
* Various other bug fixes.
See-also: gnupg-announce/2015q3/000370.html
Noteworthy changes in version 2.1.5 (2015-06-11)
------------------------------------------------
* Support for an external passphrase cache.
* Support for the forthcoming version 3 OpenPGP smartcard.
* Manuals now show the actual used file names.
* Prepared for improved integration with Emacs.
* Code cleanups and minor bug fixes.
See-also: gnupg-announce/2015q2/000369.html
Noteworthy changes in version 2.1.4 (2015-05-12)
------------------------------------------------
* gpg: Add command --quick-adduid to non-interactively add a new user
id to an existing key.
* gpg: Do no enable honor-keyserver-url by default. Make it work if
enabled.
* gpg: Display the serial number in the --card-status output again.
* agent: Support for external password managers.
Add option --no-allow-external-cache.
* scdaemon: Improved handling of extended APDUs.
* Make HTTP proxies work again.
* All network access including DNS as been moved to Dirmngr.
* Allow building without LDAP support.
* Fixed lots of smaller bugs.
See-also: gnupg-announce/2015q2/000366.html
Noteworthy changes in version 2.1.3 (2015-04-11)
------------------------------------------------
* gpg: LDAP keyservers are now supported by 2.1.
* gpg: New option --with-icao-spelling.
* gpg: New option --print-pka-records. Changed the PKA method to use
CERT records and hashed names. [Update: --print-pka-records
replaced in 2.1.14.]
* gpg: New command --list-gcrypt-config. New parameter "curve"
for --list-config.
* gpg: Print a NEWSIG status line like gpgsm always did.
* gpg: Print MPI values with --list-packets and --verbose.
* gpg: Write correct MPI lengths with ECC keys.
* gpg: Skip legacy PGP-2 keys while searching.
* gpg: Improved searching for mail addresses when using a keybox.
* gpgsm: Changed default algos to AES-128 and SHA-256.
* gpgtar: Fixed extracting files with sizes of a multiple of 512.
* dirmngr: Fixed SNI handling for hkps pools.
* dirmngr: extra-certs and trusted-certs are now always loaded from
the sysconfig dir instead of the homedir.
* Fixed possible problems due to compiler optimization, two minor
regressions, and other bugs.
See-also: gnupg-announce/2015q2/000365.html
Noteworthy changes in version 2.1.2 (2015-02-11)
------------------------------------------------
* gpg: The parameter 'Passphrase' for batch key generation works
again.
* gpg: Using a passphrase option in batch mode now has the expected
effect on --quick-gen-key.
* gpg: Improved reporting of unsupported PGP-2 keys.
* gpg: Added support for algo names when generating keys using
--command-fd.
* gpg: Fixed DoS based on bogus and overlong key packets.
* agent: When setting --default-cache-ttl the value
for --max-cache-ttl is adjusted to be not lower than the former.
* agent: Fixed problems with the new --extra-socket.
* agent: Made --allow-loopback-pinentry changeable with gpgconf.
* agent: Fixed importing of unprotected openpgp keys.
* agent: Now tries to use a fallback pinentry if the standard
pinentry is not installed.
* scd: Added support for ECDH.
* Fixed several bugs related to bogus keyrings and improved some
other code.
See-also: gnupg-announce/2015q1/000361.html
Noteworthy changes in version 2.1.1 (2014-12-16)
------------------------------------------------
* gpg: Detect faulty use of --verify on detached signatures.
* gpg: New import option "keep-ownertrust".
* gpg: New sub-command "factory-reset" for --card-edit.
* gpg: A stub key for smartcards is now created by --card-status.
* gpg: Fixed regression in --refresh-keys.
* gpg: Fixed regression in %g and %p codes for --sig-notation.
* gpg: Fixed best matching hash algo detection for ECDSA and EdDSA.
* gpg: Improved perceived speed of secret key listisngs.
* gpg: Print number of skipped PGP-2 keys on import.
* gpg: Removed the option aliases --throw-keyid and --notation-data;
use --throw-keyids and --set-notation instead.
* gpg: New import option "keep-ownertrust".
* gpg: Skip too large keys during import.
* gpg,gpgsm: New option --no-autostart to avoid starting gpg-agent or
dirmngr.
* gpg-agent: New option --extra-socket to provide a restricted
command set for use with remote clients.
* gpgconf --kill does not anymore start a service only to kill it.
* gpg-pconnect-agent: Add convenience option --uiserver.
* Fixed keyserver access for Windows.
* Fixed build problems on Mac OS X
* The Windows installer does now install development files
* More translations (but most of them are not complete).
* To support remotely mounted home directories, the IPC sockets may
now be redirected. This feature requires Libassuan 2.2.0.
* Improved portability and the usual bunch of bug fixes.
See-also: gnupg-announce/2014q4/000360.html
Noteworthy changes in version 2.1.0 (2014-11-06)
------------------------------------------------
This release introduces a lot of changes. Most of them are internal
and thus not user visible. However, some long standing behavior has
slightly changed and it is strongly suggested that an existing
"~/.gnupg" directory is backed up before this version is used.
A verbose description of the major new features and changes can be
found in the file doc/whats-new-in-2.1.txt.
* gpg: All support for v3 (PGP 2) keys has been dropped. All
signatures are now created as v4 signatures. v3 keys will be
removed from the keyring.
* gpg: With pinentry-0.9.0 the passphrase "enter again" prompt shows
up in the same window as the "new passphrase" prompt.
* gpg: Allow importing keys with duplicated long key ids.
* dirmngr: May now be build without support for LDAP.
* For a complete list of changes see the lists of changes for the
2.1.0 beta versions below. Note that all relevant fixes from
versions 2.0.14 to 2.0.26 are also applied to this version.
[Noteworthy changes in version 2.1.0-beta864 (2014-10-03)]
* gpg: Removed the GPG_AGENT_INFO related code. GnuPG does now
always use a fixed socket name in its home directory.
* gpg: Renamed --gen-key to --full-gen-key and re-added a --gen-key
command with less choices.
* gpg: Use SHA-256 for all signature types also on RSA keys.
* gpg: Default keyring is now created with a .kbx suffix.
* gpg: Add a shortcut to the key capabilities menu (e.g. "=e" sets the
encryption capabilities).
* gpg: Fixed obsolete options parsing.
* Further improvements for the alternative speedo build system.
[Noteworthy changes in version 2.1.0-beta834 (2014-09-18)]
* gpg: Improved passphrase caching.
* gpg: Switched to algorithm number 22 for EdDSA.
* gpg: Removed CAST5 from the default preferences.
* gpg: Order SHA-1 last in the hash preferences.
* gpg: Changed default cipher for --symmetric to AES-128.
* gpg: Fixed export of ECC keys and import of EdDSA keys.
* dirmngr: Fixed the KS_FETCH command.
* The speedo build system now downloads related packages and works
for non-Windows platforms.
[Noteworthy changes in version 2.1.0-beta783 (2014-08-14)]
* gpg: Add command --quick-gen-key.
* gpg: Make --quick-sign-key promote local key signatures.
* gpg: Added "show-usage" sub-option to --list-options.
* gpg: Screen keyserver responses to avoid importing unwanted keys
from rogue servers.
* gpg: Removed the option --pgp2 and --rfc1991 and the ability to
create PGP-2 compatible messages.
* gpg: Removed options --compress-keys and --compress-sigs.
* gpg: Cap attribute packets at 16MB.
* gpg: Improved output of --list-packets.
* gpg: Make with-colons output of --search-keys work again.
* gpgsm: Auto-create the ".gnupg" directory like gpg does.
* agent: Fold new passphrase warning prompts into one.
* scdaemon: Add support for the Smartcard-HSM card.
* scdaemon: Remove the use of the pcsc-wrapper.
[Noteworthy changes in version 2.1.0-beta751 (2014-07-03)]
* gpg: Create revocation certificates during key generation.
* gpg: Create exported secret keys and revocation certifciates with
mode 0700
* gpg: The validity of user ids is now shown by default. To revert
this add "list-options no-show-uid-validity" to gpg.conf.
* gpg: Make export of secret keys work again.
* gpg: The output of --list-packets does now print the offset of the
packet and information about the packet header.
* gpg: Avoid DoS due to garbled compressed data packets. [CVE-2014-4617]
* gpg: Print more specific reason codes with the INV_RECP status.
* gpg: Cap RSA and Elgamal keysize at 4096 bit also for unattended
key generation.
* scdaemon: Support reader Gemalto IDBridge CT30 and pinpad of SCT
cyberJack go.
* The speedo build system has been improved. It is now also possible
to build a partly working installer for Windows.
[Noteworthy changes in version 2.1.0-beta442 (2014-06-05)]
* gpg: Changed the format of key listings. To revert to the old
format the option --legacy-list-mode is available.
* gpg: Add experimental signature support using curve Ed25519 and
with a patched Libgcrypt also encryption support with Curve25519.
[Update: this encryption support has been removed from 2.1.0 until
we have agreed on a suitable format.]
* gpg: Allow use of Brainpool curves.
* gpg: Accepts a space separated fingerprint as user ID. This
allows to copy and paste the fingerprint from the key listing.
* gpg: The hash algorithm is now printed for signature records in key
listings.
* gpg: Reject signatures made using the MD5 hash algorithm unless the
new option --allow-weak-digest-algos or --pgp2 are given.
* gpg: Print a warning if the Gnome-Keyring-Daemon intercepts the
communication with the gpg-agent.
* gpg: New option --pinentry-mode.
* gpg: Fixed decryption using an OpenPGP card.
* gpg: Fixed bug with deeply nested compressed packets.
* gpg: Only the major version number is by default included in the
armored output.
* gpg: Do not create a trustdb file if --trust-model=always is used.
* gpg: Protect against rogue keyservers sending secret keys.
* gpg: The format of the fallback key listing ("gpg KEYFILE") is now
more aligned to the regular key listing ("gpg -k").
* gpg: The option--show-session-key prints its output now before the
decryption of the bulk message starts.
* gpg: New %U expando for the photo viewer.
* gpg,gpgsm: New option --with-secret.
* gpgsm: By default the users are now asked via the Pinentry whether
they trust an X.509 root key. To prohibit interactive marking of
such keys, the new option --no-allow-mark-trusted may be used.
* gpgsm: New commands to export a secret RSA key in PKCS#1 or PKCS#8
format.
* gpgsm: Improved handling of re-issued CA certificates.
* agent: The included ssh agent does now support ECDSA keys.
* agent: New option --enable-putty-support to allow gpg-agent on
Windows to act as a Pageant replacement with full smartcard support.
* scdaemon: New option --enable-pinpad-varlen.
* scdaemon: Various fixes for pinpad equipped card readers.
* scdaemon: Rename option --disable-pinpad (was --disable-keypad).
* scdaemon: Better support fo CCID readers. Now, internal CCID
driver supports readers with no auto configuration feature.
* dirmngr: Removed support for the original HKP keyserver which is
not anymore used by any site.
* dirmngr: Improved support for keyserver pools.
* tools: New option --dirmngr for gpg-connect-agent.
* The GNU Pth library has been replaced by the new nPth library.
* Support installation as portable application under Windows.
* All kind of other improvements - see the git log.
[Noteworthy changes in version 2.1.0beta3 (2011-12-20)]
* gpg: Fixed regression in the secret key export function.
* gpg: Allow generation of card keys up to 4096 bit.
* gpgsm: Preliminary support for the validation model "steed".
* gpgsm: Improved certificate creation.
* agent: Support the SSH confirm flag.
* agent: New option to select a passphrase mode. The loopback
mode may be used to bypass Pinentry.
* agent: The Assuan commands KILLAGENT and KILLSCD are working again.
* scdaemon: Does not anymore block after changing a card (regression
fix).
* tools: gpg-connect-agent does now properly display the help output
for "SCD HELP" commands.
[Noteworthy changes in version 2.1.0beta2 (2011-03-08)]
* gpg: ECC support as described by draft-jivsov-openpgp-ecc-06.txt
[Update: now known as RFC-6637].
* gpg: Print "AES128" instead of "AES". This change introduces a
little incompatibility for tools using "gpg --list-config". We
hope that these tools are written robust enough to accept this new
algorithm name as well.
* gpgsm: New feature to create certificates from a parameter file.
Add prompt to the --gen-key UI to create self-signed certificates.
* agent: TMPDIR is now also honored when creating a socket using
the --no-standard-socket option and with symcryptrun's temp files.
* scdaemon: Fixed a bug where scdaemon sends a signal to gpg-agent
running in non-daemon mode.
* dirmngr: Fixed CRL loading under W32 (bug#1010).
* Dirmngr has taken over the function of the keyserver helpers. Thus
we now have a specified direct interface to keyservers via Dirmngr.
LDAP, DNS and mail backends are not yet implemented.
* Fixed TTY management for pinentries and session variable update
problem.
[Noteworthy changes in version 2.1.0beta1 (2010-10-26)]
* gpg: secring.gpg is not anymore used but all secret key operations
are delegated to gpg-agent. The import command moves secret keys
to the agent.
* gpg: The OpenPGP import command is now able to merge secret keys.
* gpg: Encrypted OpenPGP messages with trailing data (e.g. other
OpenPGP packets) are now correctly parsed.
* gpg: Given sufficient permissions Dirmngr is started automagically.
* gpg: Fixed output of "gpgconf --check-options".
* gpg: Removed options --export-options(export-secret-subkey-passwd)
and --simple-sk-checksum.
* gpg: New options --try-secret-key.
* gpg: Support DNS lookups for SRV, PKA and CERT on W32.
* gpgsm: The --audit-log feature is now more complete.
* gpgsm: The default for --include-cert is now to include all
certificates in the chain except for the root certificate.
* gpgsm: New option --ignore-cert-extension.
* g13: The G13 tool for disk encryption key management has been
added.
* agent: If the agent's --use-standard-socket option is active, all
tools try to start and daemonize the agent on the fly. In the past
this was only supported on W32; on non-W32 systems the new
configure option --disable-standard-socket may now be used to
disable this new default.
* agent: New and changed passphrases are now created with an
iteration count requiring about 100ms of CPU work.
* dirmngr: Dirmngr is now a part of this package. It is now also
expected to run as a system service and the configuration
directories are changed to the GnuPG name space. [Update: 2.1.0
starts dirmngr on demand as user daemon.]
* Support for Windows CE. [Update: This has not been tested for the
2.1.0 release]
* Numerical values may now be used as an alternative to the
debug-level keywords.
See-also: gnupg-announce/2014q4/000358.html
Version 2.0.28 (2015-06-02)
Version 2.0.27 (2015-02-18)
Version 2.0.26 (2014-08-12)
Version 2.0.25 (2014-06-30)
Version 2.0.24 (2014-06-24)
Version 2.0.23 (2014-06-03)
Version 2.0.22 (2013-10-04)
Version 2.0.21 (2013-08-19)
Version 2.0.20 (2013-05-10)
Version 2.0.19 (2012-03-27)
Version 2.0.18 (2011-08-04)
Version 2.0.17 (2011-01-13)
Version 2.0.16 (2010-07-19)
Version 2.0.15 (2010-03-09)
Version 2.0.14 (2009-12-21)
Noteworthy changes in version 2.0.13 (2009-09-04)
-------------------------------------------------
* GPG now generates 2048 bit RSA keys by default. The default hash
algorithm preferences has changed to prefer SHA-256 over SHA-1.
2048 bit DSA keys are now generated to use a 256 bit hash algorithm
* The envvars XMODIFIERS, GTK_IM_MODULE and QT_IM_MODULE are now
passed to the Pinentry to make SCIM work.
* The GPGSM command --gen-key features a --batch mode and implements
all features of gpgsm-gencert.sh in standard mode.
* New option --re-import for GPGSM's IMPORT server command.
* Enhanced writing of existing keys to OpenPGP v2 cards.
* Add hack to the internal CCID driver to allow the use of some
Omnikey based card readers with 2048 bit keys.
* GPG now repeatedly asks the user to insert the requested OpenPGP
card. This can be disabled with --limit-card-insert-tries=1.
* Minor bug fixes.
See-also: gnupg-announce/2009q3/000294.html
Noteworthy changes in version 2.0.12 (2009-06-17)
-------------------------------------------------
* GPGSM now always lists ephemeral certificates if specified by
fingerprint or keygrip.
* New command "KEYINFO" for GPG_AGENT. GPGSM now also returns
information about smartcards.
* Made sure not to leak file descriptors if running gpg-agent with a
command. Restore the signal mask to solve a problem in Mono.
* Changed order of the confirmation questions for root certificates
and store negative answers in trustlist.txt.
* Better synchronization of concurrent smartcard sessions.
* Support 2048 bit OpenPGP cards.
* Support Telesec Netkey 3 cards.
* The gpg-protect-tool now uses gpg-agent via libassuan. Under
Windows the Pinentry will now be put into the foreground.
* Changed code to avoid a possible Mac OS X system freeze.
See-also: gnupg-announce/2009q2/000288.html
Noteworthy changes in version 2.0.11 (2009-03-03)
-------------------------------------------------
* Fixed a problem in SCDAEMON which caused unexpected card resets.
* SCDAEMON is now aware of the Geldkarte.
* The SCDAEMON option --allow-admin is now used by default.
* GPGCONF now restarts SCdaemon if necessary.
* The default cipher algorithm in GPGSM is now again 3DES. This is
due to interoperability problems with Outlook 2003 which still
can't cope with AES.
See-also: gnupg-announce/2009q1/000287.html
Noteworthy changes in version 2.0.10 (2009-01-12)
-------------------------------------------------
* [gpg] New keyserver helper gpg2keys_kdns as generic DNS CERT
lookup. Run with --help for a short description. Requires the
ADNS library.
* [gpg] New mechanisms "local" and "nodefault" for --auto-key-locate.
Fixed a few problems with this option.
* [gpg] New command --locate-keys.
* [gpg] New options --with-sig-list and --with-sig-check.
* [gpg] The option "-sat" is no longer an alias for --clearsign.
* [gpg] The option --fixed-list-mode is now implicitly used and obsolete.
* [gpg] New control statement %ask-passphrase for the unattended key
generation.
* [gpg] The algorithm to compute the SIG_ID status has been changed.
* [gpgsm] Now uses AES by default.
* [gpgsm] Made --output option work with --export-secret-key-p12.
* [gpg-agent] Terminate process if the own listening socket is not
anymore served by ourself.
* [scdaemon] Made it more robust on W32.
* [gpg-connect-agent] Accept commands given as command line arguments.
* [w32] Initialized the socket subsystem for all keyserver helpers.
* [w32] The sysconf directory has been moved from a subdirectory of
the installation directory to %CSIDL_COMMON_APPDATA%/GNU/etc/gnupg.
* [w32] The gnupg2.nls directory is not anymore used. The standard
locale directory is now used.
* [w32] Fixed a race condition between gpg and gpgsm in the use of
temporary file names.
* The gpg-preset-passphrase mechanism works again. An arbitrary
string may now be used for a custom cache ID.
* Admin PINs are cached again (bug in 2.0.9).
* Support for version 2 OpenPGP cards.
* Libgcrypt 1.4 is now required.
See-also: gnupg-announce/2009q1/000284.html
Noteworthy changes in version 2.0.9 (2008-03-26)
------------------------------------------------
* Gpgsm always tries to locate missing certificates from a running
Dirmngr's cache.
* Tweaks for Windows.
* The Admin PIN for OpenPGP cards may now be entered with the pinpad.
* Improved certificate chain construction.
* Extended the PKITS framework.
* Fixed a bug in the ambiguous name detection.
* Fixed possible memory corruption while importing OpenPGP keys (bug
introduced with 2.0.8). [CVE-2008-1530]
* Minor bug fixes.
Noteworthy changes in version 2.0.8 (2007-12-20)
------------------------------------------------
* Enhanced gpg-connect-agent with a small scripting language.
* New option --list-config for gpgconf.
* Fixed a crash in gpgconf.
* Gpg-agent now supports the passphrase quality bar of the latest
Pinentry.
* The envvars XAUTHORITY and PINENTRY_USER_DATA are now passed to the
Pinentry.
* Fixed the auto creation of the key stub for smartcards.
* Fixed a rare bug in decryption using the OpenPGP card.
* Creating DSA2 keys is now possible.
* New option --extra-digest-algo for gpgsm to allow verification of
broken signatures.
* Allow encryption with legacy Elgamal sign+encrypt keys with option
--rfc2440.
* Windows is now a supported platform.
* Made sure that under Windows the file permissions of the socket are
taken into account. This required a change of our socket emulation
code and changed the IPC protocol under Windows.
See-also: gnupg-announce/2007q4/000267.html
Noteworthy changes in version 2.0.7 (2007-09-10)
------------------------------------------------
* Fixed encryption problem if duplicate certificates are in the
keybox.
* Made it work on Windows Vista. Note that the entire Windows port
is still considered Beta.
* Add new options min-passphrase-nonalpha, check-passphrase-pattern,
enforce-passphrase-constraints and max-passphrase-days to
gpg-agent.
* Add command --check-components to gpgconf. Gpgconf now uses the
installed versions of the programs and does not anymore search via
PATH for them.
See-also: gnupg-announce/2007q3/000259.html
Noteworthy changes in version 2.0.6 (2007-08-16)
------------------------------------------------
* GPGSM does now grok --default-key.
* GPGCONF is now aware of --default-key and --encrypt-to.
* GPGSM does again correctly print the serial number as well the the
various keyids. This was broken since 2.0.4.
* New option --validation-model and support for the chain-model.
* Improved Windows support.
See-also: gnupg-announce/2007q3/000258.html
Noteworthy changes in version 2.0.5 (2007-07-05)
------------------------------------------------
* Switched license to GPLv3.
* Basic support for Windows. Run "./autogen.sh --build-w32" to build
it. As usual the mingw cross compiling toolchain is required.
* Fixed bug when using the --p12-charset without --armor.
* The command --gen-key may now be used instead of the
gpgsm-gencert.sh script.
* Changed key generation to reveal less information about the
machine. Bug fixes for gpg2's card key generation.
See-also: gnupg-announce/2007q3/000255.html
Noteworthy changes in version 2.0.4 (2007-05-09)
------------------------------------------------
* The server mode key listing commands are now also working for
systems without the funopen/fopencookie API.
* PKCS#12 import now tries several encodings in case the passphrase
was not utf-8 encoded. New option --p12-charset for gpgsm.
* Improved the libgcrypt logging support in all modules.
See-also: gnupg-announce/2007q2/000254.html
Noteworthy changes in version 2.0.3 (2007-03-08)
------------------------------------------------
* By default, do not allow processing multiple plaintexts in a single
stream. Many programs that called GnuPG were assuming that GnuPG
did not permit this, and were thus not using the plaintext boundary
status tags that GnuPG provides. This change makes GnuPG reject
such messages by default which makes those programs safe again.
--allow-multiple-messages returns to the old behavior. [CVE-2007-1263].
* New --verify-option show-primary-uid-only.
* gpgconf may now reads a global configuration file to select which
options are changeable by a frontend. The new applygnupgdefaults
tool may be used by an admin to set default options for all users.
* The PIN pad of the Cherry XX44 keyboard is now supported. The
DINSIG and the NKS applications are now also aware of PIN pads.
See-also: gnupg-announce/2007q1/000252.html
Noteworthy changes in version 2.0.2 (2007-01-31)
------------------------------------------------
* Fixed a serious and exploitable bug in processing encrypted
packages. [CVE-2006-6235].
* Added --passphrase-repeat to set the number of times GPG will
prompt for a new passphrase to be repeated. This is useful to help
memorize a new passphrase. The default is 1 repetition.
* Using a PIN pad does now also work for the signing key.
* A warning is displayed by gpg-agent if a new passphrase is too
short. New option --min-passphrase-len defaults to 8.
* The status code BEGIN_SIGNING now shows the used hash algorithms.
See-also: gnupg-announce/2007q1/000249.html
Noteworthy changes in version 2.0.1 (2006-11-28)
------------------------------------------------
* Experimental support for the PIN pads of the SPR 532 and the Kaan
Advanced card readers. Add "disable-keypad" scdaemon.conf if you
don't want it. Does currently only work for the OpenPGP card and
its authentication and decrypt keys.
* Fixed build problems on some some platforms and crashes on amd64.
* Fixed a buffer overflow in gpg2. [bug#728,CVE-2006-6169]
See-also: gnupg-announce/2006q4/000242.html
Noteworthy changes in version 2.0.0 (2006-11-11)
------------------------------------------------
* First stable version of a GnuPG integrating OpenPGP and S/MIME.
See-also: gnupg-announce/2006q4/000239.html
Noteworthy changes in version 1.9.95 (2006-11-06)
-------------------------------------------------
* Minor bug fixes.
Noteworthy changes in version 1.9.94 (2006-10-24)
-------------------------------------------------
* Keys for gpgsm may now be specified using a keygrip. A keygrip is
indicated by a prefixing it with an ampersand.
* gpgconf now supports switching the CMS cipher algo (e.g. to AES).
* New command --gpgconf-test for all major tools. This may be used to
check whether the configuration file is sane.
Noteworthy changes in version 1.9.93 (2006-10-18)
-------------------------------------------------
* In --with-validation mode gpgsm will now also ask whether a root
certificate should be trusted.
* Link to Pth only if really necessary.
* Fixed a pubring corruption bug in gpg2 occurring when importing
signatures or keys with insane lengths.
* Fixed v3 keyID calculation bug in gpg2.
* More tweaks for certificates without extensions.
Noteworthy changes in version 1.9.92 (2006-10-11)
-------------------------------------------------
* Bug fixes.
See-also: gnupg-announce/2006q4/000236.html
Noteworthy changes in version 1.9.91 (2006-10-04)
-------------------------------------------------
* New "relax" flag for trustlist.txt to allow root CA certificates
without BasicContraints.
* [gpg2] Removed the -k PGP 2 compatibility hack. -k is now an
alias for --list-keys.
* [gpg2] Print a warning if "-sat" is used instead of "--clearsign".
Noteworthy changes in version 1.9.90 (2006-09-25)
-------------------------------------------------
* Made readline work for gpg.
* Cleanups und minor bug fixes.
* Included translations from gnupg 1.4.5.
Noteworthy changes in version 1.9.23 (2006-09-18)
-------------------------------------------------
* Regular man pages for most tools are now build directly from the
Texinfo source.
* The gpg code from 1.4.5 has been fully merged into this release.
The configure option --enable-gpg is still required to build this
gpg part. For production use of OpenPGP the gpg version 1.4.5 is
still recommended. Note, that gpg will be installed under the name
gpg2 to allow coexisting with an 1.4.x gpg.
* API change in gpg-agent's pkdecrypt command. Thus an older gpgsm
may not be used with the current gpg-agent.
* The scdaemon will now call a script on reader status changes.
* gpgsm now allows file descriptor passing for "INPUT", "OUTPUT" and
"MESSAGE".
* The gpgsm server may now output a key listing to the output file
handle. This needs to be enabled using "OPTION list-to-output=1".
* The --output option of gpgsm has now an effect on list-keys.
* New gpgsm commands --dump-chain and list-chain.
* gpg-connect-agent has new options to utilize descriptor passing.
* A global trustlist may now be used. See doc/examples/trustlist.txt.
* When creating a new pubring.kbx keybox common certificates are
imported.
Noteworthy changes in version 1.9.22 (2006-07-27)
-------------------------------------------------
* Enhanced pkcs#12 support to allow import from simple keyBags.
* Exporting to pkcs#12 now create bag attributes so that Mozilla is
able to import the files.
* Fixed uploading of certain keys to the smart card.
Noteworthy changes in version 1.9.21 (2006-06-20)
-------------------------------------------------
* New command APDU for scdaemon to allow using it for general card
access. Might be used through gpg-connect-agent by using the SCD
prefix command.
* Support for the CardMan 4040 PCMCIA reader (Linux 2.6.15 required).
* Scdaemon does not anymore reset cards at the end of a connection.
* Kludge to allow use of Bundesnetzagentur issued X.509 certificates.
* Added --hash=xxx option to scdaemon's PKSIGN command.
* Pkcs#12 files are now created with a MAC. This is for better
interoperability.
* Collected bug fixes and minor other changes.
Noteworthy changes in version 1.9.20 (2005-12-20)
-------------------------------------------------
* Importing pkcs#12 files created be recent versions of Mozilla works
again.
* Basic support for qualified signatures.
* New debug tool gpgparsemail.
Noteworthy changes in version 1.9.19 (2005-09-12)
-------------------------------------------------
* The Belgian eID card is now supported for signatures and ssh.
Other pkcs#15 cards should work as well.
* Fixed bug in --export-secret-key-p12 so that certificates are again
included.
Noteworthy changes in version 1.9.18 (2005-08-01)
-------------------------------------------------
* [gpgsm] Now allows for more than one email address as well as URIs
and dnsNames in certificate request generation. A keygrip may be
given to create a request from an existing key.
* A couple of minor bug fixes.
Noteworthy changes in version 1.9.17 (2005-06-20)
-------------------------------------------------
* gpg-connect-agent has now features to handle Assuan INQUIRE
commands.
* Internal changes for OpenPGP cards. New Assuan command WRITEKEY.
* GNU Pth is now a hard requirement.
* [scdaemon] Support for OpenSC has been removed. Instead a new and
straightforward pkcs#15 modules has been written. As of now it
does allows only signing using TCOS cards but we are going to
enhance it to match all the old capabilities.
* [gpg-agent] New option --write-env-file and Assuan command
UPDATESTARTUPTTY.
* [gpg-agent] New option --default-cache-ttl-ssh to set the TTL for
SSH passphrase caching independent from the other passphrases.
Noteworthy changes in version 1.9.16 (2005-04-21)
-------------------------------------------------
* gpg-agent does now support the ssh-agent protocol and thus allows
to use the pinentry as well as the OpenPGP smartcard with ssh.
* New tool gpg-connect-agent as a general client for the gpg-agent.
* New tool symcryptrun as a wrapper for certain encryption tools.
* The gpg tool is not anymore build by default because those gpg
versions available in the gnupg 1.4 series are far more matured.
Noteworthy changes in version 1.9.15 (2005-01-13)
-------------------------------------------------
* Fixed passphrase caching bug.
* Better support for CCID readers; the reader from Cherry RS 6700 USB
does now work.
Noteworthy changes in version 1.9.14 (2004-12-22)
-------------------------------------------------
* [gpg-agent] New option --use-standard-socket to allow the use of a
fixed socket. gpgsm falls back to this socket if GPG_AGENT_INFO
has not been set.
* Ported to MS Windows with some functional limitations.
* New tool gpg-preset-passphrase.
Noteworthy changes in version 1.9.13 (2004-12-03)
-------------------------------------------------
* [gpgsm] New option --prefer-system-dirmngr.
* Minor cleanups and debugging aids.
Noteworthy changes in version 1.9.12 (2004-10-22)
-------------------------------------------------
* [scdaemon] Partly rewrote the PC/SC code.
* Removed the sc-investigate tool. It is now in a separate package
available at ftp://ftp.g10code.com/g10code/gscutils/ .
* [gpg-agent] Fixed logging problem.
Noteworthy changes in version 1.9.11 (2004-10-01)
-------------------------------------------------
* When using --import along with --with-validation, the imported
certificates are validated and only imported if they are fully
valid.
* [gpg-agent] New option --max-cache-ttl.
* [gpg-agent] When used without --daemon or --server, gpg-agent now
check whether a agent is already running and usable.
* Fixed some i18n problems.
Noteworthy changes in version 1.9.10 (2004-07-22)
-------------------------------------------------
* Fixed a serious bug in the checking of trusted root certificates.
* New configure option --enable-agent-pnly allows to build and
install just the agent.
* Fixed a problem with the log file handling.
Noteworthy changes in version 1.9.9 (2004-06-08)
------------------------------------------------
* [gpg-agent] The new option --allow-mark-trusted is now required to
allow gpg-agent to add a key to the trustlist.txt after user
confirmation.
* Creating PKCS#10 requests does now honor the key usage.
Noteworthy changes in version 1.9.8 (2004-04-29)
------------------------------------------------
* [scdaemon] Overhauled the internal CCID driver.
* [scdaemon] Status files named ~/.gnupg/reader_<n>.status are now
written when using the internal CCID driver.
* [gpgsm] New commands --dump-{,secret,external}-keys to show a very
detailed view of the certificates.
* The keybox gets now compressed after 3 hours and ephemeral
stored certificates are deleted after about a day.
* [gpg] Usability fixes for --card-edit. Note, that this has already
been ported back to gnupg-1.3
Noteworthy changes in version 1.9.7 (2004-04-06)
------------------------------------------------
* Instrumented the modules for gpgconf.
* Added support for DINSIG card applications.
* Include the smimeCapabilities attribute with signed messages.
* Now uses the gettext domain "gnupg2" to avoid conflicts with gnupg
versions < 1.9.
Noteworthy changes in version 1.9.6 (2004-03-06)
------------------------------------------------
* Code cleanups and bug fixes.
Noteworthy changes in version 1.9.5 (2004-02-21)
------------------------------------------------
* gpg-protect-tool gets now installed into libexec as it ought to be.
Cleaned up the build system to better comply with the coding
standards.
* [gpgsm] The --import command is now able to autodetect pkcs#12
files and import secret and private keys from this file format.
A new command --export-secret-key-p12 is provided to allow
exporting of secret keys in PKCS\#12 format.
* [gpgsm] The pinentry will now present a description of the key for
whom the passphrase is requested.
* [gpgsm] New option --with-validation to check the validity of key
while listing it.
* New option --debug-level={none,basic,advanced,expert,guru} to map
the debug flags to sensitive levels on a per program base.
Noteworthy changes in version 1.9.4 (2004-01-30)
------------------------------------------------
* Added support for the Telesec NKS 2.0 card application.
* Added simple tool addgnupghome to create .gnupg directories from
/etc/skel/.gnupg.
* Various minor bug fixes and cleanups; mainly gpgsm and gpg-agent
related.
Noteworthy changes in version 1.9.3 (2003-12-23)
------------------------------------------------
* New gpgsm options --{enable,disable}-ocsp to validate keys using
OCSP. This option requires a not yet released DirMngr version.
Default is disabled.
* The --log-file option may now be used to print logs to a socket.
Prefix the socket name with "socket://" to enable this. This does
not work on all systems and falls back to stderr if there is a
problem with the socket.
* The options --encrypt-to and --no-encrypt-to now work the same in
gpgsm as in gpg. Note, they are also used in server mode.
* Duplicated recipients are now silently removed in gpgsm.
Noteworthy changes in version 1.9.2 (2003-11-17)
------------------------------------------------
* On card key generation is no longer done using the --gen-key
command but from the menu provided by the new --card-edit command.
* PINs are now properly cached and there are only 2 PINs visible.
The 3rd PIN (CHV2) is internally synchronized with the regular PIN.
* All kind of other internal stuff.
Noteworthy changes in version 1.9.1 (2003-09-06)
------------------------------------------------
* Support for OpenSC is back. scdaemon supports a --disable-opensc to
disable OpenSC use at runtime, so that PC/SC or ct-API can still be
used directly.
* Rudimentary support for the SCR335 smartcard reader using an
internal driver. Requires current libusb from CVS.
* Bug fixes.
Noteworthy changes in version 1.9.0 (2003-08-05)
------------------------------------------------
====== PLEASE SEE README-alpha =======
* gpg has been renamed to gpg2 and gpgv to gpgv2. This is a
temporary change to allow co-existing with stable gpg versions.
* ~/.gnupg/gpg.conf-1.9.0 is fist tried as config file before the
usual gpg.conf.
* Removed the -k, -kv and -kvv commands. -k is now an alias to
--list-keys. New command -K as alias for --list-secret-keys.
* Removed --run-as-shm-coprocess feature.
* gpg does now also use libgcrypt, libgpg-error is required.
* New gpgsm commands --call-dirmngr and --call-protect-tool.
* Changing a passphrase is now possible using "gpgsm --passwd"
* The content-type attribute is now recognized and created.
* The agent does now reread certain options on receiving a HUP.
* The pinentry is now forked for each request so that clients with
different environments are supported. When running in daemon mode
and --keep-display is not used the DISPLAY variable is ignored.
* Merged stuff from the newpg branch and started this new
development branch.
Version 1.4.19 (2015-02-27)
Version 1.4.18 (2014-06-30)
Version 1.4.17 (2014-06-23)
Version 1.4.16 (2013-12-18)
Version 1.4.15 (2013-10-04)
Version 1.4.14 (2013-07-25)
Version 1.4.13 (2012-12-20)
Version 1.4.12 (2012-01-30)
Version 1.4.11 (2010-10-18)
Version 1.4.10 (2009-09-02)
Version 1.4.9 (2008-03-26)
Version 1.4.8 (2007-12-20)
Version 1.4.7 (2007-03-05)
Version 1.4.6 (2006-12-06)
Version 1.4.5 (2006-08-01)
Version 1.4.4 (2006-06-25)
Version 1.4.3 (2006-04-03)
Version 1.4.2 (2005-07-26)
Version 1.4.1 (2005-03-15)
Version 1.4.0 (2004-12-16)
Noteworthy changes in version 1.3.2 (2003-05-27)
------------------------------------------------
* New "--gnupg" option (set by default) that disables --openpgp,
and the various --pgpX emulation options. This replaces
--no-openpgp, and --no-pgpX, and also means that GnuPG has
finally grown a --gnupg option to make GnuPG act like GnuPG.
* A bug in key validation has been fixed. This bug only affects
keys with more than one user ID (photo IDs do not count here),
and results in all user IDs on a given key being treated with
the validity of the most-valid user ID on that key.
* Notation names that do not contain a '@' are no longer allowed
unless --expert is set. This is to help prevent pollution of
the (as yet unused) IETF notation namespace.
* Multiple trust models are now supported via the --trust-model
option. The options are "pgp" (web-of-trust plus trust
signatures), "classic" (web-of-trust only), and "always"
(identical to the --always-trust option).
* The --personal-{cipher|digest|compression}-preferences are now
consulted to get default algorithms before resorting to the
last-ditch defaults of --s2k-cipher-algo, SHA1, and ZIP
respectively. This allows a user to set algorithms to use in a
safe manner so they are used when legal to do so, without
forcing them on for all messages.
* New --primary-keyring option to designate the keyring that the
user wants new keys imported into.
* --s2k-digest-algo is now used for all password mangling.
Earlier versions used both --s2k-digest-algo and --digest-algo
for passphrase mangling.
* Handling of --hidden-recipient or --throw-keyid messages is now
easier - the user only needs to give their passphrase once, and
GnuPG will try it against all of the available secret keys.
* Care is taken to prevent compiler optimization from removing
memory wiping code.
* New option --no-mangle-dos-filenames so that filenames are not
truncated in the W32 version.
* A "convert-from-106" script has been added. This is a simple
script that automates the conversion from a 1.0.6 or earlier
version of GnuPG to a 1.0.7 or later version.
* Disabled keys are now skipped when selecting keys for
encryption. If you are using the --with-colons key listings to
detect disabled keys, please see doc/DETAILS for a minor format
change in this release.
* Minor trustdb changes to make the trust calculations match
common usage.
* New command "revuid" in the --edit-key menu to revoke a user ID.
This is a simpler interface to the old method (which still
works) of revoking the user ID self-signature.
* Status VALIDSIG does now also print the primary key's
fingerprint, as well as the signature version, pubkey algorithm,
hash algorithm, and signature class.
* Add read-only support for the SHA-256 hash, and optional
read-only support for the SHA-384 and SHA-512 hashes.
* New option --enable-progress-filter for use with frontends.
* DNS SRV records are used in HKP keyserver lookups to allow
administrators to load balance and select keyserver ports
automatically. This is as specified in
draft-shaw-openpgp-hkp-00.txt.
* When using the "keyid!" syntax during a key export, only that
specified key is exported. If the key in question is a subkey,
the primary key plus only that subkey is exported.
* configure --disable-xxx options to disable individual algorithms
at build time. This can be used to build a smaller gpg binary
for embedded uses where space is tight. See the README file for
the algorithms that can be used with this option, or use
--enable-minimal to build the smallest gpg possible (disables
all optional algorithms, disables keyserver access, and disables
photo IDs).
* The keyserver no-modify flag on a key can now be displayed and
modified.
* Note that the TIGER/192 digest algorithm is in the process of
being dropped from the OpenPGP standard. While this release of
GnuPG still contains it, it is disabled by default. To ensure
you will still be able to use your messages with future versions
of GnuPG and other OpenPGP programs, please do not use this
algorithm.
See-also: gnupg-announce/2003q2/000153.html
Noteworthy changes in version 1.3.1 (2002-11-12)
------------------------------------------------
* Trust signature support. This is based on the Maurer trust
model where a user can specify the trust level along with the
signature with multiple levels so users can delegate
certification ability to other users, possibly restricted by a
regular expression on the user ID. Note that full trust
signature support requires a regular expression parsing library.
The regexp code from glibc 2.3.1 is included for those platforms
that don't have working regexp functions available. The
configure option --disable-regex may be used to disable any
regular expression code, which will make GnuPG ignore any trust
signature with a regular expression included.
* Two new commands --hidden-recipient (-R) and --hidden-encrypt-to
encrypt to a user, but hide the identity of that user. This is
the same functionality as --throw-keyid, but can be used on a
per-user basis.
* Full algorithm names (e.g. "3DES", "SHA1", "ZIP") can now be
used interchangeably with the short algorithm names (e.g. "S2",
"H2", "Z1") anywhere algorithm names are used in GnuPG.
Noteworthy changes in version 1.3.0 (2002-10-18)
------------------------------------------------
* The last piece of internal keyserver support has been removed,
and now all keyserver access is done via the keyserver plugins.
There is also a newer keyserver protocol used between GnuPG and
the plugins, so plugins from earlier versions of GnuPG may not
work properly.
* The HKP keyserver plugin supports the new machine-readable key
listing format for those keyservers that provide it.
* When using a HKP keyserver with multiple DNS records (such as
wwwkeys.pgp.net which has the addresses of multiple servers
around the world), try all records until one succeeds. Note
that it depends on the LDAP library used whether the LDAP
keyserver plugin does this as well.
* The library dependencies for OpenLDAP seem to change fairly
frequently, and GnuPG's configure script cannot guess all the
combinations. Use ./configure LDAPLIBS="-L libdir -l libs" to
override the script and use the libraries selected.
* Secret keys generated with --export-secret-subkeys are now
indicated in key listings with a '#' after the "sec", and in
--with-colons listings by showing no capabilities (no lowercase
characters).
* --trusted-key has been un-obsoleted, as it is useful for adding
ultimately trusted keys from the config file. It is identical
to using --edit and "trust" to change a key to ultimately
trusted.
* Translations other than de are no longer distributed with the
development branch. This is due to the frequent text changes
during development, which cause the translations to rapidly go
out of date.
Version 1.2.8 (2006-12-07)
Version 1.2.7 (2004-12-27)
Version 1.2.6 (2004-08-25)
Version 1.2.5 (2004-07-26)
Version 1.2.4 (2003-12-23)
Version 1.2.3 (2003-08-21)
Version 1.2.2 (2003-05-01)
Version 1.2.1 (2002-10-25)
Version 1.2.0 (2002-09-21)
Noteworthy changes in version 1.1.92 (2002-09-11)
-------------------------------------------------
* [IMPORTANT] The default configuration file is now
~/.gnupg/gpg.conf. If an old ~/.gnupg/options is found it will
still be used. This change is required to have a more
consistent naming scheme with forthcoming tools.
* The use of MDCs have increased. A MDC will be used if the
recipients directly request it, if the recipients have AES,
AES192, AES256, or TWOFISH in their cipher preferences, or if
the chosen cipher has a blocksize not equal to 64 bits
(currently this is also AES, AES192, AES256, and TWOFISH).
* GnuPG will no longer automatically disable compression when
processing an already-compressed file unless a MDC is being
used. This is to give the message a certain amount of
resistance to the chosen-ciphertext attack while communicating
with other programs (most commonly PGP earlier than version 7.x)
that do not support MDCs.
* The option --interactive now has the desired effect when
importing keys.
* The file permission and ownership checks on files have been
clarified. Specifically, the homedir (usually ~/.gnupg) is
checked to protect everything within it. If the user specifies
keyrings outside this homedir, they are presumed to be shared
keyrings and therefore *not* checked. Configuration files
specified with the --options option and the IDEA cipher
extension specified with --load-extension are checked, along
with their enclosing directories.
* The configure option --with-static-rnd=auto allows to build gpg
with all available entropy gathering modules included. At
runtime the best usable one will be selected from the list
linux, egd, unix. This is also the default for systems lacking
a /dev/random device.
* The default character set is now taken from the current locale;
it can still be overridden by the --charset option. Using the
option -vvv shows the used character set.
* [REMOVED] --emulate-checksum-bug and --emulate-3des-s2k-bug have
been removed.
Noteworthy changes in version 1.1.91 (2002-08-04)
-------------------------------------------------
* All modules are now linked statically; the --load-extension
option is in general not useful anymore. The only exception is
to specify the deprecated idea cipher.
* The IDEA plugin has changed. Previous versions of the IDEA
plugin will no longer work with GnuPG. However, the current
version of the plugin will work with earlier GnuPG versions.
* When using --batch with one of the --delete-key commands, the
key must be specified by fingerprint. See the man page for
details.
* There are now various ways to restrict the ability GnuPG has to
exec external programs (for the keyserver helpers or photo ID
viewers). Read the README file for the complete list.
* New export option to leave off attribute packets (photo IDs)
during export. This is useful when exporting to HKP keyservers
which do not understand attribute packets.
* New import option to repair during import the HKP keyserver
mangling multiple subkeys bug. Note that this cannot completely
repair the damaged key as some crucial data is removed by the
keyserver, but it does at least give you back one subkey. This
is on by default for keyserver --recv-keys, and off by default
for regular --import.
* The keyserver helper programs now live in
/usr/[local/]libexec/gnupg by default. If you are upgrading
from 1.0.7, you might want to delete your old copies in
/usr/[local/]bin. If you use an OS that does not use libexec
for whatever reason, use configure --libexecdir=/usr/local/lib
to place the keyserver helpers there.
* The LDAP keyserver handler now works properly with very old
(version 1) LDAP keyservers.
Noteworthy changes in version 1.1.90 (2002-07-01)
-------------------------------------------------
* New commands: --personal-cipher-preferences,
--personal-digest-preferences, and
--personal-compress-preferences allow the user to specify which
algorithms are to be preferred. Note that this does not permit
using an algorithm that is not present in the recipient's
preferences (which would violate the OpenPGP standard). This
just allows sorting the preferences differently.
* New "group" command to refer to several keys with one name.
* A warning is issued if the user forces the use of an algorithm
that is not listed in the recipient's preferences.
* Full revocation key (aka "designated revoker") support.
* The preferred hash algorithms on a key are consulted when
encrypting a signed message to that key. Note that this is
disabled by default by a SHA1 preference in
--personal-digest-preferences.
* --cert-digest-algo allows the user to specify the hash algorithm
to use when signing a key rather than the default SHA1 (or MD5
for PGP2 keys). Do not use this feature unless you fully
understand the implications of this.
* --pgp7 mode automatically sets all necessary options to ensure
that the resulting message will be usable by a user of PGP 7.x.
* New --attribute-fd command for frontends and scripts to get the
contents of attribute packets (i.e. photos)
* In expert mode, the user can now re-sign a v3 key with a v4
self-signature. This does not change the v3 key into a v4 key,
but it does allow the user to use preferences, primary ID flags,
etc.
* Significantly improved photo ID support on non-unixlike
platforms.
* The version number has jumped ahead to 1.1.90 to skip over the
old version 1.1 and to get ready for the upcoming 1.2.
* ElGamal sign and encrypt is not anymore allowed in the key
generation dialog unless in expert mode. RSA sign and encrypt
has been added with the same restrictions.
* [W32] Keyserver access does work with Windows NT.
Noteworthy changes in version 1.0.7 (2002-04-29)
------------------------------------------------
* Secret keys are now stored and exported in a new format which
uses SHA-1 for integrity checks. This format renders the
Rosa/Klima attack useless. Other OpenPGP implementations might
not yet support this, so the option --simple-sk-checksum creates
the old vulnerable format.
* The default cipher algorithm for encryption is now CAST5,
default hash algorithm is SHA-1. This will give us better
interoperability with other OpenPGP implementations.
* Symmetric encrypted messages now use a fixed file size if
possible. This is a tradeoff: it breaks PGP 5, but fixes PGP 2,
6, and 7. Note this was only an issue with RFC-1991 style
symmetric messages.
* Photographic user ID support. This uses an external program to
view the images.
* Enhanced keyserver support via keyserver "plugins". GnuPG comes
with plugins for the NAI LDAP keyserver as well as the HKP email
keyserver. It retains internal support for the HKP HTTP
keyserver.
* Nonrevocable signatures are now supported. If a user signs a
key nonrevocably, this signature cannot be taken back so be
careful!
* Multiple signature classes are usable when signing a key to
specify how carefully the key information (fingerprint, photo
ID, etc) was checked.
* --pgp2 mode automatically sets all necessary options to ensure
that the resulting message will be usable by a user of PGP 2.x.
* --pgp6 mode automatically sets all necessary options to ensure
that the resulting message will be usable by a user of PGP 6.x.
* Signatures may now be given an expiration date. When signing a
key with an expiration date, the user is prompted whether they
want their signature to expire at the same time.
* Revocation keys (designated revokers) are now supported if
present. There is currently no way to designate new keys as
designated revokers.
* Permissions on the .gnupg directory and its files are checked
for safety.
* --expert mode enables certain silly things such as signing a
revoked user id, expired key, or revoked key.
* Some fixes to build cleanly under Cygwin32.
* New tool gpgsplit to split OpenPGP data formats into packets.
* New option --preserve-permissions.
* Subkeys created in the future are not used for encryption or
signing unless the new option --ignore-valid-from is used.
* Revoked user-IDs are not listed unless signatures are listed too
or we are in verbose mode.
* There is no default comment string with ascii armors anymore
except for revocation certificates and --enarmor mode.
* The command "primary" in the edit menu can be used to change the
primary UID, "setpref" and "updpref" can be used to change the
preferences.
* Fixed the preference handling; since 1.0.5 they were erroneously
matched against against the latest user ID and not the given one.
* RSA key generation.
* Merged Stefan's patches for RISC OS in. See comments in
scripts/build-riscos.
* It is now possible to sign and conventional encrypt a message (-cs).
* The MDC feature flag is supported and can be set by using
the "updpref" edit command.
* The status messages GOODSIG and BADSIG are now returning the primary
UID, encoded using %XX escaping (but with spaces left as spaces,
so that it should not break too much)
* Support for GDBM based keyrings has been removed.
* The entire keyring management has been revamped.
* The way signature stati are store has changed so that v3
signatures can be supported. To increase the speed of many
operations for existing keyrings you can use the new
--rebuild-keydb-caches command.
* The entire key validation process (trustdb) has been revamped.
See the man page entries for --update-trustdb, --check-trustdb
and --no-auto-check-trustdb.
* --trusted-keys is again obsolete, --edit can be used to set the
ownertrust of any key to ultimately trusted.
* A subkey is never used to sign keys.
* Read only keyrings are now handled as expected.
See-also: gnupg-announce/2002q2/000135.html
Noteworthy changes in version 1.0.6 (2001-05-29)
------------------------------------------------
* Security fix for a format string bug in the tty code.
* Fixed format string bugs in all PO files.
* Removed Russian translation due to too many bugs. The FTP
server has an unofficial but better translation in the contrib
directory.
* Fixed expire time calculation and keyserver access.
* The usual set of minor bug fixes and enhancements.
* non-writable keyrings are now correctly handled.
See-also: gnupg-announce/2001q2/000123.html
Noteworthy changes in version 1.0.5 (2001-04-29)
------------------------------------------------
* WARNING: The semantics of --verify have changed to address a
problem with detached signature detection. --verify now ignores
signed material given on stdin unless this is requested by using
a "-" as the name for the file with the signed material. Please
check all your detached signature handling applications and make
sure that they don't pipe the signed material to stdin without
using a filename together with "-" on the the command line.
* WARNING: Corrected hash calculation for input data larger than
512M - it was just wrong, so you might notice bad signature in
some very big files. It may be wise to keep an old copy of
GnuPG around.
* Secret keys are no longer imported unless you use the new option
--allow-secret-key-import. This is a kludge and future versions will
handle it in another way.
* New command "showpref" in the --edit-key menu to show an easier
to understand preference listing.
* There is now the notation of a primary user ID. For example, it
is printed with a signature verification as the first user ID;
revoked user IDs are not printed there anymore. In general the
primary user ID is the one with the latest self-signature.
* New --charset=utf-8 to bypass all internal conversions.
* Large File Support (LFS) is now working.
* New options: --ignore-crc-error, --no-sig-create-check,
--no-sig-cache, --fixed-list-mode, --no-expensive-trust-checks,
--enable-special-filenames and --use-agent. See man page.
* New command --pipemode, which can be used to run gpg as a
co-process. Currently only the verification of detached
signatures are working. See doc/DETAILS.
* Keyserver support for the W32 version.
* Rewritten key selection code so that GnuPG can better cope with
multiple subkeys, expire dates and so. The drawback is that it
is slower.
* A whole lot of bug fixes.
* The verification status of self-signatures are now cached. To
increase the speed of key list operations for existing keys you
can do the following in your GnuPG homedir (~/.gnupg):
cp pubring.gpg pubring.gpg.save && gpg --export-all >x && \
rm pubring.gpg && gpg --import x
Only v4 keys (i.e not the old RSA keys) benefit from this caching.
* New translations: Estonian, Turkish.
See-also: gnupg-announce/2001q2/000122.html
Noteworthy changes in version 1.0.4 (2000-10-17)
------------------------------------------------
* Fixed a serious bug which could lead to false signature verification
results when more than one signature is fed to gpg. This is the
primary reason for releasing this version.
* New utility gpgv which is a stripped down version of gpg to
be used to verify signatures against a list of trusted keys.
* Rijndael (AES) is now supported and listed with top preference.
* --with-colons now works with --print-md[s].
See-also: gnupg-announce/2000q4/000082.html
Noteworthy changes in version 1.0.3 (2000-09-18)
------------------------------------------------
* Fixed problems with piping to/from other MS-Windows software
* Expiration time of the primary key can be changed again.
* Revoked user IDs are now marked in the output of --list-key
* New options --show-session-key and --override-session-key
to help the British folks to somewhat minimize the danger
of this Orwellian RIP bill.
* New options --merge-only and --try-all-secrets.
* New configuration option --with-egd-socket.
* The --trusted-key option is back after it left us with 0.9.5
* RSA is supported. Key generation does not yet work but will come
soon.
* CAST5 and SHA-1 are now the default algorithms to protect the key
and for symmetric-only encryption. This should solve a couple
of compatibility problems because the old algorithms are optional
according to RFC2440
* Twofish and MDC enhanced encryption is now used. PGP 7 supports
this. Older versions of GnuPG don't support it, so they should be
upgraded to at least 1.0.2
See-also: gnupg-announce/2000q3/000075.html
Noteworthy changes in version 1.0.2 (2000-07-12)
----------------------------------------------
* Fixed expiration handling of encryption keys.
* Add an experimental feature to do unattended key generation.
* The user is now asked for the reason of revocation as required
by the new OpenPGP draft.
* There is a ~/.gnupg/random_seed file now which saves the
state of the internal RNG and increases system performance
somewhat. This way the full entropy source is only used in
cases were it is really required.
Use the option --no-random-seed-file to disable this feature.
* New options --ignore-time-conflict and --lock-never.
* Some fixes for the W32 version.
* The entropy.dll is not anymore used by the W32 version but replaced
by code derived from Cryptlib.
* Encryption is now much faster: About 2 times for 1k bit keys
and 8 times for 4k keys.
* New encryption keys are generated in a way which allows a much
faster decryption.
* New command --export-secret-subkeys which outputs the
the _primary_ key with it's secret parts deleted. This is
useful for automated decryption/signature creation as it
allows to keep the real secret primary key offline and
thereby protecting the key certificates and allowing to
create revocations for the subkeys. See the FAQ for a
procedure to install such secret keys.
* Keygeneration now writes to the first writeable keyring or
as default to the one in the homedirectory. Prior versions
ignored all --keyring options.
* New option --command-fd to take user input from a file descriptor;
to be used with --status-fd by software which uses GnuPG as a backend.
* There is a new status PROGRESS which is used to show progress during
key generation.
* Support for the new MDC encryption packets. To create them either
--force-mdc must be use or cipher algorithm with a blocksize other
than 64 bits is to be used. --openpgp currently disables MDC packets
entirely. This option should not yet be used.
* New option --no-auto-key-retrieve to disable retrieving of
a missing public key from a keyserver, when a keyserver has been set.
* Danish translation
See-also: gnupg-announce/2000q3/000069.html
Noteworthy changes in version 1.0.1 (1999-12-16)
-----------------------------------
* New command --verify-files. New option --fast-list-mode.
* $http_proxy is now used when --honor-http-proxy is set.
* Fixed some minor bugs and the problem with conventional encrypted
packets which did use the gpg v3 partial length headers.
* Add Indonesian and Portuguese translations.
* Fixed a bug with symmetric-only encryption using the non-default 3DES.
The option --emulate-3des-s2k-bug may be used to decrypt documents
which have been encrypted this way; this should be done immediately
as this workaround will be remove in 1.1
* Can now handle (but not display) PGP's photo IDs. I don't know the
format of that packet but after stripping a few bytes from the start
it looks like a JPEG (at least my test data). Handling of this
package is required because otherwise it would mix up the
self signatures and you can't import those keys.
* Passing non-ascii user IDs on the commandline should now work in all
cases.
* New keys are now generated with an additional preference to Blowfish.
* Removed the GNU Privacy Handbook from the distribution as it will go
into a separate one.
See-also: gnupg-announce/1999q4/000050.html
Noteworthy changes in version 1.0.0 (1999-09-07)
-----------------------------------
* Add a very preliminary version of the GNU Privacy Handbook to
the distribution (lynx doc/gph/index.html).
* Changed the version number to GnuPG 2001 ;-)
See-also: gnupg-announce/1999q3/000037.html
Noteworthy changes in version 0.9.11 (1999-09-03)
------------------------------------
* UTF-8 strings are now correctly printed (if --charset is set correctly).
Output of --with-colons remains C-style escaped UTF-8.
* Workaround for a problem with PGP 5 detached signature in textmode.
* Fixed a problem when importing new subkeys (duplicated signatures).
See-also: gnupg-announce/1999q3/000036.html
Noteworthy changes in version 0.9.10 (1999-07-23)
------------------------------------
* Some strange new options to help pgpgpg
* Cleaned up the dox a bit.
See-also: gnupg-announce/1999q3/000034.html
Noteworthy changes in version 0.9.9
-----------------------------------
* New options --[no-]utf8-strings.
* New edit-menu commands "enable" and "disable" for entire keys.
* You will be asked for a filename if gpg cannot deduce one.
* Changes to support libtool which is needed for the development
of libgcrypt.
* New script tools/lspgpot to help transferring assigned
trustvalues from PGP to GnuPG.
* New commands --lsign-key and made --sign-key a shortcut for --edit
and sign.
* New options (#122--126 ;-) --[no-]default-recipient[-self],
--disable-{cipher,pubkey}-algo. See the man page.
* Enhanced info output in case of multiple recipients and fixed exit code.
* New option --allow-non-selfsigned-uid to work around a problem with
the German IN way of separating signing and encryption keys.
See-also: gnupg-announce/1999q3/000028.html
Noteworthy changes in version 0.9.8 (1999-06-26)
-----------------------------------
* New subcommand "delsig" in the edit menu.
* The name of the output file is not anymore the one which is
embedded in the processed message, but the used filename with
the extension stripped. To revert to the old behaviour you can
use the option --use-embedded-filename.
* Another hack to cope with pgp2 generated detached signatures.
* latin-2 character set works (--charset=iso-8859-2).
* New option --with-key-data to list the public key parameters.
New option -N to insert notations and a --set-policy-url.
A couple of other options to allow resetting of options.
* Better support for HPUX.
See-also: gnupg-announce/1999q2/000016.html
Noteworthy changes in version 0.9.7 (1999-05-23)
-----------------------------------
* Add some work arounds for a bugs in pgp 2 which led to bad signatures
when used with canonical texts in some cases.
* Enhanced some status outputs.
See-also: gnupg-announce/1999q2/000000.html
Noteworthy changes in version 0.9.6 (1999-05-06)
-----------------------------------
* Twofish is now statically linked by default. The experimental 128 bit
version is now disabled. Full support will be available as soon as
the OpenPGP WG has decided on an interpretation of rfc2440.
* Dropped support for the ancient Blowfish160 which is not OpenPGP.
* Merged gpgm and gpg into one binary.
* Add "revsig" and "revkey" commands to the edit menu. It is now
possible to revoke signature and subkeys.
Noteworthy changes in version 0.9.5 (1999-03-20)
-----------------------------------
* New command "lsign" in the keyedit menu to create non-exportable
signatures. Removed --trusted-keys option.
* A bunch of changes to the key validation code.
* --list-trust-path now has an optional --with-colons format.
* New command --recv-keys to import keys from an keyserver.
Noteworthy changes in version 0.9.4 (1999-03-08)
-----------------------------------
* New configure option --enable-static-rnd=[egd|linux|unix|none]
to select a random gathering module for static linking.
* The original text is now verbatim copied to a cleartext signed message.
* Bugfixes but there are still a couple of bugs.
Noteworthy changes in version 0.9.3 (1999-02-19)
-----------------------------------
* Changed the internal design of getkey which now allows a
efficient lookup of multiple keys and add a word match mode.
* New options --[no-]encrypt-to.
* Some changes to the configure stuff. Switched to automake 1.4.
Removed intl/ from CVS, autogen.sh now uses gettextize.
* Preferences now include Twofish. Removed preference to Blowfish with
a special hack to suppress the "not listed in preferences" warning;
this is to allow us to switch completely to Twofish in the near future.
* Changed the locking stuff.
* Print all user ids of a good signature.
Noteworthy changes in version 0.9.2 (1999-01-01)
-----------------------------------
* add some additional time warp checks.
* Option --keyserver and command --send-keys to utilize HKP servers.
* Upgraded to zlib 1.1.3 and fixed an inflate bug
* More cleanup on the cleartext signatures.
Noteworthy changes in version 0.9.1 (1999-01-01)
-----------------------------------
* Polish language support.
* When querying the passphrase, the key ID of the primary key is
displayed along with the one of the used secondary key.
* Fixed a bug occurring when decrypting pgp 5 encrypted messages,
fixed an infinite loop bug in the 3DES code and in the code
which looks for trusted signatures.
* Fixed a bug in the mpi library which caused signatures not to
compare okay.
* Rewrote the handling of cleartext signatures; the code is now
better maintainable (I hope so).
* New status output VALIDSIG only for valid signatures together
with the fingerprint of the signer's key.
Noteworthy changes in version 0.9.0 (1998-12-23)
-----------------------------------
* --export does now only exports rfc2440 compatible keys; the
old behaviour is available with --export-all.
Generation of v3 ElGamal (sign and encrypt) keys is not longer
supported.
* Fixed the uncompress bug.
* Rewrote the rndunix module. There are two environment variables
used for debugging now: GNUPG_RNDUNIX_DBG give the file to write
debugging information (use "-" for stdout) and if GNUPG_RNDUNIX_DBGALL
is set, all programs which are only tried are also printed.
* New option --escape-from-lines to "dash-escape" "From " lines to
prevent mailers to change them to ">From ". This is not enabled by
default because it is not in compliance with rfc2440 - however, you
should turn it on.
Noteworthy changes in version 0.4.5 (1998-12-08)
-----------------------------------
* The keyrings and the trustdb is now locked, so that
other GnuPG processes won't damage these files. You
may want to put the option --lock-once into your options file.
* The latest self-signatures are now used; this enables --import
to see updated preferences etc.
* Import of subkeys should now work.
* Random gathering modules may now be loaded as extensions. Add
such a module for most Unices but it is very experimental!
* Brazilian language support.
Noteworthy changes in version 0.4.4 (1998-11-20)
-----------------------------------
* Fixed the way the key expiration time is stored. If you have
an expiration time on your key you should fix it with --edit-key
and the command "expire". I apologize for this inconvenience.
* Add option --charset to support "koi8-r" encoding of user ids.
(Not yet tested).
* Preferences should now work again. You should run
"gpgm --check-trustdb \*" to rebuild all preferences.
* Checking of certificates should now work but this needs a lot
of testing. Key validation values are now cached in the
trustdb; they should be recalculated as needed, but you may
use --check-trustdb or --update-trustdb to do this.
* Spanish translation by Urko Lusa.
* Patch files are from now on signed. See the man page
for the new option --not-dash-escaped.
* New syntax: --edit-key <userID> [<commands>]
If you run it without --batch the commands are executed and then
you are put into normal mode unless you use "quit" or "save" as
one of the commands. When in batch mode, the program quits after
the last command, so you have to use "save" if you did some changes.
It does not yet work completely, but may be used to list so the
keys etc.
Noteworthy changes in version 0.4.3 (1998-11-08)
-----------------------------------
* Fixed the gettext configure bug.
* Kludge for RSA keys: keyid and length of a RSA key are
correctly reported, but you get an error if you try to use
this key (If you do not have the non-US version).
* Experimental support for keyrings stored in a GDBM database.
This is *much* faster than a standard keyring. You will notice
that the import gets slower with time; the reason is that all
new keys are used to verify signatures of previous inserted
keys. Use "--keyring gnupg-gdbm:<name-of-gdbm-file>". This is
not (yet) supported for secret keys.
* A Russian language file in the distribution (alternatives are in
the contrib directory of the FTP servers)
* commandline option processing now works as expected for GNU programs
with the exception that you can't mix options and normal arguments.
* Now --list-key lists all matching keys. This is needed in some
other places too.
Noteworthy changes in version 0.4.2 (1998-10-18)
-----------------------------------
* This is only a snapshot: There are still a few bugs.
* Fixed this huge memory leak.
* Redesigned the trust database: You should run "gpgm --check-trustdb".
New command --update-trustdb, which adds new key from the public
keyring into your trustdb
* Fixed a bug in the armor code, leading to invalid packet errors.
(a workaround for this was to use --no-armor). The shorten line
length (64 instead of 72) fixes a problem with pgp5 and keyservers.
* comment packets are not anymore generated. "--export" filters
them out. One Exception: The comment packets in a secret keyring
are still used because they carry the factorization of the public
prime product.
* --import now only looks for KEYBLOCK headers, so you can now simply
remove the "- " in front of such a header if someone accidentally signed
such a message or the keyblock is part of a cleartext signed message.
* --with-colons now lists the key expiration time and not anymore
the valid period.
* Some keyblocks created with old releases have a wrong sequence
of packets, so that the keyservers don't accept these keys.
Simply using "--edit-key" fixes the problem.
* New option --force-v3-sigs to generate signed messages which are
compatible to PGP 5.
* Add some code to support DLD (for non ELF systems) - but this is
not tested because my BSD box is currently broken.
* New command "expire" in the edit-key menu.
Noteworthy changes in version 0.4.1 (1998-10-07)
-----------------------------------
* A secondary key is used when the primary key is specified but cannot
be used for the operation (if it is a sign-only key).
* GNUPG can now handle concatenated armored messages: There is still a
bug if different kinds of messages are mixed.
* Iterated+Salted passphrases now work. If want to be sure that PGP5
is able to handle them you may want to use the options
"--s2k-mode 3 --s2k-cipher-algo cast5 --s2k-digest-algo sha1"
when changing a passphrase.
* doc/OpenPGP talks about OpenPGP compliance, doc/HACKING gives
a few hints about the internal structure.
* Checked gnupg against the August 1998 draft (07) and I believe
it is in compliance with this document (except for one point).
* Fixed some bugs in the import merging code and rewrote some
code for the trustdb.
Noteworthy changes in version 0.4.0 (1998-09-18)
-----------------------------------
* Triple DES is now supported. Michael Roth did this piece of
needed work. We have now all the coded needed to be OpenPGP
compliant.
* Added a simple rpm spec file (see INSTALL).
* detached and armored signatures are now using "PGP SIGNATURE",
except when --rfc1991 is used.
* All times which are not in the yyyy-mm-dd format are now printed
in local time.
Noteworthy changes in version 0.3.5 (1998-09-14)
-----------------------------------
* New option --throw-keyid to create anonymous enciphered messages.
If gpg detects such a message it tires all available secret keys
in turn so decode it. This is a gnupg extension and not in OpenPGP
but it has been discussed there and afaik some products use this
scheme too (Suggested by Nimrod Zimmerman).
* Fixed a bug with 5 byte length headers.
* --delete-[secret-]key is now also available in gpgm.
* cleartext signatures are not anymore converted to LF only.
* Fixed a trustdb problem. Run "gpgm --check-trustdb" to fix old
trust dbs.
* Building in another directory should now work.
* Weak key detection mechanism (Niklas Hernaeus).
Noteworthy changes in version 0.3.4 (1998-08-11)
-----------------------------------
* New options --comment and --set-filename; see g10/OPTIONS
* yes/no, y/n localized.
* Fixed some bugs.
Noteworthy changes in version 0.3.3 (1998-08-08)
-----------------------------------
* IMPORTANT: I found yet another bug in the way the secret keys
are encrypted - I did it the way pgp 2.x did it, but OpenPGP
and pgp 5.x specify another (in some aspects simpler) method.
To convert your secret keys you have to do this:
1. Build the new release but don't install it and keep
a copy of the old program.
2. Disable the network, make sure that you are the only
user, be sure that there are no Trojan horses etc ....
3. Use your old gpg (version 0.3.1 or 0.3.2) and set the
passphrases of ALL your secret keys to empty!
(gpg --change-passphrase your-user-id).
4. Save your ownertrusts (see the next point)
5. rm ~/.gnupg/trustdb.gpg
6. install the new version of gpg (0.3.3)
7. For every secret key call "gpg --edit-key your-user-id",
enter "passwd" at the prompt, follow the instructions and
change your password back, enter "save" to store it.
8. Restore the ownertrust (see next point).
* The format of the trust database has changed; you must delete
the old one, so gnupg can create a new one.
IMPORTANT: Use version 0.3.1 or .2 to save your assigned ownertrusts
("gpgm --list-ownertrust >saved-trust"); then build this new version
and restore the ownertrust with this new version
("gpgm --import-ownertrust saved-trust"). Please note that
--list-ownertrust has been renamed to --export-ownertrust in this
release and it does now only export defined ownertrusts.
* The command --edit-key now provides a commandline driven menu
which can be used for various tasks. --sign-key is only an
an alias to --edit-key and maybe removed in future: use the
command "sign" of this new menu - you can select which user ids
you want to sign.
* Alternate user ids can now be created an signed.
* Owner trust values can now be changed with --edit-key (trust)
* GNUPG can now run as a coprocess; this enables sophisticated
frontends. tools/shmtest.c is a simple sample implementation.
This needs some more work: all tty_xxx() are to be replaced
by cpr_xxx() and some changes in the display logics is needed.
* Removed options --gen-prime and --gen-random.
* Removed option --add-key; use --edit-key instead.
* Removed option --change-passphrase; use --edit-key instead.
* Signatures are now checked even if the output file could not
be created. Command "--verify" tries to find the detached data.
* gpg now disables core dumps.
* compress and symmetric cipher preferences are now used.
Because there is no 3DES yet, this is replaced by Blowfish.
* We have added the Twofish as an experimental cipher algorithm.
Many thanks to Matthew Skala for doing this work.
Twofish is the AES submission from Schneier et al.; see
"www.counterpane.com/twofish.html" for more information.
* Started with a help system: If you enter a question mark at some
prompt; you should get a specific help for this prompt.
* There is no more backup copy of the secret keyring.
* A lot of new bugs. I think this release is not as stable as
the previous one.
Noteworthy changes in version 0.3.2 (1998-07-09)
-----------------------------------
* Fixed some bugs when using --textmode (-seat)
* Now displays the trust status of a positive verified message.
* Keyrings are now scanned in the sequence they are added with
--[secret-]keyring. Note that the default keyring is implicitly
added as the very first one unless --no-default-keyring is used.
* Fixed setuid and dlopen bug.
Noteworthy changes in version 0.3.1 (1998-07-06)
-----------------------------------
* Partial headers are now written in the OpenPGP format if
a key in a v4 packet is used.
* Removed some unused options, removed the gnupg.sig stuff.
* Key lookup by name now returns a key which can be used for
the desired action.
* New options --list-ownertrust (gpgm) to make a backup copy
of the ownertrust values you assigned.
* clear signature headers are now in compliance with OpenPGP.
Noteworthy changes in version 0.3.0 (1998-06-25)
-----------------------------------
* New option --emulate-checksum-bug. If your passphrase does not
work anymore, use this option and --change-passphrase to rewrite
your passphrase.
* More complete v4 key support: Preferences and expiration time
is set into the self signature.
* Key generation defaults to DSA/ElGamal keys, so that new keys are
interoperable with pgp5
* DSA key generation is faster and key generation does not anymore
remove entropy from the random generator (the primes are public
parameters, so there is really no need for a cryptographic secure
prime number generator which we had used).
* A complete new structure for representing the key parameters.
* Removed most public key knowledge into the cipher library.
* Support for dynamic loading of new algorithms.
* Moved tiger to an extension module.
Noteworthy changes in version 0.2.19 (1998-05-29)
------------------------------------
* Replaced /dev/urandom in checks with new tool mk-tdata.
* Some assembler file cleanups; some more functions for the Alpha.
* Tiger has now the OpenPGP assigned number 6. Because the OID has
changed, old signatures using this algorithm can't be verified.
* gnupg now encrypts the compressed packed and not any longer in the
reverse order; anyway it can decrypt both versions. Thanks to Tom
for telling me this (not security related) bug.
* --add-key works and you are now able to generate subkeys.
* It is now possible to generate ElGamal keys in v4 packets to create
valid OpenPGP keys.
* Some new features for better integration into MUAs.
Noteworthy changes in version 0.2.18 (1998-05-15)
------------------------------------
* Split cipher/random.c, add new option "--disable-dev-random"
to configure to support the development of a random source for
other systems. Prepared sourcefiles rand-unix.c, rand-w32.c
and rand-dummy.c (which is used to allow compilation on systems
without a random source).
* Fixed a small bug in the key generation (it was possible that 48 bits
of a key were not taken from the random pool)
* Add key generation for DSA and v4 signatures.
* Add a function trap_unaligned(), so that a SIGBUS is issued on
Alphas and not the slow emulation code is used. And success: rmd160
raised a SIGBUS.
* Enhanced the formatting facility of argparse and changed the use of
\r,\v to @ because gettext does not like it.
* New option "--compress-algo 1" to allow the creation of compressed
messages which are readable by PGP and "--print-md" (gpgm) to make
speed measurement easier.
Noteworthy changes in version 0.2.17 (1998-05-04)
------------------------------------
* Comment packets are now of private type 61.
* Passphrase code still used a 160 bit blowfish key, added a
silly workaround. Please change your passphrase again - sorry.
* Conventional encryption now uses a type 3 packet to describe the
used algorithms.
* The new algorithm number for Blowfish is 20, 16 is still used for
encryption only; for signing it is only used when it is in a v3 packet,
so that GNUPG keys are still valid.
Noteworthy changes in version 0.2.16 (1998-04-28)
------------------------------------
* Add experimental support for the TIGER/192 message digest algorithm.
(But there is only a dummy ASN OID).
* Standard cipher is now Blowfish with 128 bit key in OpenPGP's CFB
mode. I renamed the old cipher to Blowfish160. Because the OpenPGP
group refused to assign me a number for Blowfish160, I have to
drop support for this in the future. You should use
"--change-passphrase" to recode your current passphrase with 128
bit Blowfish.
Noteworthy changes in version 0.2.15 (1998-04-09)
------------------------------------
* Fixed a bug with the old checksum calculation for secret keys.
If you run the program without --batch, a warning does inform
you if your secret key needs to be converted; simply use
--change-passphrase to recalculate the checksum. Please do this
soon, as the compatible mode will be removed sometime in the future.
* CAST5 works (using the PGP's special CFB mode).
* Again somewhat more PGP 5 compatible.
* Some new test cases
Noteworthy changes in version 0.2.14 (1998-04-02)
------------------------------------
* Changed the internal handling of keyrings.
* Add support to list PGP 5 keyrings with subkeys
* Timestamps of signatures are now verified.
* A expiration time can now be specified during key generation.
* Some speedups for Blowfish and SHA-1, rewrote SHA-1 transform.
Reduced the amount of random bytes needed for key generation in
some cases.
Noteworthy changes in version 0.2.13 (1998-03-10)
------------------------------------
* Verify of DSA signatures works.
* Re-implemented the slower random number generator.
Noteworthy changes in version 0.2.12 (1998-03-07)
------------------------------------
* --delete-key checks that there is no secret key. The new
option --delete-secret-key maybe used to delete a secret key.
* "-kv" now works as expected. Options "--list-{keys,sigs]"
and "--check-sigs" are now working.
* New options "--verify" and "--decrypt" to better support integration
into MUAs (partly done for Mutt).
* New option "--with-colons" to make parsing of key lists easier.
Noteworthy changes in version 0.2.11 (1998-03-02)
------------------------------------
* GPG now asks for a recipient's name if option "-r" is not used.
* If there is no good trust path, the program asks whether to use
the public keys anyway.
* "--delete-key" works for public keys. What semantics shall I use
when there is a secret key too? Delete the secret key or leave him
and auto-regenerate the public key, next time the secret key is used?
Noteworthy changes in version 0.2.10 (1998-02-27)
------------------------------------
* Code for the alpha is much faster (about 20 times); the data
was misaligned and the kernel traps this, so nearly all time
was used by system to trap the misalignments and to write
syslog messages. Shame on me and thanks to Ralph for
pointing me at this while drinking some beer yesterday.
* Changed some configure options and add an option
--disable-m-guard to remove the memory checking code
and to compile everything with optimization on.
* New environment variable GNUPGHOME, which can be used to set
another homedir than ~/.gnupg. Changed default homedir for
Windoze version to c:/gnupg.
* Fixed detached signatures; detached PGP signatures caused a SEGV.
* The Windoze version works (as usual w/o a strong RNG).
Noteworthy changes in version 0.2.9 (1998-02-26)
-----------------------------------
* Fixed FreeBSD bug.
* Added a simple man page.
* Switched to automake1.2f and a newer gettext.
Noteworthy changes in version 0.2.8 (1998-02-24)
-----------------------------------
* Changed the name to GNUPG, the binaries are called gpg and gpgm.
You must rename rename the directory "~/.g10" to ~/.gnupg/, rename
{pub,sec}ring.g10 to {pub,sec}ring.gpg, trustdb.g10 to trustdb.gpg
and g10.sig to gnupg.sig.
* New or changed passphrases are now salted.
Noteworthy changes in version 0.2.7 (1998-02-18)
-----------------------------------
* New command "gen-revoke" to create a key revocation certificate.
* New option "homedir" to set the homedir (which defaults to "~/.g10").
This directory is created if it does not exists (only the last
part of the name and not the complete hierarchy)
* Command "import" works. (Try: "finger gcrypt@ftp.guug.de|g10 --import")
* New commands "dearmor/enarmor" for g10maint. These are mainly
used for internal test purposes.
* Option --version now conforming to the GNU standards and lists
the available ciphers, message digests and public key algorithms.
* Assembler code for m68k (not tested).
* "make check" works.
Noteworthy changes in version 0.2.6 (1998-02-13)
-----------------------------------
* Option "--export" works.
Noteworthy changes in version 0.2.5 (1998-02-12)
-----------------------------------
* Added zlib for systems which don't have it.
Use "./configure --with-zlib" to link with the static version.
* Generalized some more functions and rewrote the encoding of
message digests into MPIs.
* Enhanced the checkit script
Noteworthy changes in version 0.2.4 (1998-02-11)
-----------------------------------
* nearly doubled the speed of the ElGamal signature verification.
* backup copies of keyrings are created.
* assembler stuff for Pentium; gives about 15% better performance.
* fixed a lot of bugs.
Noteworthy changes in version 0.2.3 (1998-02-09)
-----------------------------------
* Found a bug in the calculation of ELG fingerprints. This is now
fixed, but all existing fingerprints and keyids for ELG keys
are not any more valid.
* armor should now work; including clear signed text.
* moved some options to the new program g10maint
* It's now 64 bit clean and runs fine on an alpha--linux.
* Key generation is much faster now. I fixed this by using not
so strong random number for the primes (this was a bug because the
ElGamal primes are public parameters and it does not make sense
to generate them from strong random). The real secret is the x value
which is still generated from strong (okay: /dev/random) random bits.
* added option "--status-fd": see g10/OPTIONS
* We have secure memory on systems which support mlock().
It is not complete yet, because we do not have signal handler
which does a cleanup in very case.
We should also check the ulimit for the user in the case
that the admin does not have set a limit on locked pages.
* started with internationalization support.
* The logic to handle the web of trust is now implemented. It is
has some bugs; but I'm going to change the algorithm anyway.
It works by calculating the trustlevel on the fly. It may ask
you to provide trust parameters if the calculated trust probability
is too low. I will write a paper which discusses this new approach.
* a couple of changes to the configure script.
* New option "--quick-random" which uses a much quicker random
number generator. Keys generated while this option is in effect
are flags with "INSECURE!" in the user-id. This is a development
only option.
* Read support for new version packets (OpenPGP).
* Comment packets are now of correct OpenPGP type 16. Old comment
packets written by G10 are detected because they always start with
a hash which is an invalid version byte.
* The string "(INSECURE!)" is appended to a new user-id if this
is generated on a system without a good random number generator.
Version 0.2.2 (1998-02-09)
Version 0.2.1 (1998-01-28)
Version 0.2.0 (1998-01-25)
Version 0.1.3 (1998-01-12)
Version 0.1.2 (1998-01-07)
Version 0.1.1 (1998-01-07)
Version 0.1.0 (1998-01-05)
Version 0.0.0 (1997-12-20)
Copyright (C) 1998-2017 Free Software Foundation, Inc.
Copyright (C) 1997-2017 Werner Koch
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/agent/agent.h b/agent/agent.h
index 0f804cd8b..84e5e782b 100644
--- a/agent/agent.h
+++ b/agent/agent.h
@@ -1,620 +1,639 @@
/* agent.h - Global definitions for the agent
* Copyright (C) 2001, 2002, 2003, 2005, 2011 Free Software Foundation, Inc.
* Copyright (C) 2015 g10 Code GmbH.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#ifndef AGENT_H
#define AGENT_H
#ifdef GPG_ERR_SOURCE_DEFAULT
#error GPG_ERR_SOURCE_DEFAULT already defined
#endif
#define GPG_ERR_SOURCE_DEFAULT GPG_ERR_SOURCE_GPGAGENT
#include <gpg-error.h>
#define map_assuan_err(a) \
map_assuan_err_with_source (GPG_ERR_SOURCE_DEFAULT, (a))
#include <errno.h>
#include <gcrypt.h>
#include "../common/util.h"
#include "../common/membuf.h"
#include "../common/sysutils.h" /* (gnupg_fd_t) */
#include "../common/session-env.h"
#include "../common/shareddefs.h"
/* To convey some special hash algorithms we use algorithm numbers
reserved for application use. */
#ifndef GCRY_MODULE_ID_USER
#define GCRY_MODULE_ID_USER 1024
#endif
#define MD_USER_TLS_MD5SHA1 (GCRY_MODULE_ID_USER+1)
/* Maximum length of a digest. */
#define MAX_DIGEST_LEN 64
/* The maximum length of a passphrase (in bytes). Note: this is
further contrained by the Assuan line length (and any other text on
the same line). However, the Assuan line length is 1k bytes so
this shouldn't be a problem in practice. */
#define MAX_PASSPHRASE_LEN 255
/* 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 dry_run; /* Don't change any persistent data */
int batch; /* Batch mode */
/* True if we handle sigusr2. */
int sigusr2_enabled;
/* Environment settings gathered at program start or changed using the
Assuan command UPDATESTARTUPTTY. */
session_env_t startup_env;
char *startup_lc_ctype;
char *startup_lc_messages;
/* Enable pinentry debugging (--debug 1024 should also be used). */
int debug_pinentry;
/* Filename of the program to start as pinentry. */
const char *pinentry_program;
/* Filename of the program to handle smartcard tasks. */
const char *scdaemon_program;
int disable_scdaemon; /* Never use the SCdaemon. */
int no_grab; /* Don't let the pinentry grab the keyboard */
/* The name of the file pinentry shall touch before exiting. If
this is not set the file name of the standard socket is used. */
const char *pinentry_touch_file;
/* A string where the first character is used by the pinentry as a
custom invisible character. */
char *pinentry_invisible_char;
/* The timeout value for the Pinentry in seconds. This is passed to
the pinentry if it is not 0. It is up to the pinentry to act
upon this timeout value. */
unsigned long pinentry_timeout;
/* The default and maximum TTL of cache entries. */
unsigned long def_cache_ttl; /* Default. */
unsigned long def_cache_ttl_ssh; /* for SSH. */
unsigned long max_cache_ttl; /* Default. */
unsigned long max_cache_ttl_ssh; /* for SSH. */
/* Flag disallowing bypassing of the warning. */
int enforce_passphrase_constraints;
/* The require minmum length of a passphrase. */
unsigned int min_passphrase_len;
/* The minimum number of non-alpha characters in a passphrase. */
unsigned int min_passphrase_nonalpha;
/* File name with a patternfile or NULL if not enabled. */
const char *check_passphrase_pattern;
/* If not 0 the user is asked to change his passphrase after these
number of days. */
unsigned int max_passphrase_days;
/* If set, a passphrase history will be written and checked at each
passphrase change. */
int enable_passphrase_history;
/* If set the extended key format is used for new keys. Note that
* this may vave the value 2 in which case
* --disable-extended-key-format won't have any effect and thus
* effectivley locking it. This is required to support existing
* profiles which lock the use of --enable-extended-key-format. */
int enable_extended_key_format;
int running_detached; /* We are running detached from the tty. */
/* If this global option is true, the passphrase cache is ignored
for signing operations. */
int ignore_cache_for_signing;
/* If this global option is true, the user is allowed to
interactively mark certificate in trustlist.txt as trusted. */
int allow_mark_trusted;
/* If this global option is true, the Assuan command
PRESET_PASSPHRASE is allowed. */
int allow_preset_passphrase;
/* If this global option is true, the Assuan option
pinentry-mode=loopback is allowed. */
int allow_loopback_pinentry;
/* Allow the use of an external password cache. If this option is
enabled (which is the default) we send an option to Pinentry
to allow it to enable such a cache. */
int allow_external_cache;
/* If this global option is true, the Assuan option of Pinentry
allow-emacs-prompt is allowed. */
int allow_emacs_pinentry;
int keep_tty; /* Don't switch the TTY (for pinentry) on request */
int keep_display; /* Don't switch the DISPLAY (for pinentry) on request */
/* This global option indicates the use of an extra socket. Note
that we use a hack for cleanup handling in gpg-agent.c: If the
value is less than 2 the name has not yet been malloced. */
int extra_socket;
/* This global option indicates the use of an extra socket for web
browsers. Note that we use a hack for cleanup handling in
gpg-agent.c: If the value is less than 2 the name has not yet
been malloced. */
int browser_socket;
/* The digest algorithm to use for ssh fingerprints when
* communicating with the user. */
int ssh_fingerprint_digest;
/* The value of the option --s2k-count. If this option is not given
* or 0 an auto-calibrated value is used. */
unsigned long s2k_count;
} opt;
/* Bit values for the --debug option. */
#define DBG_MPI_VALUE 2 /* debug mpi details */
#define DBG_CRYPTO_VALUE 4 /* debug low level crypto */
#define DBG_MEMORY_VALUE 32 /* debug memory allocation stuff */
#define DBG_CACHE_VALUE 64 /* debug the caching */
#define DBG_MEMSTAT_VALUE 128 /* show memory statistics */
#define DBG_HASHING_VALUE 512 /* debug hashing operations */
#define DBG_IPC_VALUE 1024 /* Enable Assuan debugging. */
/* Test macros for the debug option. */
#define DBG_CRYPTO (opt.debug & DBG_CRYPTO_VALUE)
#define DBG_MEMORY (opt.debug & DBG_MEMORY_VALUE)
#define DBG_CACHE (opt.debug & DBG_CACHE_VALUE)
#define DBG_HASHING (opt.debug & DBG_HASHING_VALUE)
#define DBG_IPC (opt.debug & DBG_IPC_VALUE)
/* Forward reference for local definitions in command.c. */
struct server_local_s;
/* Declaration of objects from command-ssh.c. */
struct ssh_control_file_s;
typedef struct ssh_control_file_s *ssh_control_file_t;
/* Forward reference for local definitions in call-scd.c. */
struct scd_local_s;
/* Collection of data per session (aka connection). */
struct server_control_s
{
/* Private data used to fire up the connection thread. We use this
structure do avoid an extra allocation for only a few bytes while
spawning a new connection thread. */
struct {
gnupg_fd_t fd;
} thread_startup;
/* Flag indicating the connection is run in restricted mode.
A value of 1 if used for --extra-socket,
a value of 2 is used for --browser-socket. */
int restricted;
/* Private data of the server (command.c). */
struct server_local_s *server_local;
/* Private data of the SCdaemon (call-scd.c). */
struct scd_local_s *scd_local;
/* Environment settings for the connection. */
session_env_t session_env;
char *lc_ctype;
char *lc_messages;
unsigned long client_pid;
int client_uid;
/* The current pinentry mode. */
pinentry_mode_t pinentry_mode;
/* The TTL used for the --preset option of certain commands. */
int cache_ttl_opt_preset;
/* Information on the currently used digest (for signing commands). */
struct {
int algo;
unsigned char value[MAX_DIGEST_LEN];
int valuelen;
int raw_value: 1;
} digest;
unsigned char keygrip[20];
int have_keygrip;
/* A flag to enable a hack to send the PKAUTH command instead of the
PKSIGN command to the scdaemon. */
int use_auth_call;
/* A flag to inhibit enforced passphrase change during an explicit
passwd command. */
int in_passwd;
/* The current S2K which might be different from the calibrated
count. */
unsigned long s2k_count;
/* If pinentry is active for this thread. It can be more than 1,
when pinentry is called recursively. */
int pinentry_active;
};
/* Status of pinentry. */
enum
{
PINENTRY_STATUS_CLOSE_BUTTON = 1 << 0,
PINENTRY_STATUS_PIN_REPEATED = 1 << 8,
PINENTRY_STATUS_PASSWORD_FROM_CACHE = 1 << 9
};
/* Information pertaining to pinentry requests. */
struct pin_entry_info_s
{
int min_digits; /* min. number of digits required or 0 for freeform entry */
int max_digits; /* max. number of allowed digits allowed*/
int max_tries; /* max. number of allowed tries. */
int failed_tries; /* Number of tries so far failed. */
int with_qualitybar; /* Set if the quality bar should be displayed. */
int with_repeat; /* Request repetition of the passphrase. */
int repeat_okay; /* Repetition worked. */
unsigned int status; /* Status. */
gpg_error_t (*check_cb)(struct pin_entry_info_s *); /* CB used to check
the PIN */
void *check_cb_arg; /* optional argument which might be of use in the CB */
const char *cb_errtext; /* used by the cb to display a specific error */
size_t max_length; /* Allocated length of the buffer PIN. */
char pin[1]; /* The buffer to hold the PIN or passphrase.
It's actual allocated length is given by
MAX_LENGTH (above). */
};
/* Types of the private keys. */
enum
{
PRIVATE_KEY_UNKNOWN = 0, /* Type of key is not known. */
PRIVATE_KEY_CLEAR = 1, /* The key is not protected. */
PRIVATE_KEY_PROTECTED = 2, /* The key is protected. */
PRIVATE_KEY_SHADOWED = 3, /* The key is a stub for a smartcard
based key. */
PROTECTED_SHARED_SECRET = 4, /* RFU. */
PRIVATE_KEY_OPENPGP_NONE = 5 /* openpgp-native with protection "none". */
};
/* Values for the cache_mode arguments. */
typedef enum
{
CACHE_MODE_IGNORE = 0, /* Special mode to bypass the cache. */
CACHE_MODE_ANY, /* Any mode except ignore and data matches. */
CACHE_MODE_NORMAL, /* Normal cache (gpg-agent). */
CACHE_MODE_USER, /* GET_PASSPHRASE related cache. */
CACHE_MODE_SSH, /* SSH related cache. */
CACHE_MODE_NONCE, /* This is a non-predictable nonce. */
CACHE_MODE_DATA /* Arbitrary data. */
}
cache_mode_t;
/* The TTL is seconds used for adding a new nonce mode cache item. */
#define CACHE_TTL_NONCE 120
/* The TTL in seconds used by the --preset option of some commands.
This is the default value changeable by an OPTION command. */
#define CACHE_TTL_OPT_PRESET 900
/* The type of a function to lookup a TTL by a keygrip. */
typedef int (*lookup_ttl_t)(const char *hexgrip);
/* This is a special version of the usual _() gettext macro. It
assumes a server connection control variable with the name "ctrl"
and uses that to translate a string according to the locale set for
the connection. The macro LunderscoreIMPL is used by i18n to
actually define the inline function when needed. */
#if defined (ENABLE_NLS) || defined (USE_SIMPLE_GETTEXT)
#define L_(a) agent_Lunderscore (ctrl, (a))
#define LunderscorePROTO \
static inline const char *agent_Lunderscore (ctrl_t ctrl, \
const char *string) \
GNUPG_GCC_ATTR_FORMAT_ARG(2);
#define LunderscoreIMPL \
static inline const char * \
agent_Lunderscore (ctrl_t ctrl, const char *string) \
{ \
return ctrl? i18n_localegettext (ctrl->lc_messages, string) \
/* */: gettext (string); \
}
#else
#define L_(a) (a)
#endif
+/* Information from scdaemon for card keys. */
+struct card_key_info_s
+{
+ struct card_key_info_s *next;
+ char keygrip[40];
+ char *serialno;
+ char *idstr;
+};
+
/*-- gpg-agent.c --*/
void agent_exit (int rc)
GPGRT_ATTR_NORETURN; /* Also implemented in other tools */
void agent_set_progress_cb (void (*cb)(ctrl_t ctrl, const char *what,
int printchar, int current, int total),
ctrl_t ctrl);
gpg_error_t agent_copy_startup_env (ctrl_t ctrl);
const char *get_agent_socket_name (void);
const char *get_agent_ssh_socket_name (void);
int get_agent_active_connection_count (void);
#ifdef HAVE_W32_SYSTEM
void *get_agent_scd_notify_event (void);
#endif
void agent_sighup_action (void);
int map_pk_openpgp_to_gcry (int openpgp_algo);
/*-- command.c --*/
gpg_error_t agent_inq_pinentry_launched (ctrl_t ctrl, unsigned long pid,
const char *extra);
gpg_error_t agent_write_status (ctrl_t ctrl, const char *keyword, ...)
GPGRT_ATTR_SENTINEL(0);
gpg_error_t agent_print_status (ctrl_t ctrl, const char *keyword,
const char *format, ...)
GPGRT_ATTR_PRINTF(3,4);
void bump_key_eventcounter (void);
void bump_card_eventcounter (void);
void start_command_handler (ctrl_t, gnupg_fd_t, gnupg_fd_t);
gpg_error_t pinentry_loopback (ctrl_t, const char *keyword,
- unsigned char **buffer, size_t *size,
- size_t max_length);
+ unsigned char **buffer, size_t *size,
+ size_t max_length);
+gpg_error_t pinentry_loopback_confirm (ctrl_t ctrl, const char *desc,
+ int ask_confirmation,
+ const char *ok, const char *notok);
#ifdef HAVE_W32_SYSTEM
int serve_mmapped_ssh_request (ctrl_t ctrl,
unsigned char *request, size_t maxreqlen);
#endif /*HAVE_W32_SYSTEM*/
/*-- command-ssh.c --*/
ssh_control_file_t ssh_open_control_file (void);
void ssh_close_control_file (ssh_control_file_t cf);
gpg_error_t ssh_read_control_file (ssh_control_file_t cf,
char *r_hexgrip, int *r_disabled,
int *r_ttl, int *r_confirm);
gpg_error_t ssh_search_control_file (ssh_control_file_t cf,
const char *hexgrip,
int *r_disabled,
int *r_ttl, int *r_confirm);
void start_command_handler_ssh (ctrl_t, gnupg_fd_t);
/*-- findkey.c --*/
gpg_error_t agent_modify_description (const char *in, const char *comment,
const gcry_sexp_t key, char **result);
int agent_write_private_key (const unsigned char *grip,
- const void *buffer, size_t length, int force);
+ const void *buffer, size_t length, int force,
+ const char *serialno, const char *keyref);
gpg_error_t agent_key_from_file (ctrl_t ctrl,
const char *cache_nonce,
const char *desc_text,
const unsigned char *grip,
unsigned char **shadow_info,
cache_mode_t cache_mode,
lookup_ttl_t lookup_ttl,
gcry_sexp_t *result,
char **r_passphrase);
gpg_error_t agent_raw_key_from_file (ctrl_t ctrl, const unsigned char *grip,
gcry_sexp_t *result);
gpg_error_t agent_public_key_from_file (ctrl_t ctrl,
const unsigned char *grip,
gcry_sexp_t *result);
int agent_is_dsa_key (gcry_sexp_t s_key);
int agent_is_eddsa_key (gcry_sexp_t s_key);
int agent_key_available (const unsigned char *grip);
gpg_error_t agent_key_info_from_file (ctrl_t ctrl, const unsigned char *grip,
int *r_keytype,
unsigned char **r_shadow_info);
gpg_error_t agent_delete_key (ctrl_t ctrl, const char *desc_text,
const unsigned char *grip,
int force, int only_stubs);
/*-- call-pinentry.c --*/
void initialize_module_call_pinentry (void);
void agent_query_dump_state (void);
void agent_reset_query (ctrl_t ctrl);
int pinentry_active_p (ctrl_t ctrl, int waitseconds);
gpg_error_t agent_askpin (ctrl_t ctrl,
const char *desc_text, const char *prompt_text,
const char *inital_errtext,
struct pin_entry_info_s *pininfo,
const char *keyinfo, cache_mode_t cache_mode);
int agent_get_passphrase (ctrl_t ctrl, char **retpass,
const char *desc, const char *prompt,
const char *errtext, int with_qualitybar,
const char *keyinfo, cache_mode_t cache_mode);
int agent_get_confirmation (ctrl_t ctrl, const char *desc, const char *ok,
const char *notokay, int with_cancel);
int agent_show_message (ctrl_t ctrl, const char *desc, const char *ok_btn);
int agent_popup_message_start (ctrl_t ctrl,
const char *desc, const char *ok_btn);
void agent_popup_message_stop (ctrl_t ctrl);
int agent_clear_passphrase (ctrl_t ctrl,
const char *keyinfo, cache_mode_t cache_mode);
/*-- cache.c --*/
void initialize_module_cache (void);
void deinitialize_module_cache (void);
void agent_cache_housekeeping (void);
void agent_flush_cache (void);
int agent_put_cache (ctrl_t ctrl, const char *key, cache_mode_t cache_mode,
const char *data, int ttl);
char *agent_get_cache (ctrl_t ctrl, const char *key, cache_mode_t cache_mode);
void agent_store_cache_hit (const char *key);
/*-- pksign.c --*/
gpg_error_t agent_pksign_do (ctrl_t ctrl, const char *cache_nonce,
const char *desc_text,
gcry_sexp_t *signature_sexp,
cache_mode_t cache_mode, lookup_ttl_t lookup_ttl,
const void *overridedata, size_t overridedatalen);
gpg_error_t agent_pksign (ctrl_t ctrl, const char *cache_nonce,
const char *desc_text,
membuf_t *outbuf, cache_mode_t cache_mode);
/*-- pkdecrypt.c --*/
int agent_pkdecrypt (ctrl_t ctrl, const char *desc_text,
const unsigned char *ciphertext, size_t ciphertextlen,
membuf_t *outbuf, int *r_padding);
/*-- genkey.c --*/
int check_passphrase_constraints (ctrl_t ctrl, const char *pw,
char **failed_constraint);
gpg_error_t agent_ask_new_passphrase (ctrl_t ctrl, const char *prompt,
char **r_passphrase);
int agent_genkey (ctrl_t ctrl, const char *cache_nonce,
const char *keyparam, size_t keyparmlen,
int no_protection, const char *override_passphrase,
int preset, membuf_t *outbuf);
gpg_error_t agent_protect_and_store (ctrl_t ctrl, gcry_sexp_t s_skey,
char **passphrase_addr);
/*-- protect.c --*/
void set_s2k_calibration_time (unsigned int milliseconds);
unsigned long get_calibrated_s2k_count (void);
unsigned long get_standard_s2k_count (void);
unsigned char get_standard_s2k_count_rfc4880 (void);
unsigned long get_standard_s2k_time (void);
int agent_protect (const unsigned char *plainkey, const char *passphrase,
unsigned char **result, size_t *resultlen,
unsigned long s2k_count, int use_ocb);
gpg_error_t agent_unprotect (ctrl_t ctrl,
const unsigned char *protectedkey, const char *passphrase,
gnupg_isotime_t protected_at,
unsigned char **result, size_t *resultlen);
int agent_private_key_type (const unsigned char *privatekey);
unsigned char *make_shadow_info (const char *serialno, const char *idstring);
int agent_shadow_key (const unsigned char *pubkey,
const unsigned char *shadow_info,
unsigned char **result);
gpg_error_t agent_get_shadow_info (const unsigned char *shadowkey,
unsigned char const **shadow_info);
gpg_error_t parse_shadow_info (const unsigned char *shadow_info,
char **r_hexsn, char **r_idstr, int *r_pinlen);
gpg_error_t s2k_hash_passphrase (const char *passphrase, int hashalgo,
int s2kmode,
const unsigned char *s2ksalt,
unsigned int s2kcount,
unsigned char *key, size_t keylen);
gpg_error_t agent_write_shadow_key (const unsigned char *grip,
const char *serialno, const char *keyid,
const unsigned char *pkbuf, int force);
/*-- trustlist.c --*/
void initialize_module_trustlist (void);
gpg_error_t agent_istrusted (ctrl_t ctrl, const char *fpr, int *r_disabled);
gpg_error_t agent_listtrusted (void *assuan_context);
gpg_error_t agent_marktrusted (ctrl_t ctrl, const char *name,
const char *fpr, int flag);
void agent_reload_trustlist (void);
/*-- divert-scd.c --*/
int divert_pksign (ctrl_t ctrl, const char *desc_text,
+ const unsigned char *grip,
const unsigned char *digest, size_t digestlen, int algo,
const unsigned char *shadow_info, unsigned char **r_sig,
size_t *r_siglen);
int divert_pkdecrypt (ctrl_t ctrl, const char *desc_text,
+ const unsigned char *grip,
const unsigned char *cipher,
const unsigned char *shadow_info,
char **r_buf, size_t *r_len, int *r_padding);
int divert_generic_cmd (ctrl_t ctrl,
const char *cmdline, void *assuan_context);
gpg_error_t divert_writekey (ctrl_t ctrl, int force, const char *serialno,
const char *keyref,
const char *keydata, size_t keydatalen);
/*-- call-scd.c --*/
void initialize_module_call_scd (void);
void agent_scd_dump_state (void);
int agent_scd_check_running (void);
int agent_reset_scd (ctrl_t ctrl);
int agent_card_learn (ctrl_t ctrl,
void (*kpinfo_cb)(void*, const char *),
void *kpinfo_cb_arg,
void (*certinfo_cb)(void*, const char *),
void *certinfo_cb_arg,
void (*sinfo_cb)(void*, const char *,
size_t, const char *),
void *sinfo_cb_arg);
int agent_card_serialno (ctrl_t ctrl, char **r_serialno, const char *demand);
int agent_card_pksign (ctrl_t ctrl,
const char *keyid,
int (*getpin_cb)(void *, const char *,
const char *, char*, size_t),
void *getpin_cb_arg,
const char *desc_text,
int mdalgo,
const unsigned char *indata, size_t indatalen,
unsigned char **r_buf, size_t *r_buflen);
int agent_card_pkdecrypt (ctrl_t ctrl,
const char *keyid,
int (*getpin_cb)(void *, const char *,
const char *, char*,size_t),
void *getpin_cb_arg,
const char *desc_text,
const unsigned char *indata, size_t indatalen,
char **r_buf, size_t *r_buflen, int *r_padding);
int agent_card_readcert (ctrl_t ctrl,
const char *id, char **r_buf, size_t *r_buflen);
int agent_card_readkey (ctrl_t ctrl, const char *id, unsigned char **r_buf);
gpg_error_t agent_card_writekey (ctrl_t ctrl, int force, const char *serialno,
const char *keyref,
const char *keydata, size_t keydatalen,
int (*getpin_cb)(void *, const char *,
const char *, char*, size_t),
void *getpin_cb_arg);
gpg_error_t agent_card_getattr (ctrl_t ctrl, const char *name, char **result);
gpg_error_t agent_card_cardlist (ctrl_t ctrl, strlist_t *result);
int agent_card_scd (ctrl_t ctrl, const char *cmdline,
int (*getpin_cb)(void *, const char *,
const char *, char*, size_t),
void *getpin_cb_arg, void *assuan_context);
+void agent_card_free_keyinfo (struct card_key_info_s *l);
+gpg_error_t agent_card_keyinfo (ctrl_t ctrl, const char *keygrip,
+ struct card_key_info_s **result);
+void agent_card_killscd (void);
/*-- learncard.c --*/
int agent_handle_learn (ctrl_t ctrl, int send, void *assuan_context, int force);
/*-- cvt-openpgp.c --*/
gpg_error_t
extract_private_key (gcry_sexp_t s_key, int req_private_key_data,
const char **r_algoname, int *r_npkey, int *r_nskey,
const char **r_format,
gcry_mpi_t *mpi_array, int arraysize,
gcry_sexp_t *r_curve, gcry_sexp_t *r_flags);
#endif /*AGENT_H*/
diff --git a/agent/cache.c b/agent/cache.c
index 799d595ab..4a3e5a547 100644
--- a/agent/cache.c
+++ b/agent/cache.c
@@ -1,544 +1,543 @@
/* cache.c - keep a cache of passphrases
* Copyright (C) 2002, 2010 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
-#include <assert.h>
#include <npth.h>
#include "agent.h"
/* The default TTL for DATA items. This has no configure
* option because it is expected that clients provide a TTL. */
#define DEF_CACHE_TTL_DATA (10 * 60) /* 10 minutes. */
/* The size of the encryption key in bytes. */
#define ENCRYPTION_KEYSIZE (128/8)
/* A mutex used to serialize access to the cache. */
static npth_mutex_t cache_lock;
/* The encryption context. This is the only place where the
encryption key for all cached entries is available. It would be nice
to keep this (or just the key) in some hardware device, for example
a TPM. Libgcrypt could be extended to provide such a service.
With the current scheme it is easy to retrieve the cached entries
if access to Libgcrypt's memory is available. The encryption
merely avoids grepping for clear texts in the memory. Nevertheless
the encryption provides the necessary infrastructure to make it
more secure. */
static gcry_cipher_hd_t encryption_handle;
struct secret_data_s {
int totallen; /* This includes the padding and space for AESWRAP. */
char data[1]; /* A string. */
};
/* The cache object. */
typedef struct cache_item_s *ITEM;
struct cache_item_s {
ITEM next;
time_t created;
time_t accessed; /* Not updated for CACHE_MODE_DATA */
int ttl; /* max. lifetime given in seconds, -1 one means infinite */
struct secret_data_s *pw;
cache_mode_t cache_mode;
int restricted; /* The value of ctrl->restricted is part of the key. */
char key[1];
};
/* The cache himself. */
static ITEM thecache;
/* NULL or the last cache key stored by agent_store_cache_hit. */
static char *last_stored_cache_key;
/* This function must be called once to initialize this module. It
has to be done before a second thread is spawned. */
void
initialize_module_cache (void)
{
int err;
err = npth_mutex_init (&cache_lock, NULL);
if (err)
log_fatal ("error initializing cache module: %s\n", strerror (err));
}
void
deinitialize_module_cache (void)
{
gcry_cipher_close (encryption_handle);
encryption_handle = NULL;
}
/* We do the encryption init on the fly. We can't do it in the module
init code because that is run before we listen for connections and
in case we are started on demand by gpg etc. it will only wait for
a few seconds to decide whether the agent may now accept
connections. Thus we should get into listen state as soon as
possible. */
static gpg_error_t
init_encryption (void)
{
gpg_error_t err;
void *key;
if (encryption_handle)
return 0; /* Shortcut - Already initialized. */
err = gcry_cipher_open (&encryption_handle, GCRY_CIPHER_AES128,
GCRY_CIPHER_MODE_AESWRAP, GCRY_CIPHER_SECURE);
if (!err)
{
key = gcry_random_bytes (ENCRYPTION_KEYSIZE, GCRY_STRONG_RANDOM);
if (!key)
err = gpg_error_from_syserror ();
else
{
err = gcry_cipher_setkey (encryption_handle, key, ENCRYPTION_KEYSIZE);
xfree (key);
}
if (err)
{
gcry_cipher_close (encryption_handle);
encryption_handle = NULL;
}
}
if (err)
log_error ("error initializing cache encryption context: %s\n",
gpg_strerror (err));
return err? gpg_error (GPG_ERR_NOT_INITIALIZED) : 0;
}
static void
release_data (struct secret_data_s *data)
{
xfree (data);
}
static gpg_error_t
new_data (const char *string, struct secret_data_s **r_data)
{
gpg_error_t err;
struct secret_data_s *d, *d_enc;
size_t length;
int total;
*r_data = NULL;
err = init_encryption ();
if (err)
return err;
length = strlen (string) + 1;
/* We pad the data to 32 bytes so that it get more complicated
finding something out by watching allocation patterns. This is
usually not possible but we better assume nothing about our secure
storage provider. To support the AESWRAP mode we need to add 8
extra bytes as well. */
total = (length + 8) + 32 - ((length+8) % 32);
d = xtrymalloc_secure (sizeof *d + total - 1);
if (!d)
return gpg_error_from_syserror ();
memcpy (d->data, string, length);
d_enc = xtrymalloc (sizeof *d_enc + total - 1);
if (!d_enc)
{
err = gpg_error_from_syserror ();
xfree (d);
return err;
}
d_enc->totallen = total;
err = gcry_cipher_encrypt (encryption_handle, d_enc->data, total,
d->data, total - 8);
xfree (d);
if (err)
{
xfree (d_enc);
return err;
}
*r_data = d_enc;
return 0;
}
/* Check whether there are items to expire. */
static void
housekeeping (void)
{
ITEM r, rprev;
time_t current = gnupg_get_time ();
/* First expire the actual data */
for (r=thecache; r; r = r->next)
{
if (r->pw && r->ttl >= 0 && r->accessed + r->ttl < current)
{
if (DBG_CACHE)
log_debug (" expired '%s'.%d (%ds after last access)\n",
r->key, r->restricted, r->ttl);
release_data (r->pw);
r->pw = NULL;
r->accessed = current;
}
}
/* Second, make sure that we also remove them based on the created
* stamp so that the user has to enter it from time to time. We
* don't do this for data items which are used to storage secrets in
* meory and are not user entered passphrases etc. */
for (r=thecache; r; r = r->next)
{
unsigned long maxttl;
switch (r->cache_mode)
{
case CACHE_MODE_DATA:
continue; /* No MAX TTL here. */
case CACHE_MODE_SSH: maxttl = opt.max_cache_ttl_ssh; break;
default: maxttl = opt.max_cache_ttl; break;
}
if (r->pw && r->created + maxttl < current)
{
if (DBG_CACHE)
log_debug (" expired '%s'.%d (%lus after creation)\n",
r->key, r->restricted, opt.max_cache_ttl);
release_data (r->pw);
r->pw = NULL;
r->accessed = current;
}
}
/* Third, make sure that we don't have too many items in the list.
* Expire old and unused entries after 30 minutes. */
for (rprev=NULL, r=thecache; r; )
{
if (!r->pw && r->ttl >= 0 && r->accessed + 60*30 < current)
{
ITEM r2 = r->next;
if (DBG_CACHE)
log_debug (" removed '%s'.%d (mode %d) (slot not used for 30m)\n",
r->key, r->restricted, r->cache_mode);
xfree (r);
if (!rprev)
thecache = r2;
else
rprev->next = r2;
r = r2;
}
else
{
rprev = r;
r = r->next;
}
}
}
void
agent_cache_housekeeping (void)
{
int res;
if (DBG_CACHE)
log_debug ("agent_cache_housekeeping\n");
res = npth_mutex_lock (&cache_lock);
if (res)
log_fatal ("failed to acquire cache mutex: %s\n", strerror (res));
housekeeping ();
res = npth_mutex_unlock (&cache_lock);
if (res)
log_fatal ("failed to release cache mutex: %s\n", strerror (res));
}
void
agent_flush_cache (void)
{
ITEM r;
int res;
if (DBG_CACHE)
log_debug ("agent_flush_cache\n");
res = npth_mutex_lock (&cache_lock);
if (res)
log_fatal ("failed to acquire cache mutex: %s\n", strerror (res));
for (r=thecache; r; r = r->next)
{
if (r->pw)
{
if (DBG_CACHE)
log_debug (" flushing '%s'.%d\n", r->key, r->restricted);
release_data (r->pw);
r->pw = NULL;
r->accessed = 0;
}
}
res = npth_mutex_unlock (&cache_lock);
if (res)
log_fatal ("failed to release cache mutex: %s\n", strerror (res));
}
/* Compare two cache modes. */
static int
cache_mode_equal (cache_mode_t a, cache_mode_t b)
{
/* CACHE_MODE_ANY matches any mode other than CACHE_MODE_IGNORE. */
return ((a == CACHE_MODE_ANY
&& !(b == CACHE_MODE_IGNORE || b == CACHE_MODE_DATA))
|| (b == CACHE_MODE_ANY
&& !(a == CACHE_MODE_IGNORE || a == CACHE_MODE_DATA))
|| a == b);
}
/* Store the string DATA in the cache under KEY and mark it with a
maximum lifetime of TTL seconds. If there is already data under
this key, it will be replaced. Using a DATA of NULL deletes the
entry. A TTL of 0 is replaced by the default TTL and a TTL of -1
set infinite timeout. CACHE_MODE is stored with the cache entry
and used to select different timeouts. */
int
agent_put_cache (ctrl_t ctrl, const char *key, cache_mode_t cache_mode,
const char *data, int ttl)
{
gpg_error_t err = 0;
ITEM r;
int res;
int restricted = ctrl? ctrl->restricted : -1;
res = npth_mutex_lock (&cache_lock);
if (res)
log_fatal ("failed to acquire cache mutex: %s\n", strerror (res));
if (DBG_CACHE)
log_debug ("agent_put_cache '%s'.%d (mode %d) requested ttl=%d\n",
key, restricted, cache_mode, ttl);
housekeeping ();
if (!ttl)
{
switch(cache_mode)
{
case CACHE_MODE_SSH: ttl = opt.def_cache_ttl_ssh; break;
case CACHE_MODE_DATA: ttl = DEF_CACHE_TTL_DATA; break;
default: ttl = opt.def_cache_ttl; break;
}
}
if ((!ttl && data) || cache_mode == CACHE_MODE_IGNORE)
goto out;
for (r=thecache; r; r = r->next)
{
if (((cache_mode != CACHE_MODE_USER
&& cache_mode != CACHE_MODE_NONCE)
|| cache_mode_equal (r->cache_mode, cache_mode))
&& r->restricted == restricted
&& !strcmp (r->key, key))
break;
}
if (r) /* Replace. */
{
if (r->pw)
{
release_data (r->pw);
r->pw = NULL;
}
if (data)
{
r->created = r->accessed = gnupg_get_time ();
r->ttl = ttl;
r->cache_mode = cache_mode;
err = new_data (data, &r->pw);
if (err)
log_error ("error replacing cache item: %s\n", gpg_strerror (err));
}
}
else if (data) /* Insert. */
{
r = xtrycalloc (1, sizeof *r + strlen (key));
if (!r)
err = gpg_error_from_syserror ();
else
{
strcpy (r->key, key);
r->restricted = restricted;
r->created = r->accessed = gnupg_get_time ();
r->ttl = ttl;
r->cache_mode = cache_mode;
err = new_data (data, &r->pw);
if (err)
xfree (r);
else
{
r->next = thecache;
thecache = r;
}
}
if (err)
log_error ("error inserting cache item: %s\n", gpg_strerror (err));
}
out:
res = npth_mutex_unlock (&cache_lock);
if (res)
log_fatal ("failed to release cache mutex: %s\n", strerror (res));
return err;
}
/* Try to find an item in the cache. */
char *
agent_get_cache (ctrl_t ctrl, const char *key, cache_mode_t cache_mode)
{
gpg_error_t err;
ITEM r;
char *value = NULL;
int res;
int last_stored = 0;
int restricted = ctrl? ctrl->restricted : -1;
if (cache_mode == CACHE_MODE_IGNORE)
return NULL;
res = npth_mutex_lock (&cache_lock);
if (res)
log_fatal ("failed to acquire cache mutex: %s\n", strerror (res));
if (!key)
{
key = last_stored_cache_key;
if (!key)
goto out;
last_stored = 1;
}
if (DBG_CACHE)
log_debug ("agent_get_cache '%s'.%d (mode %d)%s ...\n",
key, ctrl->restricted, cache_mode,
last_stored? " (stored cache key)":"");
housekeeping ();
for (r=thecache; r; r = r->next)
{
if (r->pw
&& ((cache_mode != CACHE_MODE_USER
&& cache_mode != CACHE_MODE_NONCE)
|| cache_mode_equal (r->cache_mode, cache_mode))
&& r->restricted == restricted
&& !strcmp (r->key, key))
{
/* Note: To avoid races KEY may not be accessed anymore
* below. Note also that we don't update the accessed time
* for data items. */
if (r->cache_mode != CACHE_MODE_DATA)
r->accessed = gnupg_get_time ();
if (DBG_CACHE)
log_debug ("... hit\n");
if (r->pw->totallen < 32)
err = gpg_error (GPG_ERR_INV_LENGTH);
else if ((err = init_encryption ()))
;
else if (!(value = xtrymalloc_secure (r->pw->totallen - 8)))
err = gpg_error_from_syserror ();
else
{
err = gcry_cipher_decrypt (encryption_handle,
value, r->pw->totallen - 8,
r->pw->data, r->pw->totallen);
}
if (err)
{
xfree (value);
value = NULL;
log_error ("retrieving cache entry '%s'.%d failed: %s\n",
key, restricted, gpg_strerror (err));
}
break;
}
}
if (DBG_CACHE && value == NULL)
log_debug ("... miss\n");
out:
res = npth_mutex_unlock (&cache_lock);
if (res)
log_fatal ("failed to release cache mutex: %s\n", strerror (res));
return value;
}
/* Store the key for the last successful cache hit. That value is
used by agent_get_cache if the requested KEY is given as NULL.
NULL may be used to remove that key. */
void
agent_store_cache_hit (const char *key)
{
char *new;
char *old;
/* To make sure the update is atomic under the non-preemptive thread
* model, we must make sure not to surrender control to a different
* thread. Therefore, we avoid calling the allocator during the
* update.
*
* Background: xtrystrdup uses gcry_strdup which may use the secure
* memory allocator of Libgcrypt. That allocator takes locks and
* since version 1.14 libgpg-error is nPth aware and thus taking a
* lock may now lead to thread switch. Note that this only happens
* when secure memory is _allocated_ (the standard allocator uses
* malloc which is not nPth aware) but not when calling _xfree_
* because gcry_free needs to check whether the pointer is in secure
* memory and thus needs to take a lock.
*/
new = key ? xtrystrdup (key) : NULL;
/* Atomic update. */
old = last_stored_cache_key;
last_stored_cache_key = new;
/* Done. */
xfree (old);
}
diff --git a/agent/call-pinentry.c b/agent/call-pinentry.c
index 34dde3744..a895a8b8f 100644
--- a/agent/call-pinentry.c
+++ b/agent/call-pinentry.c
@@ -1,1654 +1,1666 @@
/* call-pinentry.c - Spawn the pinentry to query stuff from the user
* Copyright (C) 2001, 2002, 2004, 2007, 2008,
* 2010 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
-#include <assert.h>
#include <unistd.h>
#include <sys/stat.h>
#ifndef HAVE_W32_SYSTEM
# include <sys/wait.h>
# include <sys/types.h>
# include <signal.h>
# include <sys/utsname.h>
#endif
#include <npth.h>
#include "agent.h"
#include <assuan.h>
#include "../common/sysutils.h"
#include "../common/i18n.h"
#ifdef _POSIX_OPEN_MAX
#define MAX_OPEN_FDS _POSIX_OPEN_MAX
#else
#define MAX_OPEN_FDS 20
#endif
/* Because access to the pinentry must be serialized (it is and shall
be a global mutually exclusive dialog) we better timeout pending
requests after some time. 1 minute seem to be a reasonable
time. */
#define LOCK_TIMEOUT (1*60)
/* The assuan context of the current pinentry. */
static assuan_context_t entry_ctx;
/* A list of features of the current pinentry. */
static struct
{
/* The Pinentry support RS+US tabbing. This means that a RS (0x1e)
* starts a new tabbing block in which a US (0x1f) followed by a
* colon marks a colon. A pinentry can use this to pretty print
* name value pairs. */
unsigned int tabbing:1;
} entry_features;
/* A mutex used to serialize access to the pinentry. */
static npth_mutex_t entry_lock;
/* The thread ID of the popup working thread. */
static npth_t popup_tid;
/* A flag used in communication between the popup working thread and
its stop function. */
static int popup_finished;
/* Data to be passed to our callbacks, */
struct entry_parm_s
{
int lines;
size_t size;
unsigned char *buffer;
int status;
};
/* This function must be called once to initialize this module. This
has to be done before a second thread is spawned. We can't do the
static initialization because Pth emulation code might not be able
to do a static init; in particular, it is not possible for W32. */
void
initialize_module_call_pinentry (void)
{
static int initialized;
int err;
if (!initialized)
{
err = npth_mutex_init (&entry_lock, NULL);
if (err)
log_fatal ("error initializing mutex: %s\n", strerror (err));
initialized = 1;
}
}
/* This function may be called to print information pertaining to the
current state of this module to the log. */
void
agent_query_dump_state (void)
{
log_info ("agent_query_dump_state: entry_ctx=%p pid=%ld popup_tid=%p\n",
entry_ctx, (long)assuan_get_pid (entry_ctx), (void*)popup_tid);
}
/* Called to make sure that a popup window owned by the current
connection gets closed. */
void
agent_reset_query (ctrl_t ctrl)
{
if (entry_ctx && popup_tid && ctrl->pinentry_active)
{
agent_popup_message_stop (ctrl);
}
}
/* Unlock the pinentry so that another thread can start one and
disconnect that pinentry - we do this after the unlock so that a
stalled pinentry does not block other threads. Fixme: We should
have a timeout in Assuan for the disconnect operation. */
static gpg_error_t
unlock_pinentry (ctrl_t ctrl, gpg_error_t rc)
{
assuan_context_t ctx = entry_ctx;
int err;
if (rc)
{
if (DBG_IPC)
log_debug ("error calling pinentry: %s <%s>\n",
gpg_strerror (rc), gpg_strsource (rc));
/* Change the source of the error to pinentry so that the final
consumer of the error code knows that the problem is with
pinentry. For backward compatibility we do not do that for
some common error codes. */
switch (gpg_err_code (rc))
{
case GPG_ERR_NO_PIN_ENTRY:
case GPG_ERR_CANCELED:
case GPG_ERR_FULLY_CANCELED:
case GPG_ERR_ASS_UNKNOWN_INQUIRE:
case GPG_ERR_ASS_TOO_MUCH_DATA:
case GPG_ERR_NO_PASSPHRASE:
case GPG_ERR_BAD_PASSPHRASE:
case GPG_ERR_BAD_PIN:
break;
case GPG_ERR_CORRUPTED_PROTECTION:
/* This comes from gpg-agent. */
break;
default:
rc = gpg_err_make (GPG_ERR_SOURCE_PINENTRY, gpg_err_code (rc));
break;
}
}
if (--ctrl->pinentry_active == 0)
{
entry_ctx = NULL;
err = npth_mutex_unlock (&entry_lock);
if (err)
{
log_error ("failed to release the entry lock: %s\n", strerror (err));
if (!rc)
rc = gpg_error_from_errno (err);
}
assuan_release (ctx);
}
return rc;
}
/* To make sure we leave no secrets in our image after forking of the
pinentry, we use this callback. */
static void
atfork_cb (void *opaque, int where)
{
ctrl_t ctrl = opaque;
if (!where)
{
int iterator = 0;
const char *name, *assname, *value;
gcry_control (GCRYCTL_TERM_SECMEM);
while ((name = session_env_list_stdenvnames (&iterator, &assname)))
{
/* For all new envvars (!ASSNAME) and the two medium old
ones which do have an assuan name but are conveyed using
environment variables, update the environment of the
forked process. */
if (!assname
|| !strcmp (name, "XAUTHORITY")
|| !strcmp (name, "PINENTRY_USER_DATA"))
{
value = session_env_getenv (ctrl->session_env, name);
if (value)
gnupg_setenv (name, value, 1);
}
}
}
}
/* Status line callback for the FEATURES status. */
static gpg_error_t
getinfo_features_cb (void *opaque, const char *line)
{
const char *args;
char **tokens;
int i;
(void)opaque;
if ((args = has_leading_keyword (line, "FEATURES")))
{
tokens = strtokenize (args, " ");
if (!tokens)
return gpg_error_from_syserror ();
for (i=0; tokens[i]; i++)
if (!strcmp (tokens[i], "tabbing"))
entry_features.tabbing = 1;
xfree (tokens);
}
return 0;
}
static gpg_error_t
getinfo_pid_cb (void *opaque, const void *buffer, size_t length)
{
unsigned long *pid = opaque;
char pidbuf[50];
/* There is only the pid in the server's response. */
if (length >= sizeof pidbuf)
length = sizeof pidbuf -1;
if (length)
{
strncpy (pidbuf, buffer, length);
pidbuf[length] = 0;
*pid = strtoul (pidbuf, NULL, 10);
}
return 0;
}
/* Fork off the pin entry if this has not already been done. Note,
that this function must always be used to acquire the lock for the
pinentry - we will serialize _all_ pinentry calls.
*/
static gpg_error_t
start_pinentry (ctrl_t ctrl)
{
int rc = 0;
const char *full_pgmname;
const char *pgmname;
assuan_context_t ctx;
const char *argv[5];
assuan_fd_t no_close_list[3];
int i;
const char *tmpstr;
unsigned long pinentry_pid;
const char *value;
struct timespec abstime;
char *flavor_version;
int err;
if (ctrl->pinentry_active)
{
/* It's trying to use pinentry recursively. In this situation,
the thread holds ENTRY_LOCK already. */
ctrl->pinentry_active++;
return 0;
}
npth_clock_gettime (&abstime);
abstime.tv_sec += LOCK_TIMEOUT;
err = npth_mutex_timedlock (&entry_lock, &abstime);
if (err)
{
if (err == ETIMEDOUT)
rc = gpg_error (GPG_ERR_TIMEOUT);
else
rc = gpg_error_from_errno (rc);
log_error (_("failed to acquire the pinentry lock: %s\n"),
gpg_strerror (rc));
return rc;
}
if (entry_ctx)
return 0;
if (opt.verbose)
log_info ("starting a new PIN Entry\n");
#ifdef HAVE_W32_SYSTEM
fflush (stdout);
fflush (stderr);
#endif
if (fflush (NULL))
{
#ifndef HAVE_W32_SYSTEM
gpg_error_t tmperr = gpg_error (gpg_err_code_from_errno (errno));
#endif
log_error ("error flushing pending output: %s\n", strerror (errno));
/* At least Windows XP fails here with EBADF. According to docs
and Wine an fflush(NULL) is the same as _flushall. However
the Wine implementation does not flush stdin,stdout and stderr
- see above. Let's try to ignore the error. */
#ifndef HAVE_W32_SYSTEM
return unlock_pinentry (ctrl, tmperr);
#endif
}
full_pgmname = opt.pinentry_program;
if (!full_pgmname || !*full_pgmname)
full_pgmname = gnupg_module_name (GNUPG_MODULE_NAME_PINENTRY);
if ( !(pgmname = strrchr (full_pgmname, '/')))
pgmname = full_pgmname;
else
pgmname++;
/* OS X needs the entire file name in argv[0], so that it can locate
the resource bundle. For other systems we stick to the usual
convention of supplying only the name of the program. */
#ifdef __APPLE__
argv[0] = full_pgmname;
#else /*!__APPLE__*/
argv[0] = pgmname;
#endif /*__APPLE__*/
if (!opt.keep_display
&& (value = session_env_getenv (ctrl->session_env, "DISPLAY")))
{
argv[1] = "--display";
argv[2] = value;
argv[3] = NULL;
}
else
argv[1] = NULL;
i=0;
if (!opt.running_detached)
{
if (log_get_fd () != -1)
no_close_list[i++] = assuan_fd_from_posix_fd (log_get_fd ());
no_close_list[i++] = assuan_fd_from_posix_fd (fileno (stderr));
}
no_close_list[i] = ASSUAN_INVALID_FD;
rc = assuan_new (&ctx);
if (rc)
{
log_error ("can't allocate assuan context: %s\n", gpg_strerror (rc));
return rc;
}
ctrl->pinentry_active = 1;
entry_ctx = ctx;
/* We don't want to log the pinentry communication to make the logs
easier to read. We might want to add a new debug option to enable
pinentry logging. */
#ifdef ASSUAN_NO_LOGGING
assuan_set_flag (ctx, ASSUAN_NO_LOGGING, !opt.debug_pinentry);
#endif
/* Connect to the pinentry and perform initial handshaking. Note
that atfork is used to change the environment for pinentry. We
start the server in detached mode to suppress the console window
under Windows. */
rc = assuan_pipe_connect (entry_ctx, full_pgmname, argv,
no_close_list, atfork_cb, ctrl,
ASSUAN_PIPE_CONNECT_DETACHED);
if (rc)
{
log_error ("can't connect to the PIN entry module '%s': %s\n",
full_pgmname, gpg_strerror (rc));
return unlock_pinentry (ctrl, gpg_error (GPG_ERR_NO_PIN_ENTRY));
}
if (DBG_IPC)
log_debug ("connection to PIN entry established\n");
value = session_env_getenv (ctrl->session_env, "PINENTRY_USER_DATA");
if (value != NULL)
{
char *optstr;
if (asprintf (&optstr, "OPTION pinentry-user-data=%s", value) < 0 )
return unlock_pinentry (ctrl, out_of_core ());
rc = assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL,
NULL);
xfree (optstr);
if (rc && gpg_err_code (rc) != GPG_ERR_UNKNOWN_OPTION)
return unlock_pinentry (ctrl, rc);
}
rc = assuan_transact (entry_ctx,
opt.no_grab? "OPTION no-grab":"OPTION grab",
NULL, NULL, NULL, NULL, NULL, NULL);
if (rc)
- return unlock_pinentry (ctrl, rc);
+ {
+ if (gpg_err_code (rc) == GPG_ERR_NOT_SUPPORTED
+ || gpg_err_code (rc) == GPG_ERR_UNKNOWN_OPTION)
+ {
+ if (opt.verbose)
+ log_info ("Option no-grab/grab is ignored by pinentry.\n");
+ /* Keep going even if the feature is not supported. */
+ }
+ else
+ return unlock_pinentry (ctrl, rc);
+ }
value = session_env_getenv (ctrl->session_env, "GPG_TTY");
if (value)
{
char *optstr;
if (asprintf (&optstr, "OPTION ttyname=%s", value) < 0 )
return unlock_pinentry (ctrl, out_of_core ());
rc = assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL,
NULL);
xfree (optstr);
if (rc)
return unlock_pinentry (ctrl, rc);
}
value = session_env_getenv (ctrl->session_env, "TERM");
- if (value)
+ if (value && *value)
{
char *optstr;
if (asprintf (&optstr, "OPTION ttytype=%s", value) < 0 )
return unlock_pinentry (ctrl, out_of_core ());
rc = assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL,
NULL);
xfree (optstr);
if (rc)
return unlock_pinentry (ctrl, rc);
}
if (ctrl->lc_ctype)
{
char *optstr;
if (asprintf (&optstr, "OPTION lc-ctype=%s", ctrl->lc_ctype) < 0 )
return unlock_pinentry (ctrl, out_of_core ());
rc = assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL,
NULL);
xfree (optstr);
if (rc)
return unlock_pinentry (ctrl, rc);
}
if (ctrl->lc_messages)
{
char *optstr;
if (asprintf (&optstr, "OPTION lc-messages=%s", ctrl->lc_messages) < 0 )
return unlock_pinentry (ctrl, out_of_core ());
rc = assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL,
NULL);
xfree (optstr);
if (rc)
return unlock_pinentry (ctrl, rc);
}
if (opt.allow_external_cache)
{
/* Indicate to the pinentry that it may read from an external cache.
It is essential that the pinentry respect this. If the
cached password is not up to date and retry == 1, then, using
a version of GPG Agent that doesn't support this, won't issue
another pin request and the user won't get a chance to
correct the password. */
rc = assuan_transact (entry_ctx, "OPTION allow-external-password-cache",
NULL, NULL, NULL, NULL, NULL, NULL);
if (rc && gpg_err_code (rc) != GPG_ERR_UNKNOWN_OPTION)
return unlock_pinentry (ctrl, rc);
}
if (opt.allow_emacs_pinentry)
{
/* Indicate to the pinentry that it may read passphrase through
Emacs minibuffer, if possible. */
rc = assuan_transact (entry_ctx, "OPTION allow-emacs-prompt",
NULL, NULL, NULL, NULL, NULL, NULL);
if (rc && gpg_err_code (rc) != GPG_ERR_UNKNOWN_OPTION)
return unlock_pinentry (ctrl, rc);
}
{
/* Provide a few default strings for use by the pinentries. This
* may help a pinentry to avoid implementing localization code.
* Note that gpg-agent has been set to utf-8 so that the strings
* are in the expected encoding. */
static const struct { const char *key, *value; int what; } tbl[] = {
/* TRANSLATORS: These are labels for buttons etc as used in
* Pinentries. In your translation copy the text before the
* second vertical bar verbatim; translate only the following
* text. An underscore indicates that the next letter should be
* used as an accelerator. Double the underscore to have
* pinentry display a literal underscore. */
{ "ok", N_("|pinentry-label|_OK") },
{ "cancel", N_("|pinentry-label|_Cancel") },
{ "yes", N_("|pinentry-label|_Yes") },
{ "no", N_("|pinentry-label|_No") },
{ "prompt", N_("|pinentry-label|PIN:") },
{ "pwmngr", N_("|pinentry-label|_Save in password manager"), 1 },
{ "cf-visi",N_("Do you really want to make your "
"passphrase visible on the screen?") },
{ "tt-visi",N_("|pinentry-tt|Make passphrase visible") },
{ "tt-hide",N_("|pinentry-tt|Hide passphrase") },
{ NULL, NULL}
};
char *optstr;
int idx;
const char *s, *s2;
for (idx=0; tbl[idx].key; idx++)
{
if (!opt.allow_external_cache && tbl[idx].what == 1)
continue; /* No need for it. */
s = L_(tbl[idx].value);
if (*s == '|' && (s2=strchr (s+1,'|')))
s = s2+1;
if (asprintf (&optstr, "OPTION default-%s=%s", tbl[idx].key, s) < 0 )
return unlock_pinentry (ctrl, out_of_core ());
assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL,
NULL);
xfree (optstr);
}
}
/* Tell the pinentry that we would prefer that the given character
is used as the invisible character by the entry widget. */
if (opt.pinentry_invisible_char)
{
char *optstr;
if ((optstr = xtryasprintf ("OPTION invisible-char=%s",
opt.pinentry_invisible_char)))
{
assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL,
NULL);
/* We ignore errors because this is just a fancy thing and
older pinentries do not support this feature. */
xfree (optstr);
}
}
if (opt.pinentry_timeout)
{
char *optstr;
if ((optstr = xtryasprintf ("SETTIMEOUT %lu", opt.pinentry_timeout)))
{
assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL,
NULL);
/* We ignore errors because this is just a fancy thing. */
xfree (optstr);
}
}
/* Tell the pinentry the name of a file it shall touch after having
messed with the tty. This is optional and only supported by
newer pinentries and thus we do no error checking. */
tmpstr = opt.pinentry_touch_file;
if (tmpstr && !strcmp (tmpstr, "/dev/null"))
tmpstr = NULL;
else if (!tmpstr)
tmpstr = get_agent_socket_name ();
if (tmpstr)
{
char *optstr;
if (asprintf (&optstr, "OPTION touch-file=%s", tmpstr ) < 0 )
;
else
{
assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL,
NULL);
xfree (optstr);
}
}
/* Tell Pinentry about our client. */
if (ctrl->client_pid)
{
char *optstr;
const char *nodename = "";
#ifndef HAVE_W32_SYSTEM
struct utsname utsbuf;
if (!uname (&utsbuf))
nodename = utsbuf.nodename;
#endif /*!HAVE_W32_SYSTEM*/
if ((optstr = xtryasprintf ("OPTION owner=%lu/%d %s",
ctrl->client_pid, ctrl->client_uid,
nodename)))
{
assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL,
NULL);
/* We ignore errors because this is just a fancy thing and
older pinentries do not support this feature. */
xfree (optstr);
}
}
/* Ask the pinentry for its version and flavor and store that as a
* string in MB. This information is useful for helping users to
* figure out Pinentry problems. Noet that "flavor" may also return
* a status line with the features; we use a dedicated handler for
* that. */
{
membuf_t mb;
init_membuf (&mb, 256);
if (assuan_transact (entry_ctx, "GETINFO flavor",
put_membuf_cb, &mb,
NULL, NULL,
getinfo_features_cb, NULL))
put_membuf_str (&mb, "unknown");
put_membuf_str (&mb, " ");
if (assuan_transact (entry_ctx, "GETINFO version",
put_membuf_cb, &mb, NULL, NULL, NULL, NULL))
put_membuf_str (&mb, "unknown");
put_membuf_str (&mb, " ");
if (assuan_transact (entry_ctx, "GETINFO ttyinfo",
put_membuf_cb, &mb, NULL, NULL, NULL, NULL))
put_membuf_str (&mb, "? ? ?");
put_membuf (&mb, "", 1);
flavor_version = get_membuf (&mb, NULL);
}
/* Now ask the Pinentry for its PID. If the Pinentry is new enough
it will send the pid back and we will use an inquire to notify
our client. The client may answer the inquiry either with END or
with CAN to cancel the pinentry. */
rc = assuan_transact (entry_ctx, "GETINFO pid",
getinfo_pid_cb, &pinentry_pid,
NULL, NULL, NULL, NULL);
if (rc)
{
log_info ("You may want to update to a newer pinentry\n");
rc = 0;
}
else if (!rc && (pid_t)pinentry_pid == (pid_t)(-1))
log_error ("pinentry did not return a PID\n");
else
{
rc = agent_inq_pinentry_launched (ctrl, pinentry_pid, flavor_version);
if (gpg_err_code (rc) == GPG_ERR_CANCELED
|| gpg_err_code (rc) == GPG_ERR_FULLY_CANCELED)
return unlock_pinentry (ctrl, gpg_err_make (GPG_ERR_SOURCE_DEFAULT,
gpg_err_code (rc)));
rc = 0;
}
xfree (flavor_version);
return rc;
}
/* Returns True if the pinentry is currently active. If WAITSECONDS is
greater than zero the function will wait for this many seconds
before returning. */
int
pinentry_active_p (ctrl_t ctrl, int waitseconds)
{
int err;
(void)ctrl;
if (waitseconds > 0)
{
struct timespec abstime;
int rc;
npth_clock_gettime (&abstime);
abstime.tv_sec += waitseconds;
err = npth_mutex_timedlock (&entry_lock, &abstime);
if (err)
{
if (err == ETIMEDOUT)
rc = gpg_error (GPG_ERR_TIMEOUT);
else
rc = gpg_error (GPG_ERR_INTERNAL);
return rc;
}
}
else
{
err = npth_mutex_trylock (&entry_lock);
if (err)
return gpg_error (GPG_ERR_LOCKED);
}
err = npth_mutex_unlock (&entry_lock);
if (err)
log_error ("failed to release the entry lock at %d: %s\n", __LINE__,
strerror (errno));
return 0;
}
static gpg_error_t
getpin_cb (void *opaque, const void *buffer, size_t length)
{
struct entry_parm_s *parm = opaque;
if (!buffer)
return 0;
/* we expect the pin to fit on one line */
if (parm->lines || length >= parm->size)
return gpg_error (GPG_ERR_ASS_TOO_MUCH_DATA);
/* fixme: we should make sure that the assuan buffer is allocated in
secure memory or read the response byte by byte */
memcpy (parm->buffer, buffer, length);
parm->buffer[length] = 0;
parm->lines++;
return 0;
}
static int
all_digitsp( const char *s)
{
for (; *s && *s >= '0' && *s <= '9'; s++)
;
return !*s;
}
/* Return a new malloced string by unescaping the string S. Escaping
is percent escaping and '+'/space mapping. A binary Nul will
silently be replaced by a 0xFF. Function returns NULL to indicate
an out of memory status. Parsing stops at the end of the string or
a white space character. */
static char *
unescape_passphrase_string (const unsigned char *s)
{
char *buffer, *d;
buffer = d = xtrymalloc_secure (strlen ((const char*)s)+1);
if (!buffer)
return NULL;
while (*s && !spacep (s))
{
if (*s == '%' && s[1] && s[2])
{
s++;
*d = xtoi_2 (s);
if (!*d)
*d = '\xff';
d++;
s += 2;
}
else if (*s == '+')
{
*d++ = ' ';
s++;
}
else
*d++ = *s++;
}
*d = 0;
return buffer;
}
/* Estimate the quality of the passphrase PW and return a value in the
range 0..100. */
static int
estimate_passphrase_quality (const char *pw)
{
int goodlength = opt.min_passphrase_len + opt.min_passphrase_len/3;
int length;
const char *s;
if (goodlength < 1)
return 0;
for (length = 0, s = pw; *s; s++)
if (!spacep (s))
length ++;
if (length > goodlength)
return 100;
return ((length*10) / goodlength)*10;
}
/* Handle the QUALITY inquiry. */
static gpg_error_t
inq_quality (void *opaque, const char *line)
{
assuan_context_t ctx = opaque;
const char *s;
char *pin;
int rc;
int percent;
char numbuf[20];
if ((s = has_leading_keyword (line, "QUALITY")))
{
pin = unescape_passphrase_string (s);
if (!pin)
rc = gpg_error_from_syserror ();
else
{
percent = estimate_passphrase_quality (pin);
if (check_passphrase_constraints (NULL, pin, NULL))
percent = -percent;
snprintf (numbuf, sizeof numbuf, "%d", percent);
rc = assuan_send_data (ctx, numbuf, strlen (numbuf));
xfree (pin);
}
}
else
{
log_error ("unsupported inquiry '%s' from pinentry\n", line);
rc = gpg_error (GPG_ERR_ASS_UNKNOWN_INQUIRE);
}
return rc;
}
/* Helper for agent_askpin and agent_get_passphrase. */
static gpg_error_t
setup_qualitybar (ctrl_t ctrl)
{
int rc;
char line[ASSUAN_LINELENGTH];
char *tmpstr, *tmpstr2;
const char *tooltip;
(void)ctrl;
/* TRANSLATORS: This string is displayed by Pinentry as the label
for the quality bar. */
tmpstr = try_percent_escape (L_("Quality:"), "\t\r\n\f\v");
snprintf (line, DIM(line), "SETQUALITYBAR %s", tmpstr? tmpstr:"");
xfree (tmpstr);
rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
if (rc == 103 /*(Old assuan error code)*/
|| gpg_err_code (rc) == GPG_ERR_ASS_UNKNOWN_CMD)
; /* Ignore Unknown Command from old Pinentry versions. */
else if (rc)
return rc;
tmpstr2 = gnupg_get_help_string ("pinentry.qualitybar.tooltip", 0);
if (tmpstr2)
tooltip = tmpstr2;
else
{
/* TRANSLATORS: This string is a tooltip, shown by pinentry when
hovering over the quality bar. Please use an appropriate
string to describe what this is about. The length of the
tooltip is limited to about 900 characters. If you do not
translate this entry, a default english text (see source)
will be used. */
tooltip = L_("pinentry.qualitybar.tooltip");
if (!strcmp ("pinentry.qualitybar.tooltip", tooltip))
tooltip = ("The quality of the text entered above.\n"
"Please ask your administrator for "
"details about the criteria.");
}
tmpstr = try_percent_escape (tooltip, "\t\r\n\f\v");
xfree (tmpstr2);
snprintf (line, DIM(line), "SETQUALITYBAR_TT %s", tmpstr? tmpstr:"");
xfree (tmpstr);
rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
if (rc == 103 /*(Old assuan error code)*/
|| gpg_err_code (rc) == GPG_ERR_ASS_UNKNOWN_CMD)
; /* Ignore Unknown Command from old pinentry versions. */
else if (rc)
return rc;
return 0;
}
/* Check the button_info line for a close action. Also check for the
PIN_REPEATED flag. */
static gpg_error_t
pinentry_status_cb (void *opaque, const char *line)
{
unsigned int *flag = opaque;
const char *args;
if ((args = has_leading_keyword (line, "BUTTON_INFO")))
{
if (!strcmp (args, "close"))
*flag |= PINENTRY_STATUS_CLOSE_BUTTON;
}
else if (has_leading_keyword (line, "PIN_REPEATED"))
{
*flag |= PINENTRY_STATUS_PIN_REPEATED;
}
else if (has_leading_keyword (line, "PASSWORD_FROM_CACHE"))
{
*flag |= PINENTRY_STATUS_PASSWORD_FROM_CACHE;
}
return 0;
}
/* Build a SETDESC command line. This is a dedicated function so that
* it can remove control characters which are not supported by the
* current Pinentry. */
static void
build_cmd_setdesc (char *line, size_t linelen, const char *desc)
{
char *src, *dst;
snprintf (line, linelen, "SETDESC %s", desc);
if (!entry_features.tabbing)
{
/* Remove RS and US. */
for (src=dst=line; *src; src++)
if (!strchr ("\x1e\x1f", *src))
*dst++ = *src;
*dst = 0;
}
}
/* Watch the socket's EOF condition, while checking finish of
foreground thread. When EOF condition is detected, terminate
the pinentry process behind the assuan pipe.
*/
static void *
watch_sock (void *arg)
{
- gnupg_fd_t *p = (gnupg_fd_t *)arg;
pid_t pid = assuan_get_pid (entry_ctx);
while (1)
{
int err;
- gnupg_fd_t sock = *p;
fd_set fdset;
struct timeval timeout = { 0, 500000 };
+ gnupg_fd_t sock = *(gnupg_fd_t *)arg;
if (sock == GNUPG_INVALID_FD)
return NULL;
FD_ZERO (&fdset);
FD_SET (FD2INT (sock), &fdset);
err = npth_select (FD2INT (sock)+1, &fdset, NULL, NULL, &timeout);
if (err < 0)
{
if (errno == EINTR)
continue;
else
return NULL;
}
/* Possibly, it's EOF. */
if (err > 0)
break;
}
if (pid == (pid_t)(-1))
; /* No pid available can't send a kill. */
#ifdef HAVE_W32_SYSTEM
/* Older versions of assuan set PID to 0 on Windows to indicate an
invalid value. */
else if (pid != (pid_t) INVALID_HANDLE_VALUE && pid != 0)
TerminateProcess ((HANDLE)pid, 1);
#else
else if (pid > 0)
kill (pid, SIGINT);
#endif
return NULL;
}
-/* Ask pinentry to get a pin by "GETPIN" command, spawning a thread
- detecting the socket's EOF.
- */
static gpg_error_t
-do_getpin (ctrl_t ctrl, struct entry_parm_s *parm)
+watch_sock_start (gnupg_fd_t *sock_p, npth_t *thread_p)
{
npth_attr_t tattr;
- gpg_error_t rc;
int err;
- npth_t thread;
- int saveflag = assuan_get_flag (entry_ctx, ASSUAN_CONFIDENTIAL);
- gnupg_fd_t sock_watched = ctrl->thread_startup.fd;
err = npth_attr_init (&tattr);
if (err)
{
log_error ("do_getpin: error npth_attr_init: %s\n", strerror (err));
return gpg_error_from_errno (err);
}
npth_attr_setdetachstate (&tattr, NPTH_CREATE_JOINABLE);
- err = npth_create (&thread, &tattr, watch_sock, (void *)&sock_watched);
+ err = npth_create (thread_p, &tattr, watch_sock, sock_p);
npth_attr_destroy (&tattr);
if (err)
{
log_error ("do_getpin: error spawning thread: %s\n", strerror (err));
return gpg_error_from_errno (err);
}
+ return 0;
+}
+
+static void
+watch_sock_end (gnupg_fd_t *sock_p, npth_t *thread_p)
+{
+ int err;
+
+ *sock_p = GNUPG_INVALID_FD;
+ err = npth_join (*thread_p, NULL);
+ if (err)
+ log_error ("watch_sock_end: error joining thread: %s\n", strerror (err));
+}
+
+
+/* Ask pinentry to get a pin by "GETPIN" command, spawning a thread
+ detecting the socket's EOF.
+ */
+static gpg_error_t
+do_getpin (ctrl_t ctrl, struct entry_parm_s *parm)
+{
+ gpg_error_t rc;
+ int saveflag = assuan_get_flag (entry_ctx, ASSUAN_CONFIDENTIAL);
+ gnupg_fd_t sock_watched = ctrl->thread_startup.fd;
+ npth_t thread;
+
+ rc = watch_sock_start (&sock_watched, &thread);
+ if (rc)
+ return rc;
+
assuan_begin_confidential (entry_ctx);
rc = assuan_transact (entry_ctx, "GETPIN", getpin_cb, parm,
inq_quality, entry_ctx,
pinentry_status_cb, &parm->status);
assuan_set_flag (entry_ctx, ASSUAN_CONFIDENTIAL, saveflag);
/* Most pinentries out in the wild return the old Assuan error code
for canceled which gets translated to an assuan Cancel error and
not to the code for a user cancel. Fix this here. */
if (rc && gpg_err_source (rc) && gpg_err_code (rc) == GPG_ERR_ASS_CANCELED)
rc = gpg_err_make (gpg_err_source (rc), GPG_ERR_CANCELED);
/* Change error code in case the window close button was clicked
to cancel the operation. */
if ((parm->status & PINENTRY_STATUS_CLOSE_BUTTON)
&& gpg_err_code (rc) == GPG_ERR_CANCELED)
rc = gpg_err_make (gpg_err_source (rc), GPG_ERR_FULLY_CANCELED);
- sock_watched = GNUPG_INVALID_FD;
- err = npth_join (thread, NULL);
- if (err)
- log_error ("do_getpin: error joining thread: %s\n", strerror (err));
+ watch_sock_end (&sock_watched, &thread);
return rc;
}
/* Call the Entry and ask for the PIN. We do check for a valid PIN
number here and repeat it as long as we have invalid formed
numbers. KEYINFO and CACHE_MODE are used to tell pinentry something
about the key. */
gpg_error_t
agent_askpin (ctrl_t ctrl,
const char *desc_text, const char *prompt_text,
const char *initial_errtext,
struct pin_entry_info_s *pininfo,
const char *keyinfo, cache_mode_t cache_mode)
{
gpg_error_t rc;
char line[ASSUAN_LINELENGTH];
struct entry_parm_s parm;
const char *errtext = NULL;
int is_pin = 0;
if (opt.batch)
return 0; /* fixme: we should return BAD PIN */
if (ctrl->pinentry_mode != PINENTRY_MODE_ASK)
{
if (ctrl->pinentry_mode == PINENTRY_MODE_CANCEL)
return gpg_error (GPG_ERR_CANCELED);
if (ctrl->pinentry_mode == PINENTRY_MODE_LOOPBACK)
{
unsigned char *passphrase;
size_t size;
*pininfo->pin = 0; /* Reset the PIN. */
rc = pinentry_loopback (ctrl, "PASSPHRASE", &passphrase, &size,
pininfo->max_length - 1);
if (rc)
return rc;
memcpy(&pininfo->pin, passphrase, size);
xfree(passphrase);
pininfo->pin[size] = 0;
if (pininfo->check_cb)
{
/* More checks by utilizing the optional callback. */
pininfo->cb_errtext = NULL;
rc = pininfo->check_cb (pininfo);
}
return rc;
}
return gpg_error(GPG_ERR_NO_PIN_ENTRY);
}
if (!pininfo || pininfo->max_length < 1)
return gpg_error (GPG_ERR_INV_VALUE);
if (!desc_text && pininfo->min_digits)
desc_text = L_("Please enter your PIN, so that the secret key "
"can be unlocked for this session");
else if (!desc_text)
desc_text = L_("Please enter your passphrase, so that the secret key "
"can be unlocked for this session");
if (prompt_text)
is_pin = !!strstr (prompt_text, "PIN");
else
is_pin = desc_text && strstr (desc_text, "PIN");
rc = start_pinentry (ctrl);
if (rc)
return rc;
/* If we have a KEYINFO string and are normal, user, or ssh cache
mode, we tell that the Pinentry so it may use it for own caching
purposes. Most pinentries won't have this implemented and thus
we do not error out in this case. */
if (keyinfo && (cache_mode == CACHE_MODE_NORMAL
|| cache_mode == CACHE_MODE_USER
|| cache_mode == CACHE_MODE_SSH))
snprintf (line, DIM(line), "SETKEYINFO %c/%s",
cache_mode == CACHE_MODE_USER? 'u' :
cache_mode == CACHE_MODE_SSH? 's' : 'n',
keyinfo);
else
snprintf (line, DIM(line), "SETKEYINFO --clear");
rc = assuan_transact (entry_ctx, line,
NULL, NULL, NULL, NULL, NULL, NULL);
if (rc && gpg_err_code (rc) != GPG_ERR_ASS_UNKNOWN_CMD)
return unlock_pinentry (ctrl, rc);
build_cmd_setdesc (line, DIM(line), desc_text);
rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
if (rc)
return unlock_pinentry (ctrl, rc);
snprintf (line, DIM(line), "SETPROMPT %s",
prompt_text? prompt_text : is_pin? L_("PIN:") : L_("Passphrase:"));
rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
if (rc)
return unlock_pinentry (ctrl, rc);
/* If a passphrase quality indicator has been requested and a
minimum passphrase length has not been disabled, send the command
to the pinentry. */
if (pininfo->with_qualitybar && opt.min_passphrase_len )
{
rc = setup_qualitybar (ctrl);
if (rc)
return unlock_pinentry (ctrl, rc);
}
if (initial_errtext)
{
snprintf (line, DIM(line), "SETERROR %s", initial_errtext);
rc = assuan_transact (entry_ctx, line,
NULL, NULL, NULL, NULL, NULL, NULL);
if (rc)
return unlock_pinentry (ctrl, rc);
}
if (pininfo->with_repeat)
{
snprintf (line, DIM(line), "SETREPEATERROR %s",
L_("does not match - try again"));
rc = assuan_transact (entry_ctx, line,
NULL, NULL, NULL, NULL, NULL, NULL);
if (rc)
pininfo->with_repeat = 0; /* Pinentry does not support it. */
}
pininfo->repeat_okay = 0;
pininfo->status = 0;
for (;pininfo->failed_tries < pininfo->max_tries; pininfo->failed_tries++)
{
memset (&parm, 0, sizeof parm);
parm.size = pininfo->max_length;
*pininfo->pin = 0; /* Reset the PIN. */
parm.buffer = (unsigned char*)pininfo->pin;
if (errtext)
{
/* TRANSLATORS: The string is appended to an error message in
the pinentry. The %s is the actual error message, the
two %d give the current and maximum number of tries. */
snprintf (line, DIM(line), L_("SETERROR %s (try %d of %d)"),
errtext, pininfo->failed_tries+1, pininfo->max_tries);
rc = assuan_transact (entry_ctx, line,
NULL, NULL, NULL, NULL, NULL, NULL);
if (rc)
return unlock_pinentry (ctrl, rc);
errtext = NULL;
}
if (pininfo->with_repeat)
{
snprintf (line, DIM(line), "SETREPEAT %s", L_("Repeat:"));
rc = assuan_transact (entry_ctx, line,
NULL, NULL, NULL, NULL, NULL, NULL);
if (rc)
return unlock_pinentry (ctrl, rc);
}
rc = do_getpin (ctrl, &parm);
pininfo->status = parm.status;
if (gpg_err_code (rc) == GPG_ERR_ASS_TOO_MUCH_DATA)
errtext = is_pin? L_("PIN too long")
: L_("Passphrase too long");
else if (rc)
return unlock_pinentry (ctrl, rc);
if (!errtext && pininfo->min_digits)
{
/* do some basic checks on the entered PIN. */
if (!all_digitsp (pininfo->pin))
errtext = L_("Invalid characters in PIN");
else if (pininfo->max_digits
&& strlen (pininfo->pin) > pininfo->max_digits)
errtext = L_("PIN too long");
else if (strlen (pininfo->pin) < pininfo->min_digits)
errtext = L_("PIN too short");
}
if (!errtext && pininfo->check_cb)
{
/* More checks by utilizing the optional callback. */
pininfo->cb_errtext = NULL;
rc = pininfo->check_cb (pininfo);
/* When pinentry cache causes an error, return now. */
if (rc
&& (pininfo->status & PINENTRY_STATUS_PASSWORD_FROM_CACHE))
return unlock_pinentry (ctrl, rc);
if (gpg_err_code (rc) == GPG_ERR_BAD_PASSPHRASE)
{
if (pininfo->cb_errtext)
errtext = pininfo->cb_errtext;
else if (gpg_err_code (rc) == GPG_ERR_BAD_PASSPHRASE
|| gpg_err_code (rc) == GPG_ERR_BAD_PIN)
errtext = (is_pin? L_("Bad PIN") : L_("Bad Passphrase"));
}
else if (rc)
return unlock_pinentry (ctrl, rc);
}
if (!errtext)
{
if (pininfo->with_repeat
&& (pininfo->status & PINENTRY_STATUS_PIN_REPEATED))
pininfo->repeat_okay = 1;
return unlock_pinentry (ctrl, 0); /* okay, got a PIN or passphrase */
}
if ((pininfo->status & PINENTRY_STATUS_PASSWORD_FROM_CACHE))
/* The password was read from the cache. Don't count this
against the retry count. */
pininfo->failed_tries --;
}
return unlock_pinentry (ctrl, gpg_error (pininfo->min_digits? GPG_ERR_BAD_PIN
: GPG_ERR_BAD_PASSPHRASE));
}
/* Ask for the passphrase using the supplied arguments. The returned
passphrase needs to be freed by the caller. */
int
agent_get_passphrase (ctrl_t ctrl,
char **retpass, const char *desc, const char *prompt,
const char *errtext, int with_qualitybar,
const char *keyinfo, cache_mode_t cache_mode)
{
int rc;
char line[ASSUAN_LINELENGTH];
struct entry_parm_s parm;
*retpass = NULL;
if (opt.batch)
return gpg_error (GPG_ERR_BAD_PASSPHRASE);
if (ctrl->pinentry_mode != PINENTRY_MODE_ASK)
{
if (ctrl->pinentry_mode == PINENTRY_MODE_CANCEL)
return gpg_error (GPG_ERR_CANCELED);
if (ctrl->pinentry_mode == PINENTRY_MODE_LOOPBACK)
{
size_t size;
return pinentry_loopback (ctrl, "PASSPHRASE",
(unsigned char **)retpass, &size,
MAX_PASSPHRASE_LEN);
}
return gpg_error (GPG_ERR_NO_PIN_ENTRY);
}
rc = start_pinentry (ctrl);
if (rc)
return rc;
if (!prompt)
prompt = desc && strstr (desc, "PIN")? L_("PIN:"): L_("Passphrase:");
/* If we have a KEYINFO string and are normal, user, or ssh cache
mode, we tell that the Pinentry so it may use it for own caching
purposes. Most pinentries won't have this implemented and thus
we do not error out in this case. */
if (keyinfo && (cache_mode == CACHE_MODE_NORMAL
|| cache_mode == CACHE_MODE_USER
|| cache_mode == CACHE_MODE_SSH))
snprintf (line, DIM(line), "SETKEYINFO %c/%s",
cache_mode == CACHE_MODE_USER? 'u' :
cache_mode == CACHE_MODE_SSH? 's' : 'n',
keyinfo);
else
snprintf (line, DIM(line), "SETKEYINFO --clear");
rc = assuan_transact (entry_ctx, line,
NULL, NULL, NULL, NULL, NULL, NULL);
if (rc && gpg_err_code (rc) != GPG_ERR_ASS_UNKNOWN_CMD)
return unlock_pinentry (ctrl, rc);
if (desc)
build_cmd_setdesc (line, DIM(line), desc);
else
snprintf (line, DIM(line), "RESET");
rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
if (rc)
return unlock_pinentry (ctrl, rc);
snprintf (line, DIM(line), "SETPROMPT %s", prompt);
rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
if (rc)
return unlock_pinentry (ctrl, rc);
if (with_qualitybar && opt.min_passphrase_len)
{
rc = setup_qualitybar (ctrl);
if (rc)
return unlock_pinentry (ctrl, rc);
}
if (errtext)
{
snprintf (line, DIM(line), "SETERROR %s", errtext);
rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
if (rc)
return unlock_pinentry (ctrl, rc);
}
memset (&parm, 0, sizeof parm);
parm.size = ASSUAN_LINELENGTH/2 - 5;
parm.buffer = gcry_malloc_secure (parm.size+10);
if (!parm.buffer)
return unlock_pinentry (ctrl, out_of_core ());
rc = do_getpin (ctrl, &parm);
if (rc)
xfree (parm.buffer);
else
*retpass = parm.buffer;
return unlock_pinentry (ctrl, rc);
}
/* Pop up the PIN-entry, display the text and the prompt and ask the
user to confirm this. We return 0 for success, ie. the user
confirmed it, GPG_ERR_NOT_CONFIRMED for what the text says or an
other error. If WITH_CANCEL it true an extra cancel button is
displayed to allow the user to easily return a GPG_ERR_CANCELED.
if the Pinentry does not support this, the user can still cancel by
closing the Pinentry window. */
int
agent_get_confirmation (ctrl_t ctrl,
const char *desc, const char *ok,
const char *notok, int with_cancel)
{
int rc;
char line[ASSUAN_LINELENGTH];
if (ctrl->pinentry_mode != PINENTRY_MODE_ASK)
{
if (ctrl->pinentry_mode == PINENTRY_MODE_CANCEL)
return gpg_error (GPG_ERR_CANCELED);
+ if (ctrl->pinentry_mode == PINENTRY_MODE_LOOPBACK)
+ return pinentry_loopback_confirm (ctrl, desc, 1, ok, notok);
+
return gpg_error (GPG_ERR_NO_PIN_ENTRY);
}
rc = start_pinentry (ctrl);
if (rc)
return rc;
if (desc)
build_cmd_setdesc (line, DIM(line), desc);
else
snprintf (line, DIM(line), "RESET");
rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
/* Most pinentries out in the wild return the old Assuan error code
for canceled which gets translated to an assuan Cancel error and
not to the code for a user cancel. Fix this here. */
if (rc && gpg_err_source (rc) && gpg_err_code (rc) == GPG_ERR_ASS_CANCELED)
rc = gpg_err_make (gpg_err_source (rc), GPG_ERR_CANCELED);
if (rc)
return unlock_pinentry (ctrl, rc);
if (ok)
{
snprintf (line, DIM(line), "SETOK %s", ok);
rc = assuan_transact (entry_ctx,
line, NULL, NULL, NULL, NULL, NULL, NULL);
if (rc)
return unlock_pinentry (ctrl, rc);
}
if (notok)
{
/* Try to use the newer NOTOK feature if a cancel button is
requested. If no cancel button is requested we keep on using
the standard cancel. */
if (with_cancel)
{
snprintf (line, DIM(line), "SETNOTOK %s", notok);
rc = assuan_transact (entry_ctx,
line, NULL, NULL, NULL, NULL, NULL, NULL);
}
else
rc = GPG_ERR_ASS_UNKNOWN_CMD;
if (gpg_err_code (rc) == GPG_ERR_ASS_UNKNOWN_CMD)
{
snprintf (line, DIM(line), "SETCANCEL %s", notok);
rc = assuan_transact (entry_ctx, line,
NULL, NULL, NULL, NULL, NULL, NULL);
}
if (rc)
return unlock_pinentry (ctrl, rc);
}
- rc = assuan_transact (entry_ctx, "CONFIRM",
- NULL, NULL, NULL, NULL, NULL, NULL);
- if (rc && gpg_err_source (rc) && gpg_err_code (rc) == GPG_ERR_ASS_CANCELED)
- rc = gpg_err_make (gpg_err_source (rc), GPG_ERR_CANCELED);
-
- return unlock_pinentry (ctrl, rc);
-}
-
-
-
-/* Pop up the PINentry, display the text DESC and a button with the
- text OK_BTN (which may be NULL to use the default of "OK") and wait
- for the user to hit this button. The return value is not
- relevant. */
-int
-agent_show_message (ctrl_t ctrl, const char *desc, const char *ok_btn)
-{
- int rc;
- char line[ASSUAN_LINELENGTH];
+ {
+ gnupg_fd_t sock_watched = ctrl->thread_startup.fd;
+ npth_t thread;
- if (ctrl->pinentry_mode != PINENTRY_MODE_ASK)
- return gpg_error (GPG_ERR_CANCELED);
+ rc = watch_sock_start (&sock_watched, &thread);
+ if (rc)
+ return rc;
- rc = start_pinentry (ctrl);
- if (rc)
- return rc;
+ rc = assuan_transact (entry_ctx, "CONFIRM",
+ NULL, NULL, NULL, NULL, NULL, NULL);
+ if (rc && gpg_err_source (rc) && gpg_err_code (rc) == GPG_ERR_ASS_CANCELED)
+ rc = gpg_err_make (gpg_err_source (rc), GPG_ERR_CANCELED);
- if (desc)
- build_cmd_setdesc (line, DIM(line), desc);
- else
- snprintf (line, DIM(line), "RESET");
- rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
- /* Most pinentries out in the wild return the old Assuan error code
- for canceled which gets translated to an assuan Cancel error and
- not to the code for a user cancel. Fix this here. */
- if (rc && gpg_err_source (rc) && gpg_err_code (rc) == GPG_ERR_ASS_CANCELED)
- rc = gpg_err_make (gpg_err_source (rc), GPG_ERR_CANCELED);
+ watch_sock_end (&sock_watched, &thread);
- if (rc)
return unlock_pinentry (ctrl, rc);
-
- if (ok_btn)
- {
- snprintf (line, DIM(line), "SETOK %s", ok_btn);
- rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL,
- NULL, NULL, NULL);
- if (rc)
- return unlock_pinentry (ctrl, rc);
- }
-
- rc = assuan_transact (entry_ctx, "CONFIRM --one-button", NULL, NULL, NULL,
- NULL, NULL, NULL);
- if (rc && gpg_err_source (rc) && gpg_err_code (rc) == GPG_ERR_ASS_CANCELED)
- rc = gpg_err_make (gpg_err_source (rc), GPG_ERR_CANCELED);
-
- return unlock_pinentry (ctrl, rc);
+ }
}
+
/* The thread running the popup message. */
static void *
popup_message_thread (void *arg)
{
- (void)arg;
+ gpg_error_t rc;
+ gnupg_fd_t sock_watched = *(gnupg_fd_t *)arg;
+ npth_t thread;
+
+ rc = watch_sock_start (&sock_watched, &thread);
+ if (rc)
+ return NULL;
/* We use the --one-button hack instead of the MESSAGE command to
allow the use of old Pinentries. Those old Pinentries will then
show an additional Cancel button but that is mostly a visual
annoyance. */
assuan_transact (entry_ctx, "CONFIRM --one-button",
NULL, NULL, NULL, NULL, NULL, NULL);
+ watch_sock_end (&sock_watched, &thread);
popup_finished = 1;
return NULL;
}
/* Pop up a message window similar to the confirm one but keep it open
until agent_popup_message_stop has been called. It is crucial for
the caller to make sure that the stop function gets called as soon
as the message is not anymore required because the message is
system modal and all other attempts to use the pinentry will fail
(after a timeout). */
int
agent_popup_message_start (ctrl_t ctrl, const char *desc, const char *ok_btn)
{
int rc;
char line[ASSUAN_LINELENGTH];
npth_attr_t tattr;
int err;
if (ctrl->pinentry_mode != PINENTRY_MODE_ASK)
- return gpg_error (GPG_ERR_CANCELED);
+ {
+ if (ctrl->pinentry_mode == PINENTRY_MODE_CANCEL)
+ return gpg_error (GPG_ERR_CANCELED);
+
+ if (ctrl->pinentry_mode == PINENTRY_MODE_LOOPBACK)
+ return pinentry_loopback_confirm (ctrl, desc, 0, ok_btn, NULL);
+
+ return gpg_error (GPG_ERR_NO_PIN_ENTRY);
+ }
rc = start_pinentry (ctrl);
if (rc)
return rc;
if (desc)
build_cmd_setdesc (line, DIM(line), desc);
else
snprintf (line, DIM(line), "RESET");
rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
if (rc)
return unlock_pinentry (ctrl, rc);
if (ok_btn)
{
snprintf (line, DIM(line), "SETOK %s", ok_btn);
rc = assuan_transact (entry_ctx, line, NULL,NULL,NULL,NULL,NULL,NULL);
if (rc)
return unlock_pinentry (ctrl, rc);
}
err = npth_attr_init (&tattr);
if (err)
return unlock_pinentry (ctrl, gpg_error_from_errno (err));
npth_attr_setdetachstate (&tattr, NPTH_CREATE_JOINABLE);
popup_finished = 0;
- err = npth_create (&popup_tid, &tattr, popup_message_thread, NULL);
+ err = npth_create (&popup_tid, &tattr, popup_message_thread,
+ &ctrl->thread_startup.fd);
npth_attr_destroy (&tattr);
if (err)
{
rc = gpg_error_from_errno (err);
log_error ("error spawning popup message handler: %s\n",
strerror (err) );
return unlock_pinentry (ctrl, rc);
}
npth_setname_np (popup_tid, "popup-message");
return 0;
}
/* Close a popup window. */
void
agent_popup_message_stop (ctrl_t ctrl)
{
int rc;
pid_t pid;
(void)ctrl;
+ if (ctrl->pinentry_mode == PINENTRY_MODE_LOOPBACK)
+ return;
+
if (!popup_tid || !entry_ctx)
{
log_debug ("agent_popup_message_stop called with no active popup\n");
return;
}
pid = assuan_get_pid (entry_ctx);
if (pid == (pid_t)(-1))
; /* No pid available can't send a kill. */
else if (popup_finished)
; /* Already finished and ready for joining. */
#ifdef HAVE_W32_SYSTEM
/* Older versions of assuan set PID to 0 on Windows to indicate an
invalid value. */
else if (pid != (pid_t) INVALID_HANDLE_VALUE
&& pid != 0)
{
HANDLE process = (HANDLE) pid;
/* Arbitrary error code. */
TerminateProcess (process, 1);
}
#else
else if (pid > 0)
kill (pid, SIGINT);
#endif
/* Now wait for the thread to terminate. */
rc = npth_join (popup_tid, NULL);
if (rc)
log_debug ("agent_popup_message_stop: pth_join failed: %s\n",
strerror (rc));
/* Thread IDs are opaque, but we try our best here by resetting it
to the same content that a static global variable has. */
memset (&popup_tid, '\0', sizeof (popup_tid));
/* Now we can close the connection. */
unlock_pinentry (ctrl, 0);
}
int
agent_clear_passphrase (ctrl_t ctrl,
const char *keyinfo, cache_mode_t cache_mode)
{
int rc;
char line[ASSUAN_LINELENGTH];
if (! (keyinfo && (cache_mode == CACHE_MODE_NORMAL
|| cache_mode == CACHE_MODE_USER
|| cache_mode == CACHE_MODE_SSH)))
return gpg_error (GPG_ERR_NOT_SUPPORTED);
rc = start_pinentry (ctrl);
if (rc)
return rc;
snprintf (line, DIM(line), "CLEARPASSPHRASE %c/%s",
cache_mode == CACHE_MODE_USER? 'u' :
cache_mode == CACHE_MODE_SSH? 's' : 'n',
keyinfo);
rc = assuan_transact (entry_ctx, line,
NULL, NULL, NULL, NULL, NULL, NULL);
return unlock_pinentry (ctrl, rc);
}
diff --git a/agent/call-scd.c b/agent/call-scd.c
index b2266225e..a96f5b783 100644
--- a/agent/call-scd.c
+++ b/agent/call-scd.c
@@ -1,1368 +1,1526 @@
/* call-scd.c - fork of the scdaemon to do SC operations
* Copyright (C) 2001, 2002, 2005, 2007, 2010,
* 2011 Free Software Foundation, Inc.
* Copyright (C) 2013 Werner Koch
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
-#include <assert.h>
#include <unistd.h>
#ifdef HAVE_SIGNAL_H
# include <signal.h>
#endif
#include <sys/stat.h>
#include <sys/types.h>
#ifndef HAVE_W32_SYSTEM
#include <sys/wait.h>
#endif
#include <npth.h>
#include "agent.h"
#include <assuan.h>
#include "../common/strlist.h"
#ifdef _POSIX_OPEN_MAX
#define MAX_OPEN_FDS _POSIX_OPEN_MAX
#else
#define MAX_OPEN_FDS 20
#endif
/* Definition of module local data of the CTRL structure. */
struct scd_local_s
{
/* We keep a list of all allocated context with an anchor at
SCD_LOCAL_LIST (see below). */
struct scd_local_s *next_local;
assuan_context_t ctx; /* NULL or session context for the SCdaemon
used with this connection. */
unsigned int in_use: 1; /* CTX is in use. */
unsigned int invalid:1; /* CTX is invalid, should be released. */
};
/* Callback parameter for learn card */
struct learn_parm_s
{
void (*kpinfo_cb)(void*, const char *);
void *kpinfo_cb_arg;
void (*certinfo_cb)(void*, const char *);
void *certinfo_cb_arg;
void (*sinfo_cb)(void*, const char *, size_t, const char *);
void *sinfo_cb_arg;
};
/* Callback parameter used by inq_getpin and inq_writekey_parms. */
struct inq_needpin_parm_s
{
assuan_context_t ctx;
int (*getpin_cb)(void *, const char *, const char *, char*, size_t);
void *getpin_cb_arg;
const char *getpin_cb_desc;
assuan_context_t passthru; /* If not NULL, pass unknown inquiries
up to the caller. */
/* The next fields are used by inq_writekey_parm. */
const unsigned char *keydata;
size_t keydatalen;
};
/* To keep track of all active SCD contexts, we keep a linked list
anchored at this variable. */
static struct scd_local_s *scd_local_list;
/* A Mutex used inside the start_scd function. */
static npth_mutex_t start_scd_lock;
/* A malloced string with the name of the socket to be used for
additional connections. May be NULL if not provided by
SCdaemon. */
static char *socket_name;
/* The context of the primary connection. This is also used as a flag
to indicate whether the scdaemon has been started. */
static assuan_context_t primary_scd_ctx;
/* To allow reuse of the primary connection, the following flag is set
to true if the primary context has been reset and is not in use by
any connection. */
static int primary_scd_ctx_reusable;
/* Local prototypes. */
/* This function must be called once to initialize this module. This
has to be done before a second thread is spawned. We can't do the
static initialization because NPth emulation code might not be able
to do a static init; in particular, it is not possible for W32. */
void
initialize_module_call_scd (void)
{
static int initialized;
int err;
if (!initialized)
{
err = npth_mutex_init (&start_scd_lock, NULL);
if (err)
log_fatal ("error initializing mutex: %s\n", strerror (err));
initialized = 1;
}
}
/* This function may be called to print information pertaining to the
current state of this module to the log. */
void
agent_scd_dump_state (void)
{
log_info ("agent_scd_dump_state: primary_scd_ctx=%p pid=%ld reusable=%d\n",
primary_scd_ctx,
(long)assuan_get_pid (primary_scd_ctx),
primary_scd_ctx_reusable);
if (socket_name)
log_info ("agent_scd_dump_state: socket='%s'\n", socket_name);
}
/* The unlock_scd function shall be called after having accessed the
SCD. It is currently not very useful but gives an opportunity to
keep track of connections currently calling SCD. Note that the
"lock" operation is done by the start_scd() function which must be
called and error checked before any SCD operation. CTRL is the
usual connection context and RC the error code to be passed trhough
the function. */
static int
unlock_scd (ctrl_t ctrl, int rc)
{
int err;
if (ctrl->scd_local->in_use == 0)
{
log_error ("unlock_scd: CTX is not in use\n");
if (!rc)
rc = gpg_error (GPG_ERR_INTERNAL);
}
err = npth_mutex_lock (&start_scd_lock);
if (err)
{
log_error ("failed to acquire the start_scd lock: %s\n", strerror (err));
return gpg_error (GPG_ERR_INTERNAL);
}
ctrl->scd_local->in_use = 0;
if (ctrl->scd_local->invalid)
{
assuan_release (ctrl->scd_local->ctx);
ctrl->scd_local->ctx = NULL;
ctrl->scd_local->invalid = 0;
}
err = npth_mutex_unlock (&start_scd_lock);
if (err)
{
log_error ("failed to release the start_scd lock: %s\n", strerror (err));
return gpg_error (GPG_ERR_INTERNAL);
}
return rc;
}
/* To make sure we leave no secrets in our image after forking of the
scdaemon, we use this callback. */
static void
atfork_cb (void *opaque, int where)
{
(void)opaque;
if (!where)
gcry_control (GCRYCTL_TERM_SECMEM);
}
static void *
wait_child_thread (void *arg)
{
int err;
struct scd_local_s *sl;
#ifdef HAVE_W32_SYSTEM
HANDLE pid = (HANDLE)arg;
npth_unprotect ();
WaitForSingleObject ((HANDLE)pid, INFINITE);
npth_protect ();
log_info ("scdaemon finished\n");
#else
int wstatus;
pid_t pid = (pid_t)(uintptr_t)arg;
again:
npth_unprotect ();
err = waitpid (pid, &wstatus, 0);
npth_protect ();
if (err < 0)
{
if (errno == EINTR)
goto again;
log_error ("waitpid failed: %s\n", strerror (errno));
return NULL;
}
else
{
if (WIFEXITED (wstatus))
log_info ("scdaemon finished (status %d)\n", WEXITSTATUS (wstatus));
else if (WIFSIGNALED (wstatus))
log_info ("scdaemon killed by signal %d\n", WTERMSIG (wstatus));
else
{
if (WIFSTOPPED (wstatus))
log_info ("scdaemon stopped by signal %d\n", WSTOPSIG (wstatus));
goto again;
}
}
#endif
err = npth_mutex_lock (&start_scd_lock);
if (err)
{
log_error ("failed to acquire the start_scd lock: %s\n",
strerror (err));
}
else
{
assuan_set_flag (primary_scd_ctx, ASSUAN_NO_WAITPID, 1);
for (sl = scd_local_list; sl; sl = sl->next_local)
{
sl->invalid = 1;
if (!sl->in_use && sl->ctx)
{
assuan_release (sl->ctx);
sl->ctx = NULL;
}
}
primary_scd_ctx = NULL;
primary_scd_ctx_reusable = 0;
xfree (socket_name);
socket_name = NULL;
err = npth_mutex_unlock (&start_scd_lock);
if (err)
log_error ("failed to release the start_scd lock after waitpid: %s\n",
strerror (err));
}
return NULL;
}
/* Fork off the SCdaemon if this has not already been done. Lock the
daemon and make sure that a proper context has been setup in CTRL.
This function might also lock the daemon, which means that the
caller must call unlock_scd after this function has returned
success and the actual Assuan transaction been done. */
static int
start_scd (ctrl_t ctrl)
{
gpg_error_t err = 0;
const char *pgmname;
assuan_context_t ctx = NULL;
const char *argv[5];
assuan_fd_t no_close_list[3];
int i;
int rc;
char *abs_homedir = NULL;
if (opt.disable_scdaemon)
return gpg_error (GPG_ERR_NOT_SUPPORTED);
if (ctrl->scd_local && ctrl->scd_local->ctx)
{
ctrl->scd_local->in_use = 1;
return 0; /* Okay, the context is fine. */
}
if (ctrl->scd_local && ctrl->scd_local->in_use)
{
log_error ("start_scd: CTX is in use\n");
return gpg_error (GPG_ERR_INTERNAL);
}
/* We need to serialize the access to scd_local_list and primary_scd_ctx. */
rc = npth_mutex_lock (&start_scd_lock);
if (rc)
{
log_error ("failed to acquire the start_scd lock: %s\n",
strerror (rc));
return gpg_error (GPG_ERR_INTERNAL);
}
/* If this is the first call for this session, setup the local data
structure. */
if (!ctrl->scd_local)
{
ctrl->scd_local = xtrycalloc (1, sizeof *ctrl->scd_local);
if (!ctrl->scd_local)
- {
- err = gpg_error_from_syserror ();
- rc = npth_mutex_unlock (&start_scd_lock);
- if (rc)
- log_error ("failed to release the start_scd lock: %s\n", strerror (rc));
- return err;
- }
+ {
+ err = gpg_error_from_syserror ();
+ rc = npth_mutex_unlock (&start_scd_lock);
+ if (rc)
+ log_error ("failed to release the start_scd lock: %s\n", strerror (rc));
+ return err;
+ }
ctrl->scd_local->next_local = scd_local_list;
scd_local_list = ctrl->scd_local;
}
ctrl->scd_local->in_use = 1;
/* Check whether the pipe server has already been started and in
this case either reuse a lingering pipe connection or establish a
new socket based one. */
if (primary_scd_ctx && primary_scd_ctx_reusable)
{
ctx = primary_scd_ctx;
primary_scd_ctx_reusable = 0;
if (opt.verbose)
log_info ("new connection to SCdaemon established (reusing)\n");
goto leave;
}
rc = assuan_new (&ctx);
if (rc)
{
log_error ("can't allocate assuan context: %s\n", gpg_strerror (rc));
err = rc;
goto leave;
}
if (socket_name)
{
rc = assuan_socket_connect (ctx, socket_name, 0, 0);
if (rc)
{
log_error ("can't connect to socket '%s': %s\n",
socket_name, gpg_strerror (rc));
err = gpg_error (GPG_ERR_NO_SCDAEMON);
goto leave;
}
if (opt.verbose)
log_info ("new connection to SCdaemon established\n");
goto leave;
}
if (primary_scd_ctx)
{
log_info ("SCdaemon is running but won't accept further connections\n");
err = gpg_error (GPG_ERR_NO_SCDAEMON);
goto leave;
}
/* Nope, it has not been started. Fire it up now. */
if (opt.verbose)
log_info ("no running SCdaemon - starting it\n");
if (fflush (NULL))
{
#ifndef HAVE_W32_SYSTEM
err = gpg_error_from_syserror ();
#endif
log_error ("error flushing pending output: %s\n", strerror (errno));
/* At least Windows XP fails here with EBADF. According to docs
and Wine an fflush(NULL) is the same as _flushall. However
the Wime implementation does not flush stdin,stdout and stderr
- see above. Lets try to ignore the error. */
#ifndef HAVE_W32_SYSTEM
goto leave;
#endif
}
if (!opt.scdaemon_program || !*opt.scdaemon_program)
opt.scdaemon_program = gnupg_module_name (GNUPG_MODULE_NAME_SCDAEMON);
if ( !(pgmname = strrchr (opt.scdaemon_program, '/')))
pgmname = opt.scdaemon_program;
else
pgmname++;
argv[0] = pgmname;
argv[1] = "--multi-server";
if (gnupg_default_homedir_p ())
argv[2] = NULL;
else
{
abs_homedir = make_absfilename_try (gnupg_homedir (), NULL);
if (!abs_homedir)
{
log_error ("error building filename: %s\n",
gpg_strerror (gpg_error_from_syserror ()));
goto leave;
}
argv[2] = "--homedir";
argv[3] = abs_homedir;
argv[4] = NULL;
}
i=0;
if (!opt.running_detached)
{
if (log_get_fd () != -1)
no_close_list[i++] = assuan_fd_from_posix_fd (log_get_fd ());
no_close_list[i++] = assuan_fd_from_posix_fd (fileno (stderr));
}
no_close_list[i] = ASSUAN_INVALID_FD;
/* Connect to the scdaemon and perform initial handshaking. Use
detached flag so that under Windows SCDAEMON does not show up a
new window. */
rc = assuan_pipe_connect (ctx, opt.scdaemon_program, argv,
no_close_list, atfork_cb, NULL,
ASSUAN_PIPE_CONNECT_DETACHED);
if (rc)
{
log_error ("can't connect to the SCdaemon: %s\n",
gpg_strerror (rc));
err = gpg_error (GPG_ERR_NO_SCDAEMON);
goto leave;
}
if (opt.verbose)
log_debug ("first connection to SCdaemon established\n");
/* Get the name of the additional socket opened by scdaemon. */
{
membuf_t data;
unsigned char *databuf;
size_t datalen;
xfree (socket_name);
socket_name = NULL;
init_membuf (&data, 256);
assuan_transact (ctx, "GETINFO socket_name",
put_membuf_cb, &data, NULL, NULL, NULL, NULL);
databuf = get_membuf (&data, &datalen);
if (databuf && datalen)
{
socket_name = xtrymalloc (datalen + 1);
if (!socket_name)
log_error ("warning: can't store socket name: %s\n",
strerror (errno));
else
{
memcpy (socket_name, databuf, datalen);
socket_name[datalen] = 0;
if (DBG_IPC)
log_debug ("additional connections at '%s'\n", socket_name);
}
}
xfree (databuf);
}
/* Tell the scdaemon we want him to send us an event signal. We
don't support this for W32CE. */
#ifndef HAVE_W32CE_SYSTEM
if (opt.sigusr2_enabled)
{
char buf[100];
#ifdef HAVE_W32_SYSTEM
snprintf (buf, sizeof buf, "OPTION event-signal=%p",
get_agent_scd_notify_event ());
#else
snprintf (buf, sizeof buf, "OPTION event-signal=%d", SIGUSR2);
#endif
assuan_transact (ctx, buf, NULL, NULL, NULL, NULL, NULL, NULL);
}
#endif /*HAVE_W32CE_SYSTEM*/
primary_scd_ctx = ctx;
primary_scd_ctx_reusable = 0;
{
npth_t thread;
npth_attr_t tattr;
pid_t pid;
pid = assuan_get_pid (primary_scd_ctx);
err = npth_attr_init (&tattr);
if (!err)
{
npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED);
err = npth_create (&thread, &tattr, wait_child_thread,
(void *)(uintptr_t)pid);
if (err)
log_error ("error spawning wait_child_thread: %s\n", strerror (err));
npth_attr_destroy (&tattr);
}
}
leave:
rc = npth_mutex_unlock (&start_scd_lock);
if (rc)
log_error ("failed to release the start_scd lock: %s\n", strerror (rc));
xfree (abs_homedir);
if (err)
{
unlock_scd (ctrl, err);
if (ctx)
assuan_release (ctx);
}
else
{
ctrl->scd_local->invalid = 0;
ctrl->scd_local->ctx = ctx;
}
return err;
}
/* Check whether the SCdaemon is active. This is a fast check without
any locking and might give a wrong result if another thread is about
to start the daemon or the daemon is about to be stopped.. */
int
agent_scd_check_running (void)
{
return !!primary_scd_ctx;
}
/* Reset the SCD if it has been used. Actually it is not a reset but
a cleanup of resources used by the current connection. */
int
agent_reset_scd (ctrl_t ctrl)
{
int err = npth_mutex_lock (&start_scd_lock);
if (err)
{
log_error ("failed to acquire the start_scd lock: %s\n",
strerror (err));
}
else
{
if (ctrl->scd_local)
{
if (ctrl->scd_local->ctx)
{
/* We send a reset and keep that connection for reuse. */
if (ctrl->scd_local->ctx == primary_scd_ctx)
{
/* Send a RESTART to the SCD. This is required for the
primary connection as a kind of virtual EOF; we don't
have another way to tell it that the next command
should be viewed as if a new connection has been
made. For the non-primary connections this is not
needed as we simply close the socket. We don't check
for an error here because the RESTART may fail for
example if the scdaemon has already been terminated.
Anyway, we need to set the reusable flag to make sure
that the aliveness check can clean it up. */
assuan_transact (primary_scd_ctx, "RESTART",
NULL, NULL, NULL, NULL, NULL, NULL);
primary_scd_ctx_reusable = 1;
}
else
assuan_release (ctrl->scd_local->ctx);
ctrl->scd_local->ctx = NULL;
}
/* Remove the local context from our list and release it. */
if (!scd_local_list)
BUG ();
else if (scd_local_list == ctrl->scd_local)
scd_local_list = ctrl->scd_local->next_local;
else
{
struct scd_local_s *sl;
for (sl=scd_local_list; sl->next_local; sl = sl->next_local)
if (sl->next_local == ctrl->scd_local)
break;
if (!sl->next_local)
BUG ();
sl->next_local = ctrl->scd_local->next_local;
}
xfree (ctrl->scd_local);
ctrl->scd_local = NULL;
}
err = npth_mutex_unlock (&start_scd_lock);
if (err)
log_error ("failed to release the start_scd lock: %s\n", strerror (err));
}
return 0;
}
static gpg_error_t
learn_status_cb (void *opaque, const char *line)
{
struct learn_parm_s *parm = opaque;
const char *keyword = line;
int keywordlen;
for (keywordlen=0; *line && !spacep (line); line++, keywordlen++)
;
while (spacep (line))
line++;
if (keywordlen == 8 && !memcmp (keyword, "CERTINFO", keywordlen))
{
parm->certinfo_cb (parm->certinfo_cb_arg, line);
}
else if (keywordlen == 11 && !memcmp (keyword, "KEYPAIRINFO", keywordlen))
{
parm->kpinfo_cb (parm->kpinfo_cb_arg, line);
}
else if (keywordlen && *line)
{
parm->sinfo_cb (parm->sinfo_cb_arg, keyword, keywordlen, line);
}
return 0;
}
/* Perform the LEARN command and return a list of all private keys
stored on the card. */
int
agent_card_learn (ctrl_t ctrl,
void (*kpinfo_cb)(void*, const char *),
void *kpinfo_cb_arg,
void (*certinfo_cb)(void*, const char *),
void *certinfo_cb_arg,
void (*sinfo_cb)(void*, const char *, size_t, const char *),
void *sinfo_cb_arg)
{
int rc;
struct learn_parm_s parm;
rc = start_scd (ctrl);
if (rc)
return rc;
memset (&parm, 0, sizeof parm);
parm.kpinfo_cb = kpinfo_cb;
parm.kpinfo_cb_arg = kpinfo_cb_arg;
parm.certinfo_cb = certinfo_cb;
parm.certinfo_cb_arg = certinfo_cb_arg;
parm.sinfo_cb = sinfo_cb;
parm.sinfo_cb_arg = sinfo_cb_arg;
rc = assuan_transact (ctrl->scd_local->ctx, "LEARN --force",
NULL, NULL, NULL, NULL,
learn_status_cb, &parm);
if (rc)
return unlock_scd (ctrl, rc);
return unlock_scd (ctrl, 0);
}
static gpg_error_t
get_serialno_cb (void *opaque, const char *line)
{
char **serialno = opaque;
const char *keyword = line;
const char *s;
int keywordlen, n;
for (keywordlen=0; *line && !spacep (line); line++, keywordlen++)
;
while (spacep (line))
line++;
if (keywordlen == 8 && !memcmp (keyword, "SERIALNO", keywordlen))
{
if (*serialno)
return gpg_error (GPG_ERR_CONFLICT); /* Unexpected status line. */
for (n=0,s=line; hexdigitp (s); s++, n++)
;
if (!n || (n&1)|| !(spacep (s) || !*s) )
return gpg_error (GPG_ERR_ASS_PARAMETER);
*serialno = xtrymalloc (n+1);
if (!*serialno)
return out_of_core ();
memcpy (*serialno, line, n);
(*serialno)[n] = 0;
}
return 0;
}
/* Return the serial number of the card or an appropriate error. The
serial number is returned as a hexstring. */
int
agent_card_serialno (ctrl_t ctrl, char **r_serialno, const char *demand)
{
int rc;
char *serialno = NULL;
char line[ASSUAN_LINELENGTH];
rc = start_scd (ctrl);
if (rc)
return rc;
if (!demand)
strcpy (line, "SERIALNO");
else
snprintf (line, DIM(line), "SERIALNO --demand=%s", demand);
rc = assuan_transact (ctrl->scd_local->ctx, line,
NULL, NULL, NULL, NULL,
get_serialno_cb, &serialno);
if (rc)
{
xfree (serialno);
return unlock_scd (ctrl, rc);
}
*r_serialno = serialno;
return unlock_scd (ctrl, 0);
}
/* Handle the NEEDPIN inquiry. */
static gpg_error_t
inq_needpin (void *opaque, const char *line)
{
struct inq_needpin_parm_s *parm = opaque;
const char *s;
char *pin;
size_t pinlen;
int rc;
if ((s = has_leading_keyword (line, "NEEDPIN")))
{
line = s;
pinlen = 90;
pin = gcry_malloc_secure (pinlen);
if (!pin)
return out_of_core ();
rc = parm->getpin_cb (parm->getpin_cb_arg, parm->getpin_cb_desc,
line, pin, pinlen);
if (!rc)
rc = assuan_send_data (parm->ctx, pin, pinlen);
xfree (pin);
}
else if ((s = has_leading_keyword (line, "POPUPPINPADPROMPT")))
{
rc = parm->getpin_cb (parm->getpin_cb_arg, parm->getpin_cb_desc,
s, NULL, 1);
}
else if ((s = has_leading_keyword (line, "DISMISSPINPADPROMPT")))
{
rc = parm->getpin_cb (parm->getpin_cb_arg, parm->getpin_cb_desc,
"", NULL, 0);
}
else if (parm->passthru)
{
unsigned char *value;
size_t valuelen;
int rest;
int needrest = !strncmp (line, "KEYDATA", 8);
/* Pass the inquiry up to our caller. We limit the maximum
amount to an arbitrary value. As we know that the KEYDATA
enquiry is pretty sensitive we disable logging then */
if ((rest = (needrest
&& !assuan_get_flag (parm->passthru, ASSUAN_CONFIDENTIAL))))
assuan_begin_confidential (parm->passthru);
rc = assuan_inquire (parm->passthru, line, &value, &valuelen, 8096);
if (rest)
assuan_end_confidential (parm->passthru);
if (!rc)
{
if ((rest = (needrest
&& !assuan_get_flag (parm->ctx, ASSUAN_CONFIDENTIAL))))
assuan_begin_confidential (parm->ctx);
rc = assuan_send_data (parm->ctx, value, valuelen);
if (rest)
assuan_end_confidential (parm->ctx);
xfree (value);
}
else
log_error ("error forwarding inquiry '%s': %s\n",
line, gpg_strerror (rc));
}
else
{
log_error ("unsupported inquiry '%s'\n", line);
rc = gpg_error (GPG_ERR_ASS_UNKNOWN_INQUIRE);
}
return rc;
}
/* Helper returning a command option to describe the used hash
algorithm. See scd/command.c:cmd_pksign. */
static const char *
hash_algo_option (int algo)
{
switch (algo)
{
case GCRY_MD_MD5 : return "--hash=md5";
case GCRY_MD_RMD160: return "--hash=rmd160";
case GCRY_MD_SHA1 : return "--hash=sha1";
case GCRY_MD_SHA224: return "--hash=sha224";
case GCRY_MD_SHA256: return "--hash=sha256";
case GCRY_MD_SHA384: return "--hash=sha384";
case GCRY_MD_SHA512: return "--hash=sha512";
default: return "";
}
}
/* Create a signature using the current card. MDALGO is either 0 or
* gives the digest algorithm. DESC_TEXT is an additional parameter
* passed to GETPIN_CB. */
int
agent_card_pksign (ctrl_t ctrl,
const char *keyid,
int (*getpin_cb)(void *, const char *,
const char *, char*, size_t),
void *getpin_cb_arg,
const char *desc_text,
int mdalgo,
const unsigned char *indata, size_t indatalen,
unsigned char **r_buf, size_t *r_buflen)
{
int rc;
char line[ASSUAN_LINELENGTH];
membuf_t data;
struct inq_needpin_parm_s inqparm;
*r_buf = NULL;
rc = start_scd (ctrl);
if (rc)
return rc;
if (indatalen*2 + 50 > DIM(line))
return unlock_scd (ctrl, gpg_error (GPG_ERR_GENERAL));
bin2hex (indata, indatalen, stpcpy (line, "SETDATA "));
rc = assuan_transact (ctrl->scd_local->ctx, line,
NULL, NULL, NULL, NULL, NULL, NULL);
if (rc)
return unlock_scd (ctrl, rc);
init_membuf (&data, 1024);
inqparm.ctx = ctrl->scd_local->ctx;
inqparm.getpin_cb = getpin_cb;
inqparm.getpin_cb_arg = getpin_cb_arg;
inqparm.getpin_cb_desc = desc_text;
inqparm.passthru = 0;
inqparm.keydata = NULL;
inqparm.keydatalen = 0;
if (ctrl->use_auth_call)
snprintf (line, sizeof line, "PKAUTH %s", keyid);
else
snprintf (line, sizeof line, "PKSIGN %s %s",
hash_algo_option (mdalgo), keyid);
rc = assuan_transact (ctrl->scd_local->ctx, line,
put_membuf_cb, &data,
inq_needpin, &inqparm,
NULL, NULL);
if (rc)
{
size_t len;
xfree (get_membuf (&data, &len));
return unlock_scd (ctrl, rc);
}
*r_buf = get_membuf (&data, r_buflen);
return unlock_scd (ctrl, 0);
}
/* Check whether there is any padding info from scdaemon. */
static gpg_error_t
padding_info_cb (void *opaque, const char *line)
{
int *r_padding = opaque;
const char *s;
if ((s=has_leading_keyword (line, "PADDING")))
{
*r_padding = atoi (s);
}
return 0;
}
/* Decipher INDATA using the current card. Note that the returned
* value is not an s-expression but the raw data as returned by
* scdaemon. The padding information is stored at R_PADDING with -1
* for not known. DESC_TEXT is an additional parameter passed to
* GETPIN_CB. */
int
agent_card_pkdecrypt (ctrl_t ctrl,
const char *keyid,
int (*getpin_cb)(void *, const char *,
const char *, char*, size_t),
void *getpin_cb_arg,
const char *desc_text,
const unsigned char *indata, size_t indatalen,
char **r_buf, size_t *r_buflen, int *r_padding)
{
int rc, i;
char *p, line[ASSUAN_LINELENGTH];
membuf_t data;
struct inq_needpin_parm_s inqparm;
size_t len;
*r_buf = NULL;
*r_padding = -1; /* Unknown. */
rc = start_scd (ctrl);
if (rc)
return rc;
/* FIXME: use secure memory where appropriate */
for (len = 0; len < indatalen;)
{
p = stpcpy (line, "SETDATA ");
if (len)
p = stpcpy (p, "--append ");
for (i=0; len < indatalen && (i*2 < DIM(line)-50); i++, len++)
{
sprintf (p, "%02X", indata[len]);
p += 2;
}
rc = assuan_transact (ctrl->scd_local->ctx, line,
NULL, NULL, NULL, NULL, NULL, NULL);
if (rc)
return unlock_scd (ctrl, rc);
}
init_membuf (&data, 1024);
inqparm.ctx = ctrl->scd_local->ctx;
inqparm.getpin_cb = getpin_cb;
inqparm.getpin_cb_arg = getpin_cb_arg;
inqparm.getpin_cb_desc = desc_text;
inqparm.passthru = 0;
inqparm.keydata = NULL;
inqparm.keydatalen = 0;
snprintf (line, DIM(line), "PKDECRYPT %s", keyid);
rc = assuan_transact (ctrl->scd_local->ctx, line,
put_membuf_cb, &data,
inq_needpin, &inqparm,
padding_info_cb, r_padding);
if (rc)
{
xfree (get_membuf (&data, &len));
return unlock_scd (ctrl, rc);
}
*r_buf = get_membuf (&data, r_buflen);
if (!*r_buf)
return unlock_scd (ctrl, gpg_error (GPG_ERR_ENOMEM));
return unlock_scd (ctrl, 0);
}
/* Read a certificate with ID into R_BUF and R_BUFLEN. */
int
agent_card_readcert (ctrl_t ctrl,
const char *id, char **r_buf, size_t *r_buflen)
{
int rc;
char line[ASSUAN_LINELENGTH];
membuf_t data;
size_t len;
*r_buf = NULL;
rc = start_scd (ctrl);
if (rc)
return rc;
init_membuf (&data, 1024);
snprintf (line, DIM(line), "READCERT %s", id);
rc = assuan_transact (ctrl->scd_local->ctx, line,
put_membuf_cb, &data,
NULL, NULL,
NULL, NULL);
if (rc)
{
xfree (get_membuf (&data, &len));
return unlock_scd (ctrl, rc);
}
*r_buf = get_membuf (&data, r_buflen);
if (!*r_buf)
return unlock_scd (ctrl, gpg_error (GPG_ERR_ENOMEM));
return unlock_scd (ctrl, 0);
}
/* Read a key with ID and return it in an allocate buffer pointed to
by r_BUF as a valid S-expression. */
int
agent_card_readkey (ctrl_t ctrl, const char *id, unsigned char **r_buf)
{
int rc;
char line[ASSUAN_LINELENGTH];
membuf_t data;
size_t len, buflen;
*r_buf = NULL;
rc = start_scd (ctrl);
if (rc)
return rc;
init_membuf (&data, 1024);
snprintf (line, DIM(line), "READKEY %s", id);
rc = assuan_transact (ctrl->scd_local->ctx, line,
put_membuf_cb, &data,
NULL, NULL,
NULL, NULL);
if (rc)
{
xfree (get_membuf (&data, &len));
return unlock_scd (ctrl, rc);
}
*r_buf = get_membuf (&data, &buflen);
if (!*r_buf)
return unlock_scd (ctrl, gpg_error (GPG_ERR_ENOMEM));
if (!gcry_sexp_canon_len (*r_buf, buflen, NULL, NULL))
{
xfree (*r_buf); *r_buf = NULL;
return unlock_scd (ctrl, gpg_error (GPG_ERR_INV_VALUE));
}
return unlock_scd (ctrl, 0);
}
/* Handle a KEYDATA inquiry. Note, we only send the data,
assuan_transact takes care of flushing and writing the end */
static gpg_error_t
inq_writekey_parms (void *opaque, const char *line)
{
struct inq_needpin_parm_s *parm = opaque;
if (has_leading_keyword (line, "KEYDATA"))
return assuan_send_data (parm->ctx, parm->keydata, parm->keydatalen);
else
return inq_needpin (opaque, line);
}
/* Call scd to write a key to a card under the id KEYREF. */
gpg_error_t
agent_card_writekey (ctrl_t ctrl, int force, const char *serialno,
const char *keyref,
const char *keydata, size_t keydatalen,
int (*getpin_cb)(void *, const char *,
const char *, char*, size_t),
void *getpin_cb_arg)
{
gpg_error_t err;
char line[ASSUAN_LINELENGTH];
struct inq_needpin_parm_s parms;
(void)serialno; /* NULL or a number to check for the correct card.
* But is is not implemented. */
err = start_scd (ctrl);
if (err)
return err;
snprintf (line, DIM(line), "WRITEKEY %s%s", force ? "--force " : "", keyref);
parms.ctx = ctrl->scd_local->ctx;
parms.getpin_cb = getpin_cb;
parms.getpin_cb_arg = getpin_cb_arg;
parms.getpin_cb_desc= NULL;
parms.passthru = 0;
parms.keydata = keydata;
parms.keydatalen = keydatalen;
err = assuan_transact (ctrl->scd_local->ctx, line, NULL, NULL,
inq_writekey_parms, &parms, NULL, NULL);
return unlock_scd (ctrl, err);
}
/* Type used with the card_getattr_cb. */
struct card_getattr_parm_s {
const char *keyword; /* Keyword to look for. */
size_t keywordlen; /* strlen of KEYWORD. */
char *data; /* Malloced and unescaped data. */
int error; /* ERRNO value or 0 on success. */
};
/* Callback function for agent_card_getattr. */
static gpg_error_t
card_getattr_cb (void *opaque, const char *line)
{
struct card_getattr_parm_s *parm = opaque;
const char *keyword = line;
int keywordlen;
if (parm->data)
return 0; /* We want only the first occurrence. */
for (keywordlen=0; *line && !spacep (line); line++, keywordlen++)
;
while (spacep (line))
line++;
if (keywordlen == parm->keywordlen
&& !memcmp (keyword, parm->keyword, keywordlen))
{
parm->data = percent_plus_unescape ((const unsigned char*)line, 0xff);
if (!parm->data)
parm->error = errno;
}
return 0;
}
/* Call the agent to retrieve a single line data object. On success
the object is malloced and stored at RESULT; it is guaranteed that
NULL is never stored in this case. On error an error code is
returned and NULL stored at RESULT. */
gpg_error_t
agent_card_getattr (ctrl_t ctrl, const char *name, char **result)
{
int err;
struct card_getattr_parm_s parm;
char line[ASSUAN_LINELENGTH];
*result = NULL;
if (!*name)
return gpg_error (GPG_ERR_INV_VALUE);
memset (&parm, 0, sizeof parm);
parm.keyword = name;
parm.keywordlen = strlen (name);
/* We assume that NAME does not need escaping. */
if (8 + strlen (name) > DIM(line)-1)
return gpg_error (GPG_ERR_TOO_LARGE);
stpcpy (stpcpy (line, "GETATTR "), name);
err = start_scd (ctrl);
if (err)
return err;
err = assuan_transact (ctrl->scd_local->ctx, line,
NULL, NULL, NULL, NULL,
card_getattr_cb, &parm);
if (!err && parm.error)
err = gpg_error_from_errno (parm.error);
if (!err && !parm.data)
err = gpg_error (GPG_ERR_NO_DATA);
if (!err)
*result = parm.data;
else
xfree (parm.data);
return unlock_scd (ctrl, err);
}
struct card_cardlist_parm_s {
int error;
strlist_t list;
};
/* Callback function for agent_card_cardlist. */
static gpg_error_t
card_cardlist_cb (void *opaque, const char *line)
{
struct card_cardlist_parm_s *parm = opaque;
const char *keyword = line;
int keywordlen;
for (keywordlen=0; *line && !spacep (line); line++, keywordlen++)
;
while (spacep (line))
line++;
if (keywordlen == 8 && !memcmp (keyword, "SERIALNO", keywordlen))
{
const char *s;
int n;
for (n=0,s=line; hexdigitp (s); s++, n++)
;
if (!n || (n&1) || *s)
parm->error = gpg_error (GPG_ERR_ASS_PARAMETER);
else
add_to_strlist (&parm->list, line);
}
return 0;
}
/* Call the scdaemon to retrieve list of available cards. On success
the allocated strlist is stored at RESULT. On error an error code is
returned and NULL stored at RESULT. */
gpg_error_t
agent_card_cardlist (ctrl_t ctrl, strlist_t *result)
{
int err;
struct card_cardlist_parm_s parm;
char line[ASSUAN_LINELENGTH];
*result = NULL;
memset (&parm, 0, sizeof parm);
strcpy (line, "GETINFO card_list");
err = start_scd (ctrl);
if (err)
return err;
err = assuan_transact (ctrl->scd_local->ctx, line,
NULL, NULL, NULL, NULL,
card_cardlist_cb, &parm);
if (!err && parm.error)
err = parm.error;
if (!err)
*result = parm.list;
else
free_strlist (parm.list);
return unlock_scd (ctrl, err);
}
+struct card_keyinfo_parm_s {
+ int error;
+ struct card_key_info_s *list;
+};
+
+/* Callback function for agent_card_keylist. */
+static gpg_error_t
+card_keyinfo_cb (void *opaque, const char *line)
+{
+ struct card_keyinfo_parm_s *parm = opaque;
+ const char *keyword = line;
+ int keywordlen;
+
+ for (keywordlen=0; *line && !spacep (line); line++, keywordlen++)
+ ;
+ while (spacep (line))
+ line++;
+
+ if (keywordlen == 7 && !memcmp (keyword, "KEYINFO", keywordlen))
+ {
+ const char *s;
+ int n;
+ struct card_key_info_s *keyinfo;
+ struct card_key_info_s **l_p = &parm->list;
+
+ while ((*l_p))
+ l_p = &(*l_p)->next;
+
+ keyinfo = xtrycalloc (1, sizeof *keyinfo);
+ if (!keyinfo)
+ {
+ alloc_error:
+ if (!parm->error)
+ parm->error = gpg_error_from_syserror ();
+ return 0;
+ }
+
+ for (n=0,s=line; hexdigitp (s); s++, n++)
+ ;
+
+ if (n != 40)
+ {
+ parm_error:
+ if (!parm->error)
+ parm->error = gpg_error (GPG_ERR_ASS_PARAMETER);
+ return 0;
+ }
+
+ memcpy (keyinfo->keygrip, line, 40);
+
+ line = s;
+
+ if (!*line)
+ goto parm_error;
+
+ while (spacep (line))
+ line++;
+
+ if (*line++ != 'T')
+ goto parm_error;
+
+ if (!*line)
+ goto parm_error;
+
+ while (spacep (line))
+ line++;
+
+ for (n=0,s=line; hexdigitp (s); s++, n++)
+ ;
+
+ if (!n)
+ goto parm_error;
+
+ keyinfo->serialno = xtrymalloc (n+1);
+ if (!keyinfo->serialno)
+ goto alloc_error;
+
+ memcpy (keyinfo->serialno, line, n);
+ keyinfo->serialno[n] = 0;
+
+ line = s;
+
+ if (!*line)
+ goto parm_error;
+
+ while (spacep (line))
+ line++;
+
+ if (!*line)
+ goto parm_error;
+
+ keyinfo->idstr = xtrystrdup (line);
+ if (!keyinfo->idstr)
+ goto alloc_error;
+
+ *l_p = keyinfo;
+ }
+
+ return 0;
+}
+
+
+void
+agent_card_free_keyinfo (struct card_key_info_s *l)
+{
+ struct card_key_info_s *l_next;
+
+ for (; l; l = l_next)
+ {
+ l_next = l->next;
+ free (l->serialno);
+ free (l->idstr);
+ free (l);
+ }
+}
+
+/* Call the scdaemon to check if a key of KEYGRIP is available, or
+ retrieve list of available keys on cards. On success the allocated
+ structure is stored at RESULT. On error an error code is returned
+ and NULL is stored at RESULT. */
+gpg_error_t
+agent_card_keyinfo (ctrl_t ctrl, const char *keygrip,
+ struct card_key_info_s **result)
+{
+ int err;
+ struct card_keyinfo_parm_s parm;
+ char line[ASSUAN_LINELENGTH];
+
+ *result = NULL;
+
+ memset (&parm, 0, sizeof parm);
+ snprintf (line, sizeof line, "KEYINFO %s", keygrip ? keygrip : "--list");
+
+ err = start_scd (ctrl);
+ if (err)
+ return err;
+
+ err = assuan_transact (ctrl->scd_local->ctx, line,
+ NULL, NULL, NULL, NULL,
+ card_keyinfo_cb, &parm);
+ if (!err && parm.error)
+ err = parm.error;
+
+ if (!err)
+ *result = parm.list;
+ else
+ agent_card_free_keyinfo (parm.list);
+
+ return unlock_scd (ctrl, err);
+}
static gpg_error_t
pass_status_thru (void *opaque, const char *line)
{
assuan_context_t ctx = opaque;
char keyword[200];
int i;
if (line[0] == '#' && (!line[1] || spacep (line+1)))
{
/* We are called in convey comments mode. Now, if we see a
comment marker as keyword we forward the line verbatim to the
the caller. This way the comment lines from scdaemon won't
appear as status lines with keyword '#'. */
assuan_write_line (ctx, line);
}
else
{
for (i=0; *line && !spacep (line) && i < DIM(keyword)-1; line++, i++)
keyword[i] = *line;
keyword[i] = 0;
/* Truncate any remaining keyword stuff. */
for (; *line && !spacep (line); line++)
;
while (spacep (line))
line++;
assuan_write_status (ctx, keyword, line);
}
return 0;
}
static gpg_error_t
pass_data_thru (void *opaque, const void *buffer, size_t length)
{
assuan_context_t ctx = opaque;
assuan_send_data (ctx, buffer, length);
return 0;
}
/* Send the line CMDLINE with command for the SCDdaemon to it and send
all status messages back. This command is used as a general quoting
mechanism to pass everything verbatim to SCDAEMON. The PIN
inquiry is handled inside gpg-agent. */
int
agent_card_scd (ctrl_t ctrl, const char *cmdline,
int (*getpin_cb)(void *, const char *,
const char *, char*, size_t),
void *getpin_cb_arg, void *assuan_context)
{
int rc;
struct inq_needpin_parm_s inqparm;
int saveflag;
rc = start_scd (ctrl);
if (rc)
return rc;
inqparm.ctx = ctrl->scd_local->ctx;
inqparm.getpin_cb = getpin_cb;
inqparm.getpin_cb_arg = getpin_cb_arg;
inqparm.getpin_cb_desc = NULL;
inqparm.passthru = assuan_context;
inqparm.keydata = NULL;
inqparm.keydatalen = 0;
saveflag = assuan_get_flag (ctrl->scd_local->ctx, ASSUAN_CONVEY_COMMENTS);
assuan_set_flag (ctrl->scd_local->ctx, ASSUAN_CONVEY_COMMENTS, 1);
rc = assuan_transact (ctrl->scd_local->ctx, cmdline,
pass_data_thru, assuan_context,
inq_needpin, &inqparm,
pass_status_thru, assuan_context);
assuan_set_flag (ctrl->scd_local->ctx, ASSUAN_CONVEY_COMMENTS, saveflag);
if (rc)
{
return unlock_scd (ctrl, rc);
}
return unlock_scd (ctrl, 0);
}
+
+void
+agent_card_killscd (void)
+{
+ if (primary_scd_ctx == NULL)
+ return;
+ assuan_transact (primary_scd_ctx, "KILLSCD",
+ NULL, NULL, NULL, NULL, NULL, NULL);
+}
diff --git a/agent/command-ssh.c b/agent/command-ssh.c
index ebd28ab5a..0849a06fc 100644
--- a/agent/command-ssh.c
+++ b/agent/command-ssh.c
@@ -1,3872 +1,3871 @@
/* command-ssh.c - gpg-agent's implementation of the ssh-agent protocol.
* Copyright (C) 2004-2006, 2009, 2012 Free Software Foundation, Inc.
* Copyright (C) 2004-2006, 2009, 2012-2014 Werner Koch
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
/* Only v2 of the ssh-agent protocol is implemented. Relevant RFCs
are:
RFC-4250 - Protocol Assigned Numbers
RFC-4251 - Protocol Architecture
RFC-4252 - Authentication Protocol
RFC-4253 - Transport Layer Protocol
RFC-5656 - ECC support
The protocol for the agent is defined in:
https://tools.ietf.org/html/draft-miller-ssh-agent
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
-#include <assert.h>
#ifndef HAVE_W32_SYSTEM
#include <sys/socket.h>
#include <sys/un.h>
#endif /*!HAVE_W32_SYSTEM*/
#ifdef HAVE_SYS_UCRED_H
#include <sys/ucred.h>
#endif
#ifdef HAVE_UCRED_H
#include <ucred.h>
#endif
#include "agent.h"
#include "../common/i18n.h"
#include "../common/util.h"
#include "../common/ssh-utils.h"
/* Request types. */
#define SSH_REQUEST_REQUEST_IDENTITIES 11
#define SSH_REQUEST_SIGN_REQUEST 13
#define SSH_REQUEST_ADD_IDENTITY 17
#define SSH_REQUEST_REMOVE_IDENTITY 18
#define SSH_REQUEST_REMOVE_ALL_IDENTITIES 19
#define SSH_REQUEST_LOCK 22
#define SSH_REQUEST_UNLOCK 23
#define SSH_REQUEST_ADD_ID_CONSTRAINED 25
/* Options. */
#define SSH_OPT_CONSTRAIN_LIFETIME 1
#define SSH_OPT_CONSTRAIN_CONFIRM 2
/* Response types. */
#define SSH_RESPONSE_SUCCESS 6
#define SSH_RESPONSE_FAILURE 5
#define SSH_RESPONSE_IDENTITIES_ANSWER 12
#define SSH_RESPONSE_SIGN_RESPONSE 14
/* Other constants. */
#define SSH_DSA_SIGNATURE_PADDING 20
#define SSH_DSA_SIGNATURE_ELEMS 2
#define SSH_AGENT_RSA_SHA2_256 0x02
#define SSH_AGENT_RSA_SHA2_512 0x04
#define SPEC_FLAG_USE_PKCS1V2 (1 << 0)
#define SPEC_FLAG_IS_ECDSA (1 << 1)
#define SPEC_FLAG_IS_EdDSA (1 << 2) /*(lowercase 'd' on purpose.)*/
#define SPEC_FLAG_WITH_CERT (1 << 7)
/* The name of the control file. */
#define SSH_CONTROL_FILE_NAME "sshcontrol"
/* The blurb we put into the header of a newly created control file. */
static const char sshcontrolblurb[] =
"# List of allowed ssh keys. Only keys present in this file are used\n"
"# in the SSH protocol. The ssh-add tool may add new entries to this\n"
"# file to enable them; you may also add them manually. Comment\n"
"# lines, like this one, as well as empty lines are ignored. Lines do\n"
"# have a certain length limit but this is not serious limitation as\n"
"# the format of the entries is fixed and checked by gpg-agent. A\n"
"# non-comment line starts with optional white spaces, followed by the\n"
"# keygrip of the key given as 40 hex digits, optionally followed by a\n"
"# caching TTL in seconds, and another optional field for arbitrary\n"
"# flags. Prepend the keygrip with an '!' mark to disable it.\n"
"\n";
/* Macros. */
/* Return a new uint32 with b0 being the most significant byte and b3
being the least significant byte. */
#define uint32_construct(b0, b1, b2, b3) \
((b0 << 24) | (b1 << 16) | (b2 << 8) | b3)
/*
* Basic types.
*/
/* Type for a request handler. */
typedef gpg_error_t (*ssh_request_handler_t) (ctrl_t ctrl,
estream_t request,
estream_t response);
struct ssh_key_type_spec;
typedef struct ssh_key_type_spec ssh_key_type_spec_t;
/* Type, which is used for associating request handlers with the
appropriate request IDs. */
typedef struct ssh_request_spec
{
unsigned char type;
ssh_request_handler_t handler;
const char *identifier;
unsigned int secret_input;
} ssh_request_spec_t;
/* Type for "key modifier functions", which are necessary since
OpenSSH and GnuPG treat key material slightly different. A key
modifier is called right after a new key identity has been received
in order to "sanitize" the material. */
typedef gpg_error_t (*ssh_key_modifier_t) (const char *elems,
gcry_mpi_t *mpis);
/* The encoding of a generated signature is dependent on the
algorithm; therefore algorithm specific signature encoding
functions are necessary. */
typedef gpg_error_t (*ssh_signature_encoder_t) (ssh_key_type_spec_t *spec,
estream_t signature_blob,
gcry_sexp_t sig);
/* Type, which is used for boundling all the algorithm specific
information together in a single object. */
struct ssh_key_type_spec
{
/* Algorithm identifier as used by OpenSSH. */
const char *ssh_identifier;
/* Human readable name of the algorithm. */
const char *name;
/* Algorithm identifier as used by GnuPG. */
int algo;
/* List of MPI names for secret keys; order matches the one of the
agent protocol. */
const char *elems_key_secret;
/* List of MPI names for public keys; order matches the one of the
agent protocol. */
const char *elems_key_public;
/* List of MPI names for signature data. */
const char *elems_signature;
/* List of MPI names for secret keys; order matches the one, which
is required by gpg-agent's key access layer. */
const char *elems_sexp_order;
/* Key modifier function. Key modifier functions are necessary in
order to fix any inconsistencies between the representation of
keys on the SSH and on the GnuPG side. */
ssh_key_modifier_t key_modifier;
/* Signature encoder function. Signature encoder functions are
necessary since the encoding of signatures depends on the used
algorithm. */
ssh_signature_encoder_t signature_encoder;
/* The name of the ECC curve or NULL for non-ECC algos. This is the
* canonical name for the curve as specified by RFC-5656. */
const char *curve_name;
/* An alias for curve_name or NULL. Actually this is Libcgrypt's
* primary name of the curve. */
const char *alt_curve_name;
/* The hash algorithm to be used with this key. 0 for using the
default. */
int hash_algo;
/* Misc flags. */
unsigned int flags;
};
/* Definition of an object to access the sshcontrol file. */
struct ssh_control_file_s
{
char *fname; /* Name of the file. */
FILE *fp; /* This is never NULL. */
int lnr; /* The current line number. */
struct {
int valid; /* True if the data of this structure is valid. */
int disabled; /* The item is disabled. */
int ttl; /* The TTL of the item. */
int confirm; /* The confirm flag is set. */
char hexgrip[40+1]; /* The hexgrip of the item (uppercase). */
} item;
};
/* Prototypes. */
static gpg_error_t ssh_handler_request_identities (ctrl_t ctrl,
estream_t request,
estream_t response);
static gpg_error_t ssh_handler_sign_request (ctrl_t ctrl,
estream_t request,
estream_t response);
static gpg_error_t ssh_handler_add_identity (ctrl_t ctrl,
estream_t request,
estream_t response);
static gpg_error_t ssh_handler_remove_identity (ctrl_t ctrl,
estream_t request,
estream_t response);
static gpg_error_t ssh_handler_remove_all_identities (ctrl_t ctrl,
estream_t request,
estream_t response);
static gpg_error_t ssh_handler_lock (ctrl_t ctrl,
estream_t request,
estream_t response);
static gpg_error_t ssh_handler_unlock (ctrl_t ctrl,
estream_t request,
estream_t response);
static gpg_error_t ssh_key_modifier_rsa (const char *elems, gcry_mpi_t *mpis);
static gpg_error_t ssh_signature_encoder_rsa (ssh_key_type_spec_t *spec,
estream_t signature_blob,
gcry_sexp_t signature);
static gpg_error_t ssh_signature_encoder_dsa (ssh_key_type_spec_t *spec,
estream_t signature_blob,
gcry_sexp_t signature);
static gpg_error_t ssh_signature_encoder_ecdsa (ssh_key_type_spec_t *spec,
estream_t signature_blob,
gcry_sexp_t signature);
static gpg_error_t ssh_signature_encoder_eddsa (ssh_key_type_spec_t *spec,
estream_t signature_blob,
gcry_sexp_t signature);
static gpg_error_t ssh_key_extract_comment (gcry_sexp_t key, char **comment);
struct peer_info_s
{
unsigned long pid;
int uid;
};
/* Global variables. */
/* Associating request types with the corresponding request
handlers. */
static const ssh_request_spec_t request_specs[] =
{
#define REQUEST_SPEC_DEFINE(id, name, secret_input) \
{ SSH_REQUEST_##id, ssh_handler_##name, #name, secret_input }
REQUEST_SPEC_DEFINE (REQUEST_IDENTITIES, request_identities, 1),
REQUEST_SPEC_DEFINE (SIGN_REQUEST, sign_request, 0),
REQUEST_SPEC_DEFINE (ADD_IDENTITY, add_identity, 1),
REQUEST_SPEC_DEFINE (ADD_ID_CONSTRAINED, add_identity, 1),
REQUEST_SPEC_DEFINE (REMOVE_IDENTITY, remove_identity, 0),
REQUEST_SPEC_DEFINE (REMOVE_ALL_IDENTITIES, remove_all_identities, 0),
REQUEST_SPEC_DEFINE (LOCK, lock, 0),
REQUEST_SPEC_DEFINE (UNLOCK, unlock, 0)
#undef REQUEST_SPEC_DEFINE
};
/* Table holding key type specifications. */
static const ssh_key_type_spec_t ssh_key_types[] =
{
{
"ssh-ed25519", "Ed25519", GCRY_PK_EDDSA, "qd", "q", "rs", "qd",
NULL, ssh_signature_encoder_eddsa,
"Ed25519", NULL, 0, SPEC_FLAG_IS_EdDSA
},
{
"ssh-rsa", "RSA", GCRY_PK_RSA, "nedupq", "en", "s", "nedpqu",
ssh_key_modifier_rsa, ssh_signature_encoder_rsa,
NULL, NULL, 0, SPEC_FLAG_USE_PKCS1V2
},
{
"ssh-dss", "DSA", GCRY_PK_DSA, "pqgyx", "pqgy", "rs", "pqgyx",
NULL, ssh_signature_encoder_dsa,
NULL, NULL, 0, 0
},
{
"ecdsa-sha2-nistp256", "ECDSA", GCRY_PK_ECC, "qd", "q", "rs", "qd",
NULL, ssh_signature_encoder_ecdsa,
"nistp256", "NIST P-256", GCRY_MD_SHA256, SPEC_FLAG_IS_ECDSA
},
{
"ecdsa-sha2-nistp384", "ECDSA", GCRY_PK_ECC, "qd", "q", "rs", "qd",
NULL, ssh_signature_encoder_ecdsa,
"nistp384", "NIST P-384", GCRY_MD_SHA384, SPEC_FLAG_IS_ECDSA
},
{
"ecdsa-sha2-nistp521", "ECDSA", GCRY_PK_ECC, "qd", "q", "rs", "qd",
NULL, ssh_signature_encoder_ecdsa,
"nistp521", "NIST P-521", GCRY_MD_SHA512, SPEC_FLAG_IS_ECDSA
},
{
"ssh-ed25519-cert-v01@openssh.com", "Ed25519",
GCRY_PK_EDDSA, "qd", "q", "rs", "qd",
NULL, ssh_signature_encoder_eddsa,
"Ed25519", NULL, 0, SPEC_FLAG_IS_EdDSA | SPEC_FLAG_WITH_CERT
},
{
"ssh-rsa-cert-v01@openssh.com", "RSA",
GCRY_PK_RSA, "nedupq", "en", "s", "nedpqu",
ssh_key_modifier_rsa, ssh_signature_encoder_rsa,
NULL, NULL, 0, SPEC_FLAG_USE_PKCS1V2 | SPEC_FLAG_WITH_CERT
},
{
"ssh-dss-cert-v01@openssh.com", "DSA",
GCRY_PK_DSA, "pqgyx", "pqgy", "rs", "pqgyx",
NULL, ssh_signature_encoder_dsa,
NULL, NULL, 0, SPEC_FLAG_WITH_CERT | SPEC_FLAG_WITH_CERT
},
{
"ecdsa-sha2-nistp256-cert-v01@openssh.com", "ECDSA",
GCRY_PK_ECC, "qd", "q", "rs", "qd",
NULL, ssh_signature_encoder_ecdsa,
"nistp256", "NIST P-256", GCRY_MD_SHA256,
SPEC_FLAG_IS_ECDSA | SPEC_FLAG_WITH_CERT
},
{
"ecdsa-sha2-nistp384-cert-v01@openssh.com", "ECDSA",
GCRY_PK_ECC, "qd", "q", "rs", "qd",
NULL, ssh_signature_encoder_ecdsa,
"nistp384", "NIST P-384", GCRY_MD_SHA384,
SPEC_FLAG_IS_ECDSA | SPEC_FLAG_WITH_CERT
},
{
"ecdsa-sha2-nistp521-cert-v01@openssh.com", "ECDSA",
GCRY_PK_ECC, "qd", "q", "rs", "qd",
NULL, ssh_signature_encoder_ecdsa,
"nistp521", "NIST P-521", GCRY_MD_SHA512,
SPEC_FLAG_IS_ECDSA | SPEC_FLAG_WITH_CERT
}
};
/*
General utility functions.
*/
/* A secure realloc, i.e. it makes sure to allocate secure memory if A
is NULL. This is required because the standard gcry_realloc does
not know whether to allocate secure or normal if NULL is passed as
existing buffer. */
static void *
realloc_secure (void *a, size_t n)
{
void *p;
if (a)
p = gcry_realloc (a, n);
else
p = gcry_malloc_secure (n);
return p;
}
/* Lookup the ssh-identifier for the ECC curve CURVE_NAME. Returns
* NULL if not found. If found the ssh indetifier is returned and a
* pointer to the canonical curve name as specified for ssh is stored
* at R_CANON_NAME. */
static const char *
ssh_identifier_from_curve_name (const char *curve_name,
const char **r_canon_name)
{
int i;
for (i = 0; i < DIM (ssh_key_types); i++)
if (ssh_key_types[i].curve_name
&& (!strcmp (ssh_key_types[i].curve_name, curve_name)
|| (ssh_key_types[i].alt_curve_name
&& !strcmp (ssh_key_types[i].alt_curve_name, curve_name))))
{
*r_canon_name = ssh_key_types[i].curve_name;
return ssh_key_types[i].ssh_identifier;
}
return NULL;
}
/*
Primitive I/O functions.
*/
/* Read a byte from STREAM, store it in B. */
static gpg_error_t
stream_read_byte (estream_t stream, unsigned char *b)
{
gpg_error_t err;
int ret;
ret = es_fgetc (stream);
if (ret == EOF)
{
if (es_ferror (stream))
err = gpg_error_from_syserror ();
else
err = gpg_error (GPG_ERR_EOF);
*b = 0;
}
else
{
*b = ret & 0xFF;
err = 0;
}
return err;
}
/* Write the byte contained in B to STREAM. */
static gpg_error_t
stream_write_byte (estream_t stream, unsigned char b)
{
gpg_error_t err;
int ret;
ret = es_fputc (b, stream);
if (ret == EOF)
err = gpg_error_from_syserror ();
else
err = 0;
return err;
}
/* Read a uint32 from STREAM, store it in UINT32. */
static gpg_error_t
stream_read_uint32 (estream_t stream, u32 *uint32)
{
unsigned char buffer[4];
size_t bytes_read;
gpg_error_t err;
int ret;
ret = es_read (stream, buffer, sizeof (buffer), &bytes_read);
if (ret)
err = gpg_error_from_syserror ();
else
{
if (bytes_read != sizeof (buffer))
err = gpg_error (GPG_ERR_EOF);
else
{
u32 n;
n = uint32_construct (buffer[0], buffer[1], buffer[2], buffer[3]);
*uint32 = n;
err = 0;
}
}
return err;
}
/* Write the uint32 contained in UINT32 to STREAM. */
static gpg_error_t
stream_write_uint32 (estream_t stream, u32 uint32)
{
unsigned char buffer[4];
gpg_error_t err;
int ret;
buffer[0] = uint32 >> 24;
buffer[1] = uint32 >> 16;
buffer[2] = uint32 >> 8;
buffer[3] = uint32 >> 0;
ret = es_write (stream, buffer, sizeof (buffer), NULL);
if (ret)
err = gpg_error_from_syserror ();
else
err = 0;
return err;
}
/* Read SIZE bytes from STREAM into BUFFER. */
static gpg_error_t
stream_read_data (estream_t stream, unsigned char *buffer, size_t size)
{
gpg_error_t err;
size_t bytes_read;
int ret;
ret = es_read (stream, buffer, size, &bytes_read);
if (ret)
err = gpg_error_from_syserror ();
else
{
if (bytes_read != size)
err = gpg_error (GPG_ERR_EOF);
else
err = 0;
}
return err;
}
/* Skip over SIZE bytes from STREAM. */
static gpg_error_t
stream_read_skip (estream_t stream, size_t size)
{
char buffer[128];
size_t bytes_to_read, bytes_read;
int ret;
do
{
bytes_to_read = size;
if (bytes_to_read > sizeof buffer)
bytes_to_read = sizeof buffer;
ret = es_read (stream, buffer, bytes_to_read, &bytes_read);
if (ret)
return gpg_error_from_syserror ();
else if (bytes_read != bytes_to_read)
return gpg_error (GPG_ERR_EOF);
else
size -= bytes_to_read;
}
while (size);
return 0;
}
/* Write SIZE bytes from BUFFER to STREAM. */
static gpg_error_t
stream_write_data (estream_t stream, const unsigned char *buffer, size_t size)
{
gpg_error_t err;
int ret;
ret = es_write (stream, buffer, size, NULL);
if (ret)
err = gpg_error_from_syserror ();
else
err = 0;
return err;
}
/* Read a binary string from STREAM into STRING, store size of string
in STRING_SIZE. Append a hidden nul so that the result may
directly be used as a C string. Depending on SECURE use secure
memory for STRING. If STRING is NULL do only a dummy read. */
static gpg_error_t
stream_read_string (estream_t stream, unsigned int secure,
unsigned char **string, u32 *string_size)
{
gpg_error_t err;
unsigned char *buffer = NULL;
u32 length = 0;
if (string_size)
*string_size = 0;
/* Read string length. */
err = stream_read_uint32 (stream, &length);
if (err)
goto out;
if (string)
{
/* Allocate space. */
if (secure)
buffer = xtrymalloc_secure (length + 1);
else
buffer = xtrymalloc (length + 1);
if (! buffer)
{
err = gpg_error_from_syserror ();
goto out;
}
/* Read data. */
err = stream_read_data (stream, buffer, length);
if (err)
goto out;
/* Finalize string object. */
buffer[length] = 0;
*string = buffer;
}
else /* Dummy read requested. */
{
err = stream_read_skip (stream, length);
if (err)
goto out;
}
if (string_size)
*string_size = length;
out:
if (err)
xfree (buffer);
return err;
}
/* Read a binary string from STREAM and store it as an opaque MPI at
R_MPI, adding 0x40 (this is the prefix for EdDSA key in OpenPGP).
Depending on SECURE use secure memory. If the string is too large
for key material return an error. */
static gpg_error_t
stream_read_blob (estream_t stream, unsigned int secure, gcry_mpi_t *r_mpi)
{
gpg_error_t err;
unsigned char *buffer = NULL;
u32 length = 0;
*r_mpi = NULL;
/* Read string length. */
err = stream_read_uint32 (stream, &length);
if (err)
goto leave;
/* To avoid excessive use of secure memory we check that an MPI is
not too large. */
if (length > (4096/8) + 8)
{
log_error (_("ssh keys greater than %d bits are not supported\n"), 4096);
err = GPG_ERR_TOO_LARGE;
goto leave;
}
/* Allocate space. */
if (secure)
buffer = xtrymalloc_secure (length+1);
else
buffer = xtrymalloc (length+1);
if (!buffer)
{
err = gpg_error_from_syserror ();
goto leave;
}
/* Read data. */
err = stream_read_data (stream, buffer + 1, length);
if (err)
goto leave;
buffer[0] = 0x40;
*r_mpi = gcry_mpi_set_opaque (NULL, buffer, 8*(length+1));
buffer = NULL;
leave:
xfree (buffer);
return err;
}
/* Read a C-string from STREAM, store copy in STRING. */
static gpg_error_t
stream_read_cstring (estream_t stream, char **string)
{
return stream_read_string (stream, 0, (unsigned char **)string, NULL);
}
/* Write a binary string from STRING of size STRING_N to STREAM. */
static gpg_error_t
stream_write_string (estream_t stream,
const unsigned char *string, u32 string_n)
{
gpg_error_t err;
err = stream_write_uint32 (stream, string_n);
if (err)
goto out;
err = stream_write_data (stream, string, string_n);
out:
return err;
}
/* Write a C-string from STRING to STREAM. */
static gpg_error_t
stream_write_cstring (estream_t stream, const char *string)
{
gpg_error_t err;
err = stream_write_string (stream,
(const unsigned char *) string, strlen (string));
return err;
}
/* Read an MPI from STREAM, store it in MPINT. Depending on SECURE
use secure memory. */
static gpg_error_t
stream_read_mpi (estream_t stream, unsigned int secure, gcry_mpi_t *mpint)
{
unsigned char *mpi_data;
u32 mpi_data_size;
gpg_error_t err;
gcry_mpi_t mpi;
mpi_data = NULL;
err = stream_read_string (stream, secure, &mpi_data, &mpi_data_size);
if (err)
goto out;
/* To avoid excessive use of secure memory we check that an MPI is
not too large. */
if (mpi_data_size > 520)
{
log_error (_("ssh keys greater than %d bits are not supported\n"), 4096);
err = GPG_ERR_TOO_LARGE;
goto out;
}
err = gcry_mpi_scan (&mpi, GCRYMPI_FMT_STD, mpi_data, mpi_data_size, NULL);
if (err)
goto out;
*mpint = mpi;
out:
xfree (mpi_data);
return err;
}
/* Write the MPI contained in MPINT to STREAM. */
static gpg_error_t
stream_write_mpi (estream_t stream, gcry_mpi_t mpint)
{
unsigned char *mpi_buffer;
size_t mpi_buffer_n;
gpg_error_t err;
mpi_buffer = NULL;
err = gcry_mpi_aprint (GCRYMPI_FMT_STD, &mpi_buffer, &mpi_buffer_n, mpint);
if (err)
goto out;
err = stream_write_string (stream, mpi_buffer, mpi_buffer_n);
out:
xfree (mpi_buffer);
return err;
}
/* Copy data from SRC to DST until EOF is reached. */
static gpg_error_t
stream_copy (estream_t dst, estream_t src)
{
char buffer[BUFSIZ];
size_t bytes_read;
gpg_error_t err;
int ret;
err = 0;
while (1)
{
ret = es_read (src, buffer, sizeof (buffer), &bytes_read);
if (ret || (! bytes_read))
{
if (ret)
err = gpg_error_from_syserror ();
break;
}
ret = es_write (dst, buffer, bytes_read, NULL);
if (ret)
{
err = gpg_error_from_syserror ();
break;
}
}
return err;
}
/* Open the ssh control file and create it if not available. With
APPEND passed as true the file will be opened in append mode,
otherwise in read only mode. On success 0 is returned and a new
control file object stored at R_CF. On error an error code is
returned and NULL is stored at R_CF. */
static gpg_error_t
open_control_file (ssh_control_file_t *r_cf, int append)
{
gpg_error_t err;
ssh_control_file_t cf;
cf = xtrycalloc (1, sizeof *cf);
if (!cf)
{
err = gpg_error_from_syserror ();
goto leave;
}
/* Note: As soon as we start to use non blocking functions here
(i.e. where Pth might switch threads) we need to employ a
mutex. */
cf->fname = make_filename_try (gnupg_homedir (), SSH_CONTROL_FILE_NAME, NULL);
if (!cf->fname)
{
err = gpg_error_from_syserror ();
goto leave;
}
/* FIXME: With "a+" we are not able to check whether this will
be created and thus the blurb needs to be written first. */
cf->fp = fopen (cf->fname, append? "a+":"r");
if (!cf->fp && errno == ENOENT)
{
estream_t stream = es_fopen (cf->fname, "wx,mode=-rw-r");
if (!stream)
{
err = gpg_error_from_syserror ();
log_error (_("can't create '%s': %s\n"),
cf->fname, gpg_strerror (err));
goto leave;
}
es_fputs (sshcontrolblurb, stream);
es_fclose (stream);
cf->fp = fopen (cf->fname, append? "a+":"r");
}
if (!cf->fp)
{
err = gpg_error_from_syserror ();
log_error (_("can't open '%s': %s\n"),
cf->fname, gpg_strerror (err));
goto leave;
}
err = 0;
leave:
if (err && cf)
{
if (cf->fp)
fclose (cf->fp);
xfree (cf->fname);
xfree (cf);
}
else
*r_cf = cf;
return err;
}
static void
rewind_control_file (ssh_control_file_t cf)
{
fseek (cf->fp, 0, SEEK_SET);
cf->lnr = 0;
clearerr (cf->fp);
}
static void
close_control_file (ssh_control_file_t cf)
{
if (!cf)
return;
fclose (cf->fp);
xfree (cf->fname);
xfree (cf);
}
/* Read the next line from the control file and store the data in CF.
Returns 0 on success, GPG_ERR_EOF on EOF, or other error codes. */
static gpg_error_t
read_control_file_item (ssh_control_file_t cf)
{
int c, i, n;
char *p, *pend, line[256];
long ttl = 0;
cf->item.valid = 0;
clearerr (cf->fp);
do
{
if (!fgets (line, DIM(line)-1, cf->fp) )
{
if (feof (cf->fp))
return gpg_error (GPG_ERR_EOF);
return gpg_error_from_syserror ();
}
cf->lnr++;
if (!*line || line[strlen(line)-1] != '\n')
{
/* Eat until end of line */
while ( (c=getc (cf->fp)) != EOF && c != '\n')
;
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 == '#');
cf->item.disabled = 0;
if (*p == '!')
{
cf->item.disabled = 1;
for (p++; spacep (p); p++)
;
}
for (i=0; hexdigitp (p) && i < 40; p++, i++)
cf->item.hexgrip[i] = (*p >= 'a'? (*p & 0xdf): *p);
cf->item.hexgrip[i] = 0;
if (i != 40 || !(spacep (p) || *p == '\n'))
{
log_error ("%s:%d: invalid formatted line\n", cf->fname, cf->lnr);
return gpg_error (GPG_ERR_BAD_DATA);
}
ttl = strtol (p, &pend, 10);
p = pend;
if (!(spacep (p) || *p == '\n') || (int)ttl < -1)
{
log_error ("%s:%d: invalid TTL value; assuming 0\n", cf->fname, cf->lnr);
cf->item.ttl = 0;
}
cf->item.ttl = ttl;
/* Now check for key-value pairs of the form NAME[=VALUE]. */
cf->item.confirm = 0;
while (*p)
{
for (; spacep (p) && *p != '\n'; p++)
;
if (!*p || *p == '\n')
break;
n = strcspn (p, "= \t\n");
if (p[n] == '=')
{
log_error ("%s:%d: assigning a value to a flag is not yet supported; "
"flag ignored\n", cf->fname, cf->lnr);
p++;
}
else if (n == 7 && !memcmp (p, "confirm", 7))
{
cf->item.confirm = 1;
}
else
log_error ("%s:%d: invalid flag '%.*s'; ignored\n",
cf->fname, cf->lnr, n, p);
p += n;
}
/* log_debug ("%s:%d: grip=%s ttl=%d%s%s\n", */
/* cf->fname, cf->lnr, */
/* cf->item.hexgrip, cf->item.ttl, */
/* cf->item.disabled? " disabled":"", */
/* cf->item.confirm? " confirm":""); */
cf->item.valid = 1;
return 0; /* Okay: valid entry found. */
}
/* Search the control file CF from the beginning until a matching
HEXGRIP is found; return success in this case and store true at
DISABLED if the found key has been disabled. If R_TTL is not NULL
a specified TTL for that key is stored there. If R_CONFIRM is not
NULL it is set to 1 if the key has the confirm flag set. */
static gpg_error_t
search_control_file (ssh_control_file_t cf, const char *hexgrip,
int *r_disabled, int *r_ttl, int *r_confirm)
{
gpg_error_t err;
- assert (strlen (hexgrip) == 40 );
+ log_assert (strlen (hexgrip) == 40 );
if (r_disabled)
*r_disabled = 0;
if (r_ttl)
*r_ttl = 0;
if (r_confirm)
*r_confirm = 0;
rewind_control_file (cf);
while (!(err=read_control_file_item (cf)))
{
if (!cf->item.valid)
continue; /* Should not happen. */
if (!strcmp (hexgrip, cf->item.hexgrip))
break;
}
if (!err)
{
if (r_disabled)
*r_disabled = cf->item.disabled;
if (r_ttl)
*r_ttl = cf->item.ttl;
if (r_confirm)
*r_confirm = cf->item.confirm;
}
return err;
}
/* Add an entry to the control file to mark the key with the keygrip
HEXGRIP as usable for SSH; i.e. it will be returned when ssh asks
for it. FMTFPR is the fingerprint string. This function is in
general used to add a key received through the ssh-add function.
We can assume that the user wants to allow ssh using this key. */
static gpg_error_t
add_control_entry (ctrl_t ctrl, ssh_key_type_spec_t *spec,
const char *hexgrip, gcry_sexp_t key,
int ttl, int confirm)
{
gpg_error_t err;
ssh_control_file_t cf;
int disabled;
char *fpr_md5 = NULL;
char *fpr_sha256 = NULL;
(void)ctrl;
err = open_control_file (&cf, 1);
if (err)
return err;
err = search_control_file (cf, hexgrip, &disabled, NULL, NULL);
if (err && gpg_err_code(err) == GPG_ERR_EOF)
{
struct tm *tp;
time_t atime = time (NULL);
err = ssh_get_fingerprint_string (key, GCRY_MD_MD5, &fpr_md5);
if (err)
goto out;
err = ssh_get_fingerprint_string (key, GCRY_MD_SHA256, &fpr_sha256);
if (err)
goto out;
/* Not yet in the file - add it. Because the file has been
opened in append mode, we simply need to write to it. */
tp = localtime (&atime);
fprintf (cf->fp,
("# %s key added on: %04d-%02d-%02d %02d:%02d:%02d\n"
"# Fingerprints: %s\n"
"# %s\n"
"%s %d%s\n"),
spec->name,
1900+tp->tm_year, tp->tm_mon+1, tp->tm_mday,
tp->tm_hour, tp->tm_min, tp->tm_sec,
fpr_md5, fpr_sha256, hexgrip, ttl, confirm? " confirm":"");
}
out:
xfree (fpr_md5);
xfree (fpr_sha256);
close_control_file (cf);
return 0;
}
/* Scan the sshcontrol file and return the TTL. */
static int
ttl_from_sshcontrol (const char *hexgrip)
{
ssh_control_file_t cf;
int disabled, ttl;
if (!hexgrip || strlen (hexgrip) != 40)
return 0; /* Wrong input: Use global default. */
if (open_control_file (&cf, 0))
return 0; /* Error: Use the global default TTL. */
if (search_control_file (cf, hexgrip, &disabled, &ttl, NULL)
|| disabled)
ttl = 0; /* Use the global default if not found or disabled. */
close_control_file (cf);
return ttl;
}
/* Scan the sshcontrol file and return the confirm flag. */
static int
confirm_flag_from_sshcontrol (const char *hexgrip)
{
ssh_control_file_t cf;
int disabled, confirm;
if (!hexgrip || strlen (hexgrip) != 40)
return 1; /* Wrong input: Better ask for confirmation. */
if (open_control_file (&cf, 0))
return 1; /* Error: Better ask for confirmation. */
if (search_control_file (cf, hexgrip, &disabled, NULL, &confirm)
|| disabled)
confirm = 0; /* If not found or disabled, there is no reason to
ask for confirmation. */
close_control_file (cf);
return confirm;
}
/* Open the ssh control file for reading. This is a public version of
open_control_file. The caller must use ssh_close_control_file to
release the returned handle. */
ssh_control_file_t
ssh_open_control_file (void)
{
ssh_control_file_t cf;
/* Then look at all the registered and non-disabled keys. */
if (open_control_file (&cf, 0))
return NULL;
return cf;
}
/* Close an ssh control file handle. This is the public version of
close_control_file. CF may be NULL. */
void
ssh_close_control_file (ssh_control_file_t cf)
{
close_control_file (cf);
}
/* Read the next item from the ssh control file. The function returns
0 if a item was read, GPG_ERR_EOF on eof or another error value.
R_HEXGRIP shall either be null or a BUFFER of at least 41 byte.
R_DISABLED, R_TTLm and R_CONFIRM return flags from the control
file; they are only set on success. */
gpg_error_t
ssh_read_control_file (ssh_control_file_t cf,
char *r_hexgrip,
int *r_disabled, int *r_ttl, int *r_confirm)
{
gpg_error_t err;
do
err = read_control_file_item (cf);
while (!err && !cf->item.valid);
if (!err)
{
if (r_hexgrip)
strcpy (r_hexgrip, cf->item.hexgrip);
if (r_disabled)
*r_disabled = cf->item.disabled;
if (r_ttl)
*r_ttl = cf->item.ttl;
if (r_confirm)
*r_confirm = cf->item.confirm;
}
return err;
}
/* Search for a key with HEXGRIP in sshcontrol and return all
info. */
gpg_error_t
ssh_search_control_file (ssh_control_file_t cf,
const char *hexgrip,
int *r_disabled, int *r_ttl, int *r_confirm)
{
gpg_error_t err;
int i;
const char *s;
char uphexgrip[41];
/* We need to make sure that HEXGRIP is all uppercase. The easiest
way to do this and also check its length is by copying to a
second buffer. */
for (i=0, s=hexgrip; i < 40 && *s; s++, i++)
uphexgrip[i] = *s >= 'a'? (*s & 0xdf): *s;
uphexgrip[i] = 0;
if (i != 40)
err = gpg_error (GPG_ERR_INV_LENGTH);
else
err = search_control_file (cf, uphexgrip, r_disabled, r_ttl, r_confirm);
if (gpg_err_code (err) == GPG_ERR_EOF)
err = gpg_error (GPG_ERR_NOT_FOUND);
return err;
}
/*
MPI lists.
*/
/* Free the list of MPIs MPI_LIST. */
static void
mpint_list_free (gcry_mpi_t *mpi_list)
{
if (mpi_list)
{
unsigned int i;
for (i = 0; mpi_list[i]; i++)
gcry_mpi_release (mpi_list[i]);
xfree (mpi_list);
}
}
/* Receive key material MPIs from STREAM according to KEY_SPEC;
depending on SECRET expect a public key or secret key. CERT is the
certificate blob used if KEY_SPEC indicates the certificate format;
it needs to be positioned to the end of the nonce. The newly
allocated list of MPIs is stored in MPI_LIST. Returns usual error
code. */
static gpg_error_t
ssh_receive_mpint_list (estream_t stream, int secret,
ssh_key_type_spec_t *spec, estream_t cert,
gcry_mpi_t **mpi_list)
{
const char *elems_public;
unsigned int elems_n;
const char *elems;
int elem_is_secret;
gcry_mpi_t *mpis = NULL;
gpg_error_t err = 0;
unsigned int i;
if (secret)
elems = spec->elems_key_secret;
else
elems = spec->elems_key_public;
elems_n = strlen (elems);
elems_public = spec->elems_key_public;
/* Check that either both, CERT and the WITH_CERT flag, are given or
none of them. */
if (!(!!(spec->flags & SPEC_FLAG_WITH_CERT) ^ !cert))
{
err = gpg_error (GPG_ERR_INV_CERT_OBJ);
goto out;
}
mpis = xtrycalloc (elems_n + 1, sizeof *mpis );
if (!mpis)
{
err = gpg_error_from_syserror ();
goto out;
}
elem_is_secret = 0;
for (i = 0; i < elems_n; i++)
{
if (secret)
elem_is_secret = !strchr (elems_public, elems[i]);
if (cert && !elem_is_secret)
err = stream_read_mpi (cert, elem_is_secret, &mpis[i]);
else
err = stream_read_mpi (stream, elem_is_secret, &mpis[i]);
if (err)
goto out;
}
*mpi_list = mpis;
mpis = NULL;
out:
if (err)
mpint_list_free (mpis);
return err;
}
/* Key modifier function for RSA. */
static gpg_error_t
ssh_key_modifier_rsa (const char *elems, gcry_mpi_t *mpis)
{
gcry_mpi_t p;
gcry_mpi_t q;
gcry_mpi_t u;
if (strcmp (elems, "nedupq"))
/* Modifying only necessary for secret keys. */
goto out;
u = mpis[3];
p = mpis[4];
q = mpis[5];
if (gcry_mpi_cmp (p, q) > 0)
{
/* P shall be smaller then Q! Swap primes. iqmp becomes u. */
gcry_mpi_t tmp;
tmp = mpis[4];
mpis[4] = mpis[5];
mpis[5] = tmp;
}
else
/* U needs to be recomputed. */
gcry_mpi_invm (u, p, q);
out:
return 0;
}
/* Signature encoder function for RSA. */
static gpg_error_t
ssh_signature_encoder_rsa (ssh_key_type_spec_t *spec,
estream_t signature_blob,
gcry_sexp_t s_signature)
{
gpg_error_t err = 0;
gcry_sexp_t valuelist = NULL;
gcry_sexp_t sublist = NULL;
gcry_mpi_t sig_value = NULL;
gcry_mpi_t *mpis = NULL;
const char *elems;
size_t elems_n;
int i;
unsigned char *data;
size_t data_n;
gcry_mpi_t s;
valuelist = gcry_sexp_nth (s_signature, 1);
if (!valuelist)
{
err = gpg_error (GPG_ERR_INV_SEXP);
goto out;
}
elems = spec->elems_signature;
elems_n = strlen (elems);
mpis = xtrycalloc (elems_n + 1, sizeof *mpis);
if (!mpis)
{
err = gpg_error_from_syserror ();
goto out;
}
for (i = 0; i < elems_n; i++)
{
sublist = gcry_sexp_find_token (valuelist, spec->elems_signature + i, 1);
if (!sublist)
{
err = gpg_error (GPG_ERR_INV_SEXP);
break;
}
sig_value = gcry_sexp_nth_mpi (sublist, 1, GCRYMPI_FMT_USG);
if (!sig_value)
{
err = gpg_error (GPG_ERR_INTERNAL); /* FIXME? */
break;
}
gcry_sexp_release (sublist);
sublist = NULL;
mpis[i] = sig_value;
}
if (err)
goto out;
/* RSA specific */
s = mpis[0];
err = gcry_mpi_aprint (GCRYMPI_FMT_USG, &data, &data_n, s);
if (err)
goto out;
err = stream_write_string (signature_blob, data, data_n);
xfree (data);
out:
gcry_sexp_release (valuelist);
gcry_sexp_release (sublist);
mpint_list_free (mpis);
return err;
}
/* Signature encoder function for DSA. */
static gpg_error_t
ssh_signature_encoder_dsa (ssh_key_type_spec_t *spec,
estream_t signature_blob,
gcry_sexp_t s_signature)
{
gpg_error_t err = 0;
gcry_sexp_t valuelist = NULL;
gcry_sexp_t sublist = NULL;
gcry_mpi_t sig_value = NULL;
gcry_mpi_t *mpis = NULL;
const char *elems;
size_t elems_n;
int i;
unsigned char buffer[SSH_DSA_SIGNATURE_PADDING * SSH_DSA_SIGNATURE_ELEMS];
unsigned char *data = NULL;
size_t data_n;
valuelist = gcry_sexp_nth (s_signature, 1);
if (!valuelist)
{
err = gpg_error (GPG_ERR_INV_SEXP);
goto out;
}
elems = spec->elems_signature;
elems_n = strlen (elems);
mpis = xtrycalloc (elems_n + 1, sizeof *mpis);
if (!mpis)
{
err = gpg_error_from_syserror ();
goto out;
}
for (i = 0; i < elems_n; i++)
{
sublist = gcry_sexp_find_token (valuelist, spec->elems_signature + i, 1);
if (!sublist)
{
err = gpg_error (GPG_ERR_INV_SEXP);
break;
}
sig_value = gcry_sexp_nth_mpi (sublist, 1, GCRYMPI_FMT_USG);
if (!sig_value)
{
err = gpg_error (GPG_ERR_INTERNAL); /* FIXME? */
break;
}
gcry_sexp_release (sublist);
sublist = NULL;
mpis[i] = sig_value;
}
if (err)
goto out;
/* DSA specific code. */
/* FIXME: Why this complicated code? Why collecting boths mpis in a
buffer instead of writing them out one after the other? */
for (i = 0; i < 2; i++)
{
err = gcry_mpi_aprint (GCRYMPI_FMT_USG, &data, &data_n, mpis[i]);
if (err)
break;
if (data_n > SSH_DSA_SIGNATURE_PADDING)
{
err = gpg_error (GPG_ERR_INTERNAL); /* FIXME? */
break;
}
memset (buffer + (i * SSH_DSA_SIGNATURE_PADDING), 0,
SSH_DSA_SIGNATURE_PADDING - data_n);
memcpy (buffer + (i * SSH_DSA_SIGNATURE_PADDING)
+ (SSH_DSA_SIGNATURE_PADDING - data_n), data, data_n);
xfree (data);
data = NULL;
}
if (err)
goto out;
err = stream_write_string (signature_blob, buffer, sizeof (buffer));
out:
xfree (data);
gcry_sexp_release (valuelist);
gcry_sexp_release (sublist);
mpint_list_free (mpis);
return err;
}
/* Signature encoder function for ECDSA. */
static gpg_error_t
ssh_signature_encoder_ecdsa (ssh_key_type_spec_t *spec,
estream_t stream, gcry_sexp_t s_signature)
{
gpg_error_t err = 0;
gcry_sexp_t valuelist = NULL;
gcry_sexp_t sublist = NULL;
gcry_mpi_t sig_value = NULL;
gcry_mpi_t *mpis = NULL;
const char *elems;
size_t elems_n;
int i;
unsigned char *data[2] = {NULL, NULL};
size_t data_n[2];
size_t innerlen;
valuelist = gcry_sexp_nth (s_signature, 1);
if (!valuelist)
{
err = gpg_error (GPG_ERR_INV_SEXP);
goto out;
}
elems = spec->elems_signature;
elems_n = strlen (elems);
mpis = xtrycalloc (elems_n + 1, sizeof *mpis);
if (!mpis)
{
err = gpg_error_from_syserror ();
goto out;
}
for (i = 0; i < elems_n; i++)
{
sublist = gcry_sexp_find_token (valuelist, spec->elems_signature + i, 1);
if (!sublist)
{
err = gpg_error (GPG_ERR_INV_SEXP);
break;
}
sig_value = gcry_sexp_nth_mpi (sublist, 1, GCRYMPI_FMT_USG);
if (!sig_value)
{
err = gpg_error (GPG_ERR_INTERNAL); /* FIXME? */
break;
}
gcry_sexp_release (sublist);
sublist = NULL;
mpis[i] = sig_value;
}
if (err)
goto out;
/* ECDSA specific */
innerlen = 0;
for (i = 0; i < DIM(data); i++)
{
err = gcry_mpi_aprint (GCRYMPI_FMT_STD, &data[i], &data_n[i], mpis[i]);
if (err)
goto out;
innerlen += 4 + data_n[i];
}
err = stream_write_uint32 (stream, innerlen);
if (err)
goto out;
for (i = 0; i < DIM(data); i++)
{
err = stream_write_string (stream, data[i], data_n[i]);
if (err)
goto out;
}
out:
for (i = 0; i < DIM(data); i++)
xfree (data[i]);
gcry_sexp_release (valuelist);
gcry_sexp_release (sublist);
mpint_list_free (mpis);
return err;
}
/* Signature encoder function for EdDSA. */
static gpg_error_t
ssh_signature_encoder_eddsa (ssh_key_type_spec_t *spec,
estream_t stream, gcry_sexp_t s_signature)
{
gpg_error_t err = 0;
gcry_sexp_t valuelist = NULL;
gcry_sexp_t sublist = NULL;
const char *elems;
size_t elems_n;
int i;
unsigned char *data[2] = {NULL, NULL};
size_t data_n[2];
size_t totallen = 0;
valuelist = gcry_sexp_nth (s_signature, 1);
if (!valuelist)
{
err = gpg_error (GPG_ERR_INV_SEXP);
goto out;
}
elems = spec->elems_signature;
elems_n = strlen (elems);
if (elems_n != DIM(data))
{
err = gpg_error (GPG_ERR_INV_SEXP);
goto out;
}
for (i = 0; i < DIM(data); i++)
{
sublist = gcry_sexp_find_token (valuelist, spec->elems_signature + i, 1);
if (!sublist)
{
err = gpg_error (GPG_ERR_INV_SEXP);
break;
}
data[i] = gcry_sexp_nth_buffer (sublist, 1, &data_n[i]);
if (!data[i])
{
err = gpg_error (GPG_ERR_INTERNAL); /* FIXME? */
break;
}
totallen += data_n[i];
gcry_sexp_release (sublist);
sublist = NULL;
}
if (err)
goto out;
err = stream_write_uint32 (stream, totallen);
if (err)
goto out;
for (i = 0; i < DIM(data); i++)
{
err = stream_write_data (stream, data[i], data_n[i]);
if (err)
goto out;
}
out:
for (i = 0; i < DIM(data); i++)
xfree (data[i]);
gcry_sexp_release (valuelist);
gcry_sexp_release (sublist);
return err;
}
/*
S-Expressions.
*/
/* This function constructs a new S-Expression for the key identified
by the KEY_SPEC, SECRET, CURVE_NAME, MPIS, and COMMENT, which is to
be stored at R_SEXP. Returns an error code. */
static gpg_error_t
sexp_key_construct (gcry_sexp_t *r_sexp,
ssh_key_type_spec_t key_spec, int secret,
const char *curve_name, gcry_mpi_t *mpis,
const char *comment)
{
gpg_error_t err;
gcry_sexp_t sexp_new = NULL;
void *formatbuf = NULL;
void **arg_list = NULL;
estream_t format = NULL;
char *algo_name = NULL;
if ((key_spec.flags & SPEC_FLAG_IS_EdDSA))
{
/* It is much easier and more readable to use a separate code
path for EdDSA. */
if (!curve_name)
err = gpg_error (GPG_ERR_INV_CURVE);
else if (!mpis[0] || !gcry_mpi_get_flag (mpis[0], GCRYMPI_FLAG_OPAQUE))
err = gpg_error (GPG_ERR_BAD_PUBKEY);
else if (secret
&& (!mpis[1]
|| !gcry_mpi_get_flag (mpis[1], GCRYMPI_FLAG_OPAQUE)))
err = gpg_error (GPG_ERR_BAD_SECKEY);
else if (secret)
err = gcry_sexp_build (&sexp_new, NULL,
"(private-key(ecc(curve %s)"
"(flags eddsa)(q %m)(d %m))"
"(comment%s))",
curve_name,
mpis[0], mpis[1],
comment? comment:"");
else
err = gcry_sexp_build (&sexp_new, NULL,
"(public-key(ecc(curve %s)"
"(flags eddsa)(q %m))"
"(comment%s))",
curve_name,
mpis[0],
comment? comment:"");
}
else
{
const char *key_identifier[] = { "public-key", "private-key" };
int arg_idx;
const char *elems;
size_t elems_n;
unsigned int i, j;
if (secret)
elems = key_spec.elems_sexp_order;
else
elems = key_spec.elems_key_public;
elems_n = strlen (elems);
format = es_fopenmem (0, "a+b");
if (!format)
{
err = gpg_error_from_syserror ();
goto out;
}
/* Key identifier, algorithm identifier, mpis, comment, and a NULL
as a safeguard. */
arg_list = xtrymalloc (sizeof (*arg_list) * (2 + 1 + elems_n + 1 + 1));
if (!arg_list)
{
err = gpg_error_from_syserror ();
goto out;
}
arg_idx = 0;
es_fputs ("(%s(%s", format);
arg_list[arg_idx++] = &key_identifier[secret];
algo_name = xtrystrdup (gcry_pk_algo_name (key_spec.algo));
if (!algo_name)
{
err = gpg_error_from_syserror ();
goto out;
}
strlwr (algo_name);
arg_list[arg_idx++] = &algo_name;
if (curve_name)
{
es_fputs ("(curve%s)", format);
arg_list[arg_idx++] = &curve_name;
}
for (i = 0; i < elems_n; i++)
{
es_fprintf (format, "(%c%%m)", elems[i]);
if (secret)
{
for (j = 0; j < elems_n; j++)
if (key_spec.elems_key_secret[j] == elems[i])
break;
}
else
j = i;
arg_list[arg_idx++] = &mpis[j];
}
es_fputs (")(comment%s))", format);
arg_list[arg_idx++] = &comment;
arg_list[arg_idx] = NULL;
es_putc (0, format);
if (es_ferror (format))
{
err = gpg_error_from_syserror ();
goto out;
}
if (es_fclose_snatch (format, &formatbuf, NULL))
{
err = gpg_error_from_syserror ();
goto out;
}
format = NULL;
err = gcry_sexp_build_array (&sexp_new, NULL, formatbuf, arg_list);
}
if (!err)
*r_sexp = sexp_new;
out:
es_fclose (format);
xfree (arg_list);
xfree (formatbuf);
xfree (algo_name);
return err;
}
/* This function extracts the key from the s-expression SEXP according
to KEY_SPEC and stores it in ssh format at (R_BLOB, R_BLOBLEN). If
WITH_SECRET is true, the secret key parts are also extracted if
possible. Returns 0 on success or an error code. Note that data
stored at R_BLOB must be freed using es_free! */
static gpg_error_t
ssh_key_to_blob (gcry_sexp_t sexp, int with_secret,
ssh_key_type_spec_t key_spec,
void **r_blob, size_t *r_blob_size)
{
gpg_error_t err = 0;
gcry_sexp_t value_list = NULL;
gcry_sexp_t value_pair = NULL;
estream_t stream = NULL;
void *blob = NULL;
size_t blob_size;
const char *elems, *p_elems;
const char *data;
size_t datalen;
*r_blob = NULL;
*r_blob_size = 0;
stream = es_fopenmem (0, "r+b");
if (!stream)
{
err = gpg_error_from_syserror ();
goto out;
}
/* Get the type of the key expression. */
data = gcry_sexp_nth_data (sexp, 0, &datalen);
if (!data)
{
err = gpg_error (GPG_ERR_INV_SEXP);
goto out;
}
if ((datalen == 10 && !strncmp (data, "public-key", 10))
|| (datalen == 21 && !strncmp (data, "protected-private-key", 21))
|| (datalen == 20 && !strncmp (data, "shadowed-private-key", 20)))
elems = key_spec.elems_key_public;
else if (datalen == 11 && !strncmp (data, "private-key", 11))
elems = with_secret? key_spec.elems_key_secret : key_spec.elems_key_public;
else
{
err = gpg_error (GPG_ERR_INV_SEXP);
goto out;
}
/* Get key value list. */
value_list = gcry_sexp_cadr (sexp);
if (!value_list)
{
err = gpg_error (GPG_ERR_INV_SEXP);
goto out;
}
/* Write the ssh algorithm identifier. */
if ((key_spec.flags & SPEC_FLAG_IS_ECDSA))
{
/* Map the curve name to the ssh name. */
const char *name, *sshname, *canon_name;
name = gcry_pk_get_curve (sexp, 0, NULL);
if (!name)
{
err = gpg_error (GPG_ERR_INV_CURVE);
goto out;
}
sshname = ssh_identifier_from_curve_name (name, &canon_name);
if (!sshname)
{
err = gpg_error (GPG_ERR_UNKNOWN_CURVE);
goto out;
}
err = stream_write_cstring (stream, sshname);
if (err)
goto out;
err = stream_write_cstring (stream, canon_name);
if (err)
goto out;
}
else
{
/* Note: This is also used for EdDSA. */
err = stream_write_cstring (stream, key_spec.ssh_identifier);
if (err)
goto out;
}
/* Write the parameters. */
for (p_elems = elems; *p_elems; p_elems++)
{
gcry_sexp_release (value_pair);
value_pair = gcry_sexp_find_token (value_list, p_elems, 1);
if (!value_pair)
{
err = gpg_error (GPG_ERR_INV_SEXP);
goto out;
}
if ((key_spec.flags & SPEC_FLAG_IS_EdDSA))
{
data = gcry_sexp_nth_data (value_pair, 1, &datalen);
if (!data)
{
err = gpg_error (GPG_ERR_INV_SEXP);
goto out;
}
if (*p_elems == 'q' && datalen)
{ /* Remove the prefix 0x40. */
data++;
datalen--;
}
err = stream_write_string (stream, data, datalen);
if (err)
goto out;
}
else
{
gcry_mpi_t mpi;
/* Note that we need to use STD format; i.e. prepend a 0x00
to indicate a positive number if the high bit is set. */
mpi = gcry_sexp_nth_mpi (value_pair, 1, GCRYMPI_FMT_STD);
if (!mpi)
{
err = gpg_error (GPG_ERR_INV_SEXP);
goto out;
}
err = stream_write_mpi (stream, mpi);
gcry_mpi_release (mpi);
if (err)
goto out;
}
}
if (es_fclose_snatch (stream, &blob, &blob_size))
{
err = gpg_error_from_syserror ();
goto out;
}
stream = NULL;
*r_blob = blob;
blob = NULL;
*r_blob_size = blob_size;
out:
gcry_sexp_release (value_list);
gcry_sexp_release (value_pair);
es_fclose (stream);
es_free (blob);
return err;
}
/*
Key I/O.
*/
/* Search for a key specification entry. If SSH_NAME is not NULL,
search for an entry whose "ssh_name" is equal to SSH_NAME;
otherwise, search for an entry whose algorithm is equal to ALGO.
Store found entry in SPEC on success, return error otherwise. */
static gpg_error_t
ssh_key_type_lookup (const char *ssh_name, int algo,
ssh_key_type_spec_t *spec)
{
gpg_error_t err;
unsigned int i;
for (i = 0; i < DIM (ssh_key_types); i++)
if ((ssh_name && (! strcmp (ssh_name, ssh_key_types[i].ssh_identifier)))
|| algo == ssh_key_types[i].algo)
break;
if (i == DIM (ssh_key_types))
err = gpg_error (GPG_ERR_NOT_FOUND);
else
{
*spec = ssh_key_types[i];
err = 0;
}
return err;
}
/* Receive a key from STREAM, according to the key specification given
as KEY_SPEC. Depending on SECRET, receive a secret or a public
key. If READ_COMMENT is true, receive a comment string as well.
Constructs a new S-Expression from received data and stores it in
KEY_NEW. Returns zero on success or an error code. */
static gpg_error_t
ssh_receive_key (estream_t stream, gcry_sexp_t *key_new, int secret,
int read_comment, ssh_key_type_spec_t *key_spec)
{
gpg_error_t err;
char *key_type = NULL;
char *comment = NULL;
estream_t cert = NULL;
gcry_sexp_t key = NULL;
ssh_key_type_spec_t spec;
gcry_mpi_t *mpi_list = NULL;
const char *elems;
const char *curve_name = NULL;
err = stream_read_cstring (stream, &key_type);
if (err)
goto out;
err = ssh_key_type_lookup (key_type, 0, &spec);
if (err)
goto out;
if ((spec.flags & SPEC_FLAG_WITH_CERT))
{
/* This is an OpenSSH certificate+private key. The certificate
is an SSH string and which we store in an estream object. */
unsigned char *buffer;
u32 buflen;
char *cert_key_type;
err = stream_read_string (stream, 0, &buffer, &buflen);
if (err)
goto out;
cert = es_fopenmem_init (0, "rb", buffer, buflen);
xfree (buffer);
if (!cert)
{
err = gpg_error_from_syserror ();
goto out;
}
/* Check that the key type matches. */
err = stream_read_cstring (cert, &cert_key_type);
if (err)
goto out;
if (strcmp (cert_key_type, key_type) )
{
xfree (cert_key_type);
log_error ("key types in received ssh certificate do not match\n");
err = gpg_error (GPG_ERR_INV_CERT_OBJ);
goto out;
}
xfree (cert_key_type);
/* Skip the nonce. */
err = stream_read_string (cert, 0, NULL, NULL);
if (err)
goto out;
}
if ((spec.flags & SPEC_FLAG_IS_EdDSA))
{
/* The format of an EdDSA key is:
* string key_type ("ssh-ed25519")
* string public_key
* string private_key
*
* Note that the private key is the concatenation of the private
* key with the public key. Thus there's are 64 bytes; however
* we only want the real 32 byte private key - Libgcrypt expects
* this.
*/
mpi_list = xtrycalloc (3, sizeof *mpi_list);
if (!mpi_list)
{
err = gpg_error_from_syserror ();
goto out;
}
err = stream_read_blob (cert? cert : stream, 0, &mpi_list[0]);
if (err)
goto out;
if (secret)
{
u32 len = 0;
unsigned char *buffer;
/* Read string length. */
err = stream_read_uint32 (stream, &len);
if (err)
goto out;
if (len != 32 && len != 64)
{
err = gpg_error (GPG_ERR_BAD_SECKEY);
goto out;
}
buffer = xtrymalloc_secure (32);
if (!buffer)
{
err = gpg_error_from_syserror ();
goto out;
}
err = stream_read_data (stream, buffer, 32);
if (err)
{
xfree (buffer);
goto out;
}
mpi_list[1] = gcry_mpi_set_opaque (NULL, buffer, 8*32);
buffer = NULL;
if (len == 64)
{
err = stream_read_skip (stream, 32);
if (err)
goto out;
}
}
}
else if ((spec.flags & SPEC_FLAG_IS_ECDSA))
{
/* The format of an ECDSA key is:
* string key_type ("ecdsa-sha2-nistp256" |
* "ecdsa-sha2-nistp384" |
* "ecdsa-sha2-nistp521" )
* string ecdsa_curve_name
* string ecdsa_public_key
* mpint ecdsa_private
*
* Note that we use the mpint reader instead of the string
* reader for ecsa_public_key. For the certificate variante
* ecdsa_curve_name+ecdsa_public_key are replaced by the
* certificate.
*/
unsigned char *buffer;
err = stream_read_string (cert? cert : stream, 0, &buffer, NULL);
if (err)
goto out;
/* Get the canonical name. Should be the same as the read
* string but we use this mapping to validate that name. */
if (!ssh_identifier_from_curve_name (buffer, &curve_name))
{
err = gpg_error (GPG_ERR_UNKNOWN_CURVE);
xfree (buffer);
goto out;
}
xfree (buffer);
err = ssh_receive_mpint_list (stream, secret, &spec, cert, &mpi_list);
if (err)
goto out;
}
else
{
err = ssh_receive_mpint_list (stream, secret, &spec, cert, &mpi_list);
if (err)
goto out;
}
if (read_comment)
{
err = stream_read_cstring (stream, &comment);
if (err)
goto out;
}
if (secret)
elems = spec.elems_key_secret;
else
elems = spec.elems_key_public;
if (spec.key_modifier)
{
err = (*spec.key_modifier) (elems, mpi_list);
if (err)
goto out;
}
if ((spec.flags & SPEC_FLAG_IS_EdDSA))
{
if (secret)
{
err = gcry_sexp_build (&key, NULL,
"(private-key(ecc(curve \"Ed25519\")"
"(flags eddsa)(q %m)(d %m))"
"(comment%s))",
mpi_list[0], mpi_list[1],
comment? comment:"");
}
else
{
err = gcry_sexp_build (&key, NULL,
"(public-key(ecc(curve \"Ed25519\")"
"(flags eddsa)(q %m))"
"(comment%s))",
mpi_list[0],
comment? comment:"");
}
}
else
{
err = sexp_key_construct (&key, spec, secret, curve_name, mpi_list,
comment? comment:"");
if (err)
goto out;
}
if (key_spec)
*key_spec = spec;
*key_new = key;
out:
es_fclose (cert);
mpint_list_free (mpi_list);
xfree (key_type);
xfree (comment);
return err;
}
/* Write the public key from KEY to STREAM in SSH key format. If
OVERRIDE_COMMENT is not NULL, it will be used instead of the
comment stored in the key. */
static gpg_error_t
ssh_send_key_public (estream_t stream, gcry_sexp_t key,
const char *override_comment)
{
ssh_key_type_spec_t spec;
int algo;
char *comment = NULL;
void *blob = NULL;
size_t bloblen;
gpg_error_t err = 0;
algo = get_pk_algo_from_key (key);
if (algo == 0)
goto out;
err = ssh_key_type_lookup (NULL, algo, &spec);
if (err)
goto out;
err = ssh_key_to_blob (key, 0, spec, &blob, &bloblen);
if (err)
goto out;
err = stream_write_string (stream, blob, bloblen);
if (err)
goto out;
if (override_comment)
err = stream_write_cstring (stream, override_comment);
else
{
err = ssh_key_extract_comment (key, &comment);
if (err)
err = stream_write_cstring (stream, "(none)");
else
err = stream_write_cstring (stream, comment);
}
if (err)
goto out;
out:
xfree (comment);
es_free (blob);
return err;
}
/* Read a public key out of BLOB/BLOB_SIZE according to the key
specification given as KEY_SPEC, storing the new key in KEY_PUBLIC.
Returns zero on success or an error code. */
static gpg_error_t
ssh_read_key_public_from_blob (unsigned char *blob, size_t blob_size,
gcry_sexp_t *key_public,
ssh_key_type_spec_t *key_spec)
{
gpg_error_t err;
estream_t blob_stream;
blob_stream = es_fopenmem (0, "r+b");
if (!blob_stream)
{
err = gpg_error_from_syserror ();
goto out;
}
err = stream_write_data (blob_stream, blob, blob_size);
if (err)
goto out;
err = es_fseek (blob_stream, 0, SEEK_SET);
if (err)
goto out;
err = ssh_receive_key (blob_stream, key_public, 0, 0, key_spec);
out:
es_fclose (blob_stream);
return err;
}
/* This function calculates the key grip for the key contained in the
S-Expression KEY and writes it to BUFFER, which must be large
enough to hold it. Returns usual error code. */
static gpg_error_t
ssh_key_grip (gcry_sexp_t key, unsigned char *buffer)
{
if (!gcry_pk_get_keygrip (key, buffer))
{
gpg_error_t err = gcry_pk_testkey (key);
return err? err : gpg_error (GPG_ERR_INTERNAL);
}
return 0;
}
static gpg_error_t
card_key_list (ctrl_t ctrl, char **r_serialno, strlist_t *result)
{
gpg_error_t err;
*r_serialno = NULL;
*result = NULL;
err = agent_card_serialno (ctrl, r_serialno, NULL);
if (err)
{
if (gpg_err_code (err) != GPG_ERR_ENODEV && opt.verbose)
log_info (_("error getting serial number of card: %s\n"),
gpg_strerror (err));
/* Nothing available. */
return 0;
}
err = agent_card_cardlist (ctrl, result);
if (err)
{
xfree (*r_serialno);
*r_serialno = NULL;
}
return err;
}
/* Check whether a smartcard is available and whether it has a usable
key. Store a copy of that key at R_PK and return 0. If no key is
available store NULL at R_PK and return an error code. If CARDSN
is not NULL, a string with the serial number of the card will be
a malloced and stored there. */
static gpg_error_t
card_key_available (ctrl_t ctrl, gcry_sexp_t *r_pk, char **cardsn)
{
gpg_error_t err;
char *authkeyid;
char *serialno = NULL;
unsigned char *pkbuf;
size_t pkbuflen;
gcry_sexp_t s_pk;
unsigned char grip[20];
*r_pk = NULL;
if (cardsn)
*cardsn = NULL;
/* First see whether a card is available and whether the application
is supported. */
err = agent_card_getattr (ctrl, "$AUTHKEYID", &authkeyid);
if ( gpg_err_code (err) == GPG_ERR_CARD_REMOVED )
{
/* Ask for the serial number to reset the card. */
err = agent_card_serialno (ctrl, &serialno, NULL);
if (err)
{
if (opt.verbose)
log_info (_("error getting serial number of card: %s\n"),
gpg_strerror (err));
return err;
}
log_info (_("detected card with S/N: %s\n"), serialno);
err = agent_card_getattr (ctrl, "$AUTHKEYID", &authkeyid);
}
if (err)
{
log_error (_("no authentication key for ssh on card: %s\n"),
gpg_strerror (err));
xfree (serialno);
return err;
}
/* Get the S/N if we don't have it yet. Use the fast getattr method. */
if (!serialno && (err = agent_card_getattr (ctrl, "SERIALNO", &serialno)) )
{
log_error (_("error getting serial number of card: %s\n"),
gpg_strerror (err));
xfree (authkeyid);
return err;
}
/* Read the public key. */
err = agent_card_readkey (ctrl, authkeyid, &pkbuf);
if (err)
{
if (opt.verbose)
log_info (_("no suitable card key found: %s\n"), gpg_strerror (err));
xfree (serialno);
xfree (authkeyid);
return err;
}
pkbuflen = gcry_sexp_canon_len (pkbuf, 0, NULL, NULL);
err = gcry_sexp_sscan (&s_pk, NULL, (char*)pkbuf, pkbuflen);
if (err)
{
log_error ("failed to build S-Exp from received card key: %s\n",
gpg_strerror (err));
xfree (pkbuf);
xfree (serialno);
xfree (authkeyid);
return err;
}
err = ssh_key_grip (s_pk, grip);
if (err)
{
log_debug ("error computing keygrip from received card key: %s\n",
gcry_strerror (err));
xfree (pkbuf);
gcry_sexp_release (s_pk);
xfree (serialno);
xfree (authkeyid);
return err;
}
if ( agent_key_available (grip) )
{
/* (Shadow)-key is not available in our key storage. */
err = agent_write_shadow_key (grip, serialno, authkeyid, pkbuf, 0);
if (err)
{
xfree (pkbuf);
gcry_sexp_release (s_pk);
xfree (serialno);
xfree (authkeyid);
return err;
}
}
if (cardsn)
{
char *dispsn;
/* If the card handler is able to return a short serialnumber,
use that one, else use the complete serialno. */
if (!agent_card_getattr (ctrl, "$DISPSERIALNO", &dispsn))
{
*cardsn = xtryasprintf ("cardno:%s", dispsn);
xfree (dispsn);
}
else
*cardsn = xtryasprintf ("cardno:%s", serialno);
if (!*cardsn)
{
err = gpg_error_from_syserror ();
xfree (pkbuf);
gcry_sexp_release (s_pk);
xfree (serialno);
xfree (authkeyid);
return err;
}
}
xfree (pkbuf);
xfree (serialno);
xfree (authkeyid);
*r_pk = s_pk;
return 0;
}
/*
Request handler. Each handler is provided with a CTRL context, a
REQUEST object and a RESPONSE object. The actual request is to be
read from REQUEST, the response needs to be written to RESPONSE.
*/
/* Handler for the "request_identities" command. */
static gpg_error_t
ssh_handler_request_identities (ctrl_t ctrl,
estream_t request, estream_t response)
{
u32 key_counter;
estream_t key_blobs;
gcry_sexp_t key_public;
gpg_error_t err;
int ret;
ssh_control_file_t cf = NULL;
gpg_error_t ret_err;
(void)request;
/* Prepare buffer stream. */
key_public = NULL;
key_counter = 0;
key_blobs = es_fopenmem (0, "r+b");
if (! key_blobs)
{
err = gpg_error_from_syserror ();
goto out;
}
/* First check whether a key is currently available in the card
reader - this should be allowed even without being listed in
sshcontrol. */
if (!opt.disable_scdaemon)
{
char *serialno;
strlist_t card_list, sl;
err = card_key_list (ctrl, &serialno, &card_list);
if (err)
{
if (opt.verbose)
log_info (_("error getting list of cards: %s\n"),
gpg_strerror (err));
goto scd_out;
}
for (sl = card_list; sl; sl = sl->next)
{
char *serialno0;
char *cardsn;
err = agent_card_serialno (ctrl, &serialno0, sl->d);
if (err)
{
if (opt.verbose)
log_info (_("error getting serial number of card: %s\n"),
gpg_strerror (err));
continue;
}
xfree (serialno0);
if (card_key_available (ctrl, &key_public, &cardsn))
continue;
err = ssh_send_key_public (key_blobs, key_public, cardsn);
if (err && opt.verbose)
gcry_log_debugsxp ("pubkey", key_public);
gcry_sexp_release (key_public);
key_public = NULL;
xfree (cardsn);
if (err)
{
xfree (serialno);
free_strlist (card_list);
goto out;
}
key_counter++;
}
xfree (serialno);
free_strlist (card_list);
}
scd_out:
/* Then look at all the registered and non-disabled keys. */
err = open_control_file (&cf, 0);
if (err)
goto out;
while (!read_control_file_item (cf))
{
unsigned char grip[20];
if (!cf->item.valid)
continue; /* Should not happen. */
if (cf->item.disabled)
continue;
- assert (strlen (cf->item.hexgrip) == 40);
+ log_assert (strlen (cf->item.hexgrip) == 40);
hex2bin (cf->item.hexgrip, grip, sizeof (grip));
err = agent_public_key_from_file (ctrl, grip, &key_public);
if (err)
{
log_error ("%s:%d: key '%s' skipped: %s\n",
cf->fname, cf->lnr, cf->item.hexgrip,
gpg_strerror (err));
continue;
}
err = ssh_send_key_public (key_blobs, key_public, NULL);
if (err)
goto out;
gcry_sexp_release (key_public);
key_public = NULL;
key_counter++;
}
err = 0;
ret = es_fseek (key_blobs, 0, SEEK_SET);
if (ret)
{
err = gpg_error_from_syserror ();
goto out;
}
out:
/* Send response. */
gcry_sexp_release (key_public);
if (!err)
{
ret_err = stream_write_byte (response, SSH_RESPONSE_IDENTITIES_ANSWER);
if (!ret_err)
ret_err = stream_write_uint32 (response, key_counter);
if (!ret_err)
ret_err = stream_copy (response, key_blobs);
}
else
{
log_error ("ssh request identities failed: %s <%s>\n",
gpg_strerror (err), gpg_strsource (err));
ret_err = stream_write_byte (response, SSH_RESPONSE_FAILURE);
}
es_fclose (key_blobs);
close_control_file (cf);
return ret_err;
}
/* This function hashes the data contained in DATA of size DATA_N
according to the message digest algorithm specified by MD_ALGORITHM
and writes the message digest to HASH, which needs to large enough
for the digest. */
static gpg_error_t
data_hash (unsigned char *data, size_t data_n,
int md_algorithm, unsigned char *hash)
{
gcry_md_hash_buffer (md_algorithm, hash, data, data_n);
return 0;
}
/* This function signs the data described by CTRL. If HASH is not
NULL, (HASH,HASHLEN) overrides the hash stored in CTRL. This is to
allow the use of signature algorithms that implement the hashing
internally (e.g. Ed25519). On success the created signature is
stored in ssh format at R_SIG and it's size at R_SIGLEN; the caller
must use es_free to release this memory. */
static gpg_error_t
data_sign (ctrl_t ctrl, ssh_key_type_spec_t *spec,
const void *hash, size_t hashlen,
unsigned char **r_sig, size_t *r_siglen)
{
gpg_error_t err;
gcry_sexp_t signature_sexp = NULL;
estream_t stream = NULL;
void *blob = NULL;
size_t bloblen;
char hexgrip[40+1];
*r_sig = NULL;
*r_siglen = 0;
/* Quick check to see whether we have a valid keygrip and convert it
to hex. */
if (!ctrl->have_keygrip)
{
err = gpg_error (GPG_ERR_NO_SECKEY);
goto out;
}
bin2hex (ctrl->keygrip, 20, hexgrip);
/* Ask for confirmation if needed. */
if (confirm_flag_from_sshcontrol (hexgrip))
{
gcry_sexp_t key;
char *fpr, *prompt;
char *comment = NULL;
err = agent_raw_key_from_file (ctrl, ctrl->keygrip, &key);
if (err)
goto out;
err = ssh_get_fingerprint_string (key, opt.ssh_fingerprint_digest, &fpr);
if (!err)
{
gcry_sexp_t tmpsxp = gcry_sexp_find_token (key, "comment", 0);
if (tmpsxp)
comment = gcry_sexp_nth_string (tmpsxp, 1);
gcry_sexp_release (tmpsxp);
}
gcry_sexp_release (key);
if (err)
goto out;
prompt = xtryasprintf (L_("An ssh process requested the use of key%%0A"
" %s%%0A"
" (%s)%%0A"
"Do you want to allow this?"),
fpr, comment? comment:"");
xfree (fpr);
gcry_free (comment);
err = agent_get_confirmation (ctrl, prompt, L_("Allow"), L_("Deny"), 0);
xfree (prompt);
if (err)
goto out;
}
/* Create signature. */
ctrl->use_auth_call = 1;
err = agent_pksign_do (ctrl, NULL,
L_("Please enter the passphrase "
"for the ssh key%%0A %F%%0A (%c)"),
&signature_sexp,
CACHE_MODE_SSH, ttl_from_sshcontrol,
hash, hashlen);
ctrl->use_auth_call = 0;
if (err)
goto out;
stream = es_fopenmem (0, "r+b");
if (!stream)
{
err = gpg_error_from_syserror ();
goto out;
}
err = stream_write_cstring (stream, spec->ssh_identifier);
if (err)
goto out;
err = spec->signature_encoder (spec, stream, signature_sexp);
if (err)
goto out;
err = es_fclose_snatch (stream, &blob, &bloblen);
if (err)
goto out;
stream = NULL;
*r_sig = blob; blob = NULL;
*r_siglen = bloblen;
out:
xfree (blob);
es_fclose (stream);
gcry_sexp_release (signature_sexp);
return err;
}
/* Handler for the "sign_request" command. */
static gpg_error_t
ssh_handler_sign_request (ctrl_t ctrl, estream_t request, estream_t response)
{
gcry_sexp_t key = NULL;
ssh_key_type_spec_t spec;
unsigned char hash[MAX_DIGEST_LEN];
unsigned int hash_n;
unsigned char key_grip[20];
unsigned char *key_blob = NULL;
u32 key_blob_size;
unsigned char *data = NULL;
unsigned char *sig = NULL;
size_t sig_n;
u32 data_size;
gpg_error_t err;
gpg_error_t ret_err;
int hash_algo;
/* Receive key. */
err = stream_read_string (request, 0, &key_blob, &key_blob_size);
if (err)
goto out;
err = ssh_read_key_public_from_blob (key_blob, key_blob_size, &key, &spec);
if (err)
goto out;
/* Receive data to sign. */
err = stream_read_string (request, 0, &data, &data_size);
if (err)
goto out;
/* Flag processing. */
{
u32 flags;
err = stream_read_uint32 (request, &flags);
if (err)
goto out;
if (spec.algo == GCRY_PK_RSA)
{
if ((flags & SSH_AGENT_RSA_SHA2_512))
{
flags &= ~SSH_AGENT_RSA_SHA2_512;
spec.ssh_identifier = "rsa-sha2-512";
spec.hash_algo = GCRY_MD_SHA512;
}
if ((flags & SSH_AGENT_RSA_SHA2_256))
{
/* Note: We prefer SHA256 over SHA512. */
flags &= ~SSH_AGENT_RSA_SHA2_256;
spec.ssh_identifier = "rsa-sha2-256";
spec.hash_algo = GCRY_MD_SHA256;
}
}
/* Some flag is present that we do not know about. Note that
* processed or known flags have been cleared at this point. */
if (flags)
{
err = gpg_error (GPG_ERR_UNKNOWN_OPTION);
goto out;
}
}
hash_algo = spec.hash_algo;
if (!hash_algo)
hash_algo = GCRY_MD_SHA1; /* Use the default. */
ctrl->digest.algo = hash_algo;
if ((spec.flags & SPEC_FLAG_USE_PKCS1V2))
ctrl->digest.raw_value = 0;
else
ctrl->digest.raw_value = 1;
/* Calculate key grip. */
err = ssh_key_grip (key, key_grip);
if (err)
goto out;
ctrl->have_keygrip = 1;
memcpy (ctrl->keygrip, key_grip, 20);
/* Hash data unless we use EdDSA. */
if ((spec.flags & SPEC_FLAG_IS_EdDSA))
{
ctrl->digest.valuelen = 0;
}
else
{
hash_n = gcry_md_get_algo_dlen (hash_algo);
if (!hash_n)
{
err = gpg_error (GPG_ERR_INTERNAL);
goto out;
}
err = data_hash (data, data_size, hash_algo, hash);
if (err)
goto out;
memcpy (ctrl->digest.value, hash, hash_n);
ctrl->digest.valuelen = hash_n;
}
/* Sign data. */
if ((spec.flags & SPEC_FLAG_IS_EdDSA))
err = data_sign (ctrl, &spec, data, data_size, &sig, &sig_n);
else
err = data_sign (ctrl, &spec, NULL, 0, &sig, &sig_n);
out:
/* Done. */
if (!err)
{
ret_err = stream_write_byte (response, SSH_RESPONSE_SIGN_RESPONSE);
if (ret_err)
goto leave;
ret_err = stream_write_string (response, sig, sig_n);
if (ret_err)
goto leave;
}
else
{
log_error ("ssh sign request failed: %s <%s>\n",
gpg_strerror (err), gpg_strsource (err));
ret_err = stream_write_byte (response, SSH_RESPONSE_FAILURE);
if (ret_err)
goto leave;
}
leave:
gcry_sexp_release (key);
xfree (key_blob);
xfree (data);
es_free (sig);
return ret_err;
}
/* This function extracts the comment contained in the key
s-expression KEY and stores a copy in COMMENT. Returns usual error
code. */
static gpg_error_t
ssh_key_extract_comment (gcry_sexp_t key, char **r_comment)
{
gcry_sexp_t comment_list;
*r_comment = NULL;
comment_list = gcry_sexp_find_token (key, "comment", 0);
if (!comment_list)
return gpg_error (GPG_ERR_INV_SEXP);
*r_comment = gcry_sexp_nth_string (comment_list, 1);
gcry_sexp_release (comment_list);
if (!*r_comment)
return gpg_error (GPG_ERR_INV_SEXP);
return 0;
}
/* This function converts the key contained in the S-Expression KEY
into a buffer, which is protected by the passphrase PASSPHRASE.
If PASSPHRASE is the empty passphrase, the key is not protected.
Returns usual error code. */
static gpg_error_t
ssh_key_to_protected_buffer (gcry_sexp_t key, const char *passphrase,
unsigned char **buffer, size_t *buffer_n)
{
unsigned char *buffer_new;
unsigned int buffer_new_n;
gpg_error_t err;
buffer_new_n = gcry_sexp_sprint (key, GCRYSEXP_FMT_CANON, NULL, 0);
buffer_new = xtrymalloc_secure (buffer_new_n);
if (! buffer_new)
{
err = gpg_error_from_syserror ();
goto out;
}
- gcry_sexp_sprint (key, GCRYSEXP_FMT_CANON, buffer_new, buffer_new_n);
- /* FIXME: guarantee? */
+ buffer_new_n = gcry_sexp_sprint (key, GCRYSEXP_FMT_CANON,
+ buffer_new, buffer_new_n);
if (*passphrase)
err = agent_protect (buffer_new, passphrase, buffer, buffer_n, 0, -1);
else
{
/* The key derivation function does not support zero length
* strings. Store key unprotected if the user wishes so. */
*buffer = buffer_new;
*buffer_n = buffer_new_n;
buffer_new = NULL;
err = 0;
}
out:
xfree (buffer_new);
return err;
}
/* Callback function to compare the first entered PIN with the one
currently being entered. */
static gpg_error_t
reenter_compare_cb (struct pin_entry_info_s *pi)
{
const char *pin1 = pi->check_cb_arg;
if (!strcmp (pin1, pi->pin))
return 0; /* okay */
return gpg_error (GPG_ERR_BAD_PASSPHRASE);
}
/* Store the ssh KEY into our local key storage and protect it after
asking for a passphrase. Cache that passphrase. TTL is the
maximum caching time for that key. If the key already exists in
our key storage, don't do anything. When entering a key also add
an entry to the sshcontrol file. */
static gpg_error_t
ssh_identity_register (ctrl_t ctrl, ssh_key_type_spec_t *spec,
gcry_sexp_t key, int ttl, int confirm)
{
gpg_error_t err;
unsigned char key_grip_raw[20];
char key_grip[41];
unsigned char *buffer = NULL;
size_t buffer_n;
char *description = NULL;
const char *description2 = L_("Please re-enter this passphrase");
char *comment = NULL;
char *key_fpr = NULL;
const char *initial_errtext = NULL;
struct pin_entry_info_s *pi = NULL;
struct pin_entry_info_s *pi2 = NULL;
err = ssh_key_grip (key, key_grip_raw);
if (err)
goto out;
bin2hex (key_grip_raw, 20, key_grip);
err = ssh_get_fingerprint_string (key, opt.ssh_fingerprint_digest, &key_fpr);
if (err)
goto out;
/* Check whether the key is already in our key storage. Don't do
anything then besides (re-)adding it to sshcontrol. */
if ( !agent_key_available (key_grip_raw) )
goto key_exists; /* Yes, key is available. */
err = ssh_key_extract_comment (key, &comment);
if (err)
goto out;
if ( asprintf (&description,
L_("Please enter a passphrase to protect"
" the received secret key%%0A"
" %s%%0A"
" %s%%0A"
"within gpg-agent's key storage"),
key_fpr, comment ? comment : "") < 0)
{
err = gpg_error_from_syserror ();
goto out;
}
pi = gcry_calloc_secure (1, sizeof (*pi) + MAX_PASSPHRASE_LEN + 1);
if (!pi)
{
err = gpg_error_from_syserror ();
goto out;
}
pi2 = gcry_calloc_secure (1, sizeof (*pi2) + MAX_PASSPHRASE_LEN + 1);
if (!pi2)
{
err = gpg_error_from_syserror ();
goto out;
}
pi->max_length = MAX_PASSPHRASE_LEN + 1;
pi->max_tries = 1;
pi->with_repeat = 1;
pi2->max_length = MAX_PASSPHRASE_LEN + 1;
pi2->max_tries = 1;
pi2->check_cb = reenter_compare_cb;
pi2->check_cb_arg = pi->pin;
next_try:
err = agent_askpin (ctrl, description, NULL, initial_errtext, pi, NULL, 0);
initial_errtext = NULL;
if (err)
goto out;
/* Unless the passphrase is empty or the pinentry told us that
it already did the repetition check, ask to confirm it. */
if (*pi->pin && !pi->repeat_okay)
{
err = agent_askpin (ctrl, description2, NULL, NULL, pi2, NULL, 0);
if (gpg_err_code (err) == GPG_ERR_BAD_PASSPHRASE)
{ /* The re-entered one did not match and the user did not
hit cancel. */
initial_errtext = L_("does not match - try again");
goto next_try;
}
}
err = ssh_key_to_protected_buffer (key, pi->pin, &buffer, &buffer_n);
if (err)
goto out;
/* Store this key to our key storage. */
- err = agent_write_private_key (key_grip_raw, buffer, buffer_n, 0);
+ err = agent_write_private_key (key_grip_raw, buffer, buffer_n, 0, NULL, NULL);
if (err)
goto out;
/* Cache this passphrase. */
err = agent_put_cache (ctrl, key_grip, CACHE_MODE_SSH, pi->pin, ttl);
if (err)
goto out;
key_exists:
/* And add an entry to the sshcontrol file. */
err = add_control_entry (ctrl, spec, key_grip, key, ttl, confirm);
out:
if (pi2 && pi2->max_length)
wipememory (pi2->pin, pi2->max_length);
xfree (pi2);
if (pi && pi->max_length)
wipememory (pi->pin, pi->max_length);
xfree (pi);
xfree (buffer);
xfree (comment);
xfree (key_fpr);
xfree (description);
return err;
}
/* This function removes the key contained in the S-Expression KEY
from the local key storage, in case it exists there. Returns usual
error code. FIXME: this function is a stub. */
static gpg_error_t
ssh_identity_drop (gcry_sexp_t key)
{
unsigned char key_grip[21] = { 0 };
gpg_error_t err;
err = ssh_key_grip (key, key_grip);
if (err)
goto out;
key_grip[sizeof (key_grip) - 1] = 0;
/* FIXME: What to do here - forgetting the passphrase or deleting
the key from key cache? */
out:
return err;
}
/* Handler for the "add_identity" command. */
static gpg_error_t
ssh_handler_add_identity (ctrl_t ctrl, estream_t request, estream_t response)
{
gpg_error_t ret_err;
ssh_key_type_spec_t spec;
gpg_error_t err;
gcry_sexp_t key;
unsigned char b;
int confirm;
int ttl;
confirm = 0;
key = NULL;
ttl = 0;
/* FIXME? */
err = ssh_receive_key (request, &key, 1, 1, &spec);
if (err)
goto out;
while (1)
{
err = stream_read_byte (request, &b);
if (err)
{
if (gpg_err_code (err) == GPG_ERR_EOF)
err = 0;
break;
}
switch (b)
{
case SSH_OPT_CONSTRAIN_LIFETIME:
{
u32 n = 0;
err = stream_read_uint32 (request, &n);
if (! err)
ttl = n;
break;
}
case SSH_OPT_CONSTRAIN_CONFIRM:
{
confirm = 1;
break;
}
default:
/* FIXME: log/bad? */
break;
}
}
if (err)
goto out;
err = ssh_identity_register (ctrl, &spec, key, ttl, confirm);
out:
gcry_sexp_release (key);
if (! err)
ret_err = stream_write_byte (response, SSH_RESPONSE_SUCCESS);
else
ret_err = stream_write_byte (response, SSH_RESPONSE_FAILURE);
return ret_err;
}
/* Handler for the "remove_identity" command. */
static gpg_error_t
ssh_handler_remove_identity (ctrl_t ctrl,
estream_t request, estream_t response)
{
unsigned char *key_blob;
u32 key_blob_size;
gcry_sexp_t key;
gpg_error_t ret_err;
gpg_error_t err;
(void)ctrl;
/* Receive key. */
key_blob = NULL;
key = NULL;
err = stream_read_string (request, 0, &key_blob, &key_blob_size);
if (err)
goto out;
err = ssh_read_key_public_from_blob (key_blob, key_blob_size, &key, NULL);
if (err)
goto out;
err = ssh_identity_drop (key);
out:
xfree (key_blob);
gcry_sexp_release (key);
if (! err)
ret_err = stream_write_byte (response, SSH_RESPONSE_SUCCESS);
else
ret_err = stream_write_byte (response, SSH_RESPONSE_FAILURE);
return ret_err;
}
/* FIXME: stub function. Actually useful? */
static gpg_error_t
ssh_identities_remove_all (void)
{
gpg_error_t err;
err = 0;
/* FIXME: shall we remove _all_ cache entries or only those
registered through the ssh-agent protocol? */
return err;
}
/* Handler for the "remove_all_identities" command. */
static gpg_error_t
ssh_handler_remove_all_identities (ctrl_t ctrl,
estream_t request, estream_t response)
{
gpg_error_t ret_err;
gpg_error_t err;
(void)ctrl;
(void)request;
err = ssh_identities_remove_all ();
if (! err)
ret_err = stream_write_byte (response, SSH_RESPONSE_SUCCESS);
else
ret_err = stream_write_byte (response, SSH_RESPONSE_FAILURE);
return ret_err;
}
/* Lock agent? FIXME: stub function. */
static gpg_error_t
ssh_lock (void)
{
gpg_error_t err;
/* FIXME */
log_error ("ssh-agent's lock command is not implemented\n");
err = 0;
return err;
}
/* Unock agent? FIXME: stub function. */
static gpg_error_t
ssh_unlock (void)
{
gpg_error_t err;
log_error ("ssh-agent's unlock command is not implemented\n");
err = 0;
return err;
}
/* Handler for the "lock" command. */
static gpg_error_t
ssh_handler_lock (ctrl_t ctrl, estream_t request, estream_t response)
{
gpg_error_t ret_err;
gpg_error_t err;
(void)ctrl;
(void)request;
err = ssh_lock ();
if (! err)
ret_err = stream_write_byte (response, SSH_RESPONSE_SUCCESS);
else
ret_err = stream_write_byte (response, SSH_RESPONSE_FAILURE);
return ret_err;
}
/* Handler for the "unlock" command. */
static gpg_error_t
ssh_handler_unlock (ctrl_t ctrl, estream_t request, estream_t response)
{
gpg_error_t ret_err;
gpg_error_t err;
(void)ctrl;
(void)request;
err = ssh_unlock ();
if (! err)
ret_err = stream_write_byte (response, SSH_RESPONSE_SUCCESS);
else
ret_err = stream_write_byte (response, SSH_RESPONSE_FAILURE);
return ret_err;
}
/* Return the request specification for the request identified by TYPE
or NULL in case the requested request specification could not be
found. */
static const ssh_request_spec_t *
request_spec_lookup (int type)
{
const ssh_request_spec_t *spec;
unsigned int i;
for (i = 0; i < DIM (request_specs); i++)
if (request_specs[i].type == type)
break;
if (i == DIM (request_specs))
{
if (opt.verbose)
log_info ("ssh request %u is not supported\n", type);
spec = NULL;
}
else
spec = request_specs + i;
return spec;
}
/* Process a single request. The request is read from and the
response is written to STREAM_SOCK. Uses CTRL as context. Returns
zero in case of success, non zero in case of failure. */
static int
ssh_request_process (ctrl_t ctrl, estream_t stream_sock)
{
const ssh_request_spec_t *spec;
estream_t response = NULL;
estream_t request = NULL;
unsigned char request_type;
gpg_error_t err;
int send_err = 0;
int ret;
unsigned char *request_data = NULL;
u32 request_data_size;
u32 response_size;
/* Create memory streams for request/response data. The entire
request will be stored in secure memory, since it might contain
secret key material. The response does not have to be stored in
secure memory, since we never give out secret keys.
Note: we only have little secure memory, but there is NO
possibility of DoS here; only trusted clients are allowed to
connect to the agent. What could happen is that the agent
returns out-of-secure-memory errors on requests in case the
agent's owner floods his own agent with many large messages.
-moritz */
/* Retrieve request. */
err = stream_read_string (stream_sock, 1, &request_data, &request_data_size);
if (err)
goto out;
if (opt.verbose > 1)
log_info ("received ssh request of length %u\n",
(unsigned int)request_data_size);
if (! request_data_size)
{
send_err = 1;
goto out;
/* Broken request; FIXME. */
}
request_type = request_data[0];
spec = request_spec_lookup (request_type);
if (! spec)
{
send_err = 1;
goto out;
/* Unknown request; FIXME. */
}
if (spec->secret_input)
request = es_mopen (NULL, 0, 0, 1, realloc_secure, gcry_free, "r+b");
else
request = es_mopen (NULL, 0, 0, 1, gcry_realloc, gcry_free, "r+b");
if (! request)
{
err = gpg_error_from_syserror ();
goto out;
}
ret = es_setvbuf (request, NULL, _IONBF, 0);
if (ret)
{
err = gpg_error_from_syserror ();
goto out;
}
err = stream_write_data (request, request_data + 1, request_data_size - 1);
if (err)
goto out;
es_rewind (request);
response = es_fopenmem (0, "r+b");
if (! response)
{
err = gpg_error_from_syserror ();
goto out;
}
if (opt.verbose)
log_info ("ssh request handler for %s (%u) started\n",
spec->identifier, spec->type);
err = (*spec->handler) (ctrl, request, response);
if (opt.verbose)
{
if (err)
log_info ("ssh request handler for %s (%u) failed: %s\n",
spec->identifier, spec->type, gpg_strerror (err));
else
log_info ("ssh request handler for %s (%u) ready\n",
spec->identifier, spec->type);
}
if (err)
{
send_err = 1;
goto out;
}
response_size = es_ftell (response);
if (opt.verbose > 1)
log_info ("sending ssh response of length %u\n",
(unsigned int)response_size);
err = es_fseek (response, 0, SEEK_SET);
if (err)
{
send_err = 1;
goto out;
}
err = stream_write_uint32 (stream_sock, response_size);
if (err)
{
send_err = 1;
goto out;
}
err = stream_copy (stream_sock, response);
if (err)
goto out;
err = es_fflush (stream_sock);
if (err)
goto out;
out:
if (err && es_feof (stream_sock))
log_error ("error occurred while processing request: %s\n",
gpg_strerror (err));
if (send_err)
{
if (opt.verbose > 1)
log_info ("sending ssh error response\n");
err = stream_write_uint32 (stream_sock, 1);
if (err)
goto leave;
err = stream_write_byte (stream_sock, SSH_RESPONSE_FAILURE);
if (err)
goto leave;
}
leave:
es_fclose (request);
es_fclose (response);
xfree (request_data);
return !!err;
}
/* Return the peer's pid. */
static void
get_client_info (int fd, struct peer_info_s *out)
{
pid_t client_pid = (pid_t)(-1);
int client_uid = -1;
#ifdef SO_PEERCRED
{
#ifdef HAVE_STRUCT_SOCKPEERCRED_PID
struct sockpeercred cr;
#else
struct ucred cr;
#endif
socklen_t cl = sizeof cr;
if (!getsockopt (fd, SOL_SOCKET, SO_PEERCRED, &cr, &cl))
{
#if defined (HAVE_STRUCT_SOCKPEERCRED_PID) || defined (HAVE_STRUCT_UCRED_PID)
client_pid = cr.pid;
client_uid = (int)cr.uid;
#elif defined (HAVE_STRUCT_UCRED_CR_PID)
client_pid = cr.cr_pid;
client_uid = (int)cr.cr_uid;
#else
#error "Unknown SO_PEERCRED struct"
#endif
}
}
#elif defined (LOCAL_PEERPID)
{
socklen_t len = sizeof (pid_t);
getsockopt (fd, SOL_LOCAL, LOCAL_PEERPID, &client_pid, &len);
#if defined (LOCAL_PEERCRED)
{
struct xucred cr;
len = sizeof (struct xucred);
if (!getsockopt (fd, SOL_LOCAL, LOCAL_PEERCRED, &cr, &len))
client_uid = (int)cr.cr_uid;
}
#endif
}
#elif defined (LOCAL_PEEREID)
{
struct unpcbid unp;
socklen_t unpl = sizeof unp;
if (getsockopt (fd, 0, LOCAL_PEEREID, &unp, &unpl) != -1)
{
client_pid = unp.unp_pid;
client_uid = (int)unp.unp_euid;
}
}
#elif defined (HAVE_GETPEERUCRED)
{
ucred_t *ucred = NULL;
if (getpeerucred (fd, &ucred) != -1)
{
client_pid = ucred_getpid (ucred);
client_uid = (int)ucred_geteuid (ucred);
ucred_free (ucred);
}
}
#else
(void)fd;
#endif
out->pid = (client_pid == (pid_t)(-1)? 0 : (unsigned long)client_pid);
out->uid = client_uid;
}
/* Start serving client on SOCK_CLIENT. */
void
start_command_handler_ssh (ctrl_t ctrl, gnupg_fd_t sock_client)
{
estream_t stream_sock = NULL;
gpg_error_t err;
int ret;
struct peer_info_s peer_info;
err = agent_copy_startup_env (ctrl);
if (err)
goto out;
get_client_info (FD2INT(sock_client), &peer_info);
ctrl->client_pid = peer_info.pid;
ctrl->client_uid = peer_info.uid;
/* Create stream from socket. */
stream_sock = es_fdopen (FD2INT(sock_client), "r+");
if (!stream_sock)
{
err = gpg_error_from_syserror ();
log_error (_("failed to create stream from socket: %s\n"),
gpg_strerror (err));
goto out;
}
/* We have to disable the estream buffering, because the estream
core doesn't know about secure memory. */
ret = es_setvbuf (stream_sock, NULL, _IONBF, 0);
if (ret)
{
err = gpg_error_from_syserror ();
log_error ("failed to disable buffering "
"on socket stream: %s\n", gpg_strerror (err));
goto out;
}
/* Main processing loop. */
while ( !ssh_request_process (ctrl, stream_sock) )
{
/* Check whether we have reached EOF before trying to read
another request. */
int c;
c = es_fgetc (stream_sock);
if (c == EOF)
break;
es_ungetc (c, stream_sock);
}
/* Reset the SCD in case it has been used. */
agent_reset_scd (ctrl);
out:
if (stream_sock)
es_fclose (stream_sock);
}
#ifdef HAVE_W32_SYSTEM
/* Serve one ssh-agent request. This is used for the Putty support.
REQUEST is the mmapped memory which may be accessed up to a
length of MAXREQLEN. Returns 0 on success which also indicates
that a valid SSH response message is now in REQUEST. */
int
serve_mmapped_ssh_request (ctrl_t ctrl,
unsigned char *request, size_t maxreqlen)
{
gpg_error_t err;
int send_err = 0;
int valid_response = 0;
const ssh_request_spec_t *spec;
u32 msglen;
estream_t request_stream, response_stream;
if (agent_copy_startup_env (ctrl))
goto leave; /* Error setting up the environment. */
if (maxreqlen < 5)
goto leave; /* Caller error. */
msglen = uint32_construct (request[0], request[1], request[2], request[3]);
if (msglen < 1 || msglen > maxreqlen - 4)
{
log_error ("ssh message len (%u) out of range", (unsigned int)msglen);
goto leave;
}
spec = request_spec_lookup (request[4]);
if (!spec)
{
send_err = 1; /* Unknown request type. */
goto leave;
}
/* Create a stream object with the data part of the request. */
if (spec->secret_input)
request_stream = es_mopen (NULL, 0, 0, 1, realloc_secure, gcry_free, "r+");
else
request_stream = es_mopen (NULL, 0, 0, 1, gcry_realloc, gcry_free, "r+");
if (!request_stream)
{
err = gpg_error_from_syserror ();
goto leave;
}
/* We have to disable the estream buffering, because the estream
core doesn't know about secure memory. */
if (es_setvbuf (request_stream, NULL, _IONBF, 0))
{
err = gpg_error_from_syserror ();
goto leave;
}
/* Copy the request to the stream but omit the request type. */
err = stream_write_data (request_stream, request + 5, msglen - 1);
if (err)
goto leave;
es_rewind (request_stream);
response_stream = es_fopenmem (0, "r+b");
if (!response_stream)
{
err = gpg_error_from_syserror ();
goto leave;
}
if (opt.verbose)
log_info ("ssh request handler for %s (%u) started\n",
spec->identifier, spec->type);
err = (*spec->handler) (ctrl, request_stream, response_stream);
if (opt.verbose)
{
if (err)
log_info ("ssh request handler for %s (%u) failed: %s\n",
spec->identifier, spec->type, gpg_strerror (err));
else
log_info ("ssh request handler for %s (%u) ready\n",
spec->identifier, spec->type);
}
es_fclose (request_stream);
request_stream = NULL;
if (err)
{
send_err = 1;
goto leave;
}
/* Put the response back into the mmapped buffer. */
{
void *response_data;
size_t response_size;
/* NB: In contrast to the request-stream, the response stream
includes the message type byte. */
if (es_fclose_snatch (response_stream, &response_data, &response_size))
{
log_error ("snatching ssh response failed: %s",
gpg_strerror (gpg_error_from_syserror ()));
send_err = 1; /* Ooops. */
goto leave;
}
if (opt.verbose > 1)
log_info ("sending ssh response of length %u\n",
(unsigned int)response_size);
if (response_size > maxreqlen - 4)
{
log_error ("invalid length of the ssh response: %s",
gpg_strerror (GPG_ERR_INTERNAL));
es_free (response_data);
send_err = 1;
goto leave;
}
request[0] = response_size >> 24;
request[1] = response_size >> 16;
request[2] = response_size >> 8;
request[3] = response_size >> 0;
memcpy (request+4, response_data, response_size);
es_free (response_data);
valid_response = 1;
}
leave:
if (send_err)
{
request[0] = 0;
request[1] = 0;
request[2] = 0;
request[3] = 1;
request[4] = SSH_RESPONSE_FAILURE;
valid_response = 1;
}
/* Reset the SCD in case it has been used. */
agent_reset_scd (ctrl);
return valid_response? 0 : -1;
}
#endif /*HAVE_W32_SYSTEM*/
diff --git a/agent/command.c b/agent/command.c
index 5e2b6df2b..b59532ce5 100644
--- a/agent/command.c
+++ b/agent/command.c
@@ -1,3641 +1,3705 @@
/* command.c - gpg-agent command handler
* Copyright (C) 2001-2011 Free Software Foundation, Inc.
* Copyright (C) 2001-2013 Werner Koch
* Copyright (C) 2015 g10 Code GmbH.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
/* FIXME: we should not use the default assuan buffering but setup
some buffering in secure mempory to protect session keys etc. */
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
-#include <assert.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include "agent.h"
#include <assuan.h>
#include "../common/i18n.h"
#include "cvt-openpgp.h"
#include "../common/ssh-utils.h"
#include "../common/asshelp.h"
#include "../common/server-help.h"
/* Maximum allowed size of the inquired ciphertext. */
#define MAXLEN_CIPHERTEXT 4096
/* Maximum allowed size of the key parameters. */
#define MAXLEN_KEYPARAM 1024
/* Maximum allowed size of key data as used in inquiries (bytes). */
#define MAXLEN_KEYDATA 8192
/* Maximum length of a secret to store under one key. */
#define MAXLEN_PUT_SECRET 4096
/* The size of the import/export KEK key (in bytes). */
#define KEYWRAP_KEYSIZE (128/8)
/* A shortcut to call assuan_set_error using an gpg_err_code_t and a
text string. */
#define set_error(e,t) assuan_set_error (ctx, gpg_error (e), (t))
/* Check that the maximum digest length we support has at least the
length of the keygrip. */
#if MAX_DIGEST_LEN < 20
#error MAX_DIGEST_LEN shorter than keygrip
#endif
/* Data used to associate an Assuan context with local server data.
This is this modules local part of the server_control_s struct. */
struct server_local_s
{
/* Our Assuan context. */
assuan_context_t assuan_ctx;
/* If this flag is true, the passphrase cache is used for signing
operations. It defaults to true but may be set on a per
connection base. The global option opt.ignore_cache_for_signing
takes precedence over this flag. */
unsigned int use_cache_for_signing : 1;
/* Flag to suppress I/O logging during a command. */
unsigned int pause_io_logging : 1;
/* Flag indicating that the connection is from ourselves. */
unsigned int connect_from_self : 1;
/* Helper flag for io_monitor to allow suppressing of our own
* greeting in some cases. See io_monitor for details. */
unsigned int greeting_seen : 1;
/* If this flag is set to true the agent will be terminated after
the end of the current session. */
unsigned int stopme : 1;
/* Flag indicating whether pinentry notifications shall be done. */
unsigned int allow_pinentry_notify : 1;
/* An allocated description for the next key operation. This is
used if a pinnetry needs to be popped up. */
char *keydesc;
/* Malloced KEK (Key-Encryption-Key) for the import_key command. */
void *import_key;
/* Malloced KEK for the export_key command. */
void *export_key;
/* Client is aware of the error code GPG_ERR_FULLY_CANCELED. */
int allow_fully_canceled;
/* Last CACHE_NONCE sent as status (malloced). */
char *last_cache_nonce;
/* Last PASSWD_NONCE sent as status (malloced). */
char *last_passwd_nonce;
};
/* An entry for the getval/putval commands. */
struct putval_item_s
{
struct putval_item_s *next;
size_t off; /* Offset to the value into DATA. */
size_t len; /* Length of the value. */
char d[1]; /* Key | Nul | value. */
};
/* A list of key value pairs fpr the getval/putval commands. */
static struct putval_item_s *putval_list;
/* To help polling clients, we keep track of the number of certain
events. This structure keeps those counters. The counters are
integers and there should be no problem if they are overflowing as
callers need to check only whether a counter changed. The actual
values are not meaningful. */
struct
{
/* Incremented if any of the other counters below changed. */
unsigned int any;
/* Incremented if a key is added or removed from the internal privat
key database. */
unsigned int key;
/* Incremented if a change of the card readers stati has been
detected. */
unsigned int card;
} eventcounter;
/* Local prototypes. */
static int command_has_option (const char *cmd, const char *cmdopt);
/* Release the memory buffer MB but first wipe out the used memory. */
static void
clear_outbuf (membuf_t *mb)
{
void *p;
size_t n;
p = get_membuf (mb, &n);
if (p)
{
wipememory (p, n);
xfree (p);
}
}
/* Write the content of memory buffer MB as assuan data to CTX and
wipe the buffer out afterwards. */
static gpg_error_t
write_and_clear_outbuf (assuan_context_t ctx, membuf_t *mb)
{
gpg_error_t ae;
void *p;
size_t n;
p = get_membuf (mb, &n);
if (!p)
return out_of_core ();
ae = assuan_send_data (ctx, p, n);
memset (p, 0, n);
xfree (p);
return ae;
}
/* Clear the nonces used to enable the passphrase cache for certain
multi-command command sequences. */
static void
clear_nonce_cache (ctrl_t ctrl)
{
if (ctrl->server_local->last_cache_nonce)
{
agent_put_cache (ctrl, ctrl->server_local->last_cache_nonce,
CACHE_MODE_NONCE, NULL, 0);
xfree (ctrl->server_local->last_cache_nonce);
ctrl->server_local->last_cache_nonce = NULL;
}
if (ctrl->server_local->last_passwd_nonce)
{
agent_put_cache (ctrl, ctrl->server_local->last_passwd_nonce,
CACHE_MODE_NONCE, NULL, 0);
xfree (ctrl->server_local->last_passwd_nonce);
ctrl->server_local->last_passwd_nonce = NULL;
}
}
/* This function is called by Libassuan whenever the client sends a
reset. It has been registered similar to the other Assuan
commands. */
static gpg_error_t
reset_notify (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
(void) line;
memset (ctrl->keygrip, 0, 20);
ctrl->have_keygrip = 0;
ctrl->digest.valuelen = 0;
xfree (ctrl->server_local->keydesc);
ctrl->server_local->keydesc = NULL;
clear_nonce_cache (ctrl);
return 0;
}
/* Replace all '+' by a blank in the string S. */
static void
plus_to_blank (char *s)
{
for (; *s; s++)
{
if (*s == '+')
*s = ' ';
}
}
/* Parse a hex string. Return an Assuan error code or 0 on success and the
length of the parsed string in LEN. */
static int
parse_hexstring (assuan_context_t ctx, const char *string, size_t *len)
{
const char *p;
size_t n;
/* parse the hash value */
for (p=string, n=0; hexdigitp (p); p++, n++)
;
if (*p != ' ' && *p != '\t' && *p)
return set_error (GPG_ERR_ASS_PARAMETER, "invalid hexstring");
if ((n&1))
return set_error (GPG_ERR_ASS_PARAMETER, "odd number of digits");
*len = n;
return 0;
}
/* Parse the keygrip in STRING into the provided buffer BUF. BUF must
provide space for 20 bytes. BUF is not changed if the function
returns an error. */
static int
parse_keygrip (assuan_context_t ctx, const char *string, unsigned char *buf)
{
int rc;
size_t n = 0;
rc = parse_hexstring (ctx, string, &n);
if (rc)
return rc;
n /= 2;
if (n != 20)
return set_error (GPG_ERR_ASS_PARAMETER, "invalid length of keygrip");
if (hex2bin (string, buf, 20) < 0)
return set_error (GPG_ERR_BUG, "hex2bin");
return 0;
}
/* Parse the TTL from STRING. Leading and trailing spaces are
* skipped. The value is constrained to -1 .. MAXINT. On error 0 is
* returned, else the number of bytes scanned. */
static size_t
parse_ttl (const char *string, int *r_ttl)
{
const char *string_orig = string;
long ttl;
char *pend;
ttl = strtol (string, &pend, 10);
string = pend;
if (string == string_orig || !(spacep (string) || !*string)
|| ttl < -1L || (int)ttl != (long)ttl)
{
*r_ttl = 0;
return 0;
}
while (spacep (string) || *string== '\n')
string++;
*r_ttl = (int)ttl;
return string - string_orig;
}
/* Write an Assuan status line. KEYWORD is the first item on the
* status line. The following arguments are all separated by a space
* in the output. The last argument must be a NULL. Linefeeds and
* carriage returns characters (which are not allowed in an Assuan
* status line) are silently quoted in C-style. */
gpg_error_t
agent_write_status (ctrl_t ctrl, const char *keyword, ...)
{
gpg_error_t err;
va_list arg_ptr;
assuan_context_t ctx = ctrl->server_local->assuan_ctx;
va_start (arg_ptr, keyword);
err = vprint_assuan_status_strings (ctx, keyword, arg_ptr);
va_end (arg_ptr);
return err;
}
/* This function is similar to print_assuan_status but takes a CTRL
arg instead of an assuan context as first argument. */
gpg_error_t
agent_print_status (ctrl_t ctrl, const char *keyword, const char *format, ...)
{
gpg_error_t err;
va_list arg_ptr;
assuan_context_t ctx = ctrl->server_local->assuan_ctx;
va_start (arg_ptr, format);
err = vprint_assuan_status (ctx, keyword, format, arg_ptr);
va_end (arg_ptr);
return err;
}
/* Helper to notify the client about a launched Pinentry. Because
that might disturb some older clients, this is only done if enabled
via an option. Returns an gpg error code. */
gpg_error_t
agent_inq_pinentry_launched (ctrl_t ctrl, unsigned long pid, const char *extra)
{
char line[256];
if (!ctrl || !ctrl->server_local
|| !ctrl->server_local->allow_pinentry_notify)
return 0;
snprintf (line, DIM(line), "PINENTRY_LAUNCHED %lu%s%s",
pid, extra?" ":"", extra? extra:"");
return assuan_inquire (ctrl->server_local->assuan_ctx, line, NULL, NULL, 0);
}
/* An agent progress callback for Libgcrypt. This has been registered
* to be called via the progress dispatcher mechanism from
* gpg-agent.c */
static void
progress_cb (ctrl_t ctrl, const char *what, int printchar,
int current, int total)
{
if (!ctrl || !ctrl->server_local || !ctrl->server_local->assuan_ctx)
;
else if (printchar == '\n' && what && !strcmp (what, "primegen"))
agent_print_status (ctrl, "PROGRESS", "%.20s X 100 100", what);
else
agent_print_status (ctrl, "PROGRESS", "%.20s %c %d %d",
what, printchar=='\n'?'X':printchar, current, total);
}
/* Helper to print a message while leaving a command. Note that this
* function does not call assuan_set_error; the caller may do this
* prior to calling us. */
static gpg_error_t
leave_cmd (assuan_context_t ctx, gpg_error_t err)
{
if (err)
{
const char *name = assuan_get_command_name (ctx);
if (!name)
name = "?";
/* Not all users of gpg-agent know about the fully canceled
error code; map it back if needed. */
if (gpg_err_code (err) == GPG_ERR_FULLY_CANCELED)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
if (!ctrl->server_local->allow_fully_canceled)
err = gpg_err_make (gpg_err_source (err), GPG_ERR_CANCELED);
}
/* Most code from common/ does not know the error source, thus
we fix this here. */
if (gpg_err_source (err) == GPG_ERR_SOURCE_UNKNOWN)
err = gpg_err_make (GPG_ERR_SOURCE_DEFAULT, gpg_err_code (err));
if (gpg_err_source (err) == GPG_ERR_SOURCE_DEFAULT)
log_error ("command '%s' failed: %s\n", name,
gpg_strerror (err));
else
log_error ("command '%s' failed: %s <%s>\n", name,
gpg_strerror (err), gpg_strsource (err));
}
return err;
}
static const char hlp_geteventcounter[] =
"GETEVENTCOUNTER\n"
"\n"
"Return a status line named EVENTCOUNTER with the current values\n"
"of all event counters. The values are decimal numbers in the range\n"
"0 to UINT_MAX and wrapping around to 0. The actual values should\n"
"not be relied upon, they shall only be used to detect a change.\n"
"\n"
"The currently defined counters are:\n"
"\n"
"ANY - Incremented with any change of any of the other counters.\n"
"KEY - Incremented for added or removed private keys.\n"
"CARD - Incremented for changes of the card readers stati.";
static gpg_error_t
cmd_geteventcounter (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
(void)line;
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
return agent_print_status (ctrl, "EVENTCOUNTER", "%u %u %u",
eventcounter.any,
eventcounter.key,
eventcounter.card);
}
/* This function should be called once for all key removals or
additions. This function is assured not to do any context
switches. */
void
bump_key_eventcounter (void)
{
eventcounter.key++;
eventcounter.any++;
}
/* This function should be called for all card reader status
changes. This function is assured not to do any context
switches. */
void
bump_card_eventcounter (void)
{
eventcounter.card++;
eventcounter.any++;
}
static const char hlp_istrusted[] =
"ISTRUSTED <hexstring_with_fingerprint>\n"
"\n"
"Return OK when we have an entry with this fingerprint in our\n"
"trustlist";
static gpg_error_t
cmd_istrusted (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc, n, i;
char *p;
char fpr[41];
/* Parse the fingerprint value. */
for (p=line,n=0; hexdigitp (p); p++, n++)
;
if (*p || !(n == 40 || n == 32))
return set_error (GPG_ERR_ASS_PARAMETER, "invalid fingerprint");
i = 0;
if (n==32)
{
strcpy (fpr, "00000000");
i += 8;
}
for (p=line; i < 40; p++, i++)
fpr[i] = *p >= 'a'? (*p & 0xdf): *p;
fpr[i] = 0;
rc = agent_istrusted (ctrl, fpr, NULL);
if (!rc || gpg_err_code (rc) == GPG_ERR_NOT_TRUSTED)
return rc;
else if (rc == -1 || gpg_err_code (rc) == GPG_ERR_EOF )
return gpg_error (GPG_ERR_NOT_TRUSTED);
else
return leave_cmd (ctx, rc);
}
static const char hlp_listtrusted[] =
"LISTTRUSTED\n"
"\n"
"List all entries from the trustlist.";
static gpg_error_t
cmd_listtrusted (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc;
(void)line;
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
rc = agent_listtrusted (ctx);
return leave_cmd (ctx, rc);
}
static const char hlp_martrusted[] =
"MARKTRUSTED <hexstring_with_fingerprint> <flag> <display_name>\n"
"\n"
"Store a new key in into the trustlist.";
static gpg_error_t
cmd_marktrusted (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc, n, i;
char *p;
char fpr[41];
int flag;
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
/* parse the fingerprint value */
for (p=line,n=0; hexdigitp (p); p++, n++)
;
if (!spacep (p) || !(n == 40 || n == 32))
return set_error (GPG_ERR_ASS_PARAMETER, "invalid fingerprint");
i = 0;
if (n==32)
{
strcpy (fpr, "00000000");
i += 8;
}
for (p=line; i < 40; p++, i++)
fpr[i] = *p >= 'a'? (*p & 0xdf): *p;
fpr[i] = 0;
while (spacep (p))
p++;
flag = *p++;
if ( (flag != 'S' && flag != 'P') || !spacep (p) )
return set_error (GPG_ERR_ASS_PARAMETER, "invalid flag - must be P or S");
while (spacep (p))
p++;
rc = agent_marktrusted (ctrl, p, fpr, flag);
return leave_cmd (ctx, rc);
}
static const char hlp_havekey[] =
"HAVEKEY <hexstrings_with_keygrips>\n"
"\n"
"Return success if at least one of the secret keys with the given\n"
"keygrips is available.";
static gpg_error_t
cmd_havekey (assuan_context_t ctx, char *line)
{
gpg_error_t err;
unsigned char buf[20];
do
{
err = parse_keygrip (ctx, line, buf);
if (err)
return err;
if (!agent_key_available (buf))
return 0; /* Found. */
while (*line && *line != ' ' && *line != '\t')
line++;
while (*line == ' ' || *line == '\t')
line++;
}
while (*line);
/* No leave_cmd() here because errors are expected and would clutter
the log. */
return gpg_error (GPG_ERR_NO_SECKEY);
}
static const char hlp_sigkey[] =
"SIGKEY <hexstring_with_keygrip>\n"
"SETKEY <hexstring_with_keygrip>\n"
"\n"
"Set the key used for a sign or decrypt operation.";
static gpg_error_t
cmd_sigkey (assuan_context_t ctx, char *line)
{
int rc;
ctrl_t ctrl = assuan_get_pointer (ctx);
rc = parse_keygrip (ctx, line, ctrl->keygrip);
if (rc)
return rc;
ctrl->have_keygrip = 1;
return 0;
}
static const char hlp_setkeydesc[] =
"SETKEYDESC plus_percent_escaped_string\n"
"\n"
"Set a description to be used for the next PKSIGN, PKDECRYPT, IMPORT_KEY\n"
"or EXPORT_KEY operation if this operation requires a passphrase. If\n"
"this command is not used a default text will be used. Note, that\n"
"this description implictly selects the label used for the entry\n"
"box; if the string contains the string PIN (which in general will\n"
"not be translated), \"PIN\" is used, otherwise the translation of\n"
"\"passphrase\" is used. The description string should not contain\n"
"blanks unless they are percent or '+' escaped.\n"
"\n"
"The description is only valid for the next PKSIGN, PKDECRYPT,\n"
"IMPORT_KEY, EXPORT_KEY, or DELETE_KEY operation.";
static gpg_error_t
cmd_setkeydesc (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
char *desc, *p;
for (p=line; *p == ' '; p++)
;
desc = p;
p = strchr (desc, ' ');
if (p)
*p = 0; /* We ignore any garbage; we might late use it for other args. */
if (!*desc)
return set_error (GPG_ERR_ASS_PARAMETER, "no description given");
/* Note, that we only need to replace the + characters and should
leave the other escaping in place because the escaped string is
send verbatim to the pinentry which does the unescaping (but not
the + replacing) */
plus_to_blank (desc);
xfree (ctrl->server_local->keydesc);
if (ctrl->restricted)
{
ctrl->server_local->keydesc = strconcat
((ctrl->restricted == 2
? _("Note: Request from the web browser.")
: _("Note: Request from a remote site.") ), "%0A%0A", desc, NULL);
}
else
ctrl->server_local->keydesc = xtrystrdup (desc);
if (!ctrl->server_local->keydesc)
return out_of_core ();
return 0;
}
static const char hlp_sethash[] =
"SETHASH (--hash=<name>)|(<algonumber>) <hexstring>\n"
"\n"
"The client can use this command to tell the server about the data\n"
"(which usually is a hash) to be signed.";
static gpg_error_t
cmd_sethash (assuan_context_t ctx, char *line)
{
int rc;
size_t n;
char *p;
ctrl_t ctrl = assuan_get_pointer (ctx);
unsigned char *buf;
char *endp;
int algo;
/* Parse the alternative hash options which may be used instead of
the algo number. */
if (has_option_name (line, "--hash"))
{
if (has_option (line, "--hash=sha1"))
algo = GCRY_MD_SHA1;
else if (has_option (line, "--hash=sha224"))
algo = GCRY_MD_SHA224;
else if (has_option (line, "--hash=sha256"))
algo = GCRY_MD_SHA256;
else if (has_option (line, "--hash=sha384"))
algo = GCRY_MD_SHA384;
else if (has_option (line, "--hash=sha512"))
algo = GCRY_MD_SHA512;
else if (has_option (line, "--hash=rmd160"))
algo = GCRY_MD_RMD160;
else if (has_option (line, "--hash=md5"))
algo = GCRY_MD_MD5;
else if (has_option (line, "--hash=tls-md5sha1"))
algo = MD_USER_TLS_MD5SHA1;
else
return set_error (GPG_ERR_ASS_PARAMETER, "invalid hash algorithm");
}
else
algo = 0;
line = skip_options (line);
if (!algo)
{
/* No hash option has been given: require an algo number instead */
algo = (int)strtoul (line, &endp, 10);
for (line = endp; *line == ' ' || *line == '\t'; line++)
;
if (!algo || gcry_md_test_algo (algo))
return set_error (GPG_ERR_UNSUPPORTED_ALGORITHM, NULL);
}
ctrl->digest.algo = algo;
ctrl->digest.raw_value = 0;
/* Parse the hash value. */
n = 0;
rc = parse_hexstring (ctx, line, &n);
if (rc)
return rc;
n /= 2;
if (algo == MD_USER_TLS_MD5SHA1 && n == 36)
;
else if (n != 16 && n != 20 && n != 24
&& n != 28 && n != 32 && n != 48 && n != 64)
return set_error (GPG_ERR_ASS_PARAMETER, "unsupported length of hash");
if (n > MAX_DIGEST_LEN)
return set_error (GPG_ERR_ASS_PARAMETER, "hash value to long");
buf = ctrl->digest.value;
ctrl->digest.valuelen = n;
for (p=line, n=0; n < ctrl->digest.valuelen; p += 2, n++)
buf[n] = xtoi_2 (p);
for (; n < ctrl->digest.valuelen; n++)
buf[n] = 0;
return 0;
}
static const char hlp_pksign[] =
"PKSIGN [<options>] [<cache_nonce>]\n"
"\n"
"Perform the actual sign operation. Neither input nor output are\n"
"sensitive to eavesdropping.";
static gpg_error_t
cmd_pksign (assuan_context_t ctx, char *line)
{
gpg_error_t err;
cache_mode_t cache_mode = CACHE_MODE_NORMAL;
ctrl_t ctrl = assuan_get_pointer (ctx);
membuf_t outbuf;
char *cache_nonce = NULL;
char *p;
line = skip_options (line);
for (p=line; *p && *p != ' ' && *p != '\t'; p++)
;
*p = '\0';
if (*line)
cache_nonce = xtrystrdup (line);
if (opt.ignore_cache_for_signing)
cache_mode = CACHE_MODE_IGNORE;
else if (!ctrl->server_local->use_cache_for_signing)
cache_mode = CACHE_MODE_IGNORE;
init_membuf (&outbuf, 512);
err = agent_pksign (ctrl, cache_nonce, ctrl->server_local->keydesc,
&outbuf, cache_mode);
if (err)
clear_outbuf (&outbuf);
else
err = write_and_clear_outbuf (ctx, &outbuf);
xfree (cache_nonce);
xfree (ctrl->server_local->keydesc);
ctrl->server_local->keydesc = NULL;
return leave_cmd (ctx, err);
}
static const char hlp_pkdecrypt[] =
"PKDECRYPT [<options>]\n"
"\n"
"Perform the actual decrypt operation. Input is not\n"
"sensitive to eavesdropping.";
static gpg_error_t
cmd_pkdecrypt (assuan_context_t ctx, char *line)
{
int rc;
ctrl_t ctrl = assuan_get_pointer (ctx);
unsigned char *value;
size_t valuelen;
membuf_t outbuf;
int padding;
(void)line;
/* First inquire the data to decrypt */
rc = print_assuan_status (ctx, "INQUIRE_MAXLEN", "%u", MAXLEN_CIPHERTEXT);
if (!rc)
rc = assuan_inquire (ctx, "CIPHERTEXT",
&value, &valuelen, MAXLEN_CIPHERTEXT);
if (rc)
return rc;
init_membuf (&outbuf, 512);
rc = agent_pkdecrypt (ctrl, ctrl->server_local->keydesc,
value, valuelen, &outbuf, &padding);
xfree (value);
if (rc)
clear_outbuf (&outbuf);
else
{
if (padding != -1)
rc = print_assuan_status (ctx, "PADDING", "%d", padding);
else
rc = 0;
if (!rc)
rc = write_and_clear_outbuf (ctx, &outbuf);
}
xfree (ctrl->server_local->keydesc);
ctrl->server_local->keydesc = NULL;
return leave_cmd (ctx, rc);
}
static const char hlp_genkey[] =
"GENKEY [--no-protection] [--preset] [--inq-passwd]\n"
" [--passwd-nonce=<s>] [<cache_nonce>]\n"
"\n"
"Generate a new key, store the secret part and return the public\n"
"part. Here is an example transaction:\n"
"\n"
" C: GENKEY\n"
" S: INQUIRE KEYPARAM\n"
" C: D (genkey (rsa (nbits 3072)))\n"
" C: END\n"
" S: D (public-key\n"
" S: D (rsa (n 326487324683264) (e 10001)))\n"
" S: OK key created\n"
"\n"
"When the --preset option is used the passphrase for the generated\n"
"key will be added to the cache. When --inq-passwd is used an inquire\n"
"with the keyword NEWPASSWD is used to request the passphrase for the\n"
"new key. When a --passwd-nonce is used, the corresponding cached\n"
"passphrase is used to protect the new key.";
static gpg_error_t
cmd_genkey (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc;
int no_protection;
unsigned char *value = NULL;
size_t valuelen;
unsigned char *newpasswd = NULL;
membuf_t outbuf;
char *cache_nonce = NULL;
char *passwd_nonce = NULL;
int opt_preset;
int opt_inq_passwd;
size_t n;
char *p, *pend;
int c;
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
no_protection = has_option (line, "--no-protection");
opt_preset = has_option (line, "--preset");
opt_inq_passwd = has_option (line, "--inq-passwd");
passwd_nonce = option_value (line, "--passwd-nonce");
if (passwd_nonce)
{
for (pend = passwd_nonce; *pend && !spacep (pend); pend++)
;
c = *pend;
*pend = '\0';
passwd_nonce = xtrystrdup (passwd_nonce);
*pend = c;
if (!passwd_nonce)
{
rc = gpg_error_from_syserror ();
goto leave;
}
}
line = skip_options (line);
for (p=line; *p && *p != ' ' && *p != '\t'; p++)
;
*p = '\0';
if (*line)
cache_nonce = xtrystrdup (line);
/* First inquire the parameters */
rc = print_assuan_status (ctx, "INQUIRE_MAXLEN", "%u", MAXLEN_KEYPARAM);
if (!rc)
rc = assuan_inquire (ctx, "KEYPARAM", &value, &valuelen, MAXLEN_KEYPARAM);
if (rc)
return rc;
init_membuf (&outbuf, 512);
/* If requested, ask for the password to be used for the key. If
this is not used the regular Pinentry mechanism is used. */
if (opt_inq_passwd && !no_protection)
{
/* (N is used as a dummy) */
assuan_begin_confidential (ctx);
rc = assuan_inquire (ctx, "NEWPASSWD", &newpasswd, &n, 256);
assuan_end_confidential (ctx);
if (rc)
goto leave;
if (!*newpasswd)
{
/* Empty password given - switch to no-protection mode. */
xfree (newpasswd);
newpasswd = NULL;
no_protection = 1;
}
}
else if (passwd_nonce)
newpasswd = agent_get_cache (ctrl, passwd_nonce, CACHE_MODE_NONCE);
rc = agent_genkey (ctrl, cache_nonce, (char*)value, valuelen, no_protection,
newpasswd, opt_preset, &outbuf);
leave:
if (newpasswd)
{
/* Assuan_inquire does not allow us to read into secure memory
thus we need to wipe it ourself. */
wipememory (newpasswd, strlen (newpasswd));
xfree (newpasswd);
}
xfree (value);
if (rc)
clear_outbuf (&outbuf);
else
rc = write_and_clear_outbuf (ctx, &outbuf);
xfree (cache_nonce);
xfree (passwd_nonce);
return leave_cmd (ctx, rc);
}
static const char hlp_readkey[] =
"READKEY <hexstring_with_keygrip>\n"
" --card <keyid>\n"
"\n"
"Return the public key for the given keygrip or keyid.\n"
"With --card, private key file with card information will be created.";
static gpg_error_t
cmd_readkey (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc;
unsigned char grip[20];
gcry_sexp_t s_pkey = NULL;
unsigned char *pkbuf = NULL;
char *serialno = NULL;
size_t pkbuflen;
const char *opt_card;
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
opt_card = has_option_name (line, "--card");
line = skip_options (line);
if (opt_card)
{
const char *keyid = opt_card;
rc = agent_card_getattr (ctrl, "SERIALNO", &serialno);
if (rc)
{
log_error (_("error getting serial number of card: %s\n"),
gpg_strerror (rc));
goto leave;
}
rc = agent_card_readkey (ctrl, keyid, &pkbuf);
if (rc)
goto leave;
pkbuflen = gcry_sexp_canon_len (pkbuf, 0, NULL, NULL);
rc = gcry_sexp_sscan (&s_pkey, NULL, (char*)pkbuf, pkbuflen);
if (rc)
goto leave;
if (!gcry_pk_get_keygrip (s_pkey, grip))
{
rc = gcry_pk_testkey (s_pkey);
if (rc == 0)
rc = gpg_error (GPG_ERR_INTERNAL);
goto leave;
}
rc = agent_write_shadow_key (grip, serialno, keyid, pkbuf, 0);
if (rc)
goto leave;
rc = assuan_send_data (ctx, pkbuf, pkbuflen);
}
else
{
rc = parse_keygrip (ctx, line, grip);
if (rc)
goto leave;
rc = agent_public_key_from_file (ctrl, grip, &s_pkey);
if (!rc)
{
pkbuflen = gcry_sexp_sprint (s_pkey, GCRYSEXP_FMT_CANON, NULL, 0);
log_assert (pkbuflen);
pkbuf = xtrymalloc (pkbuflen);
if (!pkbuf)
rc = gpg_error_from_syserror ();
else
{
pkbuflen = gcry_sexp_sprint (s_pkey, GCRYSEXP_FMT_CANON,
pkbuf, pkbuflen);
rc = assuan_send_data (ctx, pkbuf, pkbuflen);
}
}
}
leave:
xfree (serialno);
xfree (pkbuf);
gcry_sexp_release (s_pkey);
return leave_cmd (ctx, rc);
}
static const char hlp_keyinfo[] =
- "KEYINFO [--[ssh-]list] [--data] [--ssh-fpr] [--with-ssh] <keygrip>\n"
+ "KEYINFO [--[ssh-]list] [--data] [--ssh-fpr[=algo]] [--with-ssh] <keygrip>\n"
"\n"
"Return information about the key specified by the KEYGRIP. If the\n"
"key is not available GPG_ERR_NOT_FOUND is returned. If the option\n"
"--list is given the keygrip is ignored and information about all\n"
"available keys are returned. If --ssh-list is given information\n"
"about all keys listed in the sshcontrol are returned. With --with-ssh\n"
"information from sshcontrol is always added to the info. Unless --data\n"
"is given, the information is returned as a status line using the format:\n"
"\n"
" KEYINFO <keygrip> <type> <serialno> <idstr> <cached> <protection> <fpr>\n"
"\n"
"KEYGRIP is the keygrip.\n"
"\n"
"TYPE is describes the type of the key:\n"
" 'D' - Regular key stored on disk,\n"
" 'T' - Key is stored on a smartcard (token),\n"
" 'X' - Unknown type,\n"
" '-' - Key is missing.\n"
"\n"
"SERIALNO is an ASCII string with the serial number of the\n"
" smartcard. If the serial number is not known a single\n"
" dash '-' is used instead.\n"
"\n"
"IDSTR is the IDSTR used to distinguish keys on a smartcard. If it\n"
" is not known a dash is used instead.\n"
"\n"
"CACHED is 1 if the passphrase for the key was found in the key cache.\n"
" If not, a '-' is used instead.\n"
"\n"
"PROTECTION describes the key protection type:\n"
" 'P' - The key is protected with a passphrase,\n"
" 'C' - The key is not protected,\n"
" '-' - Unknown protection.\n"
"\n"
"FPR returns the formatted ssh-style fingerprint of the key. It is only\n"
- " printed if the option --ssh-fpr has been used. It defaults to '-'.\n"
+ " printed if the option --ssh-fpr has been used. If ALGO is not given\n"
+ " to that option the default ssh fingerprint algo is used. Without the\n"
+ " option a '-' is printed.\n"
"\n"
"TTL is the TTL in seconds for that key or '-' if n/a.\n"
"\n"
"FLAGS is a word consisting of one-letter flags:\n"
" 'D' - The key has been disabled,\n"
" 'S' - The key is listed in sshcontrol (requires --with-ssh),\n"
" 'c' - Use of the key needs to be confirmed,\n"
+ " 'A' - The key is available on card,\n"
" '-' - No flags given.\n"
"\n"
"More information may be added in the future.";
static gpg_error_t
do_one_keyinfo (ctrl_t ctrl, const unsigned char *grip, assuan_context_t ctx,
int data, int with_ssh_fpr, int in_ssh,
- int ttl, int disabled, int confirm)
+ int ttl, int disabled, int confirm, int on_card)
{
gpg_error_t err;
char hexgrip[40+1];
char *fpr = NULL;
int keytype;
unsigned char *shadow_info = NULL;
char *serialno = NULL;
char *idstr = NULL;
const char *keytypestr;
const char *cached;
const char *protectionstr;
char *pw;
int missing_key = 0;
char ttlbuf[20];
char flagsbuf[5];
err = agent_key_info_from_file (ctrl, grip, &keytype, &shadow_info);
if (err)
{
if (in_ssh && gpg_err_code (err) == GPG_ERR_NOT_FOUND)
missing_key = 1;
else
goto leave;
}
/* Reformat the grip so that we use uppercase as good style. */
bin2hex (grip, 20, hexgrip);
if (ttl > 0)
snprintf (ttlbuf, sizeof ttlbuf, "%d", ttl);
else
strcpy (ttlbuf, "-");
*flagsbuf = 0;
if (disabled)
strcat (flagsbuf, "D");
if (in_ssh)
strcat (flagsbuf, "S");
if (confirm)
strcat (flagsbuf, "c");
+ if (on_card)
+ strcat (flagsbuf, "A");
if (!*flagsbuf)
strcpy (flagsbuf, "-");
if (missing_key)
{
protectionstr = "-"; keytypestr = "-";
}
else
{
switch (keytype)
{
case PRIVATE_KEY_CLEAR:
case PRIVATE_KEY_OPENPGP_NONE:
protectionstr = "C"; keytypestr = "D";
break;
case PRIVATE_KEY_PROTECTED: protectionstr = "P"; keytypestr = "D";
break;
case PRIVATE_KEY_SHADOWED: protectionstr = "-"; keytypestr = "T";
break;
default: protectionstr = "-"; keytypestr = "X";
break;
}
}
/* Compute the ssh fingerprint if requested. */
if (with_ssh_fpr)
{
gcry_sexp_t key;
if (!agent_raw_key_from_file (ctrl, grip, &key))
{
- ssh_get_fingerprint_string (key, GCRY_MD_MD5, &fpr);
+ ssh_get_fingerprint_string (key, with_ssh_fpr, &fpr);
gcry_sexp_release (key);
}
}
/* Here we have a little race by doing the cache check separately
from the retrieval function. Given that the cache flag is only a
hint, it should not really matter. */
pw = agent_get_cache (ctrl, hexgrip, CACHE_MODE_NORMAL);
cached = pw ? "1" : "-";
xfree (pw);
if (shadow_info)
{
err = parse_shadow_info (shadow_info, &serialno, &idstr, NULL);
if (err)
goto leave;
}
if (!data)
err = agent_write_status (ctrl, "KEYINFO",
hexgrip,
keytypestr,
serialno? serialno : "-",
idstr? idstr : "-",
cached,
protectionstr,
fpr? fpr : "-",
ttlbuf,
flagsbuf,
NULL);
else
{
char *string;
string = xtryasprintf ("%s %s %s %s %s %s %s %s %s\n",
hexgrip, keytypestr,
serialno? serialno : "-",
idstr? idstr : "-", cached, protectionstr,
fpr? fpr : "-",
ttlbuf,
flagsbuf);
if (!string)
err = gpg_error_from_syserror ();
else
err = assuan_send_data (ctx, string, strlen(string));
xfree (string);
}
leave:
xfree (fpr);
xfree (shadow_info);
xfree (serialno);
xfree (idstr);
return err;
}
-/* Entry int for the command KEYINFO. This function handles the
- command option processing. For details see hlp_keyinfo above. */
+/* Entry into the command KEYINFO. This function handles the
+ * command option processing. For details see hlp_keyinfo above. */
static gpg_error_t
cmd_keyinfo (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int err;
unsigned char grip[20];
DIR *dir = NULL;
int list_mode;
int opt_data, opt_ssh_fpr, opt_with_ssh;
ssh_control_file_t cf = NULL;
char hexgrip[41];
int disabled, ttl, confirm, is_ssh;
+ struct card_key_info_s *keyinfo_on_cards;
+ struct card_key_info_s *l;
+ int on_card;
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
if (has_option (line, "--ssh-list"))
list_mode = 2;
else
list_mode = has_option (line, "--list");
opt_data = has_option (line, "--data");
- opt_ssh_fpr = has_option (line, "--ssh-fpr");
+
+ if (has_option_name (line, "--ssh-fpr"))
+ {
+ if (has_option (line, "--ssh-fpr=md5"))
+ opt_ssh_fpr = GCRY_MD_MD5;
+ else if (has_option (line, "--ssh-fpr=sha1"))
+ opt_ssh_fpr = GCRY_MD_SHA1;
+ else if (has_option (line, "--ssh-fpr=sha256"))
+ opt_ssh_fpr = GCRY_MD_SHA256;
+ else
+ opt_ssh_fpr = opt.ssh_fingerprint_digest;
+ }
+ else
+ opt_ssh_fpr = 0;
+
opt_with_ssh = has_option (line, "--with-ssh");
line = skip_options (line);
if (opt_with_ssh || list_mode == 2)
cf = ssh_open_control_file ();
+ agent_card_keyinfo (ctrl, NULL, &keyinfo_on_cards);
+
if (list_mode == 2)
{
if (cf)
{
while (!ssh_read_control_file (cf, hexgrip,
&disabled, &ttl, &confirm))
{
if (hex2bin (hexgrip, grip, 20) < 0 )
continue; /* Bad hex string. */
+
+ on_card = 0;
+ for (l = keyinfo_on_cards; l; l = l->next)
+ if (!memcmp (l->keygrip, hexgrip, 40))
+ on_card = 1;
+
err = do_one_keyinfo (ctrl, grip, ctx, opt_data, opt_ssh_fpr, 1,
- ttl, disabled, confirm);
+ ttl, disabled, confirm, on_card);
if (err)
goto leave;
}
}
err = 0;
}
else if (list_mode)
{
char *dirname;
struct dirent *dir_entry;
dirname = make_filename_try (gnupg_homedir (),
GNUPG_PRIVATE_KEYS_DIR, NULL);
if (!dirname)
{
err = gpg_error_from_syserror ();
goto leave;
}
dir = opendir (dirname);
if (!dir)
{
err = gpg_error_from_syserror ();
xfree (dirname);
goto leave;
}
xfree (dirname);
while ( (dir_entry = readdir (dir)) )
{
if (strlen (dir_entry->d_name) != 44
|| strcmp (dir_entry->d_name + 40, ".key"))
continue;
strncpy (hexgrip, dir_entry->d_name, 40);
hexgrip[40] = 0;
if ( hex2bin (hexgrip, grip, 20) < 0 )
continue; /* Bad hex string. */
disabled = ttl = confirm = is_ssh = 0;
if (opt_with_ssh)
{
err = ssh_search_control_file (cf, hexgrip,
&disabled, &ttl, &confirm);
if (!err)
is_ssh = 1;
else if (gpg_err_code (err) != GPG_ERR_NOT_FOUND)
goto leave;
}
+ on_card = 0;
+ for (l = keyinfo_on_cards; l; l = l->next)
+ if (!memcmp (l->keygrip, hexgrip, 40))
+ on_card = 1;
+
err = do_one_keyinfo (ctrl, grip, ctx, opt_data, opt_ssh_fpr, is_ssh,
- ttl, disabled, confirm);
+ ttl, disabled, confirm, on_card);
if (err)
goto leave;
}
err = 0;
}
else
{
err = parse_keygrip (ctx, line, grip);
if (err)
goto leave;
disabled = ttl = confirm = is_ssh = 0;
if (opt_with_ssh)
{
err = ssh_search_control_file (cf, line,
&disabled, &ttl, &confirm);
if (!err)
is_ssh = 1;
else if (gpg_err_code (err) != GPG_ERR_NOT_FOUND)
goto leave;
}
+ on_card = 0;
+ for (l = keyinfo_on_cards; l; l = l->next)
+ if (!memcmp (l->keygrip, line, 40))
+ on_card = 1;
+
err = do_one_keyinfo (ctrl, grip, ctx, opt_data, opt_ssh_fpr, is_ssh,
- ttl, disabled, confirm);
+ ttl, disabled, confirm, on_card);
}
leave:
+ agent_card_free_keyinfo (keyinfo_on_cards);
ssh_close_control_file (cf);
if (dir)
closedir (dir);
if (err && gpg_err_code (err) != GPG_ERR_NOT_FOUND)
leave_cmd (ctx, err);
return err;
}
/* Helper for cmd_get_passphrase. */
static int
send_back_passphrase (assuan_context_t ctx, int via_data, const char *pw)
{
size_t n;
int rc;
assuan_begin_confidential (ctx);
n = strlen (pw);
if (via_data)
rc = assuan_send_data (ctx, pw, n);
else
{
char *p = xtrymalloc_secure (n*2+1);
if (!p)
rc = gpg_error_from_syserror ();
else
{
bin2hex (pw, n, p);
rc = assuan_set_okay_line (ctx, p);
xfree (p);
}
}
return rc;
}
static const char hlp_get_passphrase[] =
"GET_PASSPHRASE [--data] [--check] [--no-ask] [--repeat[=N]]\n"
" [--qualitybar] <cache_id>\n"
" [<error_message> <prompt> <description>]\n"
"\n"
"This function is usually used to ask for a passphrase to be used\n"
"for conventional encryption, but may also be used by programs which\n"
"need specal handling of passphrases. This command uses a syntax\n"
"which helps clients to use the agent with minimum effort. The\n"
"agent either returns with an error or with a OK followed by the hex\n"
"encoded passphrase. Note that the length of the strings is\n"
"implicitly limited by the maximum length of a command.\n"
"\n"
"If the option \"--data\" is used the passphrase is returned by usual\n"
"data lines and not on the okay line.\n"
"\n"
"If the option \"--check\" is used the passphrase constraints checks as\n"
"implemented by gpg-agent are applied. A check is not done if the\n"
"passphrase has been found in the cache.\n"
"\n"
"If the option \"--no-ask\" is used and the passphrase is not in the\n"
"cache the user will not be asked to enter a passphrase but the error\n"
"code GPG_ERR_NO_DATA is returned. \n"
"\n"
"If the option \"--qualitybar\" is used a visual indication of the\n"
"entered passphrase quality is shown. (Unless no minimum passphrase\n"
"length has been configured.)";
static gpg_error_t
cmd_get_passphrase (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc;
char *pw;
char *response;
char *cacheid = NULL, *desc = NULL, *prompt = NULL, *errtext = NULL;
const char *desc2 = _("Please re-enter this passphrase");
char *p;
int opt_data, opt_check, opt_no_ask, opt_qualbar;
int opt_repeat = 0;
char *entry_errtext = NULL;
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
opt_data = has_option (line, "--data");
opt_check = has_option (line, "--check");
opt_no_ask = has_option (line, "--no-ask");
if (has_option_name (line, "--repeat"))
{
p = option_value (line, "--repeat");
if (p)
opt_repeat = atoi (p);
else
opt_repeat = 1;
}
opt_qualbar = has_option (line, "--qualitybar");
line = skip_options (line);
cacheid = line;
p = strchr (cacheid, ' ');
if (p)
{
*p++ = 0;
while (*p == ' ')
p++;
errtext = p;
p = strchr (errtext, ' ');
if (p)
{
*p++ = 0;
while (*p == ' ')
p++;
prompt = p;
p = strchr (prompt, ' ');
if (p)
{
*p++ = 0;
while (*p == ' ')
p++;
desc = p;
p = strchr (desc, ' ');
if (p)
*p = 0; /* Ignore trailing garbage. */
}
}
}
if (!*cacheid || strlen (cacheid) > 50)
return set_error (GPG_ERR_ASS_PARAMETER, "invalid length of cacheID");
if (!desc)
return set_error (GPG_ERR_ASS_PARAMETER, "no description given");
if (!strcmp (cacheid, "X"))
cacheid = NULL;
if (!strcmp (errtext, "X"))
errtext = NULL;
if (!strcmp (prompt, "X"))
prompt = NULL;
if (!strcmp (desc, "X"))
desc = NULL;
pw = cacheid ? agent_get_cache (ctrl, cacheid, CACHE_MODE_USER) : NULL;
if (pw)
{
rc = send_back_passphrase (ctx, opt_data, pw);
xfree (pw);
}
else if (opt_no_ask)
rc = gpg_error (GPG_ERR_NO_DATA);
else
{
/* Note, that we only need to replace the + characters and
should leave the other escaping in place because the escaped
string is send verbatim to the pinentry which does the
unescaping (but not the + replacing) */
if (errtext)
plus_to_blank (errtext);
if (prompt)
plus_to_blank (prompt);
if (desc)
plus_to_blank (desc);
next_try:
rc = agent_get_passphrase (ctrl, &response, desc, prompt,
entry_errtext? entry_errtext:errtext,
opt_qualbar, cacheid, CACHE_MODE_USER);
xfree (entry_errtext);
entry_errtext = NULL;
if (!rc)
{
int i;
if (opt_check
&& check_passphrase_constraints (ctrl, response, &entry_errtext))
{
xfree (response);
goto next_try;
}
for (i = 0; i < opt_repeat; i++)
{
char *response2;
if (ctrl->pinentry_mode == PINENTRY_MODE_LOOPBACK)
break;
rc = agent_get_passphrase (ctrl, &response2, desc2, prompt,
errtext, 0,
cacheid, CACHE_MODE_USER);
if (rc)
break;
if (strcmp (response2, response))
{
xfree (response2);
xfree (response);
entry_errtext = try_percent_escape
(_("does not match - try again"), NULL);
if (!entry_errtext)
{
rc = gpg_error_from_syserror ();
break;
}
goto next_try;
}
xfree (response2);
}
if (!rc)
{
if (cacheid)
agent_put_cache (ctrl, cacheid, CACHE_MODE_USER, response, 0);
rc = send_back_passphrase (ctx, opt_data, response);
}
xfree (response);
}
}
return leave_cmd (ctx, rc);
}
static const char hlp_clear_passphrase[] =
"CLEAR_PASSPHRASE [--mode=normal] <cache_id>\n"
"\n"
"may be used to invalidate the cache entry for a passphrase. The\n"
"function returns with OK even when there is no cached passphrase.\n"
"The --mode=normal option is used to clear an entry for a cacheid\n"
"added by the agent. The --mode=ssh option is used for a cacheid\n"
"added for ssh.\n";
static gpg_error_t
cmd_clear_passphrase (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
char *cacheid = NULL;
char *p;
cache_mode_t cache_mode = CACHE_MODE_USER;
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
if (has_option (line, "--mode=normal"))
cache_mode = CACHE_MODE_NORMAL;
else if (has_option (line, "--mode=ssh"))
cache_mode = CACHE_MODE_SSH;
line = skip_options (line);
/* parse the stuff */
for (p=line; *p == ' '; p++)
;
cacheid = p;
p = strchr (cacheid, ' ');
if (p)
*p = 0; /* ignore garbage */
if (!*cacheid || strlen (cacheid) > 50)
return set_error (GPG_ERR_ASS_PARAMETER, "invalid length of cacheID");
agent_put_cache (ctrl, cacheid, cache_mode, NULL, 0);
agent_clear_passphrase (ctrl, cacheid, cache_mode);
return 0;
}
static const char hlp_get_confirmation[] =
"GET_CONFIRMATION <description>\n"
"\n"
"This command may be used to ask for a simple confirmation.\n"
"DESCRIPTION is displayed along with a Okay and Cancel button. This\n"
"command uses a syntax which helps clients to use the agent with\n"
"minimum effort. The agent either returns with an error or with a\n"
"OK. Note, that the length of DESCRIPTION is implicitly limited by\n"
"the maximum length of a command. DESCRIPTION should not contain\n"
"any spaces, those must be encoded either percent escaped or simply\n"
"as '+'.";
static gpg_error_t
cmd_get_confirmation (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc;
char *desc = NULL;
char *p;
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
/* parse the stuff */
for (p=line; *p == ' '; p++)
;
desc = p;
p = strchr (desc, ' ');
if (p)
*p = 0; /* We ignore any garbage -may be later used for other args. */
if (!*desc)
return set_error (GPG_ERR_ASS_PARAMETER, "no description given");
if (!strcmp (desc, "X"))
desc = NULL;
/* Note, that we only need to replace the + characters and should
leave the other escaping in place because the escaped string is
send verbatim to the pinentry which does the unescaping (but not
the + replacing) */
if (desc)
plus_to_blank (desc);
rc = agent_get_confirmation (ctrl, desc, NULL, NULL, 0);
return leave_cmd (ctx, rc);
}
static const char hlp_learn[] =
"LEARN [--send] [--sendinfo] [--force]\n"
"\n"
"Learn something about the currently inserted smartcard. With\n"
"--sendinfo information about the card is returned; with --send\n"
"the available certificates are returned as D lines; with --force\n"
"private key storage will be updated by the result.";
static gpg_error_t
cmd_learn (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err;
int send, sendinfo, force;
send = has_option (line, "--send");
sendinfo = send? 1 : has_option (line, "--sendinfo");
force = has_option (line, "--force");
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
err = agent_handle_learn (ctrl, send, sendinfo? ctx : NULL, force);
return leave_cmd (ctx, err);
}
static const char hlp_passwd[] =
"PASSWD [--cache-nonce=<c>] [--passwd-nonce=<s>] [--preset]\n"
" [--verify] <hexkeygrip>\n"
"\n"
"Change the passphrase/PIN for the key identified by keygrip in LINE. If\n"
"--preset is used then the new passphrase will be added to the cache.\n"
"If --verify is used the command asks for the passphrase and verifies\n"
"that the passphrase valid.\n";
static gpg_error_t
cmd_passwd (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err;
int c;
char *cache_nonce = NULL;
char *passwd_nonce = NULL;
unsigned char grip[20];
gcry_sexp_t s_skey = NULL;
unsigned char *shadow_info = NULL;
char *passphrase = NULL;
char *pend;
int opt_preset, opt_verify;
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
opt_preset = has_option (line, "--preset");
cache_nonce = option_value (line, "--cache-nonce");
opt_verify = has_option (line, "--verify");
if (cache_nonce)
{
for (pend = cache_nonce; *pend && !spacep (pend); pend++)
;
c = *pend;
*pend = '\0';
cache_nonce = xtrystrdup (cache_nonce);
*pend = c;
if (!cache_nonce)
{
err = gpg_error_from_syserror ();
goto leave;
}
}
passwd_nonce = option_value (line, "--passwd-nonce");
if (passwd_nonce)
{
for (pend = passwd_nonce; *pend && !spacep (pend); pend++)
;
c = *pend;
*pend = '\0';
passwd_nonce = xtrystrdup (passwd_nonce);
*pend = c;
if (!passwd_nonce)
{
err = gpg_error_from_syserror ();
goto leave;
}
}
line = skip_options (line);
err = parse_keygrip (ctx, line, grip);
if (err)
goto leave;
ctrl->in_passwd++;
err = agent_key_from_file (ctrl,
opt_verify? NULL : cache_nonce,
ctrl->server_local->keydesc,
grip, &shadow_info, CACHE_MODE_IGNORE, NULL,
&s_skey, &passphrase);
if (err)
;
else if (shadow_info)
{
log_error ("changing a smartcard PIN is not yet supported\n");
err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
}
else if (opt_verify)
{
/* All done. */
if (passphrase)
{
if (!passwd_nonce)
{
char buf[12];
gcry_create_nonce (buf, 12);
passwd_nonce = bin2hex (buf, 12, NULL);
}
if (passwd_nonce
&& !agent_put_cache (ctrl, passwd_nonce, CACHE_MODE_NONCE,
passphrase, CACHE_TTL_NONCE))
{
assuan_write_status (ctx, "PASSWD_NONCE", passwd_nonce);
xfree (ctrl->server_local->last_passwd_nonce);
ctrl->server_local->last_passwd_nonce = passwd_nonce;
passwd_nonce = NULL;
}
}
}
else
{
char *newpass = NULL;
if (passwd_nonce)
newpass = agent_get_cache (ctrl, passwd_nonce, CACHE_MODE_NONCE);
err = agent_protect_and_store (ctrl, s_skey, &newpass);
if (!err && passphrase)
{
/* A passphrase existed on the old key and the change was
successful. Return a nonce for that old passphrase to
let the caller try to unprotect the other subkeys with
the same key. */
if (!cache_nonce)
{
char buf[12];
gcry_create_nonce (buf, 12);
cache_nonce = bin2hex (buf, 12, NULL);
}
if (cache_nonce
&& !agent_put_cache (ctrl, cache_nonce, CACHE_MODE_NONCE,
passphrase, CACHE_TTL_NONCE))
{
assuan_write_status (ctx, "CACHE_NONCE", cache_nonce);
xfree (ctrl->server_local->last_cache_nonce);
ctrl->server_local->last_cache_nonce = cache_nonce;
cache_nonce = NULL;
}
if (newpass)
{
/* If we have a new passphrase (which might be empty) we
store it under a passwd nonce so that the caller may
send that nonce again to use it for another key. */
if (!passwd_nonce)
{
char buf[12];
gcry_create_nonce (buf, 12);
passwd_nonce = bin2hex (buf, 12, NULL);
}
if (passwd_nonce
&& !agent_put_cache (ctrl, passwd_nonce, CACHE_MODE_NONCE,
newpass, CACHE_TTL_NONCE))
{
assuan_write_status (ctx, "PASSWD_NONCE", passwd_nonce);
xfree (ctrl->server_local->last_passwd_nonce);
ctrl->server_local->last_passwd_nonce = passwd_nonce;
passwd_nonce = NULL;
}
}
}
if (!err && opt_preset)
{
char hexgrip[40+1];
bin2hex(grip, 20, hexgrip);
err = agent_put_cache (ctrl, hexgrip, CACHE_MODE_ANY, newpass,
ctrl->cache_ttl_opt_preset);
}
xfree (newpass);
}
ctrl->in_passwd--;
xfree (ctrl->server_local->keydesc);
ctrl->server_local->keydesc = NULL;
leave:
xfree (passphrase);
gcry_sexp_release (s_skey);
xfree (shadow_info);
xfree (cache_nonce);
xfree (passwd_nonce);
return leave_cmd (ctx, err);
}
static const char hlp_preset_passphrase[] =
"PRESET_PASSPHRASE [--inquire] <string_or_keygrip> <timeout> [<hexstring>]\n"
"\n"
"Set the cached passphrase/PIN for the key identified by the keygrip\n"
"to passwd for the given time, where -1 means infinite and 0 means\n"
"the default (currently only a timeout of -1 is allowed, which means\n"
"to never expire it). If passwd is not provided, ask for it via the\n"
"pinentry module unless --inquire is passed in which case the passphrase\n"
"is retrieved from the client via a server inquire.\n";
static gpg_error_t
cmd_preset_passphrase (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc;
char *grip_clear = NULL;
unsigned char *passphrase = NULL;
int ttl;
size_t len;
int opt_inquire;
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
if (!opt.allow_preset_passphrase)
return set_error (GPG_ERR_NOT_SUPPORTED, "no --allow-preset-passphrase");
opt_inquire = has_option (line, "--inquire");
line = skip_options (line);
grip_clear = line;
while (*line && (*line != ' ' && *line != '\t'))
line++;
if (!*line)
return gpg_error (GPG_ERR_MISSING_VALUE);
*line = '\0';
line++;
while (*line && (*line == ' ' || *line == '\t'))
line++;
/* Currently, only infinite timeouts are allowed. */
ttl = -1;
if (line[0] != '-' || line[1] != '1')
return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
line++;
line++;
while (!(*line != ' ' && *line != '\t'))
line++;
/* Syntax check the hexstring. */
len = 0;
rc = parse_hexstring (ctx, line, &len);
if (rc)
return rc;
line[len] = '\0';
/* If there is a passphrase, use it. Currently, a passphrase is
required. */
if (*line)
{
if (opt_inquire)
{
rc = set_error (GPG_ERR_ASS_PARAMETER,
"both --inquire and passphrase specified");
goto leave;
}
/* Do in-place conversion. */
passphrase = line;
if (!hex2str (passphrase, passphrase, strlen (passphrase)+1, NULL))
rc = set_error (GPG_ERR_ASS_PARAMETER, "invalid hexstring");
}
else if (opt_inquire)
{
/* Note that the passphrase will be truncated at any null byte and the
* limit is 480 characters. */
size_t maxlen = 480;
rc = print_assuan_status (ctx, "INQUIRE_MAXLEN", "%zu", maxlen);
if (!rc)
rc = assuan_inquire (ctx, "PASSPHRASE", &passphrase, &len, maxlen);
}
else
rc = set_error (GPG_ERR_NOT_IMPLEMENTED, "passphrase is required");
if (!rc)
{
rc = agent_put_cache (ctrl, grip_clear, CACHE_MODE_ANY, passphrase, ttl);
if (opt_inquire)
xfree (passphrase);
}
leave:
return leave_cmd (ctx, rc);
}
static const char hlp_scd[] =
"SCD <commands to pass to the scdaemon>\n"
" \n"
"This is a general quote command to redirect everything to the\n"
"SCdaemon.";
static gpg_error_t
cmd_scd (assuan_context_t ctx, char *line)
{
int rc;
#ifdef BUILD_WITH_SCDAEMON
ctrl_t ctrl = assuan_get_pointer (ctx);
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
rc = divert_generic_cmd (ctrl, line, ctx);
#else
(void)ctx; (void)line;
rc = gpg_error (GPG_ERR_NOT_SUPPORTED);
#endif
return rc;
}
static const char hlp_keywrap_key[] =
"KEYWRAP_KEY [--clear] <mode>\n"
"\n"
"Return a key to wrap another key. For now the key is returned\n"
"verbatim and thus makes not much sense because an eavesdropper on\n"
"the gpg-agent connection will see the key as well as the wrapped key.\n"
"However, this function may either be equipped with a public key\n"
"mechanism or not used at all if the key is a pre-shared key. In any\n"
"case wrapping the import and export of keys is a requirement for\n"
"certain cryptographic validations and thus useful. The key persists\n"
"until a RESET command but may be cleared using the option --clear.\n"
"\n"
"Supported modes are:\n"
" --import - Return a key to import a key into gpg-agent\n"
" --export - Return a key to export a key from gpg-agent";
static gpg_error_t
cmd_keywrap_key (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err = 0;
int clearopt = has_option (line, "--clear");
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
assuan_begin_confidential (ctx);
if (has_option (line, "--import"))
{
xfree (ctrl->server_local->import_key);
if (clearopt)
ctrl->server_local->import_key = NULL;
else if (!(ctrl->server_local->import_key =
gcry_random_bytes (KEYWRAP_KEYSIZE, GCRY_STRONG_RANDOM)))
err = gpg_error_from_syserror ();
else
err = assuan_send_data (ctx, ctrl->server_local->import_key,
KEYWRAP_KEYSIZE);
}
else if (has_option (line, "--export"))
{
xfree (ctrl->server_local->export_key);
if (clearopt)
ctrl->server_local->export_key = NULL;
else if (!(ctrl->server_local->export_key =
gcry_random_bytes (KEYWRAP_KEYSIZE, GCRY_STRONG_RANDOM)))
err = gpg_error_from_syserror ();
else
err = assuan_send_data (ctx, ctrl->server_local->export_key,
KEYWRAP_KEYSIZE);
}
else
err = set_error (GPG_ERR_ASS_PARAMETER, "unknown value for MODE");
assuan_end_confidential (ctx);
return leave_cmd (ctx, err);
}
static const char hlp_import_key[] =
"IMPORT_KEY [--unattended] [--force] [<cache_nonce>]\n"
"\n"
"Import a secret key into the key store. The key is expected to be\n"
"encrypted using the current session's key wrapping key (cf. command\n"
"KEYWRAP_KEY) using the AESWRAP-128 algorithm. This function takes\n"
"no arguments but uses the inquiry \"KEYDATA\" to ask for the actual\n"
"key data. The unwrapped key must be a canonical S-expression. The\n"
"option --unattended tries to import the key as-is without any\n"
"re-encryption. Existing key can be overwritten with --force.";
static gpg_error_t
cmd_import_key (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err;
int opt_unattended;
int force;
unsigned char *wrappedkey = NULL;
size_t wrappedkeylen;
gcry_cipher_hd_t cipherhd = NULL;
unsigned char *key = NULL;
size_t keylen, realkeylen;
char *passphrase = NULL;
unsigned char *finalkey = NULL;
size_t finalkeylen;
unsigned char grip[20];
gcry_sexp_t openpgp_sexp = NULL;
char *cache_nonce = NULL;
char *p;
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
if (!ctrl->server_local->import_key)
{
err = gpg_error (GPG_ERR_MISSING_KEY);
goto leave;
}
opt_unattended = has_option (line, "--unattended");
force = has_option (line, "--force");
line = skip_options (line);
for (p=line; *p && *p != ' ' && *p != '\t'; p++)
;
*p = '\0';
if (*line)
cache_nonce = xtrystrdup (line);
assuan_begin_confidential (ctx);
err = assuan_inquire (ctx, "KEYDATA",
&wrappedkey, &wrappedkeylen, MAXLEN_KEYDATA);
assuan_end_confidential (ctx);
if (err)
goto leave;
if (wrappedkeylen < 24)
{
err = gpg_error (GPG_ERR_INV_LENGTH);
goto leave;
}
keylen = wrappedkeylen - 8;
key = xtrymalloc_secure (keylen);
if (!key)
{
err = gpg_error_from_syserror ();
goto leave;
}
err = gcry_cipher_open (&cipherhd, GCRY_CIPHER_AES128,
GCRY_CIPHER_MODE_AESWRAP, 0);
if (err)
goto leave;
err = gcry_cipher_setkey (cipherhd,
ctrl->server_local->import_key, KEYWRAP_KEYSIZE);
if (err)
goto leave;
err = gcry_cipher_decrypt (cipherhd, key, keylen, wrappedkey, wrappedkeylen);
if (err)
goto leave;
gcry_cipher_close (cipherhd);
cipherhd = NULL;
xfree (wrappedkey);
wrappedkey = NULL;
realkeylen = gcry_sexp_canon_len (key, keylen, NULL, &err);
if (!realkeylen)
goto leave; /* Invalid canonical encoded S-expression. */
err = keygrip_from_canon_sexp (key, realkeylen, grip);
if (err)
{
/* This might be due to an unsupported S-expression format.
Check whether this is openpgp-private-key and trigger that
import code. */
if (!gcry_sexp_sscan (&openpgp_sexp, NULL, key, realkeylen))
{
const char *tag;
size_t taglen;
tag = gcry_sexp_nth_data (openpgp_sexp, 0, &taglen);
if (tag && taglen == 19 && !memcmp (tag, "openpgp-private-key", 19))
;
else
{
gcry_sexp_release (openpgp_sexp);
openpgp_sexp = NULL;
}
}
if (!openpgp_sexp)
goto leave; /* Note that ERR is still set. */
}
if (openpgp_sexp)
{
/* In most cases the key is encrypted and thus the conversion
function from the OpenPGP format to our internal format will
ask for a passphrase. That passphrase will be returned and
used to protect the key using the same code as for regular
key import. */
xfree (key);
key = NULL;
err = convert_from_openpgp (ctrl, openpgp_sexp, force, grip,
ctrl->server_local->keydesc, cache_nonce,
&key, opt_unattended? NULL : &passphrase);
if (err)
goto leave;
realkeylen = gcry_sexp_canon_len (key, 0, NULL, &err);
if (!realkeylen)
goto leave; /* Invalid canonical encoded S-expression. */
if (passphrase)
{
- assert (!opt_unattended);
+ log_assert (!opt_unattended);
if (!cache_nonce)
{
char buf[12];
gcry_create_nonce (buf, 12);
cache_nonce = bin2hex (buf, 12, NULL);
}
if (cache_nonce
&& !agent_put_cache (ctrl, cache_nonce, CACHE_MODE_NONCE,
passphrase, CACHE_TTL_NONCE))
assuan_write_status (ctx, "CACHE_NONCE", cache_nonce);
}
}
else if (opt_unattended)
{
err = set_error (GPG_ERR_ASS_PARAMETER,
"\"--unattended\" may only be used with OpenPGP keys");
goto leave;
}
else
{
if (!force && !agent_key_available (grip))
err = gpg_error (GPG_ERR_EEXIST);
else
{
char *prompt = xtryasprintf
(_("Please enter the passphrase to protect the "
"imported object within the %s system."), GNUPG_NAME);
if (!prompt)
err = gpg_error_from_syserror ();
else
err = agent_ask_new_passphrase (ctrl, prompt, &passphrase);
xfree (prompt);
}
if (err)
goto leave;
}
if (passphrase)
{
err = agent_protect (key, passphrase, &finalkey, &finalkeylen,
ctrl->s2k_count, -1);
if (!err)
- err = agent_write_private_key (grip, finalkey, finalkeylen, force);
+ err = agent_write_private_key (grip, finalkey, finalkeylen, force,
+ NULL, NULL);
}
else
- err = agent_write_private_key (grip, key, realkeylen, force);
+ err = agent_write_private_key (grip, key, realkeylen, force, NULL, NULL);
leave:
gcry_sexp_release (openpgp_sexp);
xfree (finalkey);
xfree (passphrase);
xfree (key);
gcry_cipher_close (cipherhd);
xfree (wrappedkey);
xfree (cache_nonce);
xfree (ctrl->server_local->keydesc);
ctrl->server_local->keydesc = NULL;
return leave_cmd (ctx, err);
}
static const char hlp_export_key[] =
"EXPORT_KEY [--cache-nonce=<nonce>] [--openpgp] <hexstring_with_keygrip>\n"
"\n"
"Export a secret key from the key store. The key will be encrypted\n"
"using the current session's key wrapping key (cf. command KEYWRAP_KEY)\n"
"using the AESWRAP-128 algorithm. The caller needs to retrieve that key\n"
"prior to using this command. The function takes the keygrip as argument.\n"
"\n"
"If --openpgp is used, the secret key material will be exported in RFC 4880\n"
"compatible passphrase-protected form. Without --openpgp, the secret key\n"
"material will be exported in the clear (after prompting the user to unlock\n"
"it, if needed).\n";
static gpg_error_t
cmd_export_key (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err;
unsigned char grip[20];
gcry_sexp_t s_skey = NULL;
unsigned char *key = NULL;
size_t keylen;
gcry_cipher_hd_t cipherhd = NULL;
unsigned char *wrappedkey = NULL;
size_t wrappedkeylen;
int openpgp;
char *cache_nonce;
char *passphrase = NULL;
unsigned char *shadow_info = NULL;
char *pend;
int c;
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
openpgp = has_option (line, "--openpgp");
cache_nonce = option_value (line, "--cache-nonce");
if (cache_nonce)
{
for (pend = cache_nonce; *pend && !spacep (pend); pend++)
;
c = *pend;
*pend = '\0';
cache_nonce = xtrystrdup (cache_nonce);
*pend = c;
if (!cache_nonce)
{
err = gpg_error_from_syserror ();
goto leave;
}
}
line = skip_options (line);
if (!ctrl->server_local->export_key)
{
err = set_error (GPG_ERR_MISSING_KEY, "did you run KEYWRAP_KEY ?");
goto leave;
}
err = parse_keygrip (ctx, line, grip);
if (err)
goto leave;
if (agent_key_available (grip))
{
err = gpg_error (GPG_ERR_NO_SECKEY);
goto leave;
}
/* Get the key from the file. With the openpgp flag we also ask for
the passphrase so that we can use it to re-encrypt it. */
err = agent_key_from_file (ctrl, cache_nonce,
ctrl->server_local->keydesc, grip,
&shadow_info, CACHE_MODE_IGNORE, NULL, &s_skey,
openpgp ? &passphrase : NULL);
if (err)
goto leave;
if (shadow_info)
{
/* Key is on a smartcard. */
err = gpg_error (GPG_ERR_UNUSABLE_SECKEY);
goto leave;
}
if (openpgp)
{
/* The openpgp option changes the key format into the OpenPGP
key transfer format. The result is already a padded
canonical S-expression. */
if (!passphrase)
{
err = agent_ask_new_passphrase
(ctrl, _("This key (or subkey) is not protected with a passphrase."
" Please enter a new passphrase to export it."),
&passphrase);
if (err)
goto leave;
}
err = convert_to_openpgp (ctrl, s_skey, passphrase, &key, &keylen);
if (!err && passphrase)
{
if (!cache_nonce)
{
char buf[12];
gcry_create_nonce (buf, 12);
cache_nonce = bin2hex (buf, 12, NULL);
}
if (cache_nonce
&& !agent_put_cache (ctrl, cache_nonce, CACHE_MODE_NONCE,
passphrase, CACHE_TTL_NONCE))
{
assuan_write_status (ctx, "CACHE_NONCE", cache_nonce);
xfree (ctrl->server_local->last_cache_nonce);
ctrl->server_local->last_cache_nonce = cache_nonce;
cache_nonce = NULL;
}
}
}
else
{
/* Convert into a canonical S-expression and wrap that. */
err = make_canon_sexp_pad (s_skey, 1, &key, &keylen);
}
if (err)
goto leave;
gcry_sexp_release (s_skey);
s_skey = NULL;
err = gcry_cipher_open (&cipherhd, GCRY_CIPHER_AES128,
GCRY_CIPHER_MODE_AESWRAP, 0);
if (err)
goto leave;
err = gcry_cipher_setkey (cipherhd,
ctrl->server_local->export_key, KEYWRAP_KEYSIZE);
if (err)
goto leave;
wrappedkeylen = keylen + 8;
wrappedkey = xtrymalloc (wrappedkeylen);
if (!wrappedkey)
{
err = gpg_error_from_syserror ();
goto leave;
}
err = gcry_cipher_encrypt (cipherhd, wrappedkey, wrappedkeylen, key, keylen);
if (err)
goto leave;
xfree (key);
key = NULL;
gcry_cipher_close (cipherhd);
cipherhd = NULL;
assuan_begin_confidential (ctx);
err = assuan_send_data (ctx, wrappedkey, wrappedkeylen);
assuan_end_confidential (ctx);
leave:
xfree (cache_nonce);
xfree (passphrase);
xfree (wrappedkey);
gcry_cipher_close (cipherhd);
xfree (key);
gcry_sexp_release (s_skey);
xfree (ctrl->server_local->keydesc);
ctrl->server_local->keydesc = NULL;
xfree (shadow_info);
return leave_cmd (ctx, err);
}
static const char hlp_delete_key[] =
"DELETE_KEY [--force|--stub-only] <hexstring_with_keygrip>\n"
"\n"
"Delete a secret key from the key store. If --force is used\n"
"and a loopback pinentry is allowed, the agent will not ask\n"
"the user for confirmation. If --stub-only is used the key will\n"
"only be deleted if it is a reference to a token.";
static gpg_error_t
cmd_delete_key (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err;
int force, stub_only;
unsigned char grip[20];
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
force = has_option (line, "--force");
stub_only = has_option (line, "--stub-only");
line = skip_options (line);
/* If the use of a loopback pinentry has been disabled, we assume
* that a silent deletion of keys shall also not be allowed. */
if (!opt.allow_loopback_pinentry)
force = 0;
err = parse_keygrip (ctx, line, grip);
if (err)
goto leave;
err = agent_delete_key (ctrl, ctrl->server_local->keydesc, grip,
force, stub_only);
if (err)
goto leave;
leave:
xfree (ctrl->server_local->keydesc);
ctrl->server_local->keydesc = NULL;
return leave_cmd (ctx, err);
}
#if SIZEOF_TIME_T > SIZEOF_UNSIGNED_LONG
#define KEYTOCARD_TIMESTAMP_FORMAT "(10:created-at10:%010llu))"
#else
#define KEYTOCARD_TIMESTAMP_FORMAT "(10:created-at10:%010lu))"
#endif
static const char hlp_keytocard[] =
"KEYTOCARD [--force] <hexgrip> <serialno> <keyref> [<timestamp>]\n"
"\n"
"TIMESTAMP is required for OpenPGP and defaults to the Epoch. The\n"
"SERIALNO is used for checking; use \"-\" to disable the check.";
static gpg_error_t
cmd_keytocard (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int force;
gpg_error_t err = 0;
char *argv[5];
int argc;
unsigned char grip[20];
const char *serialno, *timestamp_str, *keyref;
gcry_sexp_t s_skey = NULL;
unsigned char *keydata;
size_t keydatalen;
unsigned char *shadow_info = NULL;
time_t timestamp;
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
force = has_option (line, "--force");
line = skip_options (line);
argc = split_fields (line, argv, DIM (argv));
if (argc < 3)
{
err = gpg_error (GPG_ERR_MISSING_VALUE);
goto leave;
}
err = parse_keygrip (ctx, argv[0], grip);
if (err)
goto leave;
if (agent_key_available (grip))
{
err =gpg_error (GPG_ERR_NO_SECKEY);
goto leave;
}
/* Note that checking of the s/n is currently not implemented but we
* want to provide a clean interface if we ever implement it. */
serialno = argv[1];
if (!strcmp (serialno, "-"))
serialno = NULL;
keyref = argv[2];
/* FIXME: Default to the creation time as stored in the private
* key. The parameter is here so that gpg can make sure that the
* timestamp as used for key creation (and thus the openPGP
* fingerprint) is used. */
timestamp_str = argc > 3? argv[3] : "19700101T000000";
if ((timestamp = isotime2epoch (timestamp_str)) == (time_t)(-1))
{
err = gpg_error (GPG_ERR_INV_TIME);
goto leave;
}
err = agent_key_from_file (ctrl, NULL, ctrl->server_local->keydesc, grip,
&shadow_info, CACHE_MODE_IGNORE, NULL,
&s_skey, NULL);
if (err)
goto leave;
if (shadow_info)
{
/* Key is already on a smartcard - we can't extract it. */
err = gpg_error (GPG_ERR_UNUSABLE_SECKEY);
goto leave;
}
/* Note: We can't use make_canon_sexp because we need to allocate a
* few extra bytes for our hack below. */
keydatalen = gcry_sexp_sprint (s_skey, GCRYSEXP_FMT_CANON, NULL, 0);
keydata = xtrymalloc_secure (keydatalen + 30);
if (keydata == NULL)
{
err = gpg_error_from_syserror ();
goto leave;
}
gcry_sexp_sprint (s_skey, GCRYSEXP_FMT_CANON, keydata, keydatalen);
gcry_sexp_release (s_skey);
s_skey = NULL;
keydatalen--; /* Decrement for last '\0'. */
/* Hack to insert the timestamp "created-at" into the private key. */
snprintf (keydata+keydatalen-1, 30, KEYTOCARD_TIMESTAMP_FORMAT, timestamp);
keydatalen += 10 + 19 - 1;
err = divert_writekey (ctrl, force, serialno, keyref, keydata, keydatalen);
xfree (keydata);
leave:
gcry_sexp_release (s_skey);
xfree (shadow_info);
return leave_cmd (ctx, err);
}
static const char hlp_get_secret[] =
"GET_SECRET <key>\n"
"\n"
"Return the secret value stored under KEY\n";
static gpg_error_t
cmd_get_secret (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err;
char *p, *key;
char *value = NULL;
size_t valuelen;
/* For now we allow this only for local connections. */
if (ctrl->restricted)
{
err = gpg_error (GPG_ERR_FORBIDDEN);
goto leave;
}
line = skip_options (line);
for (p=line; *p == ' '; p++)
;
key = p;
p = strchr (key, ' ');
if (p)
{
*p++ = 0;
for (; *p == ' '; p++)
;
if (*p)
{
err = set_error (GPG_ERR_ASS_PARAMETER, "too many arguments");
goto leave;
}
}
if (!*key)
{
err = set_error (GPG_ERR_ASS_PARAMETER, "no key given");
goto leave;
}
value = agent_get_cache (ctrl, key, CACHE_MODE_DATA);
if (!value)
{
err = gpg_error (GPG_ERR_NO_DATA);
goto leave;
}
valuelen = percent_unescape_inplace (value, 0);
err = assuan_send_data (ctx, value, valuelen);
wipememory (value, valuelen);
leave:
xfree (value);
return leave_cmd (ctx, err);
}
static const char hlp_put_secret[] =
"PUT_SECRET [--clear] <key> <ttl> [<percent_escaped_value>]\n"
"\n"
"This commands stores a secret under KEY in gpg-agent's in-memory\n"
"cache. The TTL must be explicitly given by TTL and the options\n"
"from the configuration file are not used. The value is either given\n"
"percent-escaped as 3rd argument or if not given inquired by gpg-agent\n"
"using the keyword \"SECRET\".\n"
"The option --clear removes the secret from the cache."
"";
static gpg_error_t
cmd_put_secret (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err = 0;
int opt_clear;
unsigned char *value = NULL;
size_t valuelen = 0;
size_t n;
char *p, *key, *ttlstr;
unsigned char *valstr;
int ttl;
char *string = NULL;
/* For now we allow this only for local connections. */
if (ctrl->restricted)
{
err = gpg_error (GPG_ERR_FORBIDDEN);
goto leave;
}
opt_clear = has_option (line, "--clear");
line = skip_options (line);
for (p=line; *p == ' '; p++)
;
key = p;
ttlstr = NULL;
valstr = NULL;
p = strchr (key, ' ');
if (p)
{
*p++ = 0;
for (; *p == ' '; p++)
;
if (*p)
{
ttlstr = p;
p = strchr (ttlstr, ' ');
if (p)
{
*p++ = 0;
for (; *p == ' '; p++)
;
if (*p)
valstr = p;
}
}
}
if (!*key)
{
err = set_error (GPG_ERR_ASS_PARAMETER, "no key given");
goto leave;
}
if (!ttlstr || !*ttlstr || !(n = parse_ttl (ttlstr, &ttl)))
{
err = set_error (GPG_ERR_ASS_PARAMETER, "no or invalid TTL given");
goto leave;
}
if (valstr && opt_clear)
{
err = set_error (GPG_ERR_ASS_PARAMETER,
"value not expected with --clear");
goto leave;
}
if (valstr)
{
valuelen = percent_unescape_inplace (valstr, 0);
value = NULL;
}
else /* Inquire the value to store */
{
err = print_assuan_status (ctx, "INQUIRE_MAXLEN", "%u",MAXLEN_PUT_SECRET);
if (!err)
err = assuan_inquire (ctx, "SECRET",
&value, &valuelen, MAXLEN_PUT_SECRET);
if (err)
goto leave;
}
/* Our cache expects strings and thus we need to turn the buffer
* into a string. Instead of resorting to base64 encoding we use a
* special percent escaping which only quoted the Nul and the
* percent character. */
string = percent_data_escape (0, NULL, value? value : valstr, valuelen);
if (!string)
{
err = gpg_error_from_syserror ();
goto leave;
}
err = agent_put_cache (ctrl, key, CACHE_MODE_DATA, string, ttl);
leave:
if (string)
{
wipememory (string, strlen (string));
xfree (string);
}
if (value)
{
wipememory (value, valuelen);
xfree (value);
}
return leave_cmd (ctx, err);
}
static const char hlp_getval[] =
"GETVAL <key>\n"
"\n"
"Return the value for KEY from the special environment as created by\n"
"PUTVAL.";
static gpg_error_t
cmd_getval (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc = 0;
char *key = NULL;
char *p;
struct putval_item_s *vl;
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
for (p=line; *p == ' '; p++)
;
key = p;
p = strchr (key, ' ');
if (p)
{
*p++ = 0;
for (; *p == ' '; p++)
;
if (*p)
return set_error (GPG_ERR_ASS_PARAMETER, "too many arguments");
}
if (!*key)
return set_error (GPG_ERR_ASS_PARAMETER, "no key given");
for (vl=putval_list; vl; vl = vl->next)
if ( !strcmp (vl->d, key) )
break;
if (vl) /* Got an entry. */
rc = assuan_send_data (ctx, vl->d+vl->off, vl->len);
else
return gpg_error (GPG_ERR_NO_DATA);
return leave_cmd (ctx, rc);
}
static const char hlp_putval[] =
"PUTVAL <key> [<percent_escaped_value>]\n"
"\n"
"The gpg-agent maintains a kind of environment which may be used to\n"
"store key/value pairs in it, so that they can be retrieved later.\n"
"This may be used by helper daemons to daemonize themself on\n"
"invocation and register them with gpg-agent. Callers of the\n"
"daemon's service may now first try connect to get the information\n"
"for that service from gpg-agent through the GETVAL command and then\n"
"try to connect to that daemon. Only if that fails they may start\n"
"an own instance of the service daemon. \n"
"\n"
"KEY is an arbitrary symbol with the same syntax rules as keys\n"
"for shell environment variables. PERCENT_ESCAPED_VALUE is the\n"
"corresponding value; they should be similar to the values of\n"
"envronment variables but gpg-agent does not enforce any\n"
"restrictions. If that value is not given any value under that KEY\n"
"is removed from this special environment.";
static gpg_error_t
cmd_putval (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc = 0;
char *key = NULL;
char *value = NULL;
size_t valuelen = 0;
char *p;
struct putval_item_s *vl, *vlprev;
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
for (p=line; *p == ' '; p++)
;
key = p;
p = strchr (key, ' ');
if (p)
{
*p++ = 0;
for (; *p == ' '; p++)
;
if (*p)
{
value = p;
p = strchr (value, ' ');
if (p)
*p = 0;
valuelen = percent_plus_unescape_inplace (value, 0);
}
}
if (!*key)
return set_error (GPG_ERR_ASS_PARAMETER, "no key given");
for (vl=putval_list,vlprev=NULL; vl; vlprev=vl, vl = vl->next)
if ( !strcmp (vl->d, key) )
break;
if (vl) /* Delete old entry. */
{
if (vlprev)
vlprev->next = vl->next;
else
putval_list = vl->next;
xfree (vl);
}
if (valuelen) /* Add entry. */
{
vl = xtrymalloc (sizeof *vl + strlen (key) + valuelen);
if (!vl)
rc = gpg_error_from_syserror ();
else
{
vl->len = valuelen;
vl->off = strlen (key) + 1;
strcpy (vl->d, key);
memcpy (vl->d + vl->off, value, valuelen);
vl->next = putval_list;
putval_list = vl;
}
}
return leave_cmd (ctx, rc);
}
static const char hlp_updatestartuptty[] =
"UPDATESTARTUPTTY\n"
"\n"
"Set startup TTY and X11 DISPLAY variables to the values of this\n"
"session. This command is useful to pull future pinentries to\n"
"another screen. It is only required because there is no way in the\n"
"ssh-agent protocol to convey this information.";
static gpg_error_t
cmd_updatestartuptty (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err = 0;
session_env_t se;
char *lc_ctype = NULL;
char *lc_messages = NULL;
int iterator;
const char *name;
(void)line;
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
se = session_env_new ();
if (!se)
err = gpg_error_from_syserror ();
iterator = 0;
while (!err && (name = session_env_list_stdenvnames (&iterator, NULL)))
{
const char *value = session_env_getenv (ctrl->session_env, name);
if (value)
err = session_env_setenv (se, name, value);
}
if (!err && ctrl->lc_ctype)
if (!(lc_ctype = xtrystrdup (ctrl->lc_ctype)))
err = gpg_error_from_syserror ();
if (!err && ctrl->lc_messages)
if (!(lc_messages = xtrystrdup (ctrl->lc_messages)))
err = gpg_error_from_syserror ();
if (err)
{
session_env_release (se);
xfree (lc_ctype);
xfree (lc_messages);
}
else
{
session_env_release (opt.startup_env);
opt.startup_env = se;
xfree (opt.startup_lc_ctype);
opt.startup_lc_ctype = lc_ctype;
xfree (opt.startup_lc_messages);
opt.startup_lc_messages = lc_messages;
}
return err;
}
static const char hlp_killagent[] =
"KILLAGENT\n"
"\n"
"Stop the agent.";
static gpg_error_t
cmd_killagent (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
(void)line;
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
ctrl->server_local->stopme = 1;
assuan_set_flag (ctx, ASSUAN_FORCE_CLOSE, 1);
return 0;
}
static const char hlp_reloadagent[] =
"RELOADAGENT\n"
"\n"
"This command is an alternative to SIGHUP\n"
"to reload the configuration.";
static gpg_error_t
cmd_reloadagent (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
(void)line;
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
agent_sighup_action ();
return 0;
}
static const char hlp_getinfo[] =
"GETINFO <what>\n"
"\n"
"Multipurpose function to return a variety of information.\n"
"Supported values for WHAT are:\n"
"\n"
" version - Return the version of the program.\n"
" pid - Return the process id of the server.\n"
" socket_name - Return the name of the socket.\n"
" ssh_socket_name - Return the name of the ssh socket.\n"
" scd_running - Return OK if the SCdaemon is already running.\n"
" s2k_time - Return the time in milliseconds required for S2K.\n"
" s2k_count - Return the standard S2K count.\n"
" s2k_count_cal - Return the calibrated S2K count.\n"
" std_env_names - List the names of the standard environment.\n"
" std_session_env - List the standard session environment.\n"
" std_startup_env - List the standard startup environment.\n"
" getenv NAME - Return value of envvar NAME.\n"
" connections - Return number of active connections.\n"
" jent_active - Returns OK if Libgcrypt's JENT is active.\n"
" restricted - Returns OK if the connection is in restricted mode.\n"
" cmd_has_option CMD OPT\n"
" - Returns OK if command CMD has option OPT.\n";
static gpg_error_t
cmd_getinfo (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc = 0;
if (!strcmp (line, "version"))
{
const char *s = VERSION;
rc = assuan_send_data (ctx, s, strlen (s));
}
else if (!strncmp (line, "cmd_has_option", 14)
&& (line[14] == ' ' || line[14] == '\t' || !line[14]))
{
char *cmd, *cmdopt;
line += 14;
while (*line == ' ' || *line == '\t')
line++;
if (!*line)
rc = gpg_error (GPG_ERR_MISSING_VALUE);
else
{
cmd = line;
while (*line && (*line != ' ' && *line != '\t'))
line++;
if (!*line)
rc = gpg_error (GPG_ERR_MISSING_VALUE);
else
{
*line++ = 0;
while (*line == ' ' || *line == '\t')
line++;
if (!*line)
rc = gpg_error (GPG_ERR_MISSING_VALUE);
else
{
cmdopt = line;
if (!command_has_option (cmd, cmdopt))
- rc = gpg_error (GPG_ERR_GENERAL);
+ rc = gpg_error (GPG_ERR_FALSE);
}
}
}
}
else if (!strcmp (line, "s2k_count"))
{
char numbuf[50];
snprintf (numbuf, sizeof numbuf, "%lu", get_standard_s2k_count ());
rc = assuan_send_data (ctx, numbuf, strlen (numbuf));
}
else if (!strcmp (line, "restricted"))
{
- rc = ctrl->restricted? 0 : gpg_error (GPG_ERR_GENERAL);
+ rc = ctrl->restricted? 0 : gpg_error (GPG_ERR_FALSE);
}
else if (ctrl->restricted)
{
rc = gpg_error (GPG_ERR_FORBIDDEN);
}
/* All sub-commands below are not allowed in restricted mode. */
else if (!strcmp (line, "pid"))
{
char numbuf[50];
snprintf (numbuf, sizeof numbuf, "%lu", (unsigned long)getpid ());
rc = assuan_send_data (ctx, numbuf, strlen (numbuf));
}
else if (!strcmp (line, "socket_name"))
{
const char *s = get_agent_socket_name ();
if (s)
rc = assuan_send_data (ctx, s, strlen (s));
else
rc = gpg_error (GPG_ERR_NO_DATA);
}
else if (!strcmp (line, "ssh_socket_name"))
{
const char *s = get_agent_ssh_socket_name ();
if (s)
rc = assuan_send_data (ctx, s, strlen (s));
else
rc = gpg_error (GPG_ERR_NO_DATA);
}
else if (!strcmp (line, "scd_running"))
{
- rc = agent_scd_check_running ()? 0 : gpg_error (GPG_ERR_GENERAL);
+ rc = agent_scd_check_running ()? 0 : gpg_error (GPG_ERR_FALSE);
}
else if (!strcmp (line, "std_env_names"))
{
int iterator;
const char *name;
iterator = 0;
while ((name = session_env_list_stdenvnames (&iterator, NULL)))
{
rc = assuan_send_data (ctx, name, strlen (name)+1);
if (!rc)
rc = assuan_send_data (ctx, NULL, 0);
if (rc)
break;
}
}
else if (!strcmp (line, "std_session_env")
|| !strcmp (line, "std_startup_env"))
{
int iterator;
const char *name, *value;
char *string;
iterator = 0;
while ((name = session_env_list_stdenvnames (&iterator, NULL)))
{
value = session_env_getenv_or_default
(line[5] == 't'? opt.startup_env:ctrl->session_env, name, NULL);
if (value)
{
string = xtryasprintf ("%s=%s", name, value);
if (!string)
rc = gpg_error_from_syserror ();
else
{
rc = assuan_send_data (ctx, string, strlen (string)+1);
if (!rc)
rc = assuan_send_data (ctx, NULL, 0);
}
if (rc)
break;
}
}
}
else if (!strncmp (line, "getenv", 6)
&& (line[6] == ' ' || line[6] == '\t' || !line[6]))
{
line += 6;
while (*line == ' ' || *line == '\t')
line++;
if (!*line)
rc = gpg_error (GPG_ERR_MISSING_VALUE);
else
{
const char *s = getenv (line);
if (!s)
rc = set_error (GPG_ERR_NOT_FOUND, "No such envvar");
else
rc = assuan_send_data (ctx, s, strlen (s));
}
}
else if (!strcmp (line, "connections"))
{
char numbuf[20];
snprintf (numbuf, sizeof numbuf, "%d",
get_agent_active_connection_count ());
rc = assuan_send_data (ctx, numbuf, strlen (numbuf));
}
else if (!strcmp (line, "jent_active"))
{
#if GCRYPT_VERSION_NUMBER >= 0x010800
char *buf;
char *fields[5];
buf = gcry_get_config (0, "rng-type");
if (buf
&& split_fields_colon (buf, fields, DIM (fields)) >= 5
&& atoi (fields[4]) > 0)
rc = 0;
else
rc = gpg_error (GPG_ERR_FALSE);
gcry_free (buf);
#else
rc = gpg_error (GPG_ERR_FALSE);
#endif
}
else if (!strcmp (line, "s2k_count_cal"))
{
char numbuf[50];
snprintf (numbuf, sizeof numbuf, "%lu", get_calibrated_s2k_count ());
rc = assuan_send_data (ctx, numbuf, strlen (numbuf));
}
else if (!strcmp (line, "s2k_time"))
{
char numbuf[50];
snprintf (numbuf, sizeof numbuf, "%lu", get_standard_s2k_time ());
rc = assuan_send_data (ctx, numbuf, strlen (numbuf));
}
else
rc = set_error (GPG_ERR_ASS_PARAMETER, "unknown value for WHAT");
return rc;
}
/* This function is called by Libassuan to parse the OPTION command.
It has been registered similar to the other Assuan commands. */
static gpg_error_t
option_handler (assuan_context_t ctx, const char *key, const char *value)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err = 0;
if (!strcmp (key, "agent-awareness"))
{
/* The value is a version string telling us of which agent
version the caller is aware of. */
ctrl->server_local->allow_fully_canceled =
gnupg_compare_version (value, "2.1.0");
}
else if (ctrl->restricted)
{
err = gpg_error (GPG_ERR_FORBIDDEN);
}
/* All options below are not allowed in restricted mode. */
else if (!strcmp (key, "putenv"))
{
/* Change the session's environment to be used for the
Pinentry. Valid values are:
<NAME> Delete envvar NAME
<KEY>= Set envvar NAME to the empty string
<KEY>=<VALUE> Set envvar NAME to VALUE
*/
err = session_env_putenv (ctrl->session_env, value);
}
else if (!strcmp (key, "display"))
{
err = session_env_setenv (ctrl->session_env, "DISPLAY", value);
}
else if (!strcmp (key, "ttyname"))
{
if (!opt.keep_tty)
err = session_env_setenv (ctrl->session_env, "GPG_TTY", value);
}
else if (!strcmp (key, "ttytype"))
{
if (!opt.keep_tty)
err = session_env_setenv (ctrl->session_env, "TERM", value);
}
else if (!strcmp (key, "lc-ctype"))
{
if (ctrl->lc_ctype)
xfree (ctrl->lc_ctype);
ctrl->lc_ctype = xtrystrdup (value);
if (!ctrl->lc_ctype)
return out_of_core ();
}
else if (!strcmp (key, "lc-messages"))
{
if (ctrl->lc_messages)
xfree (ctrl->lc_messages);
ctrl->lc_messages = xtrystrdup (value);
if (!ctrl->lc_messages)
return out_of_core ();
}
else if (!strcmp (key, "xauthority"))
{
err = session_env_setenv (ctrl->session_env, "XAUTHORITY", value);
}
else if (!strcmp (key, "pinentry-user-data"))
{
err = session_env_setenv (ctrl->session_env, "PINENTRY_USER_DATA", value);
}
else if (!strcmp (key, "use-cache-for-signing"))
ctrl->server_local->use_cache_for_signing = *value? !!atoi (value) : 0;
else if (!strcmp (key, "allow-pinentry-notify"))
ctrl->server_local->allow_pinentry_notify = 1;
else if (!strcmp (key, "pinentry-mode"))
{
int tmp = parse_pinentry_mode (value);
if (tmp == -1)
err = gpg_error (GPG_ERR_INV_VALUE);
else if (tmp == PINENTRY_MODE_LOOPBACK && !opt.allow_loopback_pinentry)
err = gpg_error (GPG_ERR_NOT_SUPPORTED);
else
ctrl->pinentry_mode = tmp;
}
else if (!strcmp (key, "cache-ttl-opt-preset"))
{
ctrl->cache_ttl_opt_preset = *value? atoi (value) : 0;
}
else if (!strcmp (key, "s2k-count"))
{
ctrl->s2k_count = *value? strtoul(value, NULL, 10) : 0;
if (ctrl->s2k_count && ctrl->s2k_count < 65536)
{
ctrl->s2k_count = 0;
}
}
else if (!strcmp (key, "pretend-request-origin"))
{
log_assert (!ctrl->restricted);
switch (parse_request_origin (value))
{
case REQUEST_ORIGIN_LOCAL: ctrl->restricted = 0; break;
case REQUEST_ORIGIN_REMOTE: ctrl->restricted = 1; break;
case REQUEST_ORIGIN_BROWSER: ctrl->restricted = 2; break;
default:
err = gpg_error (GPG_ERR_INV_VALUE);
/* Better pretend to be remote in case of a bad value. */
ctrl->restricted = 1;
break;
}
}
else
err = gpg_error (GPG_ERR_UNKNOWN_OPTION);
return err;
}
/* Called by libassuan after all commands. ERR is the error from the
last assuan operation and not the one returned from the command. */
static void
post_cmd_notify (assuan_context_t ctx, gpg_error_t err)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
(void)err;
/* Switch off any I/O monitor controlled logging pausing. */
ctrl->server_local->pause_io_logging = 0;
}
/* This function is called by libassuan for all I/O. We use it here
to disable logging for the GETEVENTCOUNTER commands. This is so
that the debug output won't get cluttered by this primitive
command. */
static unsigned int
io_monitor (assuan_context_t ctx, void *hook, int direction,
const char *line, size_t linelen)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
(void) hook;
/* We want to suppress all Assuan log messages for connections from
* self. However, assuan_get_pid works only after
* assuan_accept. Now, assuan_accept already logs a line ending with
* the process id. We use this hack here to get the peers pid so
* that we can compare it to our pid. We should add an assuan
* function to return the pid for a file descriptor and use that to
* detect connections to self. */
if (ctx && !ctrl->server_local->greeting_seen
&& direction == ASSUAN_IO_TO_PEER)
{
ctrl->server_local->greeting_seen = 1;
if (linelen > 32
&& !strncmp (line, "OK Pleased to meet you, process ", 32)
&& strtoul (line+32, NULL, 10) == getpid ())
return ASSUAN_IO_MONITOR_NOLOG;
}
/* Do not log self-connections. This makes the log cleaner because
* we won't see the check-our-own-socket calls. */
if (ctx && ctrl->server_local->connect_from_self)
return ASSUAN_IO_MONITOR_NOLOG;
/* Note that we only check for the uppercase name. This allows the user to
see the logging for debugging if using a non-upercase command
name. */
if (ctx && direction == ASSUAN_IO_FROM_PEER
&& linelen >= 15
&& !strncmp (line, "GETEVENTCOUNTER", 15)
&& (linelen == 15 || spacep (line+15)))
{
ctrl->server_local->pause_io_logging = 1;
}
return ctrl->server_local->pause_io_logging? ASSUAN_IO_MONITOR_NOLOG : 0;
}
/* Return true if the command CMD implements the option OPT. */
static int
command_has_option (const char *cmd, const char *cmdopt)
{
if (!strcmp (cmd, "GET_PASSPHRASE"))
{
if (!strcmp (cmdopt, "repeat"))
return 1;
}
return 0;
}
/* Tell Libassuan about our commands. Also register the other Assuan
handlers. */
static int
register_commands (assuan_context_t ctx)
{
static struct {
const char *name;
assuan_handler_t handler;
const char * const help;
} table[] = {
{ "GETEVENTCOUNTER",cmd_geteventcounter, hlp_geteventcounter },
{ "ISTRUSTED", cmd_istrusted, hlp_istrusted },
{ "HAVEKEY", cmd_havekey, hlp_havekey },
{ "KEYINFO", cmd_keyinfo, hlp_keyinfo },
{ "SIGKEY", cmd_sigkey, hlp_sigkey },
{ "SETKEY", cmd_sigkey, hlp_sigkey },
{ "SETKEYDESC", cmd_setkeydesc,hlp_setkeydesc },
{ "SETHASH", cmd_sethash, hlp_sethash },
{ "PKSIGN", cmd_pksign, hlp_pksign },
{ "PKDECRYPT", cmd_pkdecrypt, hlp_pkdecrypt },
{ "GENKEY", cmd_genkey, hlp_genkey },
{ "READKEY", cmd_readkey, hlp_readkey },
{ "GET_PASSPHRASE", cmd_get_passphrase, hlp_get_passphrase },
{ "PRESET_PASSPHRASE", cmd_preset_passphrase, hlp_preset_passphrase },
{ "CLEAR_PASSPHRASE", cmd_clear_passphrase, hlp_clear_passphrase },
{ "GET_CONFIRMATION", cmd_get_confirmation, hlp_get_confirmation },
{ "LISTTRUSTED", cmd_listtrusted, hlp_listtrusted },
{ "MARKTRUSTED", cmd_marktrusted, hlp_martrusted },
{ "LEARN", cmd_learn, hlp_learn },
{ "PASSWD", cmd_passwd, hlp_passwd },
{ "INPUT", NULL },
{ "OUTPUT", NULL },
{ "SCD", cmd_scd, hlp_scd },
{ "KEYWRAP_KEY", cmd_keywrap_key, hlp_keywrap_key },
{ "IMPORT_KEY", cmd_import_key, hlp_import_key },
{ "EXPORT_KEY", cmd_export_key, hlp_export_key },
{ "DELETE_KEY", cmd_delete_key, hlp_delete_key },
{ "GET_SECRET", cmd_get_secret, hlp_get_secret },
{ "PUT_SECRET", cmd_put_secret, hlp_put_secret },
{ "GETVAL", cmd_getval, hlp_getval },
{ "PUTVAL", cmd_putval, hlp_putval },
{ "UPDATESTARTUPTTY", cmd_updatestartuptty, hlp_updatestartuptty },
{ "KILLAGENT", cmd_killagent, hlp_killagent },
{ "RELOADAGENT", cmd_reloadagent,hlp_reloadagent },
{ "GETINFO", cmd_getinfo, hlp_getinfo },
{ "KEYTOCARD", cmd_keytocard, hlp_keytocard },
{ NULL }
};
int i, rc;
for (i=0; table[i].name; i++)
{
rc = assuan_register_command (ctx, table[i].name, table[i].handler,
table[i].help);
if (rc)
return rc;
}
assuan_register_post_cmd_notify (ctx, post_cmd_notify);
assuan_register_reset_notify (ctx, reset_notify);
assuan_register_option_handler (ctx, option_handler);
return 0;
}
/* Startup the server. If LISTEN_FD and FD is given as -1, this is a
simple piper server, otherwise it is a regular server. CTRL is the
control structure for this connection; it has only the basic
initialization. */
void
start_command_handler (ctrl_t ctrl, gnupg_fd_t listen_fd, gnupg_fd_t fd)
{
int rc;
assuan_context_t ctx = NULL;
if (ctrl->restricted)
{
if (agent_copy_startup_env (ctrl))
return;
}
rc = assuan_new (&ctx);
if (rc)
{
log_error ("failed to allocate assuan context: %s\n", gpg_strerror (rc));
agent_exit (2);
}
if (listen_fd == GNUPG_INVALID_FD && fd == GNUPG_INVALID_FD)
{
assuan_fd_t filedes[2];
filedes[0] = assuan_fdopen (0);
filedes[1] = assuan_fdopen (1);
rc = assuan_init_pipe_server (ctx, filedes);
}
else if (listen_fd != GNUPG_INVALID_FD)
{
rc = assuan_init_socket_server (ctx, listen_fd, 0);
/* FIXME: Need to call assuan_sock_set_nonce for Windows. But
this branch is currently not used. */
}
else
{
rc = assuan_init_socket_server (ctx, fd, ASSUAN_SOCKET_SERVER_ACCEPTED);
}
if (rc)
{
log_error ("failed to initialize the server: %s\n",
gpg_strerror(rc));
agent_exit (2);
}
rc = register_commands (ctx);
if (rc)
{
log_error ("failed to register commands with Assuan: %s\n",
gpg_strerror(rc));
agent_exit (2);
}
assuan_set_pointer (ctx, ctrl);
ctrl->server_local = xcalloc (1, sizeof *ctrl->server_local);
ctrl->server_local->assuan_ctx = ctx;
ctrl->server_local->use_cache_for_signing = 1;
ctrl->digest.raw_value = 0;
assuan_set_io_monitor (ctx, io_monitor, NULL);
agent_set_progress_cb (progress_cb, ctrl);
for (;;)
{
assuan_peercred_t client_creds; /* Note: Points into CTX. */
pid_t pid;
rc = assuan_accept (ctx);
if (gpg_err_code (rc) == GPG_ERR_EOF || rc == -1)
{
break;
}
else if (rc)
{
log_info ("Assuan accept problem: %s\n", gpg_strerror (rc));
break;
}
rc = assuan_get_peercred (ctx, &client_creds);
if (rc)
{
if (listen_fd == GNUPG_INVALID_FD && fd == GNUPG_INVALID_FD)
;
else
log_info ("Assuan get_peercred failed: %s\n", gpg_strerror (rc));
pid = assuan_get_pid (ctx);
ctrl->client_uid = -1;
}
else
{
#ifdef HAVE_W32_SYSTEM
pid = assuan_get_pid (ctx);
ctrl->client_uid = -1;
#else
pid = client_creds->pid;
ctrl->client_uid = client_creds->uid;
#endif
}
ctrl->client_pid = (pid == ASSUAN_INVALID_PID)? 0 : (unsigned long)pid;
ctrl->server_local->connect_from_self = (pid == getpid ());
rc = assuan_process (ctx);
if (rc)
{
log_info ("Assuan processing failed: %s\n", gpg_strerror (rc));
continue;
}
}
/* Reset the nonce caches. */
clear_nonce_cache (ctrl);
/* Reset the SCD if needed. */
agent_reset_scd (ctrl);
/* Reset the pinentry (in case of popup messages). */
agent_reset_query (ctrl);
/* Cleanup. */
assuan_release (ctx);
xfree (ctrl->server_local->keydesc);
xfree (ctrl->server_local->import_key);
xfree (ctrl->server_local->export_key);
if (ctrl->server_local->stopme)
agent_exit (0);
xfree (ctrl->server_local);
ctrl->server_local = NULL;
}
/* Helper for the pinentry loopback mode. It merely passes the
parameters on to the client. */
gpg_error_t
pinentry_loopback(ctrl_t ctrl, const char *keyword,
unsigned char **buffer, size_t *size,
size_t max_length)
{
gpg_error_t rc;
assuan_context_t ctx = ctrl->server_local->assuan_ctx;
rc = print_assuan_status (ctx, "INQUIRE_MAXLEN", "%zu", max_length);
if (rc)
return rc;
assuan_begin_confidential (ctx);
rc = assuan_inquire (ctx, keyword, buffer, size, max_length);
assuan_end_confidential (ctx);
return rc;
}
+
+/* Helper for the pinentry loopback mode to ask confirmation
+ or just to show message. */
+gpg_error_t
+pinentry_loopback_confirm (ctrl_t ctrl, const char *desc,
+ int ask_confirmation,
+ const char *ok, const char *notok)
+{
+ gpg_error_t err = 0;
+ assuan_context_t ctx = ctrl->server_local->assuan_ctx;
+
+ if (desc)
+ err = print_assuan_status (ctx, "SETDESC", "%s", desc);
+ if (!err && ok)
+ err = print_assuan_status (ctx, "SETOK", "%s", ok);
+ if (!err && notok)
+ err = print_assuan_status (ctx, "SETNOTOK", "%s", notok);
+
+ if (!err)
+ err = assuan_inquire (ctx, ask_confirmation ? "CONFIRM 1" : "CONFIRM 0",
+ NULL, NULL, 0);
+ return err;
+}
diff --git a/agent/cvt-openpgp.c b/agent/cvt-openpgp.c
index 06cd1c840..003402956 100644
--- a/agent/cvt-openpgp.c
+++ b/agent/cvt-openpgp.c
@@ -1,1410 +1,1410 @@
/* cvt-openpgp.c - Convert an OpenPGP key to our internal format.
* Copyright (C) 1998-2002, 2006, 2009, 2010 Free Software Foundation, Inc.
* Copyright (C) 2013, 2014 Werner Koch
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
-#include <assert.h>
#include "agent.h"
#include "../common/i18n.h"
#include "cvt-openpgp.h"
#include "../common/host2net.h"
/* Helper to pass data via the callback to do_unprotect. */
struct try_do_unprotect_arg_s
{
int is_v4;
int is_protected;
int pubkey_algo;
const char *curve;
int protect_algo;
char *iv;
int ivlen;
int s2k_mode;
int s2k_algo;
byte *s2k_salt;
u32 s2k_count;
u16 desired_csum;
gcry_mpi_t *skey;
size_t skeysize;
int skeyidx;
gcry_sexp_t *r_key;
};
/* Compute the keygrip from the public key and store it at GRIP. */
static gpg_error_t
get_keygrip (int pubkey_algo, const char *curve, gcry_mpi_t *pkey,
unsigned char *grip)
{
gpg_error_t err;
gcry_sexp_t s_pkey = NULL;
switch (pubkey_algo)
{
case GCRY_PK_DSA:
err = gcry_sexp_build (&s_pkey, NULL,
"(public-key(dsa(p%m)(q%m)(g%m)(y%m)))",
pkey[0], pkey[1], pkey[2], pkey[3]);
break;
case GCRY_PK_ELG:
err = gcry_sexp_build (&s_pkey, NULL,
"(public-key(elg(p%m)(g%m)(y%m)))",
pkey[0], pkey[1], pkey[2]);
break;
case GCRY_PK_RSA:
err = gcry_sexp_build (&s_pkey, NULL,
"(public-key(rsa(n%m)(e%m)))", pkey[0], pkey[1]);
break;
case GCRY_PK_ECC:
if (!curve)
err = gpg_error (GPG_ERR_BAD_SECKEY);
else
{
const char *format;
if (!strcmp (curve, "Ed25519"))
format = "(public-key(ecc(curve %s)(flags eddsa)(q%m)))";
else if (!strcmp (curve, "Curve25519"))
format = "(public-key(ecc(curve %s)(flags djb-tweak)(q%m)))";
else
format = "(public-key(ecc(curve %s)(q%m)))";
err = gcry_sexp_build (&s_pkey, NULL, format, curve, pkey[0]);
}
break;
default:
err = gpg_error (GPG_ERR_PUBKEY_ALGO);
break;
}
if (!err && !gcry_pk_get_keygrip (s_pkey, grip))
err = gpg_error (GPG_ERR_INTERNAL);
gcry_sexp_release (s_pkey);
return err;
}
/* Convert a secret key given as algorithm id and an array of key
parameters into our s-expression based format. Note that
PUBKEY_ALGO has an gcrypt algorithm number. */
static gpg_error_t
convert_secret_key (gcry_sexp_t *r_key, int pubkey_algo, gcry_mpi_t *skey,
const char *curve)
{
gpg_error_t err;
gcry_sexp_t s_skey = NULL;
*r_key = NULL;
switch (pubkey_algo)
{
case GCRY_PK_DSA:
err = gcry_sexp_build (&s_skey, NULL,
"(private-key(dsa(p%m)(q%m)(g%m)(y%m)(x%m)))",
skey[0], skey[1], skey[2], skey[3], skey[4]);
break;
case GCRY_PK_ELG:
case GCRY_PK_ELG_E:
err = gcry_sexp_build (&s_skey, NULL,
"(private-key(elg(p%m)(g%m)(y%m)(x%m)))",
skey[0], skey[1], skey[2], skey[3]);
break;
case GCRY_PK_RSA:
case GCRY_PK_RSA_E:
case GCRY_PK_RSA_S:
err = gcry_sexp_build (&s_skey, NULL,
"(private-key(rsa(n%m)(e%m)(d%m)(p%m)(q%m)(u%m)))",
skey[0], skey[1], skey[2], skey[3], skey[4],
skey[5]);
break;
case GCRY_PK_ECC:
if (!curve)
err = gpg_error (GPG_ERR_BAD_SECKEY);
else
{
const char *format;
if (!strcmp (curve, "Ed25519"))
/* Do not store the OID as name but the real name and the
EdDSA flag. */
format = "(private-key(ecc(curve %s)(flags eddsa)(q%m)(d%m)))";
else if (!strcmp (curve, "Curve25519"))
format = "(private-key(ecc(curve %s)(flags djb-tweak)(q%m)(d%m)))";
else
format = "(private-key(ecc(curve %s)(q%m)(d%m)))";
err = gcry_sexp_build (&s_skey, NULL, format, curve, skey[0], skey[1]);
}
break;
default:
err = gpg_error (GPG_ERR_PUBKEY_ALGO);
break;
}
if (!err)
*r_key = s_skey;
return err;
}
/* Convert a secret key given as algorithm id, an array of key
parameters, and an S-expression of the original OpenPGP transfer
key into our s-expression based format. This is a variant of
convert_secret_key which is used for the openpgp-native protection
mode. Note that PUBKEY_ALGO has an gcrypt algorithm number. */
static gpg_error_t
convert_transfer_key (gcry_sexp_t *r_key, int pubkey_algo, gcry_mpi_t *skey,
const char *curve, gcry_sexp_t transfer_key)
{
gpg_error_t err;
gcry_sexp_t s_skey = NULL;
*r_key = NULL;
switch (pubkey_algo)
{
case GCRY_PK_DSA:
err = gcry_sexp_build
(&s_skey, NULL,
"(protected-private-key(dsa(p%m)(q%m)(g%m)(y%m)"
"(protected openpgp-native%S)))",
skey[0], skey[1], skey[2], skey[3], transfer_key);
break;
case GCRY_PK_ELG:
err = gcry_sexp_build
(&s_skey, NULL,
"(protected-private-key(elg(p%m)(g%m)(y%m)"
"(protected openpgp-native%S)))",
skey[0], skey[1], skey[2], transfer_key);
break;
case GCRY_PK_RSA:
err = gcry_sexp_build
(&s_skey, NULL,
"(protected-private-key(rsa(n%m)(e%m)"
"(protected openpgp-native%S)))",
skey[0], skey[1], transfer_key );
break;
case GCRY_PK_ECC:
if (!curve)
err = gpg_error (GPG_ERR_BAD_SECKEY);
else
{
const char *format;
if (!strcmp (curve, "Ed25519"))
/* Do not store the OID as name but the real name and the
EdDSA flag. */
format = "(protected-private-key(ecc(curve %s)(flags eddsa)(q%m)"
"(protected openpgp-native%S)))";
else if (!strcmp (curve, "Curve25519"))
format = "(protected-private-key(ecc(curve %s)(flags djb-tweak)(q%m)"
"(protected openpgp-native%S)))";
else
format = "(protected-private-key(ecc(curve %s)(q%m)"
"(protected openpgp-native%S)))";
err = gcry_sexp_build (&s_skey, NULL, format, curve, skey[0], transfer_key);
}
break;
default:
err = gpg_error (GPG_ERR_PUBKEY_ALGO);
break;
}
if (!err)
*r_key = s_skey;
return err;
}
/* Hash the passphrase and set the key. */
static gpg_error_t
hash_passphrase_and_set_key (const char *passphrase,
gcry_cipher_hd_t hd, int protect_algo,
int s2k_mode, int s2k_algo,
byte *s2k_salt, u32 s2k_count)
{
gpg_error_t err;
unsigned char *key;
size_t keylen;
keylen = gcry_cipher_get_algo_keylen (protect_algo);
if (!keylen)
return gpg_error (GPG_ERR_INTERNAL);
key = xtrymalloc_secure (keylen);
if (!key)
return gpg_error_from_syserror ();
err = s2k_hash_passphrase (passphrase,
s2k_algo, s2k_mode, s2k_salt, s2k_count,
key, keylen);
if (!err)
err = gcry_cipher_setkey (hd, key, keylen);
xfree (key);
return err;
}
static u16
checksum (const unsigned char *p, unsigned int n)
{
u16 a;
for (a=0; n; n-- )
a += *p++;
return a;
}
/* Return the number of expected key parameters. */
static void
get_npkey_nskey (int pubkey_algo, size_t *npkey, size_t *nskey)
{
switch (pubkey_algo)
{
case GCRY_PK_RSA: *npkey = 2; *nskey = 6; break;
case GCRY_PK_ELG: *npkey = 3; *nskey = 4; break;
case GCRY_PK_ELG_E: *npkey = 3; *nskey = 4; break;
case GCRY_PK_DSA: *npkey = 4; *nskey = 5; break;
case GCRY_PK_ECC: *npkey = 1; *nskey = 2; break;
default: *npkey = 0; *nskey = 0; break;
}
}
/* Helper for do_unprotect. PUBKEY_ALOGO is the gcrypt algo number.
On success R_NPKEY and R_NSKEY receive the number or parameters for
the algorithm PUBKEY_ALGO and R_SKEYLEN the used length of
SKEY. */
static int
prepare_unprotect (int pubkey_algo, gcry_mpi_t *skey, size_t skeysize,
int s2k_mode,
unsigned int *r_npkey, unsigned int *r_nskey,
unsigned int *r_skeylen)
{
size_t npkey, nskey, skeylen;
int i;
/* Count the actual number of MPIs is in the array and set the
remainder to NULL for easier processing later on. */
for (skeylen = 0; skey[skeylen]; skeylen++)
;
for (i=skeylen; i < skeysize; i++)
skey[i] = NULL;
/* Check some args. */
if (s2k_mode == 1001)
{
/* Stub key. */
log_info (_("secret key parts are not available\n"));
return gpg_error (GPG_ERR_UNUSABLE_SECKEY);
}
if (gcry_pk_test_algo (pubkey_algo))
{
log_info (_("public key algorithm %d (%s) is not supported\n"),
pubkey_algo, gcry_pk_algo_name (pubkey_algo));
return gpg_error (GPG_ERR_PUBKEY_ALGO);
}
/* Get properties of the public key algorithm and do some
consistency checks. Note that we need at least NPKEY+1 elements
in the SKEY array. */
get_npkey_nskey (pubkey_algo, &npkey, &nskey);
if (!npkey || !nskey || npkey >= nskey)
return gpg_error (GPG_ERR_INTERNAL);
if (skeylen <= npkey)
return gpg_error (GPG_ERR_MISSING_VALUE);
if (nskey+1 >= skeysize)
return gpg_error (GPG_ERR_BUFFER_TOO_SHORT);
/* Check that the public key parameters are all available and not
encrypted. */
for (i=0; i < npkey; i++)
{
if (!skey[i] || gcry_mpi_get_flag (skey[i], GCRYMPI_FLAG_USER1))
return gpg_error (GPG_ERR_BAD_SECKEY);
}
if (r_npkey)
*r_npkey = npkey;
if (r_nskey)
*r_nskey = nskey;
if (r_skeylen)
*r_skeylen = skeylen;
return 0;
}
/* Note that this function modifies SKEY. SKEYSIZE is the allocated
size of the array including the NULL item; this is used for a
bounds check. On success a converted key is stored at R_KEY. */
static int
do_unprotect (const char *passphrase,
int pkt_version, int pubkey_algo, int is_protected,
const char *curve, gcry_mpi_t *skey, size_t skeysize,
int protect_algo, void *protect_iv, size_t protect_ivlen,
int s2k_mode, int s2k_algo, byte *s2k_salt, u32 s2k_count,
u16 desired_csum, gcry_sexp_t *r_key)
{
gpg_error_t err;
unsigned int npkey, nskey, skeylen;
gcry_cipher_hd_t cipher_hd = NULL;
u16 actual_csum;
size_t nbytes;
int i;
gcry_mpi_t tmpmpi;
*r_key = NULL;
err = prepare_unprotect (pubkey_algo, skey, skeysize, s2k_mode,
&npkey, &nskey, &skeylen);
if (err)
return err;
/* Check whether SKEY is at all protected. If it is not protected
merely verify the checksum. */
if (!is_protected)
{
actual_csum = 0;
for (i=npkey; i < nskey; i++)
{
if (!skey[i] || gcry_mpi_get_flag (skey[i], GCRYMPI_FLAG_USER1))
return gpg_error (GPG_ERR_BAD_SECKEY);
if (gcry_mpi_get_flag (skey[i], GCRYMPI_FLAG_OPAQUE))
{
unsigned int nbits;
const unsigned char *buffer;
buffer = gcry_mpi_get_opaque (skey[i], &nbits);
nbytes = (nbits+7)/8;
actual_csum += checksum (buffer, nbytes);
}
else
{
unsigned char *buffer;
err = gcry_mpi_aprint (GCRYMPI_FMT_PGP, &buffer, &nbytes,
skey[i]);
if (!err)
actual_csum += checksum (buffer, nbytes);
xfree (buffer);
}
if (err)
return err;
}
if (actual_csum != desired_csum)
return gpg_error (GPG_ERR_CHECKSUM);
goto do_convert;
}
if (gcry_cipher_test_algo (protect_algo))
{
/* The algorithm numbers are Libgcrypt numbers but fortunately
the OpenPGP algorithm numbers map one-to-one to the Libgcrypt
numbers. */
log_info (_("protection algorithm %d (%s) is not supported\n"),
protect_algo, gnupg_cipher_algo_name (protect_algo));
return gpg_error (GPG_ERR_CIPHER_ALGO);
}
if (gcry_md_test_algo (s2k_algo))
{
log_info (_("protection hash algorithm %d (%s) is not supported\n"),
s2k_algo, gcry_md_algo_name (s2k_algo));
return gpg_error (GPG_ERR_DIGEST_ALGO);
}
err = gcry_cipher_open (&cipher_hd, protect_algo,
GCRY_CIPHER_MODE_CFB,
(GCRY_CIPHER_SECURE
| (protect_algo >= 100 ?
0 : GCRY_CIPHER_ENABLE_SYNC)));
if (err)
{
log_error ("failed to open cipher_algo %d: %s\n",
protect_algo, gpg_strerror (err));
return err;
}
err = hash_passphrase_and_set_key (passphrase, cipher_hd, protect_algo,
s2k_mode, s2k_algo, s2k_salt, s2k_count);
if (err)
{
gcry_cipher_close (cipher_hd);
return err;
}
gcry_cipher_setiv (cipher_hd, protect_iv, protect_ivlen);
actual_csum = 0;
if (pkt_version >= 4)
{
int ndata;
unsigned int ndatabits;
const unsigned char *p;
unsigned char *data;
u16 csum_pgp7 = 0;
if (!gcry_mpi_get_flag (skey[npkey], GCRYMPI_FLAG_OPAQUE ))
{
gcry_cipher_close (cipher_hd);
return gpg_error (GPG_ERR_BAD_SECKEY);
}
p = gcry_mpi_get_opaque (skey[npkey], &ndatabits);
ndata = (ndatabits+7)/8;
if (ndata > 1)
csum_pgp7 = buf16_to_u16 (p+ndata-2);
data = xtrymalloc_secure (ndata);
if (!data)
{
err = gpg_error_from_syserror ();
gcry_cipher_close (cipher_hd);
return err;
}
gcry_cipher_decrypt (cipher_hd, data, ndata, p, ndata);
p = data;
if (is_protected == 2)
{
/* This is the new SHA1 checksum method to detect tampering
with the key as used by the Klima/Rosa attack. */
desired_csum = 0;
actual_csum = 1; /* Default to bad checksum. */
if (ndata < 20)
log_error ("not enough bytes for SHA-1 checksum\n");
else
{
gcry_md_hd_t h;
if (gcry_md_open (&h, GCRY_MD_SHA1, 1))
BUG(); /* Algo not available. */
gcry_md_write (h, data, ndata - 20);
gcry_md_final (h);
if (!memcmp (gcry_md_read (h, GCRY_MD_SHA1), data+ndata-20, 20))
actual_csum = 0; /* Digest does match. */
gcry_md_close (h);
}
}
else
{
/* Old 16 bit checksum method. */
if (ndata < 2)
{
log_error ("not enough bytes for checksum\n");
desired_csum = 0;
actual_csum = 1; /* Mark checksum bad. */
}
else
{
desired_csum = buf16_to_u16 (data+ndata-2);
actual_csum = checksum (data, ndata-2);
if (desired_csum != actual_csum)
{
/* This is a PGP 7.0.0 workaround */
desired_csum = csum_pgp7; /* Take the encrypted one. */
}
}
}
/* Better check it here. Otherwise the gcry_mpi_scan would fail
because the length may have an arbitrary value. */
if (desired_csum == actual_csum)
{
for (i=npkey; i < nskey; i++ )
{
if (gcry_mpi_scan (&tmpmpi, GCRYMPI_FMT_PGP, p, ndata, &nbytes))
{
/* Checksum was okay, but not correctly decrypted. */
desired_csum = 0;
actual_csum = 1; /* Mark checksum bad. */
break;
}
gcry_mpi_release (skey[i]);
skey[i] = tmpmpi;
ndata -= nbytes;
p += nbytes;
}
skey[i] = NULL;
skeylen = i;
- assert (skeylen <= skeysize);
+ log_assert (skeylen <= skeysize);
/* Note: at this point NDATA should be 2 for a simple
checksum or 20 for the sha1 digest. */
}
xfree(data);
}
else /* Packet version <= 3. */
{
unsigned char *buffer;
for (i = npkey; i < nskey; i++)
{
const unsigned char *p;
size_t ndata;
unsigned int ndatabits;
if (!skey[i] || !gcry_mpi_get_flag (skey[i], GCRYMPI_FLAG_OPAQUE))
{
gcry_cipher_close (cipher_hd);
return gpg_error (GPG_ERR_BAD_SECKEY);
}
p = gcry_mpi_get_opaque (skey[i], &ndatabits);
ndata = (ndatabits+7)/8;
if (!(ndata >= 2) || !(ndata == (buf16_to_ushort (p) + 7)/8 + 2))
{
gcry_cipher_close (cipher_hd);
return gpg_error (GPG_ERR_BAD_SECKEY);
}
buffer = xtrymalloc_secure (ndata);
if (!buffer)
{
err = gpg_error_from_syserror ();
gcry_cipher_close (cipher_hd);
return err;
}
gcry_cipher_sync (cipher_hd);
buffer[0] = p[0];
buffer[1] = p[1];
gcry_cipher_decrypt (cipher_hd, buffer+2, ndata-2, p+2, ndata-2);
actual_csum += checksum (buffer, ndata);
err = gcry_mpi_scan (&tmpmpi, GCRYMPI_FMT_PGP, buffer, ndata, &ndata);
xfree (buffer);
if (err)
{
/* Checksum was okay, but not correctly decrypted. */
desired_csum = 0;
actual_csum = 1; /* Mark checksum bad. */
break;
}
gcry_mpi_release (skey[i]);
skey[i] = tmpmpi;
}
}
gcry_cipher_close (cipher_hd);
/* Now let's see whether we have used the correct passphrase. */
if (actual_csum != desired_csum)
return gpg_error (GPG_ERR_BAD_PASSPHRASE);
do_convert:
if (nskey != skeylen)
err = gpg_error (GPG_ERR_BAD_SECKEY);
else
err = convert_secret_key (r_key, pubkey_algo, skey, curve);
if (err)
return err;
/* The checksum may fail, thus we also check the key itself. */
err = gcry_pk_testkey (*r_key);
if (err)
{
gcry_sexp_release (*r_key);
*r_key = NULL;
return gpg_error (GPG_ERR_BAD_PASSPHRASE);
}
return 0;
}
/* Callback function to try the unprotection from the passphrase query
code. */
static gpg_error_t
try_do_unprotect_cb (struct pin_entry_info_s *pi)
{
gpg_error_t err;
struct try_do_unprotect_arg_s *arg = pi->check_cb_arg;
err = do_unprotect (pi->pin,
arg->is_v4? 4:3,
arg->pubkey_algo, arg->is_protected,
arg->curve,
arg->skey, arg->skeysize,
arg->protect_algo, arg->iv, arg->ivlen,
arg->s2k_mode, arg->s2k_algo,
arg->s2k_salt, arg->s2k_count,
arg->desired_csum, arg->r_key);
/* SKEY may be modified now, thus we need to re-compute SKEYIDX. */
for (arg->skeyidx = 0; (arg->skeyidx < arg->skeysize
&& arg->skey[arg->skeyidx]); arg->skeyidx++)
;
return err;
}
/* See convert_from_openpgp for the core of the description. This
function adds an optional PASSPHRASE argument and uses this to
silently decrypt the key; CACHE_NONCE and R_PASSPHRASE must both be
NULL in this mode. */
static gpg_error_t
convert_from_openpgp_main (ctrl_t ctrl, gcry_sexp_t s_pgp, int dontcare_exist,
unsigned char *grip, const char *prompt,
const char *cache_nonce, const char *passphrase,
unsigned char **r_key, char **r_passphrase)
{
gpg_error_t err;
int unattended;
int from_native;
gcry_sexp_t top_list;
gcry_sexp_t list = NULL;
const char *value;
size_t valuelen;
char *string;
int idx;
int is_v4, is_protected;
int pubkey_algo;
int protect_algo = 0;
char iv[16];
int ivlen = 0;
int s2k_mode = 0;
int s2k_algo = 0;
byte s2k_salt[8];
u32 s2k_count = 0;
size_t npkey, nskey;
gcry_mpi_t skey[10]; /* We support up to 9 parameters. */
char *curve = NULL;
u16 desired_csum;
int skeyidx = 0;
gcry_sexp_t s_skey = NULL;
*r_key = NULL;
if (r_passphrase)
*r_passphrase = NULL;
unattended = !r_passphrase;
from_native = (!cache_nonce && passphrase && !r_passphrase);
top_list = gcry_sexp_find_token (s_pgp, "openpgp-private-key", 0);
if (!top_list)
goto bad_seckey;
list = gcry_sexp_find_token (top_list, "version", 0);
if (!list)
goto bad_seckey;
value = gcry_sexp_nth_data (list, 1, &valuelen);
if (!value || valuelen != 1 || !(value[0] == '3' || value[0] == '4'))
goto bad_seckey;
is_v4 = (value[0] == '4');
gcry_sexp_release (list);
list = gcry_sexp_find_token (top_list, "protection", 0);
if (!list)
goto bad_seckey;
value = gcry_sexp_nth_data (list, 1, &valuelen);
if (!value)
goto bad_seckey;
if (valuelen == 4 && !memcmp (value, "sha1", 4))
is_protected = 2;
else if (valuelen == 3 && !memcmp (value, "sum", 3))
is_protected = 1;
else if (valuelen == 4 && !memcmp (value, "none", 4))
is_protected = 0;
else
goto bad_seckey;
if (is_protected)
{
string = gcry_sexp_nth_string (list, 2);
if (!string)
goto bad_seckey;
protect_algo = gcry_cipher_map_name (string);
xfree (string);
value = gcry_sexp_nth_data (list, 3, &valuelen);
if (!value || !valuelen || valuelen > sizeof iv)
goto bad_seckey;
memcpy (iv, value, valuelen);
ivlen = valuelen;
string = gcry_sexp_nth_string (list, 4);
if (!string)
goto bad_seckey;
s2k_mode = strtol (string, NULL, 10);
xfree (string);
string = gcry_sexp_nth_string (list, 5);
if (!string)
goto bad_seckey;
s2k_algo = gcry_md_map_name (string);
xfree (string);
value = gcry_sexp_nth_data (list, 6, &valuelen);
if (!value || !valuelen || valuelen > sizeof s2k_salt)
goto bad_seckey;
memcpy (s2k_salt, value, valuelen);
string = gcry_sexp_nth_string (list, 7);
if (!string)
goto bad_seckey;
s2k_count = strtoul (string, NULL, 10);
xfree (string);
}
gcry_sexp_release (list);
list = gcry_sexp_find_token (top_list, "algo", 0);
if (!list)
goto bad_seckey;
string = gcry_sexp_nth_string (list, 1);
if (!string)
goto bad_seckey;
pubkey_algo = gcry_pk_map_name (string);
xfree (string);
get_npkey_nskey (pubkey_algo, &npkey, &nskey);
if (!npkey || !nskey || npkey >= nskey)
goto bad_seckey;
if (npkey == 1) /* This is ECC */
{
gcry_sexp_release (list);
list = gcry_sexp_find_token (top_list, "curve", 0);
if (!list)
goto bad_seckey;
curve = gcry_sexp_nth_string (list, 1);
if (!curve)
goto bad_seckey;
}
gcry_sexp_release (list);
list = gcry_sexp_find_token (top_list, "skey", 0);
if (!list)
goto bad_seckey;
for (idx=0;;)
{
int is_enc;
value = gcry_sexp_nth_data (list, ++idx, &valuelen);
if (!value && skeyidx >= npkey)
break; /* Ready. */
/* Check for too many parameters. Note that depending on the
protection mode and version number we may see less than NSKEY
(but at least NPKEY+1) parameters. */
if (idx >= 2*nskey)
goto bad_seckey;
if (skeyidx >= DIM (skey)-1)
goto bad_seckey;
if (!value || valuelen != 1 || !(value[0] == '_' || value[0] == 'e'))
goto bad_seckey;
is_enc = (value[0] == 'e');
value = gcry_sexp_nth_data (list, ++idx, &valuelen);
if (!value || !valuelen)
goto bad_seckey;
if (is_enc)
{
/* Encrypted parameters need to be stored as opaque. */
skey[skeyidx] = gcry_mpi_set_opaque_copy (NULL, value, valuelen*8);
if (!skey[skeyidx])
goto outofmem;
gcry_mpi_set_flag (skey[skeyidx], GCRYMPI_FLAG_USER1);
}
else
{
if (gcry_mpi_scan (skey + skeyidx, GCRYMPI_FMT_STD,
value, valuelen, NULL))
goto bad_seckey;
}
skeyidx++;
}
skey[skeyidx++] = NULL;
gcry_sexp_release (list);
list = gcry_sexp_find_token (top_list, "csum", 0);
if (list)
{
string = gcry_sexp_nth_string (list, 1);
if (!string)
goto bad_seckey;
desired_csum = strtoul (string, NULL, 10);
xfree (string);
}
else
desired_csum = 0;
gcry_sexp_release (list); list = NULL;
gcry_sexp_release (top_list); top_list = NULL;
#if 0
log_debug ("XXX is_v4=%d\n", is_v4);
log_debug ("XXX pubkey_algo=%d\n", pubkey_algo);
log_debug ("XXX is_protected=%d\n", is_protected);
log_debug ("XXX protect_algo=%d\n", protect_algo);
log_printhex (iv, ivlen, "XXX iv");
log_debug ("XXX ivlen=%d\n", ivlen);
log_debug ("XXX s2k_mode=%d\n", s2k_mode);
log_debug ("XXX s2k_algo=%d\n", s2k_algo);
log_printhex (s2k_salt, sizeof s2k_salt, "XXX s2k_salt");
log_debug ("XXX s2k_count=%lu\n", (unsigned long)s2k_count);
log_debug ("XXX curve='%s'\n", curve);
for (idx=0; skey[idx]; idx++)
gcry_log_debugmpi (gcry_mpi_get_flag (skey[idx], GCRYMPI_FLAG_USER1)
? "skey(e)" : "skey(_)", skey[idx]);
#endif /*0*/
err = get_keygrip (pubkey_algo, curve, skey, grip);
if (err)
goto leave;
if (!dontcare_exist && !from_native && !agent_key_available (grip))
{
err = gpg_error (GPG_ERR_EEXIST);
goto leave;
}
if (unattended && !from_native)
{
err = prepare_unprotect (pubkey_algo, skey, DIM(skey), s2k_mode,
NULL, NULL, NULL);
if (err)
goto leave;
err = convert_transfer_key (&s_skey, pubkey_algo, skey, curve, s_pgp);
if (err)
goto leave;
}
else
{
struct pin_entry_info_s *pi;
struct try_do_unprotect_arg_s pi_arg;
pi = xtrycalloc_secure (1, sizeof (*pi) + MAX_PASSPHRASE_LEN + 1);
if (!pi)
return gpg_error_from_syserror ();
pi->max_length = MAX_PASSPHRASE_LEN + 1;
pi->min_digits = 0; /* We want a real passphrase. */
pi->max_digits = 16;
pi->max_tries = 3;
pi->check_cb = try_do_unprotect_cb;
pi->check_cb_arg = &pi_arg;
pi_arg.is_v4 = is_v4;
pi_arg.is_protected = is_protected;
pi_arg.pubkey_algo = pubkey_algo;
pi_arg.curve = curve;
pi_arg.protect_algo = protect_algo;
pi_arg.iv = iv;
pi_arg.ivlen = ivlen;
pi_arg.s2k_mode = s2k_mode;
pi_arg.s2k_algo = s2k_algo;
pi_arg.s2k_salt = s2k_salt;
pi_arg.s2k_count = s2k_count;
pi_arg.desired_csum = desired_csum;
pi_arg.skey = skey;
pi_arg.skeysize = DIM (skey);
pi_arg.skeyidx = skeyidx;
pi_arg.r_key = &s_skey;
err = gpg_error (GPG_ERR_BAD_PASSPHRASE);
if (!is_protected)
{
err = try_do_unprotect_cb (pi);
}
else if (cache_nonce)
{
char *cache_value;
cache_value = agent_get_cache (ctrl, cache_nonce, CACHE_MODE_NONCE);
if (cache_value)
{
if (strlen (cache_value) < pi->max_length)
strcpy (pi->pin, cache_value);
xfree (cache_value);
}
if (*pi->pin)
err = try_do_unprotect_cb (pi);
}
else if (from_native)
{
if (strlen (passphrase) < pi->max_length)
strcpy (pi->pin, passphrase);
err = try_do_unprotect_cb (pi);
}
if (gpg_err_code (err) == GPG_ERR_BAD_PASSPHRASE && !from_native)
err = agent_askpin (ctrl, prompt, NULL, NULL, pi, NULL, 0);
skeyidx = pi_arg.skeyidx;
if (!err && r_passphrase && is_protected)
{
*r_passphrase = xtrystrdup (pi->pin);
if (!*r_passphrase)
err = gpg_error_from_syserror ();
}
xfree (pi);
if (err)
goto leave;
}
/* Save some memory and get rid of the SKEY array now. */
for (idx=0; idx < skeyidx; idx++)
gcry_mpi_release (skey[idx]);
skeyidx = 0;
/* Note that the padding is not required - we use it only because
that function allows us to create the result in secure memory. */
err = make_canon_sexp_pad (s_skey, 1, r_key, NULL);
leave:
xfree (curve);
gcry_sexp_release (s_skey);
gcry_sexp_release (list);
gcry_sexp_release (top_list);
for (idx=0; idx < skeyidx; idx++)
gcry_mpi_release (skey[idx]);
if (err && r_passphrase)
{
xfree (*r_passphrase);
*r_passphrase = NULL;
}
return err;
bad_seckey:
err = gpg_error (GPG_ERR_BAD_SECKEY);
goto leave;
outofmem:
err = gpg_error (GPG_ERR_ENOMEM);
goto leave;
}
/* Convert an OpenPGP transfer key into our internal format. Before
asking for a passphrase we check whether the key already exists in
our key storage. S_PGP is the OpenPGP key in transfer format. If
CACHE_NONCE is given the passphrase will be looked up in the cache.
On success R_KEY will receive a canonical encoded S-expression with
the unprotected key in our internal format; the caller needs to
release that memory. The passphrase used to decrypt the OpenPGP
key will be returned at R_PASSPHRASE; the caller must release this
passphrase. If R_PASSPHRASE is NULL the unattended conversion mode
will be used which uses the openpgp-native protection format for
the key. The keygrip will be stored at the 20 byte buffer pointed
to by GRIP. On error NULL is stored at all return arguments. */
gpg_error_t
convert_from_openpgp (ctrl_t ctrl, gcry_sexp_t s_pgp, int dontcare_exist,
unsigned char *grip, const char *prompt,
const char *cache_nonce,
unsigned char **r_key, char **r_passphrase)
{
return convert_from_openpgp_main (ctrl, s_pgp, dontcare_exist, grip, prompt,
cache_nonce, NULL,
r_key, r_passphrase);
}
/* This function is called by agent_unprotect to re-protect an
openpgp-native protected private-key into the standard private-key
protection format. */
gpg_error_t
convert_from_openpgp_native (ctrl_t ctrl,
gcry_sexp_t s_pgp, const char *passphrase,
unsigned char **r_key)
{
gpg_error_t err;
unsigned char grip[20];
if (!passphrase)
return gpg_error (GPG_ERR_INTERNAL);
err = convert_from_openpgp_main (ctrl, s_pgp, 0, grip, NULL,
NULL, passphrase,
r_key, NULL);
/* On success try to re-write the key. */
if (!err)
{
if (*passphrase)
{
unsigned char *protectedkey = NULL;
size_t protectedkeylen;
if (!agent_protect (*r_key, passphrase,
&protectedkey, &protectedkeylen,
ctrl->s2k_count, -1))
- agent_write_private_key (grip, protectedkey, protectedkeylen, 1);
+ agent_write_private_key (grip, protectedkey, protectedkeylen, 1,
+ NULL, NULL);
xfree (protectedkey);
}
else
{
/* Empty passphrase: write key without protection. */
agent_write_private_key (grip,
*r_key,
gcry_sexp_canon_len (*r_key, 0, NULL,NULL),
- 1);
+ 1, NULL, NULL);
}
}
return err;
}
/* Given an ARRAY of mpis with the key parameters, protect the secret
parameters in that array and replace them by one opaque encoded
mpi. NPKEY is the number of public key parameters and NSKEY is
the number of secret key parameters (including the public ones).
On success the array will have NPKEY+1 elements. */
static gpg_error_t
apply_protection (gcry_mpi_t *array, int npkey, int nskey,
const char *passphrase,
int protect_algo, void *protect_iv, size_t protect_ivlen,
int s2k_mode, int s2k_algo, byte *s2k_salt, u32 s2k_count)
{
gpg_error_t err;
int i, j;
gcry_cipher_hd_t cipherhd;
unsigned char *bufarr[10];
size_t narr[10];
unsigned int nbits[10];
int ndata;
unsigned char *p, *data;
- assert (npkey < nskey);
- assert (nskey < DIM (bufarr));
+ log_assert (npkey < nskey);
+ log_assert (nskey < DIM (bufarr));
/* Collect only the secret key parameters into BUFARR et al and
compute the required size of the data buffer. */
ndata = 20; /* Space for the SHA-1 checksum. */
for (i = npkey, j = 0; i < nskey; i++, j++ )
{
err = gcry_mpi_aprint (GCRYMPI_FMT_USG, bufarr+j, narr+j, array[i]);
if (err)
{
for (i = 0; i < j; i++)
xfree (bufarr[i]);
return err;
}
nbits[j] = gcry_mpi_get_nbits (array[i]);
ndata += 2 + narr[j];
}
/* Allocate data buffer and stuff it with the secret key parameters. */
data = xtrymalloc_secure (ndata);
if (!data)
{
err = gpg_error_from_syserror ();
for (i = 0; i < (nskey-npkey); i++ )
xfree (bufarr[i]);
return err;
}
p = data;
for (i = 0; i < (nskey-npkey); i++ )
{
*p++ = nbits[i] >> 8 ;
*p++ = nbits[i];
memcpy (p, bufarr[i], narr[i]);
p += narr[i];
xfree (bufarr[i]);
bufarr[i] = NULL;
}
- assert (p == data + ndata - 20);
+ log_assert (p == data + ndata - 20);
/* Append a hash of the secret key parameters. */
gcry_md_hash_buffer (GCRY_MD_SHA1, p, data, ndata - 20);
/* Encrypt it. */
err = gcry_cipher_open (&cipherhd, protect_algo,
GCRY_CIPHER_MODE_CFB, GCRY_CIPHER_SECURE);
if (!err)
err = hash_passphrase_and_set_key (passphrase, cipherhd, protect_algo,
s2k_mode, s2k_algo, s2k_salt, s2k_count);
if (!err)
err = gcry_cipher_setiv (cipherhd, protect_iv, protect_ivlen);
if (!err)
err = gcry_cipher_encrypt (cipherhd, data, ndata, NULL, 0);
gcry_cipher_close (cipherhd);
if (err)
{
xfree (data);
return err;
}
/* Replace the secret key parameters in the array by one opaque value. */
for (i = npkey; i < nskey; i++ )
{
gcry_mpi_release (array[i]);
array[i] = NULL;
}
array[npkey] = gcry_mpi_set_opaque (NULL, data, ndata*8);
return 0;
}
/*
* Examining S_KEY in S-Expression and extract data.
* When REQ_PRIVATE_KEY_DATA == 1, S_KEY's CAR should be 'private-key',
* but it also allows shadowed or protected versions.
* On success, it returns 0, otherwise error number.
* R_ALGONAME is static string which is no need to free by caller.
* R_NPKEY is pointer to number of public key data.
* R_NSKEY is pointer to number of private key data.
* R_ELEMS is static string which is no need to free by caller.
* ARRAY contains public and private key data.
* ARRAYSIZE is the allocated size of the array for cross-checking.
* R_CURVE is pointer to S-Expression of the curve (can be NULL).
* R_FLAGS is pointer to S-Expression of the flags (can be NULL).
*/
gpg_error_t
extract_private_key (gcry_sexp_t s_key, int req_private_key_data,
const char **r_algoname, int *r_npkey, int *r_nskey,
const char **r_elems,
gcry_mpi_t *array, int arraysize,
gcry_sexp_t *r_curve, gcry_sexp_t *r_flags)
{
gpg_error_t err;
gcry_sexp_t list, l2;
char *name;
const char *algoname, *format;
int npkey, nskey;
gcry_sexp_t curve = NULL;
gcry_sexp_t flags = NULL;
*r_curve = NULL;
*r_flags = NULL;
if (!req_private_key_data)
{
list = gcry_sexp_find_token (s_key, "shadowed-private-key", 0 );
if (!list)
list = gcry_sexp_find_token (s_key, "protected-private-key", 0 );
if (!list)
list = gcry_sexp_find_token (s_key, "private-key", 0 );
}
else
list = gcry_sexp_find_token (s_key, "private-key", 0);
if (!list)
{
log_error ("invalid private key format\n");
return gpg_error (GPG_ERR_BAD_SECKEY);
}
l2 = gcry_sexp_cadr (list);
gcry_sexp_release (list);
list = l2;
name = gcry_sexp_nth_string (list, 0);
if (!name)
{
gcry_sexp_release (list);
return gpg_error (GPG_ERR_INV_OBJ); /* Invalid structure of object. */
}
if (arraysize < 7)
BUG ();
/* Map NAME to a name as used by Libgcrypt. We do not use the
Libgcrypt function here because we need a lowercase name and
require special treatment for some algorithms. */
strlwr (name);
if (!strcmp (name, "rsa"))
{
algoname = "rsa";
format = "ned?p?q?u?";
npkey = 2;
nskey = 6;
err = gcry_sexp_extract_param (list, NULL, format,
array+0, array+1, array+2, array+3,
array+4, array+5, NULL);
}
else if (!strcmp (name, "elg"))
{
algoname = "elg";
format = "pgyx?";
npkey = 3;
nskey = 4;
err = gcry_sexp_extract_param (list, NULL, format,
array+0, array+1, array+2, array+3,
NULL);
}
else if (!strcmp (name, "dsa"))
{
algoname = "dsa";
format = "pqgyx?";
npkey = 4;
nskey = 5;
err = gcry_sexp_extract_param (list, NULL, format,
array+0, array+1, array+2, array+3,
array+4, NULL);
}
else if (!strcmp (name, "ecc") || !strcmp (name, "ecdsa"))
{
algoname = "ecc";
format = "qd?";
npkey = 1;
nskey = 2;
curve = gcry_sexp_find_token (list, "curve", 0);
flags = gcry_sexp_find_token (list, "flags", 0);
err = gcry_sexp_extract_param (list, NULL, format,
array+0, array+1, NULL);
}
else
{
err = gpg_error (GPG_ERR_PUBKEY_ALGO);
}
xfree (name);
gcry_sexp_release (list);
if (err)
{
gcry_sexp_release (curve);
gcry_sexp_release (flags);
return err;
}
else
{
*r_algoname = algoname;
if (r_elems)
*r_elems = format;
*r_npkey = npkey;
if (r_nskey)
*r_nskey = nskey;
*r_curve = curve;
*r_flags = flags;
return 0;
}
}
/* Convert our key S_KEY into an OpenPGP key transfer format. On
success a canonical encoded S-expression is stored at R_TRANSFERKEY
and its length at R_TRANSFERKEYLEN; this S-expression is also
padded to a multiple of 64 bits. */
gpg_error_t
convert_to_openpgp (ctrl_t ctrl, gcry_sexp_t s_key, const char *passphrase,
unsigned char **r_transferkey, size_t *r_transferkeylen)
{
gpg_error_t err;
const char *algoname;
int npkey, nskey;
gcry_mpi_t array[10];
gcry_sexp_t curve = NULL;
gcry_sexp_t flags = NULL;
char protect_iv[16];
char salt[8];
unsigned long s2k_count;
int i, j;
(void)ctrl;
*r_transferkey = NULL;
for (i=0; i < DIM (array); i++)
array[i] = NULL;
err = extract_private_key (s_key, 1, &algoname, &npkey, &nskey, NULL,
array, DIM (array), &curve, &flags);
if (err)
return err;
gcry_create_nonce (protect_iv, sizeof protect_iv);
gcry_create_nonce (salt, sizeof salt);
/* We need to use the encoded S2k count. It is not possible to
encode it after it has been used because the encoding procedure
may round the value up. */
s2k_count = get_standard_s2k_count_rfc4880 ();
err = apply_protection (array, npkey, nskey, passphrase,
GCRY_CIPHER_AES, protect_iv, sizeof protect_iv,
3, GCRY_MD_SHA1, salt, s2k_count);
/* Turn it into the transfer key S-expression. Note that we always
return a protected key. */
if (!err)
{
char countbuf[35];
membuf_t mbuf;
void *format_args[10+2];
gcry_sexp_t tmpkey;
gcry_sexp_t tmpsexp = NULL;
snprintf (countbuf, sizeof countbuf, "%lu", s2k_count);
init_membuf (&mbuf, 50);
put_membuf_str (&mbuf, "(skey");
for (i=j=0; i < npkey; i++)
{
put_membuf_str (&mbuf, " _ %m");
format_args[j++] = array + i;
}
put_membuf_str (&mbuf, " e %m");
format_args[j++] = array + npkey;
put_membuf_str (&mbuf, ")\n");
put_membuf (&mbuf, "", 1);
tmpkey = NULL;
{
char *format = get_membuf (&mbuf, NULL);
if (!format)
err = gpg_error_from_syserror ();
else
err = gcry_sexp_build_array (&tmpkey, NULL, format, format_args);
xfree (format);
}
if (!err)
err = gcry_sexp_build (&tmpsexp, NULL,
"(openpgp-private-key\n"
" (version 1:4)\n"
" (algo %s)\n"
" %S%S\n"
" (protection sha1 aes %b 1:3 sha1 %b %s))\n",
algoname,
curve,
tmpkey,
(int)sizeof protect_iv, protect_iv,
(int)sizeof salt, salt,
countbuf);
gcry_sexp_release (tmpkey);
if (!err)
err = make_canon_sexp_pad (tmpsexp, 0, r_transferkey, r_transferkeylen);
gcry_sexp_release (tmpsexp);
}
for (i=0; i < DIM (array); i++)
gcry_mpi_release (array[i]);
gcry_sexp_release (curve);
gcry_sexp_release (flags);
return err;
}
diff --git a/agent/divert-scd.c b/agent/divert-scd.c
index e89c74a19..cfa2347c7 100644
--- a/agent/divert-scd.c
+++ b/agent/divert-scd.c
@@ -1,613 +1,636 @@
/* divert-scd.c - divert operations to the scdaemon
* Copyright (C) 2002, 2003, 2009 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <assert.h>
#include <unistd.h>
#include <sys/stat.h>
#include "agent.h"
#include "../common/i18n.h"
#include "../common/sexp-parse.h"
-static int
-ask_for_card (ctrl_t ctrl, const unsigned char *shadow_info, char **r_kid)
+static gpg_error_t
+ask_for_card (ctrl_t ctrl, const unsigned char *shadow_info,
+ const unsigned char *grip, char **r_kid)
{
- int rc, i;
+ int i;
char *serialno;
int no_card = 0;
char *desc;
char *want_sn, *want_kid, *want_sn_disp;
int len;
+ struct card_key_info_s *keyinfo;
+ gpg_error_t err;
+ char hexgrip[41];
*r_kid = NULL;
- rc = parse_shadow_info (shadow_info, &want_sn, &want_kid, NULL);
- if (rc)
- return rc;
+ /* Scan device(s), and check if key for GRIP is available. */
+ err = agent_card_serialno (ctrl, &serialno, NULL);
+ if (!err)
+ {
+ xfree (serialno);
+ bin2hex (grip, 20, hexgrip);
+ err = agent_card_keyinfo (ctrl, hexgrip, &keyinfo);
+ if (!err)
+ {
+ /* Key for GRIP found, use it directly. */
+ agent_card_free_keyinfo (keyinfo);
+ if ((*r_kid = xtrystrdup (hexgrip)))
+ return 0;
+ else
+ return gpg_error_from_syserror ();
+ }
+ }
+
+ err = parse_shadow_info (shadow_info, &want_sn, &want_kid, NULL);
+ if (err)
+ return err;
want_sn_disp = xtrystrdup (want_sn);
if (!want_sn_disp)
{
- rc = gpg_error_from_syserror ();
+ err = gpg_error_from_syserror ();
xfree (want_sn);
xfree (want_kid);
- return rc;
+ return err;
}
len = strlen (want_sn_disp);
if (len == 32 && !strncmp (want_sn_disp, "D27600012401", 12))
{
/* This is an OpenPGP card - reformat */
memmove (want_sn_disp, want_sn_disp+16, 4);
want_sn_disp[4] = ' ';
memmove (want_sn_disp+5, want_sn_disp+20, 8);
want_sn_disp[13] = 0;
}
else if (len == 20 && want_sn_disp[19] == '0')
{
/* We assume that a 20 byte serial number is a standard one
* which has the property to have a zero in the last nibble (Due
* to BCD representation). We don't display this '0' because it
* may confuse the user. */
want_sn_disp[19] = 0;
}
for (;;)
{
- rc = agent_card_serialno (ctrl, &serialno, want_sn);
- if (!rc)
+ err = agent_card_serialno (ctrl, &serialno, want_sn);
+ if (!err)
{
log_debug ("detected card with S/N %s\n", serialno);
i = strcmp (serialno, want_sn);
xfree (serialno);
serialno = NULL;
if (!i)
{
xfree (want_sn_disp);
xfree (want_sn);
*r_kid = want_kid;
return 0; /* yes, we have the correct card */
}
}
- else if (gpg_err_code (rc) == GPG_ERR_ENODEV)
+ else if (gpg_err_code (err) == GPG_ERR_ENODEV)
{
log_debug ("no device present\n");
- rc = 0;
+ err = 0;
no_card = 1;
}
- else if (gpg_err_code (rc) == GPG_ERR_CARD_NOT_PRESENT)
+ else if (gpg_err_code (err) == GPG_ERR_CARD_NOT_PRESENT)
{
log_debug ("no card present\n");
- rc = 0;
+ err = 0;
no_card = 2;
}
else
{
- log_error ("error accessing card: %s\n", gpg_strerror (rc));
+ log_error ("error accessing card: %s\n", gpg_strerror (err));
}
- if (!rc)
+ if (!err)
{
if (asprintf (&desc,
"%s:%%0A%%0A"
" %s",
no_card
? L_("Please insert the card with serial number")
: L_("Please remove the current card and "
"insert the one with serial number"),
want_sn_disp) < 0)
{
- rc = out_of_core ();
+ err = out_of_core ();
}
else
{
- rc = agent_get_confirmation (ctrl, desc, NULL, NULL, 0);
+ err = agent_get_confirmation (ctrl, desc, NULL, NULL, 0);
if (ctrl->pinentry_mode == PINENTRY_MODE_LOOPBACK &&
- gpg_err_code (rc) == GPG_ERR_NO_PIN_ENTRY)
- rc = gpg_error (GPG_ERR_CARD_NOT_PRESENT);
+ gpg_err_code (err) == GPG_ERR_NO_PIN_ENTRY)
+ err = gpg_error (GPG_ERR_CARD_NOT_PRESENT);
xfree (desc);
}
}
- if (rc)
+ if (err)
{
xfree (want_sn_disp);
xfree (want_sn);
xfree (want_kid);
- return rc;
+ return err;
}
}
}
/* Put the DIGEST into an DER encoded container and return it in R_VAL. */
static int
encode_md_for_card (const unsigned char *digest, size_t digestlen, int algo,
unsigned char **r_val, size_t *r_len)
{
unsigned char *frame;
unsigned char asn[100];
size_t asnlen;
*r_val = NULL;
*r_len = 0;
asnlen = DIM(asn);
if (!algo || gcry_md_test_algo (algo))
return gpg_error (GPG_ERR_DIGEST_ALGO);
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);
}
frame = xtrymalloc (asnlen + digestlen);
if (!frame)
return out_of_core ();
memcpy (frame, asn, asnlen);
memcpy (frame+asnlen, digest, digestlen);
if (DBG_CRYPTO)
log_printhex (frame, asnlen+digestlen, "encoded hash:");
*r_val = frame;
*r_len = asnlen+digestlen;
return 0;
}
/* Return true if STRING ends in "%0A". */
static int
has_percent0A_suffix (const char *string)
{
size_t n;
return (string
&& (n = strlen (string)) >= 3
&& !strcmp (string + n - 3, "%0A"));
}
/* Callback used to ask for the PIN which should be set into BUF. The
buf has been allocated by the caller and is of size MAXBUF which
includes the terminating null. The function should return an UTF-8
string with the passphrase, the buffer may optionally be padded
with arbitrary characters.
If DESC_TEXT is not NULL it can be used as further information shown
atop of the INFO message.
INFO gets displayed as part of a generic string. However if the
first character of INFO is a vertical bar all up to the next
verical bar are considered flags and only everything after the
second vertical bar gets displayed as the full prompt.
Flags:
'N' = New PIN, this requests a second prompt to repeat the
PIN. If the PIN is not correctly repeated it starts from
all over.
'A' = The PIN is an Admin PIN, SO-PIN or alike.
'P' = The PIN is a PUK (Personal Unblocking Key).
'R' = The PIN is a Reset Code.
Example:
"|AN|Please enter the new security officer's PIN"
The text "Please ..." will get displayed and the flags 'A' and 'N'
are considered.
*/
static int
getpin_cb (void *opaque, const char *desc_text, const char *info,
char *buf, size_t maxbuf)
{
struct pin_entry_info_s *pi;
int rc;
ctrl_t ctrl = opaque;
const char *ends, *s;
int any_flags = 0;
int newpin = 0;
int resetcode = 0;
int is_puk = 0;
const char *again_text = NULL;
const char *prompt = "PIN";
if (buf && maxbuf < 2)
return gpg_error (GPG_ERR_INV_VALUE);
/* Parse the flags. */
if (info && *info =='|' && (ends=strchr (info+1, '|')))
{
for (s=info+1; s < ends; s++)
{
if (*s == 'A')
prompt = L_("Admin PIN");
else if (*s == 'P')
{
/* TRANSLATORS: A PUK is the Personal Unblocking Code
used to unblock a PIN. */
prompt = L_("PUK");
is_puk = 1;
}
else if (*s == 'N')
newpin = 1;
else if (*s == 'R')
{
prompt = L_("Reset Code");
resetcode = 1;
}
}
info = ends+1;
any_flags = 1;
}
else if (info && *info == '|')
log_debug ("pin_cb called without proper PIN info hack\n");
/* If BUF has been passed as NULL, we are in pinpad mode: The
callback opens the popup and immediately returns. */
if (!buf)
{
if (maxbuf == 0) /* Close the pinentry. */
{
agent_popup_message_stop (ctrl);
rc = 0;
}
else if (maxbuf == 1) /* Open the pinentry. */
{
if (info)
{
char *desc;
const char *desc2;
if (!strcmp (info, "--ack"))
{
desc2 = L_("Push ACK button on card/token.");
if (desc_text)
{
desc = strconcat (desc_text,
has_percent0A_suffix (desc_text)
? "%0A" : "%0A%0A",
desc2, NULL);
desc2 = NULL;
}
else
desc = NULL;
}
else
{
desc2 = NULL;
if (desc_text)
desc = strconcat (desc_text,
has_percent0A_suffix (desc_text)
? "%0A" : "%0A%0A",
info, "%0A%0A",
L_("Use the reader's pinpad for input."),
NULL);
else
desc = strconcat (info, "%0A%0A",
L_("Use the reader's pinpad for input."),
NULL);
}
if (!desc2 && !desc)
rc = gpg_error_from_syserror ();
else
{
rc = agent_popup_message_start (ctrl,
desc2? desc2:desc, NULL);
xfree (desc);
}
}
else
rc = agent_popup_message_start (ctrl, desc_text, NULL);
}
else
rc = gpg_error (GPG_ERR_INV_VALUE);
return rc;
}
/* FIXME: keep PI and TRIES in OPAQUE. Frankly this is a whole
mess because we should call the card's verify function from the
pinentry check pin CB. */
again:
pi = gcry_calloc_secure (1, sizeof (*pi) + maxbuf + 10);
if (!pi)
return gpg_error_from_syserror ();
pi->max_length = maxbuf-1;
pi->min_digits = 0; /* we want a real passphrase */
pi->max_digits = 16;
pi->max_tries = 3;
if (any_flags)
{
{
char *desc2;
if (desc_text)
desc2 = strconcat (desc_text,
has_percent0A_suffix (desc_text)
? "%0A" : "%0A%0A",
info, NULL);
else
desc2 = NULL;
rc = agent_askpin (ctrl, desc2? desc2 : info,
prompt, again_text, pi, NULL, 0);
xfree (desc2);
}
again_text = NULL;
if (!rc && newpin)
{
struct pin_entry_info_s *pi2;
pi2 = gcry_calloc_secure (1, sizeof (*pi) + maxbuf + 10);
if (!pi2)
{
rc = gpg_error_from_syserror ();
xfree (pi);
return rc;
}
pi2->max_length = maxbuf-1;
pi2->min_digits = 0;
pi2->max_digits = 16;
pi2->max_tries = 1;
rc = agent_askpin (ctrl,
(resetcode?
L_("Repeat this Reset Code"):
is_puk?
L_("Repeat this PUK"):
L_("Repeat this PIN")),
prompt, NULL, pi2, NULL, 0);
if (!rc && strcmp (pi->pin, pi2->pin))
{
again_text = (resetcode?
L_("Reset Code not correctly repeated; try again"):
is_puk?
L_("PUK not correctly repeated; try again"):
L_("PIN not correctly repeated; try again"));
xfree (pi2);
xfree (pi);
goto again;
}
xfree (pi2);
}
}
else
{
char *desc, *desc2;
if ( asprintf (&desc,
L_("Please enter the PIN%s%s%s to unlock the card"),
info? " (":"",
info? info:"",
info? ")":"") < 0)
desc = NULL;
if (desc_text)
desc2 = strconcat (desc_text,
has_percent0A_suffix (desc_text)
? "%0A" : "%0A%0A",
desc, NULL);
else
desc2 = NULL;
rc = agent_askpin (ctrl, desc2? desc2 : desc? desc : info,
prompt, NULL, pi, NULL, 0);
xfree (desc2);
xfree (desc);
}
if (!rc)
{
strncpy (buf, pi->pin, maxbuf-1);
buf[maxbuf-1] = 0;
}
xfree (pi);
return rc;
}
/* This function is used when a sign operation has been diverted to a
* smartcard. DESC_TEXT is the original text for a prompt has send by
* gpg to gpg-agent.
*
* FIXME: Explain the other args. */
int
-divert_pksign (ctrl_t ctrl, const char *desc_text,
+divert_pksign (ctrl_t ctrl, const char *desc_text, const unsigned char *grip,
const unsigned char *digest, size_t digestlen, int algo,
const unsigned char *shadow_info, unsigned char **r_sig,
size_t *r_siglen)
{
int rc;
char *kid;
size_t siglen;
unsigned char *sigval = NULL;
(void)desc_text;
- rc = ask_for_card (ctrl, shadow_info, &kid);
+ rc = ask_for_card (ctrl, shadow_info, grip, &kid);
if (rc)
return rc;
if (algo == MD_USER_TLS_MD5SHA1)
{
int save = ctrl->use_auth_call;
ctrl->use_auth_call = 1;
rc = agent_card_pksign (ctrl, kid, getpin_cb, ctrl, NULL,
algo, digest, digestlen, &sigval, &siglen);
ctrl->use_auth_call = save;
}
else
{
unsigned char *data;
size_t ndata;
rc = encode_md_for_card (digest, digestlen, algo, &data, &ndata);
if (!rc)
{
rc = agent_card_pksign (ctrl, kid, getpin_cb, ctrl, NULL,
algo, data, ndata, &sigval, &siglen);
xfree (data);
}
}
if (!rc)
{
*r_sig = sigval;
*r_siglen = siglen;
}
xfree (kid);
return rc;
}
/* Decrypt the value given asn an S-expression in CIPHER using the
key identified by SHADOW_INFO and return the plaintext in an
allocated buffer in R_BUF. The padding information is stored at
R_PADDING with -1 for not known. */
int
divert_pkdecrypt (ctrl_t ctrl, const char *desc_text,
+ const unsigned char *grip,
const unsigned char *cipher,
const unsigned char *shadow_info,
char **r_buf, size_t *r_len, int *r_padding)
{
int rc;
char *kid;
const unsigned char *s;
size_t n;
int depth;
const unsigned char *ciphertext;
size_t ciphertextlen;
char *plaintext;
size_t plaintextlen;
(void)desc_text;
*r_padding = -1;
s = cipher;
if (*s != '(')
return gpg_error (GPG_ERR_INV_SEXP);
s++;
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
if (!smatch (&s, n, "enc-val"))
return gpg_error (GPG_ERR_UNKNOWN_SEXP);
if (*s != '(')
return gpg_error (GPG_ERR_UNKNOWN_SEXP);
s++;
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
/* First check whether we have a flags parameter and skip it. */
if (smatch (&s, n, "flags"))
{
depth = 1;
if (sskip (&s, &depth) || depth)
return gpg_error (GPG_ERR_INV_SEXP);
if (*s != '(')
return gpg_error (GPG_ERR_INV_SEXP);
s++;
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
}
if (smatch (&s, n, "rsa"))
{
if (*s != '(')
return gpg_error (GPG_ERR_UNKNOWN_SEXP);
s++;
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
if (!smatch (&s, n, "a"))
return gpg_error (GPG_ERR_UNKNOWN_SEXP);
n = snext (&s);
}
else if (smatch (&s, n, "ecdh"))
{
if (*s != '(')
return gpg_error (GPG_ERR_UNKNOWN_SEXP);
s++;
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
if (smatch (&s, n, "s"))
{
n = snext (&s);
s += n;
if (*s++ != ')')
return gpg_error (GPG_ERR_INV_SEXP);
if (*s++ != '(')
return gpg_error (GPG_ERR_UNKNOWN_SEXP);
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
}
if (!smatch (&s, n, "e"))
return gpg_error (GPG_ERR_UNKNOWN_SEXP);
n = snext (&s);
}
else
return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM);
if (!n)
return gpg_error (GPG_ERR_UNKNOWN_SEXP);
ciphertext = s;
ciphertextlen = n;
- rc = ask_for_card (ctrl, shadow_info, &kid);
+ rc = ask_for_card (ctrl, shadow_info, grip, &kid);
if (rc)
return rc;
rc = agent_card_pkdecrypt (ctrl, kid, getpin_cb, ctrl, NULL,
ciphertext, ciphertextlen,
&plaintext, &plaintextlen, r_padding);
if (!rc)
{
*r_buf = plaintext;
*r_len = plaintextlen;
}
xfree (kid);
return rc;
}
gpg_error_t
divert_writekey (ctrl_t ctrl, int force, const char *serialno,
const char *keyref, const char *keydata, size_t keydatalen)
{
return agent_card_writekey (ctrl, force, serialno, keyref,
keydata, keydatalen, getpin_cb, ctrl);
}
int
divert_generic_cmd (ctrl_t ctrl, const char *cmdline, void *assuan_context)
{
return agent_card_scd (ctrl, cmdline, getpin_cb, ctrl, assuan_context);
}
diff --git a/agent/findkey.c b/agent/findkey.c
index 89a18fa9e..370050d8b 100644
--- a/agent/findkey.c
+++ b/agent/findkey.c
@@ -1,1602 +1,1746 @@
/* findkey.c - Locate the secret key
* Copyright (C) 2001, 2002, 2003, 2004, 2005, 2007,
* 2010, 2011 Free Software Foundation, Inc.
- * Copyright (C) 2014 Werner Koch
+ * Copyright (C) 2014, 2019 Werner Koch
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <fcntl.h>
-#include <assert.h>
#include <unistd.h>
#include <sys/stat.h>
-#include <assert.h>
#include <npth.h> /* (we use pth_sleep) */
#include "agent.h"
#include "../common/i18n.h"
#include "../common/ssh-utils.h"
#include "../common/name-value.h"
#ifndef O_BINARY
#define O_BINARY 0
#endif
/* Helper to pass data to the check callback of the unprotect function. */
struct try_unprotect_arg_s
{
ctrl_t ctrl;
const unsigned char *protected_key;
unsigned char *unprotected_key;
int change_required; /* Set by the callback to indicate that the
user should change the passphrase. */
};
+/* Repalce all linefeeds in STRING by "%0A" and return a new malloced
+ * string. May return NULL on memory error. */
+static char *
+linefeed_to_percent0A (const char *string)
+{
+ const char *s;
+ size_t n;
+ char *buf, *p;
+
+ for (n=0, s=string; *s; s++)
+ if (*s == '\n')
+ n += 3;
+ else
+ n++;
+ p = buf = xtrymalloc (n+1);
+ if (!buf)
+ return NULL;
+ for (s=string; *s; s++)
+ if (*s == '\n')
+ {
+ memcpy (p, "%0A", 3);
+ p += 3;
+ }
+ else
+ *p++ = *s;
+ *p = 0;
+ return buf;
+}
+
+
/* Note: Ownership of FNAME and FP are moved to this function. */
static gpg_error_t
write_extended_private_key (char *fname, estream_t fp, int update,
- const void *buf, size_t len)
+ const void *buf, size_t len,
+ const char *serialno, const char *keyref)
{
gpg_error_t err;
nvc_t pk = NULL;
gcry_sexp_t key = NULL;
int remove = 0;
+ char *token = NULL;
if (update)
{
int line;
err = nvc_parse_private_key (&pk, &line, fp);
if (err && gpg_err_code (err) != GPG_ERR_ENOENT)
{
log_error ("error parsing '%s' line %d: %s\n",
fname, line, gpg_strerror (err));
goto leave;
}
}
else
{
pk = nvc_new_private_key ();
if (!pk)
{
err = gpg_error_from_syserror ();
goto leave;
}
}
es_clearerr (fp);
err = gcry_sexp_sscan (&key, NULL, buf, len);
if (err)
goto leave;
err = nvc_set_private_key (pk, key);
if (err)
goto leave;
+ /* If requested write a Token line. */
+ if (serialno && keyref)
+ {
+ nve_t item;
+ const char *s;
+
+ token = strconcat (serialno, " ", keyref, NULL);
+ if (!token)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+
+ /* fixme: the strcmp should compare only the first two strings. */
+ for (item = nvc_lookup (pk, "Token:");
+ item;
+ item = nve_next_value (item, "Token:"))
+ if ((s = nve_value (item)) && !strcmp (s, token))
+ break;
+ if (!item)
+ {
+ /* No token or no token with that value exists. Add a new
+ * one so that keys which have been stored on several cards
+ * are well supported. */
+ err = nvc_add (pk, "Token:", token);
+ if (err)
+ goto leave;
+ }
+ }
+
+
err = es_fseek (fp, 0, SEEK_SET);
if (err)
goto leave;
err = nvc_write (pk, fp);
if (err)
{
log_error ("error writing '%s': %s\n", fname, gpg_strerror (err));
remove = 1;
goto leave;
}
if (ftruncate (es_fileno (fp), es_ftello (fp)))
{
err = gpg_error_from_syserror ();
log_error ("error truncating '%s': %s\n", fname, gpg_strerror (err));
remove = 1;
goto leave;
}
if (es_fclose (fp))
{
err = gpg_error_from_syserror ();
log_error ("error closing '%s': %s\n", fname, gpg_strerror (err));
remove = 1;
goto leave;
}
else
fp = NULL;
bump_key_eventcounter ();
leave:
es_fclose (fp);
if (remove)
gnupg_remove (fname);
xfree (fname);
gcry_sexp_release (key);
nvc_release (pk);
+ xfree (token);
return err;
}
/* Write an S-expression formatted key to our key storage. With FORCE
- passed as true an existing key with the given GRIP will get
- overwritten. */
+ * passed as true an existing key with the given GRIP will get
+ * overwritten. If SERIALNO and KEYREF are give an a Token line is added to
+ * th key if the extended format ist used. */
int
agent_write_private_key (const unsigned char *grip,
- const void *buffer, size_t length, int force)
+ const void *buffer, size_t length, int force,
+ const char *serialno, const char *keyref)
{
char *fname;
estream_t fp;
char hexgrip[40+4+1];
bin2hex (grip, 20, hexgrip);
strcpy (hexgrip+40, ".key");
fname = make_filename (gnupg_homedir (), GNUPG_PRIVATE_KEYS_DIR,
hexgrip, NULL);
/* FIXME: Write to a temp file first so that write failures during
key updates won't lead to a key loss. */
if (!force && !access (fname, F_OK))
{
log_error ("secret key file '%s' already exists\n", fname);
xfree (fname);
return gpg_error (GPG_ERR_EEXIST);
}
fp = es_fopen (fname, force? "rb+,mode=-rw" : "wbx,mode=-rw");
if (!fp)
{
gpg_error_t tmperr = gpg_error_from_syserror ();
if (force && gpg_err_code (tmperr) == GPG_ERR_ENOENT)
{
fp = es_fopen (fname, "wbx,mode=-rw");
if (!fp)
tmperr = gpg_error_from_syserror ();
}
if (!fp)
{
log_error ("can't create '%s': %s\n", fname, gpg_strerror (tmperr));
xfree (fname);
return tmperr;
}
}
else if (force)
{
gpg_error_t rc;
char first;
/* See if an existing key is in extended format. */
if (es_fread (&first, 1, 1, fp) != 1)
{
rc = gpg_error_from_syserror ();
log_error ("error reading first byte from '%s': %s\n",
fname, strerror (errno));
xfree (fname);
es_fclose (fp);
return rc;
}
rc = es_fseek (fp, 0, SEEK_SET);
if (rc)
{
log_error ("error seeking in '%s': %s\n", fname, strerror (errno));
xfree (fname);
es_fclose (fp);
return rc;
}
if (first != '(')
{
/* Key is already in the extended format. */
- return write_extended_private_key (fname, fp, 1, buffer, length);
+ return write_extended_private_key (fname, fp, 1, buffer, length,
+ serialno, keyref);
}
if (first == '(' && opt.enable_extended_key_format)
{
/* Key is in the old format - but we want the extended format. */
- return write_extended_private_key (fname, fp, 0, buffer, length);
+ return write_extended_private_key (fname, fp, 0, buffer, length,
+ serialno, keyref);
}
}
if (opt.enable_extended_key_format)
- return write_extended_private_key (fname, fp, 0, buffer, length);
+ return write_extended_private_key (fname, fp, 0, buffer, length,
+ serialno, keyref);
if (es_fwrite (buffer, length, 1, fp) != 1)
{
gpg_error_t tmperr = gpg_error_from_syserror ();
log_error ("error writing '%s': %s\n", fname, gpg_strerror (tmperr));
es_fclose (fp);
gnupg_remove (fname);
xfree (fname);
return tmperr;
}
/* When force is given, the file might have to be truncated. */
if (force && ftruncate (es_fileno (fp), es_ftello (fp)))
{
gpg_error_t tmperr = gpg_error_from_syserror ();
log_error ("error truncating '%s': %s\n", fname, gpg_strerror (tmperr));
es_fclose (fp);
gnupg_remove (fname);
xfree (fname);
return tmperr;
}
if (es_fclose (fp))
{
gpg_error_t tmperr = gpg_error_from_syserror ();
log_error ("error closing '%s': %s\n", fname, gpg_strerror (tmperr));
gnupg_remove (fname);
xfree (fname);
return tmperr;
}
bump_key_eventcounter ();
xfree (fname);
return 0;
}
/* Callback function to try the unprotection from the passphrase query
code. */
static gpg_error_t
try_unprotect_cb (struct pin_entry_info_s *pi)
{
struct try_unprotect_arg_s *arg = pi->check_cb_arg;
ctrl_t ctrl = arg->ctrl;
size_t dummy;
gpg_error_t err;
gnupg_isotime_t now, protected_at, tmptime;
char *desc = NULL;
- assert (!arg->unprotected_key);
+ log_assert (!arg->unprotected_key);
arg->change_required = 0;
err = agent_unprotect (ctrl, arg->protected_key, pi->pin, protected_at,
&arg->unprotected_key, &dummy);
if (err)
return err;
if (!opt.max_passphrase_days || ctrl->in_passwd)
return 0; /* No regular passphrase change required. */
if (!*protected_at)
{
/* No protection date known - must force passphrase change. */
desc = xtrystrdup (L_("Note: This passphrase has never been changed.%0A"
"Please change it now."));
if (!desc)
return gpg_error_from_syserror ();
}
else
{
gnupg_get_isotime (now);
gnupg_copy_time (tmptime, protected_at);
err = add_days_to_isotime (tmptime, opt.max_passphrase_days);
if (err)
return err;
if (strcmp (now, tmptime) > 0 )
{
/* Passphrase "expired". */
desc = xtryasprintf
(L_("This passphrase has not been changed%%0A"
"since %.4s-%.2s-%.2s. Please change it now."),
protected_at, protected_at+4, protected_at+6);
if (!desc)
return gpg_error_from_syserror ();
}
}
if (desc)
{
/* Change required. */
if (opt.enforce_passphrase_constraints)
{
err = agent_get_confirmation (ctrl, desc,
L_("Change passphrase"), NULL, 0);
if (!err)
arg->change_required = 1;
}
else
{
err = agent_get_confirmation (ctrl, desc,
L_("Change passphrase"),
L_("I'll change it later"), 0);
if (!err)
arg->change_required = 1;
else if (gpg_err_code (err) == GPG_ERR_CANCELED
|| gpg_err_code (err) == GPG_ERR_FULLY_CANCELED)
err = 0;
}
xfree (desc);
}
return err;
}
+/* Return true if the STRING has an %C or %c expando. */
+static int
+has_comment_expando (const char *string)
+{
+ const char *s;
+ int percent = 0;
+
+ if (!string)
+ return 0;
+
+ for (s = string; *s; s++)
+ {
+ if (percent)
+ {
+ if (*s == 'c' || *s == 'C')
+ return 1;
+ percent = 0;
+ }
+ else if (*s == '%')
+ percent = 1;
+ }
+ return 0;
+}
+
+
+
+
/* Modify a Key description, replacing certain special format
characters. List of currently supported replacements:
%% - Replaced by a single %
%c - Replaced by the content of COMMENT.
%C - Same as %c but put into parentheses.
%F - Replaced by an ssh style fingerprint computed from KEY.
The functions returns 0 on success or an error code. On success a
newly allocated string is stored at the address of RESULT.
*/
gpg_error_t
agent_modify_description (const char *in, const char *comment,
const gcry_sexp_t key, char **result)
{
size_t comment_length;
size_t in_len;
size_t out_len;
char *out;
size_t i;
int special, pass;
char *ssh_fpr = NULL;
char *p;
*result = NULL;
if (!comment)
comment = "";
comment_length = strlen (comment);
in_len = strlen (in);
/* First pass calculates the length, second pass does the actual
copying. */
/* FIXME: This can be simplified by using es_fopenmem. */
out = NULL;
out_len = 0;
for (pass=0; pass < 2; pass++)
{
special = 0;
for (i = 0; i < in_len; i++)
{
if (special)
{
special = 0;
switch (in[i])
{
case '%':
if (out)
*out++ = '%';
else
out_len++;
break;
case 'c': /* Comment. */
if (out)
{
memcpy (out, comment, comment_length);
out += comment_length;
}
else
out_len += comment_length;
break;
case 'C': /* Comment. */
if (!comment_length)
;
else if (out)
{
*out++ = '(';
memcpy (out, comment, comment_length);
out += comment_length;
*out++ = ')';
}
else
out_len += comment_length + 2;
break;
case 'F': /* SSH style fingerprint. */
if (!ssh_fpr && key)
ssh_get_fingerprint_string (key, opt.ssh_fingerprint_digest,
&ssh_fpr);
if (ssh_fpr)
{
if (out)
out = stpcpy (out, ssh_fpr);
else
out_len += strlen (ssh_fpr);
}
break;
default: /* Invalid special sequences are kept as they are. */
if (out)
{
*out++ = '%';
*out++ = in[i];
}
else
out_len+=2;
break;
}
}
else if (in[i] == '%')
special = 1;
else
{
if (out)
*out++ = in[i];
else
out_len++;
}
}
if (!pass)
{
*result = out = xtrymalloc (out_len + 1);
if (!out)
{
xfree (ssh_fpr);
return gpg_error_from_syserror ();
}
}
}
*out = 0;
log_assert (*result + out_len == out);
xfree (ssh_fpr);
/* The ssh prompt may sometimes end in
* "...%0A ()"
* The empty parentheses doesn't look very good. We use this hack
* here to remove them as well as the indentation spaces. */
p = *result;
i = strlen (p);
if (i > 2 && !strcmp (p + i - 2, "()"))
{
p += i - 2;
*p-- = 0;
while (p > *result && spacep (p))
*p-- = 0;
}
return 0;
}
/* Unprotect the canconical encoded S-expression key in KEYBUF. GRIP
should be the hex encoded keygrip of that key to be used with the
caching mechanism. DESC_TEXT may be set to override the default
description used for the pinentry. If LOOKUP_TTL is given this
function is used to lookup the default ttl. If R_PASSPHRASE is not
NULL, the function succeeded and the key was protected the used
passphrase (entered or from the cache) is stored there; if not NULL
will be stored. The caller needs to free the returned
passphrase. */
static gpg_error_t
unprotect (ctrl_t ctrl, const char *cache_nonce, const char *desc_text,
unsigned char **keybuf, const unsigned char *grip,
cache_mode_t cache_mode, lookup_ttl_t lookup_ttl,
char **r_passphrase)
{
struct pin_entry_info_s *pi;
struct try_unprotect_arg_s arg;
int rc;
unsigned char *result;
size_t resultlen;
char hexgrip[40+1];
if (r_passphrase)
*r_passphrase = NULL;
bin2hex (grip, 20, hexgrip);
/* Initially try to get it using a cache nonce. */
if (cache_nonce)
{
char *pw;
pw = agent_get_cache (ctrl, cache_nonce, CACHE_MODE_NONCE);
if (pw)
{
rc = agent_unprotect (ctrl, *keybuf, pw, NULL, &result, &resultlen);
if (!rc)
{
if (r_passphrase)
*r_passphrase = pw;
else
xfree (pw);
xfree (*keybuf);
*keybuf = result;
return 0;
}
xfree (pw);
}
}
/* First try to get it from the cache - if there is none or we can't
unprotect it, we fall back to ask the user */
if (cache_mode != CACHE_MODE_IGNORE)
{
char *pw;
retry:
pw = agent_get_cache (ctrl, hexgrip, cache_mode);
if (pw)
{
rc = agent_unprotect (ctrl, *keybuf, pw, NULL, &result, &resultlen);
if (!rc)
{
if (cache_mode == CACHE_MODE_NORMAL)
agent_store_cache_hit (hexgrip);
if (r_passphrase)
*r_passphrase = pw;
else
xfree (pw);
xfree (*keybuf);
*keybuf = result;
return 0;
}
xfree (pw);
}
else if (cache_mode == CACHE_MODE_NORMAL)
{
/* The standard use of GPG keys is to have a signing and an
encryption subkey. Commonly both use the same
passphrase. We try to help the user to enter the
passphrase only once by silently trying the last
correctly entered passphrase. Checking one additional
passphrase should be acceptable; despite the S2K
introduced delays. The assumed workflow is:
1. Read encrypted message in a MUA and thus enter a
passphrase for the encryption subkey.
2. Reply to that mail with an encrypted and signed
mail, thus entering the passphrase for the signing
subkey.
We can often avoid the passphrase entry in the second
step. We do this only in normal mode, so not to
interfere with unrelated cache entries. */
pw = agent_get_cache (ctrl, NULL, cache_mode);
if (pw)
{
rc = agent_unprotect (ctrl, *keybuf, pw, NULL,
&result, &resultlen);
if (!rc)
{
if (r_passphrase)
*r_passphrase = pw;
else
xfree (pw);
xfree (*keybuf);
*keybuf = result;
return 0;
}
xfree (pw);
}
}
/* If the pinentry is currently in use, we wait up to 60 seconds
for it to close and check the cache again. This solves a common
situation where several requests for unprotecting a key have
been made but the user is still entering the passphrase for
the first request. Because all requests to agent_askpin are
serialized they would then pop up one after the other to
request the passphrase - despite that the user has already
entered it and is then available in the cache. This
implementation is not race free but in the worst case the
user has to enter the passphrase only once more. */
if (pinentry_active_p (ctrl, 0))
{
/* Active - wait */
if (!pinentry_active_p (ctrl, 60))
{
/* We need to give the other thread a chance to actually put
it into the cache. */
npth_sleep (1);
goto retry;
}
/* Timeout - better call pinentry now the plain way. */
}
}
pi = gcry_calloc_secure (1, sizeof (*pi) + MAX_PASSPHRASE_LEN + 1);
if (!pi)
return gpg_error_from_syserror ();
pi->max_length = MAX_PASSPHRASE_LEN + 1;
pi->min_digits = 0; /* we want a real passphrase */
pi->max_digits = 16;
pi->max_tries = 3;
pi->check_cb = try_unprotect_cb;
arg.ctrl = ctrl;
arg.protected_key = *keybuf;
arg.unprotected_key = NULL;
arg.change_required = 0;
pi->check_cb_arg = &arg;
rc = agent_askpin (ctrl, desc_text, NULL, NULL, pi, hexgrip, cache_mode);
if (rc)
{
if ((pi->status & PINENTRY_STATUS_PASSWORD_FROM_CACHE))
{
log_error ("Clearing pinentry cache which caused error %s\n",
gpg_strerror (rc));
agent_clear_passphrase (ctrl, hexgrip, cache_mode);
}
}
else
{
- assert (arg.unprotected_key);
+ log_assert (arg.unprotected_key);
if (arg.change_required)
{
/* The callback told as that the user should change their
passphrase. Present the dialog to do. */
size_t canlen, erroff;
gcry_sexp_t s_skey;
- assert (arg.unprotected_key);
+ log_assert (arg.unprotected_key);
canlen = gcry_sexp_canon_len (arg.unprotected_key, 0, NULL, NULL);
rc = gcry_sexp_sscan (&s_skey, &erroff,
(char*)arg.unprotected_key, canlen);
if (rc)
{
log_error ("failed to build S-Exp (off=%u): %s\n",
(unsigned int)erroff, gpg_strerror (rc));
wipememory (arg.unprotected_key, canlen);
xfree (arg.unprotected_key);
xfree (pi);
return rc;
}
rc = agent_protect_and_store (ctrl, s_skey, NULL);
gcry_sexp_release (s_skey);
if (rc)
{
log_error ("changing the passphrase failed: %s\n",
gpg_strerror (rc));
wipememory (arg.unprotected_key, canlen);
xfree (arg.unprotected_key);
xfree (pi);
return rc;
}
}
else
{
/* Passphrase is fine. */
agent_put_cache (ctrl, hexgrip, cache_mode, pi->pin,
lookup_ttl? lookup_ttl (hexgrip) : 0);
agent_store_cache_hit (hexgrip);
if (r_passphrase && *pi->pin)
*r_passphrase = xtrystrdup (pi->pin);
}
xfree (*keybuf);
*keybuf = arg.unprotected_key;
}
xfree (pi);
return rc;
}
/* Read the key identified by GRIP from the private key directory and
- return it as an gcrypt S-expression object in RESULT. On failure
- returns an error code and stores NULL at RESULT. */
+ * return it as an gcrypt S-expression object in RESULT. If R_KEYMETA
+ * is not NULl and the extended key format is used, the meta data
+ * items are stored there. However the "Key:" item is removed from
+ * it. On failure returns an error code and stores NULL at RESULT and
+ * R_KEYMETA. */
static gpg_error_t
-read_key_file (const unsigned char *grip, gcry_sexp_t *result)
+read_key_file (const unsigned char *grip, gcry_sexp_t *result, nvc_t *r_keymeta)
{
gpg_error_t err;
char *fname;
estream_t fp;
struct stat st;
unsigned char *buf;
size_t buflen, erroff;
gcry_sexp_t s_skey;
char hexgrip[40+4+1];
char first;
*result = NULL;
+ if (r_keymeta)
+ *r_keymeta = NULL;
bin2hex (grip, 20, hexgrip);
strcpy (hexgrip+40, ".key");
fname = make_filename (gnupg_homedir (), GNUPG_PRIVATE_KEYS_DIR,
hexgrip, NULL);
fp = es_fopen (fname, "rb");
if (!fp)
{
err = gpg_error_from_syserror ();
if (gpg_err_code (err) != GPG_ERR_ENOENT)
log_error ("can't open '%s': %s\n", fname, gpg_strerror (err));
xfree (fname);
return err;
}
if (es_fread (&first, 1, 1, fp) != 1)
{
err = gpg_error_from_syserror ();
log_error ("error reading first byte from '%s': %s\n",
fname, gpg_strerror (err));
xfree (fname);
es_fclose (fp);
return err;
}
if (es_fseek (fp, 0, SEEK_SET))
{
err = gpg_error_from_syserror ();
log_error ("error seeking in '%s': %s\n", fname, gpg_strerror (err));
xfree (fname);
es_fclose (fp);
return err;
}
if (first != '(')
{
/* Key is in extended format. */
- nvc_t pk;
+ nvc_t pk = NULL;
int line;
err = nvc_parse_private_key (&pk, &line, fp);
es_fclose (fp);
if (err)
log_error ("error parsing '%s' line %d: %s\n",
fname, line, gpg_strerror (err));
else
{
err = nvc_get_private_key (pk, result);
- nvc_release (pk);
if (err)
log_error ("error getting private key from '%s': %s\n",
fname, gpg_strerror (err));
+ else
+ nvc_delete_named (pk, "Key:");
}
+ if (!err && r_keymeta)
+ *r_keymeta = pk;
+ else
+ nvc_release (pk);
xfree (fname);
return err;
}
if (fstat (es_fileno (fp), &st))
{
err = gpg_error_from_syserror ();
log_error ("can't stat '%s': %s\n", fname, gpg_strerror (err));
xfree (fname);
es_fclose (fp);
return err;
}
buflen = st.st_size;
buf = xtrymalloc (buflen+1);
if (!buf)
{
err = gpg_error_from_syserror ();
log_error ("error allocating %zu bytes for '%s': %s\n",
buflen, fname, gpg_strerror (err));
xfree (fname);
es_fclose (fp);
xfree (buf);
return err;
}
if (es_fread (buf, buflen, 1, fp) != 1)
{
err = gpg_error_from_syserror ();
log_error ("error reading %zu bytes from '%s': %s\n",
buflen, fname, gpg_strerror (err));
xfree (fname);
es_fclose (fp);
xfree (buf);
return err;
}
/* Convert the file into a gcrypt S-expression object. */
err = gcry_sexp_sscan (&s_skey, &erroff, (char*)buf, buflen);
xfree (fname);
es_fclose (fp);
xfree (buf);
if (err)
{
log_error ("failed to build S-Exp (off=%u): %s\n",
(unsigned int)erroff, gpg_strerror (err));
return err;
}
*result = s_skey;
return 0;
}
/* Remove the key identified by GRIP from the private key directory. */
static gpg_error_t
remove_key_file (const unsigned char *grip)
{
gpg_error_t err = 0;
char *fname;
char hexgrip[40+4+1];
bin2hex (grip, 20, hexgrip);
strcpy (hexgrip+40, ".key");
fname = make_filename (gnupg_homedir (), GNUPG_PRIVATE_KEYS_DIR,
hexgrip, NULL);
if (gnupg_remove (fname))
err = gpg_error_from_syserror ();
xfree (fname);
return err;
}
/* Return the secret key as an S-Exp in RESULT after locating it using
the GRIP. If the operation shall be diverted to a token, an
allocated S-expression with the shadow_info part from the file is
stored at SHADOW_INFO; if not NULL will be stored at SHADOW_INFO.
CACHE_MODE defines now the cache shall be used. DESC_TEXT may be
set to present a custom description for the pinentry. LOOKUP_TTL
is an optional function to convey a TTL to the cache manager; we do
not simply pass the TTL value because the value is only needed if
an unprotect action was needed and looking up the TTL may have some
overhead (e.g. scanning the sshcontrol file). If a CACHE_NONCE is
given that cache item is first tried to get a passphrase. If
R_PASSPHRASE is not NULL, the function succeeded and the key was
protected the used passphrase (entered or from the cache) is stored
there; if not NULL will be stored. The caller needs to free the
returned passphrase. */
gpg_error_t
agent_key_from_file (ctrl_t ctrl, const char *cache_nonce,
const char *desc_text,
const unsigned char *grip, unsigned char **shadow_info,
cache_mode_t cache_mode, lookup_ttl_t lookup_ttl,
gcry_sexp_t *result, char **r_passphrase)
{
gpg_error_t err;
unsigned char *buf;
size_t len, buflen, erroff;
gcry_sexp_t s_skey;
+ nvc_t keymeta = NULL;
+ char *desc_text_buffer = NULL; /* Used in case we extend DESC_TEXT. */
*result = NULL;
if (shadow_info)
*shadow_info = NULL;
if (r_passphrase)
*r_passphrase = NULL;
- err = read_key_file (grip, &s_skey);
+ err = read_key_file (grip, &s_skey, &keymeta);
if (err)
{
if (gpg_err_code (err) == GPG_ERR_ENOENT)
err = gpg_error (GPG_ERR_NO_SECKEY);
return err;
}
/* For use with the protection functions we also need the key as an
canonical encoded S-expression in a buffer. Create this buffer
now. */
err = make_canon_sexp (s_skey, &buf, &len);
if (err)
- return err;
+ {
+ nvc_release (keymeta);
+ xfree (desc_text_buffer);
+ return err;
+ }
switch (agent_private_key_type (buf))
{
case PRIVATE_KEY_CLEAR:
break; /* no unprotection needed */
case PRIVATE_KEY_OPENPGP_NONE:
{
unsigned char *buf_new;
size_t buf_newlen;
err = agent_unprotect (ctrl, buf, "", NULL, &buf_new, &buf_newlen);
if (err)
log_error ("failed to convert unprotected openpgp key: %s\n",
gpg_strerror (err));
else
{
xfree (buf);
buf = buf_new;
}
}
break;
case PRIVATE_KEY_PROTECTED:
{
char *desc_text_final;
- char *comment = NULL;
+ char *comment_buffer = NULL;
+ const char *comment = NULL;
/* Note, that we will take the comment as a C string for
- display purposes; i.e. all stuff beyond a Nul character is
- ignored. */
- {
- gcry_sexp_t comment_sexp;
+ * display purposes; i.e. all stuff beyond a Nul character is
+ * ignored. If a "Label" entry is available in the meta data
+ * this is used instead of the s-ecpression comment. */
+ if (keymeta && (comment = nvc_get_string (keymeta, "Label:")))
+ {
+ if (strchr (comment, '\n')
+ && (comment_buffer = linefeed_to_percent0A (comment)))
+ comment = comment_buffer;
+ /* In case DESC_TEXT has no escape pattern for a comment
+ * we append one. */
+ if (desc_text && !has_comment_expando (desc_text))
+ {
+ desc_text_buffer = strconcat (desc_text, "%0A%C", NULL);
+ if (desc_text_buffer)
+ desc_text = desc_text_buffer;
+ }
+ }
+ else
+ {
+ gcry_sexp_t comment_sexp;
- comment_sexp = gcry_sexp_find_token (s_skey, "comment", 0);
- if (comment_sexp)
- comment = gcry_sexp_nth_string (comment_sexp, 1);
- gcry_sexp_release (comment_sexp);
- }
+ comment_sexp = gcry_sexp_find_token (s_skey, "comment", 0);
+ if (comment_sexp)
+ comment_buffer = gcry_sexp_nth_string (comment_sexp, 1);
+ gcry_sexp_release (comment_sexp);
+ comment = comment_buffer;
+ }
desc_text_final = NULL;
if (desc_text)
err = agent_modify_description (desc_text, comment, s_skey,
&desc_text_final);
- gcry_free (comment);
+ gcry_free (comment_buffer);
if (!err)
{
err = unprotect (ctrl, cache_nonce, desc_text_final, &buf, grip,
cache_mode, lookup_ttl, r_passphrase);
if (err)
log_error ("failed to unprotect the secret key: %s\n",
gpg_strerror (err));
}
xfree (desc_text_final);
}
break;
case PRIVATE_KEY_SHADOWED:
if (shadow_info)
{
const unsigned char *s;
size_t n;
err = agent_get_shadow_info (buf, &s);
if (!err)
{
n = gcry_sexp_canon_len (s, 0, NULL,NULL);
log_assert (n);
*shadow_info = xtrymalloc (n);
if (!*shadow_info)
err = out_of_core ();
else
{
memcpy (*shadow_info, s, n);
err = 0;
}
}
if (err)
log_error ("get_shadow_info failed: %s\n", gpg_strerror (err));
}
else
err = gpg_error (GPG_ERR_UNUSABLE_SECKEY);
break;
default:
log_error ("invalid private key format\n");
err = gpg_error (GPG_ERR_BAD_SECKEY);
break;
}
gcry_sexp_release (s_skey);
s_skey = NULL;
if (err)
{
xfree (buf);
if (r_passphrase)
{
xfree (*r_passphrase);
*r_passphrase = NULL;
}
+ nvc_release (keymeta);
+ xfree (desc_text_buffer);
return err;
}
buflen = gcry_sexp_canon_len (buf, 0, NULL, NULL);
err = gcry_sexp_sscan (&s_skey, &erroff, (char*)buf, buflen);
wipememory (buf, buflen);
xfree (buf);
if (err)
{
log_error ("failed to build S-Exp (off=%u): %s\n",
(unsigned int)erroff, gpg_strerror (err));
if (r_passphrase)
{
xfree (*r_passphrase);
*r_passphrase = NULL;
}
+ nvc_release (keymeta);
+ xfree (desc_text_buffer);
return err;
}
*result = s_skey;
+ nvc_release (keymeta);
+ xfree (desc_text_buffer);
return 0;
}
/* Return the string name from the S-expression S_KEY as well as a
string describing the names of the parameters. ALGONAMESIZE and
ELEMSSIZE give the allocated size of the provided buffers. The
buffers may be NULL if not required. If R_LIST is not NULL the top
level list will be stored there; the caller needs to release it in
this case. */
static gpg_error_t
key_parms_from_sexp (gcry_sexp_t s_key, gcry_sexp_t *r_list,
char *r_algoname, size_t algonamesize,
char *r_elems, size_t elemssize)
{
gcry_sexp_t list, l2;
const char *name, *algoname, *elems;
size_t n;
if (r_list)
*r_list = NULL;
list = gcry_sexp_find_token (s_key, "shadowed-private-key", 0 );
if (!list)
list = gcry_sexp_find_token (s_key, "protected-private-key", 0 );
if (!list)
list = gcry_sexp_find_token (s_key, "private-key", 0 );
if (!list)
{
log_error ("invalid private key format\n");
return gpg_error (GPG_ERR_BAD_SECKEY);
}
l2 = gcry_sexp_cadr (list);
gcry_sexp_release (list);
list = l2;
name = gcry_sexp_nth_data (list, 0, &n);
if (n==3 && !memcmp (name, "rsa", 3))
{
algoname = "rsa";
elems = "ne";
}
else if (n==3 && !memcmp (name, "dsa", 3))
{
algoname = "dsa";
elems = "pqgy";
}
else if (n==3 && !memcmp (name, "ecc", 3))
{
algoname = "ecc";
elems = "pabgnq";
}
else if (n==5 && !memcmp (name, "ecdsa", 5))
{
algoname = "ecdsa";
elems = "pabgnq";
}
else if (n==4 && !memcmp (name, "ecdh", 4))
{
algoname = "ecdh";
elems = "pabgnq";
}
else if (n==3 && !memcmp (name, "elg", 3))
{
algoname = "elg";
elems = "pgy";
}
else
{
log_error ("unknown private key algorithm\n");
gcry_sexp_release (list);
return gpg_error (GPG_ERR_BAD_SECKEY);
}
if (r_algoname)
{
if (strlen (algoname) >= algonamesize)
return gpg_error (GPG_ERR_BUFFER_TOO_SHORT);
strcpy (r_algoname, algoname);
}
if (r_elems)
{
if (strlen (elems) >= elemssize)
return gpg_error (GPG_ERR_BUFFER_TOO_SHORT);
strcpy (r_elems, elems);
}
if (r_list)
*r_list = list;
else
gcry_sexp_release (list);
return 0;
}
/* Return true if KEYPARMS holds an EdDSA key. */
static int
is_eddsa (gcry_sexp_t keyparms)
{
int result = 0;
gcry_sexp_t list;
const char *s;
size_t n;
int i;
list = gcry_sexp_find_token (keyparms, "flags", 0);
for (i = list ? gcry_sexp_length (list)-1 : 0; i > 0; i--)
{
s = gcry_sexp_nth_data (list, i, &n);
if (!s)
continue; /* Not a data element. */
if (n == 5 && !memcmp (s, "eddsa", 5))
{
result = 1;
break;
}
}
gcry_sexp_release (list);
return result;
}
/* Return the public key algorithm number if S_KEY is a DSA style key.
If it is not a DSA style key, return 0. */
int
agent_is_dsa_key (gcry_sexp_t s_key)
{
int result;
gcry_sexp_t list;
char algoname[6];
if (!s_key)
return 0;
if (key_parms_from_sexp (s_key, &list, algoname, sizeof algoname, NULL, 0))
return 0; /* Error - assume it is not an DSA key. */
if (!strcmp (algoname, "dsa"))
result = GCRY_PK_DSA;
else if (!strcmp (algoname, "ecc"))
{
if (is_eddsa (list))
result = 0;
else
result = GCRY_PK_ECDSA;
}
else if (!strcmp (algoname, "ecdsa"))
result = GCRY_PK_ECDSA;
else
result = 0;
gcry_sexp_release (list);
return result;
}
/* Return true if S_KEY is an EdDSA key as used with curve Ed25519. */
int
agent_is_eddsa_key (gcry_sexp_t s_key)
{
int result;
gcry_sexp_t list;
char algoname[6];
if (!s_key)
return 0;
if (key_parms_from_sexp (s_key, &list, algoname, sizeof algoname, NULL, 0))
return 0; /* Error - assume it is not an EdDSA key. */
if (!strcmp (algoname, "ecc") && is_eddsa (list))
result = 1;
else if (!strcmp (algoname, "eddsa")) /* backward compatibility. */
result = 1;
else
result = 0;
gcry_sexp_release (list);
return result;
}
/* Return the key for the keygrip GRIP. The result is stored at
RESULT. This function extracts the key from the private key
database and returns it as an S-expression object as it is. On
failure an error code is returned and NULL stored at RESULT. */
gpg_error_t
agent_raw_key_from_file (ctrl_t ctrl, const unsigned char *grip,
gcry_sexp_t *result)
{
gpg_error_t err;
gcry_sexp_t s_skey;
(void)ctrl;
*result = NULL;
- err = read_key_file (grip, &s_skey);
+ err = read_key_file (grip, &s_skey, NULL);
if (!err)
*result = s_skey;
return err;
}
/* Return the public key for the keygrip GRIP. The result is stored
at RESULT. This function extracts the public key from the private
key database. On failure an error code is returned and NULL stored
at RESULT. */
gpg_error_t
agent_public_key_from_file (ctrl_t ctrl,
const unsigned char *grip,
gcry_sexp_t *result)
{
gpg_error_t err;
int i, idx;
gcry_sexp_t s_skey;
const char *algoname, *elems;
int npkey;
gcry_mpi_t array[10];
gcry_sexp_t curve = NULL;
gcry_sexp_t flags = NULL;
gcry_sexp_t uri_sexp, comment_sexp;
const char *uri, *comment;
size_t uri_length, comment_length;
+ int uri_intlen, comment_intlen;
char *format, *p;
void *args[2+7+2+2+1]; /* Size is 2 + max. # of elements + 2 for uri + 2
for comment + end-of-list. */
int argidx;
gcry_sexp_t list = NULL;
const char *s;
(void)ctrl;
*result = NULL;
- err = read_key_file (grip, &s_skey);
+ err = read_key_file (grip, &s_skey, NULL);
if (err)
return err;
for (i=0; i < DIM (array); i++)
array[i] = NULL;
err = extract_private_key (s_skey, 0, &algoname, &npkey, NULL, &elems,
array, DIM (array), &curve, &flags);
if (err)
{
gcry_sexp_release (s_skey);
return err;
}
uri = NULL;
uri_length = 0;
uri_sexp = gcry_sexp_find_token (s_skey, "uri", 0);
if (uri_sexp)
uri = gcry_sexp_nth_data (uri_sexp, 1, &uri_length);
comment = NULL;
comment_length = 0;
comment_sexp = gcry_sexp_find_token (s_skey, "comment", 0);
if (comment_sexp)
comment = gcry_sexp_nth_data (comment_sexp, 1, &comment_length);
gcry_sexp_release (s_skey);
s_skey = NULL;
/* FIXME: The following thing is pretty ugly code; we should
investigate how to make it cleaner. Probably code to handle
canonical S-expressions in a memory buffer is better suited for
such a task. After all that is what we do in protect.c. Need
to find common patterns and write a straightformward API to use
them. */
- assert (sizeof (size_t) <= sizeof (void*));
+ log_assert (sizeof (size_t) <= sizeof (void*));
format = xtrymalloc (15+4+7*npkey+10+15+1+1);
if (!format)
{
err = gpg_error_from_syserror ();
for (i=0; array[i]; i++)
gcry_mpi_release (array[i]);
gcry_sexp_release (curve);
gcry_sexp_release (flags);
gcry_sexp_release (uri_sexp);
gcry_sexp_release (comment_sexp);
return err;
}
argidx = 0;
p = stpcpy (stpcpy (format, "(public-key("), algoname);
p = stpcpy (p, "%S%S"); /* curve name and flags. */
args[argidx++] = &curve;
args[argidx++] = &flags;
for (idx=0, s=elems; idx < npkey; idx++)
{
*p++ = '(';
*p++ = *s++;
p = stpcpy (p, " %m)");
- assert (argidx < DIM (args));
+ log_assert (argidx < DIM (args));
args[argidx++] = &array[idx];
}
*p++ = ')';
if (uri)
{
p = stpcpy (p, "(uri %b)");
- assert (argidx+1 < DIM (args));
- args[argidx++] = (void *)&uri_length;
+ log_assert (argidx+1 < DIM (args));
+ uri_intlen = (int)uri_length;
+ args[argidx++] = (void *)&uri_intlen;
args[argidx++] = (void *)&uri;
}
if (comment)
{
p = stpcpy (p, "(comment %b)");
- assert (argidx+1 < DIM (args));
- args[argidx++] = (void *)&comment_length;
+ log_assert (argidx+1 < DIM (args));
+ comment_intlen = (int)comment_length;
+ args[argidx++] = (void *)&comment_intlen;
args[argidx++] = (void*)&comment;
}
*p++ = ')';
*p = 0;
- assert (argidx < DIM (args));
+ log_assert (argidx < DIM (args));
args[argidx] = NULL;
err = gcry_sexp_build_array (&list, NULL, format, args);
xfree (format);
for (i=0; array[i]; i++)
gcry_mpi_release (array[i]);
gcry_sexp_release (curve);
gcry_sexp_release (flags);
gcry_sexp_release (uri_sexp);
gcry_sexp_release (comment_sexp);
if (!err)
*result = list;
return err;
}
/* Check whether the secret key identified by GRIP is available.
Returns 0 is the key is available. */
int
agent_key_available (const unsigned char *grip)
{
int result;
char *fname;
char hexgrip[40+4+1];
bin2hex (grip, 20, hexgrip);
strcpy (hexgrip+40, ".key");
fname = make_filename (gnupg_homedir (), GNUPG_PRIVATE_KEYS_DIR,
hexgrip, NULL);
result = !access (fname, R_OK)? 0 : -1;
xfree (fname);
return result;
}
/* Return the information about the secret key specified by the binary
keygrip GRIP. If the key is a shadowed one the shadow information
will be stored at the address R_SHADOW_INFO as an allocated
S-expression. */
gpg_error_t
agent_key_info_from_file (ctrl_t ctrl, const unsigned char *grip,
int *r_keytype, unsigned char **r_shadow_info)
{
gpg_error_t err;
unsigned char *buf;
size_t len;
int keytype;
(void)ctrl;
if (r_keytype)
*r_keytype = PRIVATE_KEY_UNKNOWN;
if (r_shadow_info)
*r_shadow_info = NULL;
{
gcry_sexp_t sexp;
- err = read_key_file (grip, &sexp);
+ err = read_key_file (grip, &sexp, NULL);
if (err)
{
if (gpg_err_code (err) == GPG_ERR_ENOENT)
return gpg_error (GPG_ERR_NOT_FOUND);
else
return err;
}
err = make_canon_sexp (sexp, &buf, &len);
gcry_sexp_release (sexp);
if (err)
return err;
}
keytype = agent_private_key_type (buf);
switch (keytype)
{
case PRIVATE_KEY_CLEAR:
case PRIVATE_KEY_OPENPGP_NONE:
break;
case PRIVATE_KEY_PROTECTED:
/* If we ever require it we could retrieve the comment fields
from such a key. */
break;
case PRIVATE_KEY_SHADOWED:
if (r_shadow_info)
{
const unsigned char *s;
size_t n;
err = agent_get_shadow_info (buf, &s);
if (!err)
{
n = gcry_sexp_canon_len (s, 0, NULL, NULL);
- assert (n);
+ log_assert (n);
*r_shadow_info = xtrymalloc (n);
if (!*r_shadow_info)
err = gpg_error_from_syserror ();
else
memcpy (*r_shadow_info, s, n);
}
}
break;
default:
err = gpg_error (GPG_ERR_BAD_SECKEY);
break;
}
if (!err && r_keytype)
*r_keytype = keytype;
xfree (buf);
return err;
}
/* Delete the key with GRIP from the disk after having asked for
* confirmation using DESC_TEXT. If FORCE is set the function won't
* require a confirmation via Pinentry or warns if the key is also
* used by ssh. If ONLY_STUBS is set only stub keys (references to
* smartcards) will be affected.
*
* Common error codes are:
* GPG_ERR_NO_SECKEY
* GPG_ERR_KEY_ON_CARD
* GPG_ERR_NOT_CONFIRMED
* GPG_ERR_FORBIDDEN - Not a stub key and ONLY_STUBS requested.
*/
gpg_error_t
agent_delete_key (ctrl_t ctrl, const char *desc_text,
const unsigned char *grip, int force, int only_stubs)
{
gpg_error_t err;
gcry_sexp_t s_skey = NULL;
unsigned char *buf = NULL;
size_t len;
char *desc_text_final = NULL;
char *comment = NULL;
ssh_control_file_t cf = NULL;
char hexgrip[40+4+1];
char *default_desc = NULL;
int key_type;
- err = read_key_file (grip, &s_skey);
+ err = read_key_file (grip, &s_skey, NULL);
if (gpg_err_code (err) == GPG_ERR_ENOENT)
err = gpg_error (GPG_ERR_NO_SECKEY);
if (err)
goto leave;
err = make_canon_sexp (s_skey, &buf, &len);
if (err)
goto leave;
key_type = agent_private_key_type (buf);
if (only_stubs && key_type != PRIVATE_KEY_SHADOWED)
{
err = gpg_error (GPG_ERR_FORBIDDEN);
goto leave;
}
switch (key_type)
{
case PRIVATE_KEY_CLEAR:
case PRIVATE_KEY_OPENPGP_NONE:
case PRIVATE_KEY_PROTECTED:
bin2hex (grip, 20, hexgrip);
if (!force)
{
if (!desc_text)
{
default_desc = xtryasprintf
(L_("Do you really want to delete the key identified by keygrip%%0A"
" %s%%0A %%C%%0A?"), hexgrip);
desc_text = default_desc;
}
/* Note, that we will take the comment as a C string for
display purposes; i.e. all stuff beyond a Nul character is
ignored. */
{
gcry_sexp_t comment_sexp;
comment_sexp = gcry_sexp_find_token (s_skey, "comment", 0);
if (comment_sexp)
comment = gcry_sexp_nth_string (comment_sexp, 1);
gcry_sexp_release (comment_sexp);
}
if (desc_text)
err = agent_modify_description (desc_text, comment, s_skey,
&desc_text_final);
if (err)
goto leave;
err = agent_get_confirmation (ctrl, desc_text_final,
L_("Delete key"), L_("No"), 0);
if (err)
goto leave;
cf = ssh_open_control_file ();
if (cf)
{
if (!ssh_search_control_file (cf, hexgrip, NULL, NULL, NULL))
{
err = agent_get_confirmation
(ctrl,
L_("Warning: This key is also listed for use with SSH!\n"
"Deleting the key might remove your ability to "
"access remote machines."),
L_("Delete key"), L_("No"), 0);
if (err)
goto leave;
}
}
}
err = remove_key_file (grip);
break;
case PRIVATE_KEY_SHADOWED:
err = remove_key_file (grip);
break;
default:
log_error ("invalid private key format\n");
err = gpg_error (GPG_ERR_BAD_SECKEY);
break;
}
leave:
ssh_close_control_file (cf);
gcry_free (comment);
xfree (desc_text_final);
xfree (default_desc);
xfree (buf);
gcry_sexp_release (s_skey);
return err;
}
/* Write an S-expression formatted shadow key to our key storage.
Shadow key is created by an S-expression public key in PKBUF and
card's SERIALNO and the IDSTRING. With FORCE passed as true an
existing key with the given GRIP will get overwritten. */
gpg_error_t
agent_write_shadow_key (const unsigned char *grip,
const char *serialno, const char *keyid,
const unsigned char *pkbuf, int force)
{
gpg_error_t err;
unsigned char *shadow_info;
unsigned char *shdkey;
size_t len;
+ /* Just in case some caller did not parse the stuff correctly, skip
+ * leading spaces. */
+ while (spacep (serialno))
+ serialno++;
+ while (spacep (keyid))
+ keyid++;
+
shadow_info = make_shadow_info (serialno, keyid);
if (!shadow_info)
return gpg_error_from_syserror ();
err = agent_shadow_key (pkbuf, shadow_info, &shdkey);
xfree (shadow_info);
if (err)
{
log_error ("shadowing the key failed: %s\n", gpg_strerror (err));
return err;
}
len = gcry_sexp_canon_len (shdkey, 0, NULL, NULL);
- err = agent_write_private_key (grip, shdkey, len, force);
+ err = agent_write_private_key (grip, shdkey, len, force, serialno, keyid);
xfree (shdkey);
if (err)
log_error ("error writing key: %s\n", gpg_strerror (err));
return err;
}
diff --git a/agent/genkey.c b/agent/genkey.c
index d5c80d0aa..0d2038016 100644
--- a/agent/genkey.c
+++ b/agent/genkey.c
@@ -1,617 +1,599 @@
/* genkey.c - Generate a keypair
* Copyright (C) 2002, 2003, 2004, 2007, 2010 Free Software Foundation, Inc.
* Copyright (C) 2015 g10 Code GmbH.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
-#include <assert.h>
#include "agent.h"
#include "../common/i18n.h"
#include "../common/exechelp.h"
#include "../common/sysutils.h"
static int
store_key (gcry_sexp_t private, const char *passphrase, int force,
unsigned long s2k_count)
{
int rc;
unsigned char *buf;
size_t len;
unsigned char grip[20];
if ( !gcry_pk_get_keygrip (private, grip) )
{
log_error ("can't calculate keygrip\n");
return gpg_error (GPG_ERR_GENERAL);
}
len = gcry_sexp_sprint (private, GCRYSEXP_FMT_CANON, NULL, 0);
- assert (len);
+ log_assert (len);
buf = gcry_malloc_secure (len);
if (!buf)
return out_of_core ();
len = gcry_sexp_sprint (private, GCRYSEXP_FMT_CANON, buf, len);
- assert (len);
+ log_assert (len);
if (passphrase)
{
unsigned char *p;
rc = agent_protect (buf, passphrase, &p, &len, s2k_count, -1);
if (rc)
{
xfree (buf);
return rc;
}
xfree (buf);
buf = p;
}
- rc = agent_write_private_key (grip, buf, len, force);
+ rc = agent_write_private_key (grip, buf, len, force, NULL, NULL);
xfree (buf);
return rc;
}
/* Count the number of non-alpha characters in S. Control characters
and non-ascii characters are not considered. */
static size_t
nonalpha_count (const char *s)
{
size_t n;
for (n=0; *s; s++)
if (isascii (*s) && ( isdigit (*s) || ispunct (*s) ))
n++;
return n;
}
/* Check PW against a list of pattern. Return 0 if PW does not match
these pattern. */
static int
check_passphrase_pattern (ctrl_t ctrl, const char *pw)
{
gpg_error_t err = 0;
const char *pgmname = gnupg_module_name (GNUPG_MODULE_NAME_CHECK_PATTERN);
FILE *infp;
const char *argv[10];
pid_t pid;
int result, i;
(void)ctrl;
infp = gnupg_tmpfile ();
if (!infp)
{
err = gpg_error_from_syserror ();
log_error (_("error creating temporary file: %s\n"), gpg_strerror (err));
return 1; /* Error - assume password should not be used. */
}
if (fwrite (pw, strlen (pw), 1, infp) != 1)
{
err = gpg_error_from_syserror ();
log_error (_("error writing to temporary file: %s\n"),
gpg_strerror (err));
fclose (infp);
return 1; /* Error - assume password should not be used. */
}
fseek (infp, 0, SEEK_SET);
clearerr (infp);
i = 0;
argv[i++] = "--null";
argv[i++] = "--",
argv[i++] = opt.check_passphrase_pattern,
argv[i] = NULL;
- assert (i < sizeof argv);
+ log_assert (i < sizeof argv);
if (gnupg_spawn_process_fd (pgmname, argv, fileno (infp), -1, -1, &pid))
result = 1; /* Execute error - assume password should no be used. */
else if (gnupg_wait_process (pgmname, pid, 1, NULL))
result = 1; /* Helper returned an error - probably a match. */
else
result = 0; /* Success; i.e. no match. */
gnupg_release_process (pid);
/* Overwrite our temporary file. */
fseek (infp, 0, SEEK_SET);
clearerr (infp);
for (i=((strlen (pw)+99)/100)*100; i > 0; i--)
putc ('\xff', infp);
fflush (infp);
fclose (infp);
return result;
}
static int
-take_this_one_anyway2 (ctrl_t ctrl, const char *desc, const char *anyway_btn)
+take_this_one_anyway (ctrl_t ctrl, const char *desc, const char *anyway_btn)
{
- gpg_error_t err;
-
- if (opt.enforce_passphrase_constraints)
- {
- err = agent_show_message (ctrl, desc, L_("Enter new passphrase"));
- if (!err)
- err = gpg_error (GPG_ERR_CANCELED);
- }
- else
- err = agent_get_confirmation (ctrl, desc,
- anyway_btn, L_("Enter new passphrase"), 0);
- return err;
-}
-
-
-static int
-take_this_one_anyway (ctrl_t ctrl, const char *desc)
-{
- return take_this_one_anyway2 (ctrl, desc, L_("Take this one anyway"));
+ return agent_get_confirmation (ctrl, desc,
+ anyway_btn, L_("Enter new passphrase"), 0);
}
/* Check whether the passphrase PW is suitable. Returns 0 if the
passphrase is suitable and true if it is not and the user should be
asked to provide a different one. If FAILED_CONSTRAINT is set, a
message describing the problem is returned in
*FAILED_CONSTRAINT. */
int
check_passphrase_constraints (ctrl_t ctrl, const char *pw,
char **failed_constraint)
{
gpg_error_t err = 0;
unsigned int minlen = opt.min_passphrase_len;
unsigned int minnonalpha = opt.min_passphrase_nonalpha;
char *msg1 = NULL;
char *msg2 = NULL;
char *msg3 = NULL;
if (ctrl && ctrl->pinentry_mode == PINENTRY_MODE_LOOPBACK)
return 0;
if (!pw)
pw = "";
/* The first check is to warn about an empty passphrase. */
if (!*pw)
{
const char *desc = (opt.enforce_passphrase_constraints?
L_("You have not entered a passphrase!%0A"
"An empty passphrase is not allowed.") :
L_("You have not entered a passphrase - "
"this is in general a bad idea!%0A"
"Please confirm that you do not want to "
"have any protection on your key."));
err = 1;
if (failed_constraint)
{
if (opt.enforce_passphrase_constraints)
*failed_constraint = xstrdup (desc);
else
- err = take_this_one_anyway2 (ctrl, desc,
- L_("Yes, protection is not needed"));
+ err = take_this_one_anyway (ctrl, desc,
+ L_("Yes, protection is not needed"));
}
goto leave;
}
/* Now check the constraints and collect the error messages unless
in silent mode which returns immediately. */
if (utf8_charcount (pw, -1) < minlen )
{
if (!failed_constraint)
{
err = gpg_error (GPG_ERR_INV_PASSPHRASE);
goto leave;
}
msg1 = xtryasprintf
( ngettext ("A passphrase should be at least %u character long.",
"A passphrase should be at least %u characters long.",
minlen), minlen );
if (!msg1)
{
err = gpg_error_from_syserror ();
goto leave;
}
}
if (nonalpha_count (pw) < minnonalpha )
{
if (!failed_constraint)
{
err = gpg_error (GPG_ERR_INV_PASSPHRASE);
goto leave;
}
msg2 = xtryasprintf
( ngettext ("A passphrase should contain at least %u digit or%%0A"
"special character.",
"A passphrase should contain at least %u digits or%%0A"
"special characters.",
minnonalpha), minnonalpha );
if (!msg2)
{
err = gpg_error_from_syserror ();
goto leave;
}
}
/* If configured check the passphrase against a list of known words
and pattern. The actual test is done by an external program.
The warning message is generic to give the user no hint on how to
circumvent this list. */
if (*pw && opt.check_passphrase_pattern &&
check_passphrase_pattern (ctrl, pw))
{
if (!failed_constraint)
{
err = gpg_error (GPG_ERR_INV_PASSPHRASE);
goto leave;
}
msg3 = xtryasprintf
(L_("A passphrase may not be a known term or match%%0A"
"certain pattern."));
if (!msg3)
{
err = gpg_error_from_syserror ();
goto leave;
}
}
if (failed_constraint && (msg1 || msg2 || msg3))
{
char *msg;
size_t n;
msg = strconcat
(L_("Warning: You have entered an insecure passphrase."),
"%0A%0A",
msg1? msg1 : "", msg1? "%0A" : "",
msg2? msg2 : "", msg2? "%0A" : "",
msg3? msg3 : "", msg3? "%0A" : "",
NULL);
if (!msg)
{
err = gpg_error_from_syserror ();
goto leave;
}
/* Strip a trailing "%0A". */
n = strlen (msg);
if (n > 3 && !strcmp (msg + n - 3, "%0A"))
msg[n-3] = 0;
err = 1;
if (opt.enforce_passphrase_constraints)
*failed_constraint = msg;
else
{
- err = take_this_one_anyway (ctrl, msg);
+ err = take_this_one_anyway (ctrl, msg, L_("Take this one anyway"));
xfree (msg);
}
}
leave:
xfree (msg1);
xfree (msg2);
xfree (msg3);
return err;
}
/* Callback function to compare the first entered PIN with the one
currently being entered. */
static gpg_error_t
reenter_compare_cb (struct pin_entry_info_s *pi)
{
const char *pin1 = pi->check_cb_arg;
if (!strcmp (pin1, pi->pin))
return 0; /* okay */
return gpg_error (GPG_ERR_BAD_PASSPHRASE);
}
/* Ask the user for a new passphrase using PROMPT. On success the
function returns 0 and store the passphrase at R_PASSPHRASE; if the
user opted not to use a passphrase NULL will be stored there. The
user needs to free the returned string. In case of an error and
error code is returned and NULL stored at R_PASSPHRASE. */
gpg_error_t
agent_ask_new_passphrase (ctrl_t ctrl, const char *prompt,
char **r_passphrase)
{
gpg_error_t err;
const char *text1 = prompt;
const char *text2 = L_("Please re-enter this passphrase");
char *initial_errtext = NULL;
struct pin_entry_info_s *pi, *pi2;
*r_passphrase = NULL;
if (ctrl->pinentry_mode == PINENTRY_MODE_LOOPBACK)
{
size_t size;
unsigned char *buffer;
err = pinentry_loopback (ctrl, "NEW_PASSPHRASE", &buffer, &size,
MAX_PASSPHRASE_LEN);
if (!err)
{
if (size)
{
buffer[size] = 0;
*r_passphrase = buffer;
}
else
*r_passphrase = NULL;
}
return err;
}
pi = gcry_calloc_secure (1, sizeof (*pi) + MAX_PASSPHRASE_LEN + 1);
if (!pi)
return gpg_error_from_syserror ();
pi2 = gcry_calloc_secure (1, sizeof (*pi2) + MAX_PASSPHRASE_LEN + 1);
if (!pi2)
{
err = gpg_error_from_syserror ();
xfree (pi2);
return err;
}
pi->max_length = MAX_PASSPHRASE_LEN + 1;
pi->max_tries = 3;
pi->with_qualitybar = 1;
pi->with_repeat = 1;
pi2->max_length = MAX_PASSPHRASE_LEN + 1;
pi2->max_tries = 3;
pi2->check_cb = reenter_compare_cb;
pi2->check_cb_arg = pi->pin;
next_try:
err = agent_askpin (ctrl, text1, NULL, initial_errtext, pi, NULL, 0);
xfree (initial_errtext);
initial_errtext = NULL;
if (!err)
{
if (check_passphrase_constraints (ctrl, pi->pin, &initial_errtext))
{
pi->failed_tries = 0;
pi2->failed_tries = 0;
goto next_try;
}
/* Unless the passphrase is empty or the pinentry told us that
it already did the repetition check, ask to confirm it. */
if (*pi->pin && !pi->repeat_okay)
{
err = agent_askpin (ctrl, text2, NULL, NULL, pi2, NULL, 0);
if (gpg_err_code (err) == GPG_ERR_BAD_PASSPHRASE)
{ /* The re-entered one did not match and the user did not
hit cancel. */
initial_errtext = xtrystrdup (L_("does not match - try again"));
if (initial_errtext)
goto next_try;
err = gpg_error_from_syserror ();
}
}
}
if (!err && *pi->pin)
{
/* User wants a passphrase. */
*r_passphrase = xtrystrdup (pi->pin);
if (!*r_passphrase)
err = gpg_error_from_syserror ();
}
xfree (initial_errtext);
xfree (pi2);
xfree (pi);
return err;
}
/* Generate a new keypair according to the parameters given in
KEYPARAM. If CACHE_NONCE is given first try to lookup a passphrase
using the cache nonce. If NO_PROTECTION is true the key will not
be protected by a passphrase. If OVERRIDE_PASSPHRASE is true that
passphrase will be used for the new key. */
int
agent_genkey (ctrl_t ctrl, const char *cache_nonce,
const char *keyparam, size_t keyparamlen, int no_protection,
const char *override_passphrase, int preset, membuf_t *outbuf)
{
gcry_sexp_t s_keyparam, s_key, s_private, s_public;
char *passphrase_buffer = NULL;
const char *passphrase;
int rc;
size_t len;
char *buf;
rc = gcry_sexp_sscan (&s_keyparam, NULL, keyparam, keyparamlen);
if (rc)
{
log_error ("failed to convert keyparam: %s\n", gpg_strerror (rc));
return gpg_error (GPG_ERR_INV_DATA);
}
/* Get the passphrase now, cause key generation may take a while. */
if (override_passphrase)
passphrase = override_passphrase;
else if (no_protection || !cache_nonce)
passphrase = NULL;
else
{
passphrase_buffer = agent_get_cache (ctrl, cache_nonce, CACHE_MODE_NONCE);
passphrase = passphrase_buffer;
}
if (passphrase || no_protection)
;
else
{
rc = agent_ask_new_passphrase (ctrl,
L_("Please enter the passphrase to%0A"
"protect your new key"),
&passphrase_buffer);
if (rc)
return rc;
passphrase = passphrase_buffer;
}
rc = gcry_pk_genkey (&s_key, s_keyparam );
gcry_sexp_release (s_keyparam);
if (rc)
{
log_error ("key generation failed: %s\n", gpg_strerror (rc));
xfree (passphrase_buffer);
return rc;
}
/* break out the parts */
s_private = gcry_sexp_find_token (s_key, "private-key", 0);
if (!s_private)
{
log_error ("key generation failed: invalid return value\n");
gcry_sexp_release (s_key);
xfree (passphrase_buffer);
return gpg_error (GPG_ERR_INV_DATA);
}
s_public = gcry_sexp_find_token (s_key, "public-key", 0);
if (!s_public)
{
log_error ("key generation failed: invalid return value\n");
gcry_sexp_release (s_private);
gcry_sexp_release (s_key);
xfree (passphrase_buffer);
return gpg_error (GPG_ERR_INV_DATA);
}
gcry_sexp_release (s_key); s_key = NULL;
/* store the secret key */
if (DBG_CRYPTO)
log_debug ("storing private key\n");
rc = store_key (s_private, passphrase, 0, ctrl->s2k_count);
if (!rc)
{
if (!cache_nonce)
{
char tmpbuf[12];
gcry_create_nonce (tmpbuf, 12);
cache_nonce = bin2hex (tmpbuf, 12, NULL);
}
if (cache_nonce
&& !no_protection
&& !agent_put_cache (ctrl, cache_nonce, CACHE_MODE_NONCE,
passphrase, ctrl->cache_ttl_opt_preset))
agent_write_status (ctrl, "CACHE_NONCE", cache_nonce, NULL);
if (preset && !no_protection)
{
unsigned char grip[20];
char hexgrip[40+1];
if (gcry_pk_get_keygrip (s_private, grip))
{
bin2hex(grip, 20, hexgrip);
rc = agent_put_cache (ctrl, hexgrip, CACHE_MODE_ANY, passphrase,
ctrl->cache_ttl_opt_preset);
}
}
}
xfree (passphrase_buffer);
passphrase_buffer = NULL;
passphrase = NULL;
gcry_sexp_release (s_private);
if (rc)
{
gcry_sexp_release (s_public);
return rc;
}
/* return the public key */
if (DBG_CRYPTO)
log_debug ("returning public key\n");
len = gcry_sexp_sprint (s_public, GCRYSEXP_FMT_CANON, NULL, 0);
- assert (len);
+ log_assert (len);
buf = xtrymalloc (len);
if (!buf)
{
gpg_error_t tmperr = out_of_core ();
gcry_sexp_release (s_private);
gcry_sexp_release (s_public);
return tmperr;
}
len = gcry_sexp_sprint (s_public, GCRYSEXP_FMT_CANON, buf, len);
- assert (len);
+ log_assert (len);
put_membuf (outbuf, buf, len);
gcry_sexp_release (s_public);
xfree (buf);
return 0;
}
/* Apply a new passphrase to the key S_SKEY and store it. If
PASSPHRASE_ADDR and *PASSPHRASE_ADDR are not NULL, use that
passphrase. If PASSPHRASE_ADDR is not NULL store a newly entered
passphrase at that address. */
gpg_error_t
agent_protect_and_store (ctrl_t ctrl, gcry_sexp_t s_skey,
char **passphrase_addr)
{
gpg_error_t err;
if (passphrase_addr && *passphrase_addr)
{
/* Take an empty string as request not to protect the key. */
err = store_key (s_skey, **passphrase_addr? *passphrase_addr:NULL, 1,
ctrl->s2k_count);
}
else
{
char *pass = NULL;
if (passphrase_addr)
{
xfree (*passphrase_addr);
*passphrase_addr = NULL;
}
err = agent_ask_new_passphrase (ctrl,
L_("Please enter the new passphrase"),
&pass);
if (!err)
err = store_key (s_skey, pass, 1, ctrl->s2k_count);
if (!err && passphrase_addr)
*passphrase_addr = pass;
else
xfree (pass);
}
return err;
}
diff --git a/agent/gpg-agent.c b/agent/gpg-agent.c
index d9e2bbf25..57d5a459c 100644
--- a/agent/gpg-agent.c
+++ b/agent/gpg-agent.c
@@ -1,3273 +1,3275 @@
/* gpg-agent.c - The GnuPG Agent
* Copyright (C) 2000-2007, 2009-2010 Free Software Foundation, Inc.
* Copyright (C) 2000-2016 Werner Koch
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>
-#include <assert.h>
#include <time.h>
#include <fcntl.h>
#include <sys/stat.h>
#ifdef HAVE_W32_SYSTEM
# ifndef WINVER
# define WINVER 0x0500 /* Same as in common/sysutils.c */
# endif
# ifdef HAVE_WINSOCK2_H
# include <winsock2.h>
# endif
# include <aclapi.h>
# include <sddl.h>
#else /*!HAVE_W32_SYSTEM*/
# include <sys/socket.h>
# include <sys/un.h>
#endif /*!HAVE_W32_SYSTEM*/
#include <unistd.h>
#ifdef HAVE_SIGNAL_H
# include <signal.h>
#endif
#include <npth.h>
#define GNUPG_COMMON_NEED_AFLOCAL
#include "agent.h"
#include <assuan.h> /* Malloc hooks and socket wrappers. */
#include "../common/i18n.h"
#include "../common/sysutils.h"
#include "../common/gc-opt-flags.h"
#include "../common/exechelp.h"
#include "../common/asshelp.h"
#include "../common/init.h"
enum cmd_and_opt_values
{ aNull = 0,
oCsh = 'c',
oQuiet = 'q',
oSh = 's',
oVerbose = 'v',
oNoVerbose = 500,
aGPGConfList,
aGPGConfTest,
aUseStandardSocketP,
oOptions,
oDebug,
oDebugAll,
oDebugLevel,
oDebugWait,
oDebugQuickRandom,
oDebugPinentry,
oNoGreeting,
oNoOptions,
oHomedir,
oNoDetach,
oGrab,
oNoGrab,
oLogFile,
oServer,
oDaemon,
oSupervised,
oBatch,
oPinentryProgram,
oPinentryTouchFile,
oPinentryInvisibleChar,
oPinentryTimeout,
oDisplay,
oTTYname,
oTTYtype,
oLCctype,
oLCmessages,
oXauthority,
oScdaemonProgram,
oDefCacheTTL,
oDefCacheTTLSSH,
oMaxCacheTTL,
oMaxCacheTTLSSH,
oEnforcePassphraseConstraints,
oMinPassphraseLen,
oMinPassphraseNonalpha,
oCheckPassphrasePattern,
oMaxPassphraseDays,
oEnablePassphraseHistory,
oDisableExtendedKeyFormat,
oEnableExtendedKeyFormat,
oUseStandardSocket,
oNoUseStandardSocket,
oExtraSocket,
oBrowserSocket,
oFakedSystemTime,
oIgnoreCacheForSigning,
oAllowMarkTrusted,
oNoAllowMarkTrusted,
oAllowPresetPassphrase,
oAllowLoopbackPinentry,
oNoAllowLoopbackPinentry,
oNoAllowExternalCache,
oAllowEmacsPinentry,
oKeepTTY,
oKeepDISPLAY,
oSSHSupport,
oSSHFingerprintDigest,
oPuttySupport,
oDisableScdaemon,
oDisableCheckOwnSocket,
oS2KCount,
oS2KCalibration,
oAutoExpandSecmem,
oListenBacklog,
oWriteEnvFile,
oNoop
};
#ifndef ENAMETOOLONG
# define ENAMETOOLONG EINVAL
#endif
static ARGPARSE_OPTS opts[] = {
ARGPARSE_c (aGPGConfList, "gpgconf-list", "@"),
ARGPARSE_c (aGPGConfTest, "gpgconf-test", "@"),
ARGPARSE_c (aUseStandardSocketP, "use-standard-socket-p", "@"),
ARGPARSE_group (301, N_("@Options:\n ")),
ARGPARSE_s_n (oDaemon, "daemon", N_("run in daemon mode (background)")),
ARGPARSE_s_n (oServer, "server", N_("run in server mode (foreground)")),
#ifndef HAVE_W32_SYSTEM
ARGPARSE_s_n (oSupervised, "supervised", N_("run in supervised mode")),
#endif
ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")),
ARGPARSE_s_n (oQuiet, "quiet", N_("be somewhat more quiet")),
ARGPARSE_s_n (oSh, "sh", N_("sh-style command output")),
ARGPARSE_s_n (oCsh, "csh", N_("csh-style command output")),
ARGPARSE_s_s (oOptions, "options", N_("|FILE|read options from FILE")),
ARGPARSE_s_s (oDebug, "debug", "@"),
ARGPARSE_s_n (oDebugAll, "debug-all", "@"),
ARGPARSE_s_s (oDebugLevel, "debug-level", "@"),
ARGPARSE_s_i (oDebugWait, "debug-wait", "@"),
ARGPARSE_s_n (oDebugQuickRandom, "debug-quick-random", "@"),
ARGPARSE_s_n (oDebugPinentry, "debug-pinentry", "@"),
ARGPARSE_s_n (oNoDetach, "no-detach", N_("do not detach from the console")),
ARGPARSE_s_n (oGrab, "grab", "@"),
/* FIXME: Add the below string for 2.3 */
/* N_("let PIN-Entry grab keyboard and mouse")), */
ARGPARSE_s_n (oNoGrab, "no-grab", "@"),
ARGPARSE_s_s (oLogFile, "log-file", N_("use a log file for the server")),
ARGPARSE_s_s (oPinentryProgram, "pinentry-program",
/* */ N_("|PGM|use PGM as the PIN-Entry program")),
ARGPARSE_s_s (oPinentryTouchFile, "pinentry-touch-file", "@"),
ARGPARSE_s_s (oPinentryInvisibleChar, "pinentry-invisible-char", "@"),
ARGPARSE_s_u (oPinentryTimeout, "pinentry-timeout", "@"),
ARGPARSE_s_s (oScdaemonProgram, "scdaemon-program",
/* */ N_("|PGM|use PGM as the SCdaemon program") ),
ARGPARSE_s_n (oDisableScdaemon, "disable-scdaemon",
/* */ N_("do not use the SCdaemon") ),
ARGPARSE_s_n (oDisableCheckOwnSocket, "disable-check-own-socket", "@"),
ARGPARSE_s_s (oExtraSocket, "extra-socket",
/* */ N_("|NAME|accept some commands via NAME")),
ARGPARSE_s_s (oBrowserSocket, "browser-socket", "@"),
ARGPARSE_s_s (oFakedSystemTime, "faked-system-time", "@"),
ARGPARSE_s_n (oBatch, "batch", "@"),
ARGPARSE_s_s (oHomedir, "homedir", "@"),
ARGPARSE_s_s (oDisplay, "display", "@"),
ARGPARSE_s_s (oTTYname, "ttyname", "@"),
ARGPARSE_s_s (oTTYtype, "ttytype", "@"),
ARGPARSE_s_s (oLCctype, "lc-ctype", "@"),
ARGPARSE_s_s (oLCmessages, "lc-messages", "@"),
ARGPARSE_s_s (oXauthority, "xauthority", "@"),
ARGPARSE_s_n (oKeepTTY, "keep-tty",
/* */ N_("ignore requests to change the TTY")),
ARGPARSE_s_n (oKeepDISPLAY, "keep-display",
/* */ N_("ignore requests to change the X display")),
ARGPARSE_s_u (oDefCacheTTL, "default-cache-ttl",
N_("|N|expire cached PINs after N seconds")),
ARGPARSE_s_u (oDefCacheTTLSSH, "default-cache-ttl-ssh", "@" ),
ARGPARSE_s_u (oMaxCacheTTL, "max-cache-ttl", "@" ),
ARGPARSE_s_u (oMaxCacheTTLSSH, "max-cache-ttl-ssh", "@" ),
ARGPARSE_s_n (oEnforcePassphraseConstraints, "enforce-passphrase-constraints",
/* */ "@"),
ARGPARSE_s_u (oMinPassphraseLen, "min-passphrase-len", "@"),
ARGPARSE_s_u (oMinPassphraseNonalpha, "min-passphrase-nonalpha", "@"),
ARGPARSE_s_s (oCheckPassphrasePattern, "check-passphrase-pattern", "@"),
ARGPARSE_s_u (oMaxPassphraseDays, "max-passphrase-days", "@"),
ARGPARSE_s_n (oEnablePassphraseHistory, "enable-passphrase-history", "@"),
ARGPARSE_s_n (oIgnoreCacheForSigning, "ignore-cache-for-signing",
/* */ N_("do not use the PIN cache when signing")),
ARGPARSE_s_n (oNoAllowExternalCache, "no-allow-external-cache",
/* */ N_("disallow the use of an external password cache")),
ARGPARSE_s_n (oNoAllowMarkTrusted, "no-allow-mark-trusted",
/* */ N_("disallow clients to mark keys as \"trusted\"")),
ARGPARSE_s_n (oAllowMarkTrusted, "allow-mark-trusted", "@"),
ARGPARSE_s_n (oAllowPresetPassphrase, "allow-preset-passphrase",
/* */ N_("allow presetting passphrase")),
ARGPARSE_s_n (oNoAllowLoopbackPinentry, "no-allow-loopback-pinentry",
N_("disallow caller to override the pinentry")),
ARGPARSE_s_n (oAllowLoopbackPinentry, "allow-loopback-pinentry", "@"),
ARGPARSE_s_n (oAllowEmacsPinentry, "allow-emacs-pinentry",
/* */ N_("allow passphrase to be prompted through Emacs")),
ARGPARSE_s_n (oSSHSupport, "enable-ssh-support", N_("enable ssh support")),
ARGPARSE_s_s (oSSHFingerprintDigest, "ssh-fingerprint-digest",
N_("|ALGO|use ALGO to show ssh fingerprints")),
ARGPARSE_s_n (oPuttySupport, "enable-putty-support",
#ifdef HAVE_W32_SYSTEM
/* */ N_("enable putty support")
#else
/* */ "@"
#endif
),
ARGPARSE_s_n (oDisableExtendedKeyFormat, "disable-extended-key-format", "@"),
ARGPARSE_s_n (oEnableExtendedKeyFormat, "enable-extended-key-format", "@"),
ARGPARSE_s_u (oS2KCount, "s2k-count", "@"),
ARGPARSE_s_u (oS2KCalibration, "s2k-calibration", "@"),
ARGPARSE_op_u (oAutoExpandSecmem, "auto-expand-secmem", "@"),
ARGPARSE_s_i (oListenBacklog, "listen-backlog", "@"),
/* Dummy options for backward compatibility. */
ARGPARSE_o_s (oWriteEnvFile, "write-env-file", "@"),
ARGPARSE_s_n (oUseStandardSocket, "use-standard-socket", "@"),
ARGPARSE_s_n (oNoUseStandardSocket, "no-use-standard-socket", "@"),
/* Dummy options. */
ARGPARSE_end () /* End of list */
};
/* The list of supported debug flags. */
static struct debug_flags_s debug_flags [] =
{
{ DBG_MPI_VALUE , "mpi" },
{ DBG_CRYPTO_VALUE , "crypto" },
{ DBG_MEMORY_VALUE , "memory" },
{ DBG_CACHE_VALUE , "cache" },
{ DBG_MEMSTAT_VALUE, "memstat" },
{ DBG_HASHING_VALUE, "hashing" },
{ DBG_IPC_VALUE , "ipc" },
{ 77, NULL } /* 77 := Do not exit on "help" or "?". */
};
#define DEFAULT_CACHE_TTL (10*60) /* 10 minutes */
#define DEFAULT_CACHE_TTL_SSH (30*60) /* 30 minutes */
#define MAX_CACHE_TTL (120*60) /* 2 hours */
#define MAX_CACHE_TTL_SSH (120*60) /* 2 hours */
#define MIN_PASSPHRASE_LEN (8)
#define MIN_PASSPHRASE_NONALPHA (1)
#define MAX_PASSPHRASE_DAYS (0)
/* The timer tick used for housekeeping stuff. Note that on Windows
* we use a SetWaitableTimer seems to signal earlier than about 2
* seconds. Thus we use 4 seconds on all platforms except for
* Windowsce. CHECK_OWN_SOCKET_INTERVAL defines how often we check
* our own socket in standard socket mode. If that value is 0 we
* don't check at all. All values are in seconds. */
#if defined(HAVE_W32CE_SYSTEM)
# define TIMERTICK_INTERVAL (60)
# define CHECK_OWN_SOCKET_INTERVAL (0) /* Never */
#else
# define TIMERTICK_INTERVAL (4)
# define CHECK_OWN_SOCKET_INTERVAL (60)
#endif
/* Flag indicating that the ssh-agent subsystem has been enabled. */
static int ssh_support;
#ifdef HAVE_W32_SYSTEM
/* Flag indicating that support for Putty has been enabled. */
static int putty_support;
/* A magic value used with WM_COPYDATA. */
#define PUTTY_IPC_MAGIC 0x804e50ba
/* To avoid surprises we limit the size of the mapped IPC file to this
value. Putty currently (0.62) uses 8k, thus 16k should be enough
for the foreseeable future. */
#define PUTTY_IPC_MAXLEN 16384
#endif /*HAVE_W32_SYSTEM*/
/* The list of open file descriptors at startup. Note that this list
* has been allocated using the standard malloc. */
#ifndef HAVE_W32_SYSTEM
static int *startup_fd_list;
#endif
/* The signal mask at startup and a flag telling whether it is valid. */
#ifdef HAVE_SIGPROCMASK
static sigset_t startup_signal_mask;
static int startup_signal_mask_valid;
#endif
/* Flag to indicate that a shutdown was requested. */
static int shutdown_pending;
/* Counter for the currently running own socket checks. */
static int check_own_socket_running;
/* Flags to indicate that check_own_socket shall not be called. */
static int disable_check_own_socket;
/* Flag indicating that we are in supervised mode. */
static int is_supervised;
/* Flag to inhibit socket removal in cleanup. */
static int inhibit_socket_removal;
/* It is possible that we are currently running under setuid permissions */
static int maybe_setuid = 1;
/* Name of the communication socket used for native gpg-agent
requests. The second variable is either NULL or a malloced string
with the real socket name in case it has been redirected. */
static char *socket_name;
static char *redir_socket_name;
/* Name of the optional extra socket used for native gpg-agent requests. */
static char *socket_name_extra;
static char *redir_socket_name_extra;
/* Name of the optional browser socket used for native gpg-agent requests. */
static char *socket_name_browser;
static char *redir_socket_name_browser;
/* Name of the communication socket used for ssh-agent protocol. */
static char *socket_name_ssh;
static char *redir_socket_name_ssh;
/* We need to keep track of the server's nonces (these are dummies for
POSIX systems). */
static assuan_sock_nonce_t socket_nonce;
static assuan_sock_nonce_t socket_nonce_extra;
static assuan_sock_nonce_t socket_nonce_browser;
static assuan_sock_nonce_t socket_nonce_ssh;
/* Value for the listen() backlog argument. We use the same value for
* all sockets - 64 is on current Linux half of the default maximum.
* Let's try this as default. Change at runtime with --listen-backlog. */
static int listen_backlog = 64;
/* Default values for options passed to the pinentry. */
static char *default_display;
static char *default_ttyname;
static char *default_ttytype;
static char *default_lc_ctype;
static char *default_lc_messages;
static char *default_xauthority;
/* Name of a config file, which will be reread on a HUP if it is not NULL. */
static char *config_filename;
/* Helper to implement --debug-level */
static const char *debug_level;
/* Keep track of the current log file so that we can avoid updating
the log file after a SIGHUP if it didn't changed. Malloced. */
static char *current_logfile;
/* The handle_tick() function may test whether a parent is still
* running. We record the PID of the parent here or -1 if it should
* be watched. */
static pid_t parent_pid = (pid_t)(-1);
/* This flag is true if the inotify mechanism for detecting the
* removal of the homedir is active. This flag is used to disable the
* alternative but portable stat based check. */
static int have_homedir_inotify;
/* Depending on how gpg-agent was started, the homedir inotify watch
* may not be reliable. This flag is set if we assume that inotify
* works reliable. */
static int reliable_homedir_inotify;
/* Number of active connections. */
static int active_connections;
/* This object is used to dispatch progress messages from Libgcrypt to
* the right thread. Given that we will have at max only a few dozen
* connections at a time, using a linked list is the easiest way to
* handle this. */
struct progress_dispatch_s
{
struct progress_dispatch_s *next;
/* The control object of the connection. If this is NULL no
* connection is associated with this item and it is free for reuse
* by new connections. */
ctrl_t ctrl;
/* The thread id of (npth_self) of the connection. */
npth_t tid;
/* The callback set by the connection. This is similar to the
* Libgcrypt callback but with the control object passed as the
* first argument. */
void (*cb)(ctrl_t ctrl,
const char *what, int printchar,
int current, int total);
};
struct progress_dispatch_s *progress_dispatch_list;
/*
Local prototypes.
*/
static char *create_socket_name (char *standard_name, int with_homedir);
static gnupg_fd_t create_server_socket (char *name, int primary, int cygwin,
char **r_redir_name,
assuan_sock_nonce_t *nonce);
static void create_directories (void);
static void agent_libgcrypt_progress_cb (void *data, const char *what,
int printchar,
int current, int total);
static void agent_init_default_ctrl (ctrl_t ctrl);
static void agent_deinit_default_ctrl (ctrl_t ctrl);
static void handle_connections (gnupg_fd_t listen_fd,
gnupg_fd_t listen_fd_extra,
gnupg_fd_t listen_fd_browser,
gnupg_fd_t listen_fd_ssh);
static void check_own_socket (void);
static int check_for_running_agent (int silent);
/* Pth wrapper function definitions. */
ASSUAN_SYSTEM_NPTH_IMPL;
/*
Functions.
*/
/* Allocate a string describing a library version by calling a GETFNC.
This function is expected to be called only once. GETFNC is
expected to have a semantic like gcry_check_version (). */
static char *
make_libversion (const char *libname, const char *(*getfnc)(const char*))
{
const char *s;
char *result;
if (maybe_setuid)
{
gcry_control (GCRYCTL_INIT_SECMEM, 0, 0); /* Drop setuid. */
maybe_setuid = 0;
}
s = getfnc (NULL);
result = xmalloc (strlen (libname) + 1 + strlen (s) + 1);
strcpy (stpcpy (stpcpy (result, libname), " "), s);
return result;
}
/* Return strings describing this program. The case values are
described in common/argparse.c:strusage. The values here override
the default values given by strusage. */
static const char *
my_strusage (int level)
{
static char *ver_gcry;
const char *p;
switch (level)
{
case 11: p = "@GPG_AGENT@ (@GNUPG@)";
break;
case 13: p = VERSION; break;
case 17: p = PRINTABLE_OS_NAME; break;
/* TRANSLATORS: @EMAIL@ will get replaced by the actual bug
reporting address. This is so that we can change the
reporting address without breaking the translations. */
case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break;
case 20:
if (!ver_gcry)
ver_gcry = make_libversion ("libgcrypt", gcry_check_version);
p = ver_gcry;
break;
case 1:
case 40: p = _("Usage: @GPG_AGENT@ [options] (-h for help)");
break;
case 41: p = _("Syntax: @GPG_AGENT@ [options] [command [args]]\n"
"Secret key management for @GNUPG@\n");
break;
default: p = NULL;
}
return p;
}
/* Setup the debugging. With the global variable DEBUG_LEVEL set to NULL
only the active debug flags are propagated to the subsystems. With
DEBUG_LEVEL set, a specific set of debug flags is set; thus overriding
all flags already set. Note that we don't fail here, because it is
important to keep gpg-agent running even after re-reading the
options due to a SIGHUP. */
static void
set_debug (void)
{
int numok = (debug_level && digitp (debug_level));
int numlvl = numok? atoi (debug_level) : 0;
if (!debug_level)
;
else if (!strcmp (debug_level, "none") || (numok && numlvl < 1))
opt.debug = 0;
else if (!strcmp (debug_level, "basic") || (numok && numlvl <= 2))
opt.debug = DBG_IPC_VALUE;
else if (!strcmp (debug_level, "advanced") || (numok && numlvl <= 5))
opt.debug = DBG_IPC_VALUE;
else if (!strcmp (debug_level, "expert") || (numok && numlvl <= 8))
opt.debug = (DBG_IPC_VALUE | DBG_CACHE_VALUE);
else if (!strcmp (debug_level, "guru") || numok)
{
opt.debug = ~0;
/* Unless the "guru" string has been used we don't want to allow
hashing debugging. The rationale is that people tend to
select the highest debug value and would then clutter their
disk with debug files which may reveal confidential data. */
if (numok)
opt.debug &= ~(DBG_HASHING_VALUE);
}
else
{
log_error (_("invalid debug-level '%s' given\n"), debug_level);
opt.debug = 0; /* Reset debugging, so that prior debug
statements won't have an undesired effect. */
}
if (opt.debug && !opt.verbose)
opt.verbose = 1;
if (opt.debug && opt.quiet)
opt.quiet = 0;
if (opt.debug & DBG_MPI_VALUE)
gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 2);
if (opt.debug & DBG_CRYPTO_VALUE )
gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 1);
gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose);
if (opt.debug)
parse_debug_flag (NULL, &opt.debug, debug_flags);
}
/* Helper for cleanup to remove one socket with NAME. REDIR_NAME is
the corresponding real name if the socket has been redirected. */
static void
remove_socket (char *name, char *redir_name)
{
if (name && *name)
{
if (redir_name)
name = redir_name;
gnupg_remove (name);
*name = 0;
}
}
/* Discover which inherited file descriptors correspond to which
* services/sockets offered by gpg-agent, using the LISTEN_FDS and
* LISTEN_FDNAMES convention. The understood labels are "ssh",
* "extra", and "browser". "std" or other labels will be interpreted
* as the standard socket.
*
* This function is designed to log errors when the expected file
* descriptors don't make sense, but to do its best to continue to
* work even in the face of minor misconfigurations.
*
* For more information on the LISTEN_FDS convention, see
* sd_listen_fds(3) on certain Linux distributions.
*/
#ifndef HAVE_W32_SYSTEM
static void
map_supervised_sockets (gnupg_fd_t *r_fd,
gnupg_fd_t *r_fd_extra,
gnupg_fd_t *r_fd_browser,
gnupg_fd_t *r_fd_ssh)
{
struct {
const char *label;
int **fdaddr;
char **nameaddr;
} tbl[] = {
{ "ssh", &r_fd_ssh, &socket_name_ssh },
{ "browser", &r_fd_browser, &socket_name_browser },
{ "extra", &r_fd_extra, &socket_name_extra },
{ "std", &r_fd, &socket_name } /* (Must be the last item.) */
};
const char *envvar;
char **fdnames;
int nfdnames;
int fd_count;
*r_fd = *r_fd_extra = *r_fd_browser = *r_fd_ssh = -1;
/* Print a warning if LISTEN_PID does not match outr pid. */
envvar = getenv ("LISTEN_PID");
if (!envvar)
log_error ("no LISTEN_PID environment variable found in "
"--supervised mode (ignoring)\n");
else if (strtoul (envvar, NULL, 10) != (unsigned long)getpid ())
log_error ("environment variable LISTEN_PID (%lu) does not match"
" our pid (%lu) in --supervised mode (ignoring)\n",
(unsigned long)strtoul (envvar, NULL, 10),
(unsigned long)getpid ());
/* Parse LISTEN_FDNAMES into the array FDNAMES. */
envvar = getenv ("LISTEN_FDNAMES");
if (envvar)
{
fdnames = strtokenize (envvar, ":");
if (!fdnames)
{
log_error ("strtokenize failed: %s\n",
gpg_strerror (gpg_error_from_syserror ()));
agent_exit (1);
}
for (nfdnames=0; fdnames[nfdnames]; nfdnames++)
;
}
else
{
fdnames = NULL;
nfdnames = 0;
}
/* Parse LISTEN_FDS into fd_count or provide a replacement. */
envvar = getenv ("LISTEN_FDS");
if (envvar)
fd_count = atoi (envvar);
else if (fdnames)
{
log_error ("no LISTEN_FDS environment variable found in --supervised"
" mode (relying on LISTEN_FDNAMES instead)\n");
fd_count = nfdnames;
}
else
{
log_error ("no LISTEN_FDS or LISTEN_FDNAMES environment variables "
"found in --supervised mode"
" (assuming 1 active descriptor)\n");
fd_count = 1;
}
if (fd_count < 1)
{
log_error ("--supervised mode expects at least one file descriptor"
" (was told %d, carrying on as though it were 1)\n",
fd_count);
fd_count = 1;
}
/* Assign the descriptors to the return values. */
if (!fdnames)
{
struct stat statbuf;
if (fd_count != 1)
log_error ("no LISTEN_FDNAMES and LISTEN_FDS (%d) != 1"
" in --supervised mode."
" (ignoring all sockets but the first one)\n",
fd_count);
if (fstat (3, &statbuf) == -1 && errno ==EBADF)
log_fatal ("file descriptor 3 must be valid in --supervised mode"
" if LISTEN_FDNAMES is not set\n");
*r_fd = 3;
socket_name = gnupg_get_socket_name (3);
}
else if (fd_count != nfdnames)
{
log_fatal ("number of items in LISTEN_FDNAMES (%d) does not match "
"LISTEN_FDS (%d) in --supervised mode\n",
nfdnames, fd_count);
}
else
{
int i, j, fd;
char *name;
for (i = 0; i < nfdnames; i++)
{
for (j = 0; j < DIM (tbl); j++)
{
if (!strcmp (fdnames[i], tbl[j].label) || j == DIM(tbl)-1)
{
fd = 3 + i;
if (**tbl[j].fdaddr == -1)
{
name = gnupg_get_socket_name (fd);
if (name)
{
**tbl[j].fdaddr = fd;
*tbl[j].nameaddr = name;
log_info ("using fd %d for %s socket (%s)\n",
fd, tbl[j].label, name);
}
else
{
log_error ("cannot listen on fd %d for %s socket\n",
fd, tbl[j].label);
close (fd);
}
}
else
{
log_error ("cannot listen on more than one %s socket\n",
tbl[j].label);
close (fd);
}
break;
}
}
}
}
xfree (fdnames);
}
#endif /*!HAVE_W32_SYSTEM*/
/* Cleanup code for this program. This is either called has an atexit
handler or directly. */
static void
cleanup (void)
{
static int done;
if (done)
return;
done = 1;
deinitialize_module_cache ();
if (!is_supervised && !inhibit_socket_removal)
{
remove_socket (socket_name, redir_socket_name);
if (opt.extra_socket > 1)
remove_socket (socket_name_extra, redir_socket_name_extra);
if (opt.browser_socket > 1)
remove_socket (socket_name_browser, redir_socket_name_browser);
remove_socket (socket_name_ssh, redir_socket_name_ssh);
}
}
/* Handle options which are allowed to be reset after program start.
Return true when the current option in PARGS could be handled and
false if not. As a special feature, passing a value of NULL for
PARGS, resets the options to the default. REREAD should be set
true if it is not the initial option parsing. */
static int
parse_rereadable_options (ARGPARSE_ARGS *pargs, int reread)
{
int i;
if (!pargs)
{ /* reset mode */
opt.quiet = 0;
opt.verbose = 0;
opt.debug = 0;
opt.no_grab = 1;
opt.debug_pinentry = 0;
opt.pinentry_program = NULL;
opt.pinentry_touch_file = NULL;
xfree (opt.pinentry_invisible_char);
opt.pinentry_invisible_char = NULL;
opt.pinentry_timeout = 0;
opt.scdaemon_program = NULL;
opt.def_cache_ttl = DEFAULT_CACHE_TTL;
opt.def_cache_ttl_ssh = DEFAULT_CACHE_TTL_SSH;
opt.max_cache_ttl = MAX_CACHE_TTL;
opt.max_cache_ttl_ssh = MAX_CACHE_TTL_SSH;
opt.enforce_passphrase_constraints = 0;
opt.min_passphrase_len = MIN_PASSPHRASE_LEN;
opt.min_passphrase_nonalpha = MIN_PASSPHRASE_NONALPHA;
opt.check_passphrase_pattern = NULL;
opt.max_passphrase_days = MAX_PASSPHRASE_DAYS;
opt.enable_passphrase_history = 0;
opt.enable_extended_key_format = 1;
opt.ignore_cache_for_signing = 0;
opt.allow_mark_trusted = 1;
opt.allow_external_cache = 1;
opt.allow_loopback_pinentry = 1;
opt.allow_emacs_pinentry = 0;
opt.disable_scdaemon = 0;
disable_check_own_socket = 0;
/* Note: When changing the next line, change also gpgconf_list. */
opt.ssh_fingerprint_digest = GCRY_MD_MD5;
opt.s2k_count = 0;
set_s2k_calibration_time (0); /* Set to default. */
return 1;
}
switch (pargs->r_opt)
{
case oQuiet: opt.quiet = 1; break;
case oVerbose: opt.verbose++; break;
case oDebug:
parse_debug_flag (pargs->r.ret_str, &opt.debug, debug_flags);
break;
case oDebugAll: opt.debug = ~0; break;
case oDebugLevel: debug_level = pargs->r.ret_str; break;
case oDebugPinentry: opt.debug_pinentry = 1; break;
case oLogFile:
if (!reread)
return 0; /* not handled */
if (!current_logfile || !pargs->r.ret_str
|| strcmp (current_logfile, pargs->r.ret_str))
{
log_set_file (pargs->r.ret_str);
xfree (current_logfile);
current_logfile = xtrystrdup (pargs->r.ret_str);
}
break;
case oNoGrab: opt.no_grab |= 1; break;
case oGrab: opt.no_grab |= 2; break;
case oPinentryProgram: opt.pinentry_program = pargs->r.ret_str; break;
case oPinentryTouchFile: opt.pinentry_touch_file = pargs->r.ret_str; break;
case oPinentryInvisibleChar:
xfree (opt.pinentry_invisible_char);
opt.pinentry_invisible_char = xtrystrdup (pargs->r.ret_str); break;
break;
case oPinentryTimeout: opt.pinentry_timeout = pargs->r.ret_ulong; break;
case oScdaemonProgram: opt.scdaemon_program = pargs->r.ret_str; break;
case oDisableScdaemon: opt.disable_scdaemon = 1; break;
case oDisableCheckOwnSocket: disable_check_own_socket = 1; break;
case oDefCacheTTL: opt.def_cache_ttl = pargs->r.ret_ulong; break;
case oDefCacheTTLSSH: opt.def_cache_ttl_ssh = pargs->r.ret_ulong; break;
case oMaxCacheTTL: opt.max_cache_ttl = pargs->r.ret_ulong; break;
case oMaxCacheTTLSSH: opt.max_cache_ttl_ssh = pargs->r.ret_ulong; break;
case oEnforcePassphraseConstraints:
opt.enforce_passphrase_constraints=1;
break;
case oMinPassphraseLen: opt.min_passphrase_len = pargs->r.ret_ulong; break;
case oMinPassphraseNonalpha:
opt.min_passphrase_nonalpha = pargs->r.ret_ulong;
break;
case oCheckPassphrasePattern:
opt.check_passphrase_pattern = pargs->r.ret_str;
break;
case oMaxPassphraseDays:
opt.max_passphrase_days = pargs->r.ret_ulong;
break;
case oEnablePassphraseHistory:
opt.enable_passphrase_history = 1;
break;
case oEnableExtendedKeyFormat:
opt.enable_extended_key_format = 2;
break;
case oDisableExtendedKeyFormat:
if (opt.enable_extended_key_format != 2)
opt.enable_extended_key_format = 0;
break;
case oIgnoreCacheForSigning: opt.ignore_cache_for_signing = 1; break;
case oAllowMarkTrusted: opt.allow_mark_trusted = 1; break;
case oNoAllowMarkTrusted: opt.allow_mark_trusted = 0; break;
case oAllowPresetPassphrase: opt.allow_preset_passphrase = 1; break;
case oAllowLoopbackPinentry: opt.allow_loopback_pinentry = 1; break;
case oNoAllowLoopbackPinentry: opt.allow_loopback_pinentry = 0; break;
case oNoAllowExternalCache: opt.allow_external_cache = 0;
break;
case oAllowEmacsPinentry: opt.allow_emacs_pinentry = 1;
break;
case oSSHFingerprintDigest:
i = gcry_md_map_name (pargs->r.ret_str);
if (!i)
log_error (_("selected digest algorithm is invalid\n"));
else
opt.ssh_fingerprint_digest = i;
break;
case oS2KCount:
opt.s2k_count = pargs->r.ret_ulong;
break;
case oS2KCalibration:
set_s2k_calibration_time (pargs->r.ret_ulong);
break;
case oNoop: break;
default:
return 0; /* not handled */
}
return 1; /* handled */
}
/* Fixup some options after all have been processed. */
static void
finalize_rereadable_options (void)
{
/* Hack to allow --grab to override --no-grab. */
if ((opt.no_grab & 2))
opt.no_grab = 0;
}
static void
thread_init_once (void)
{
static int npth_initialized = 0;
if (!npth_initialized)
{
npth_initialized++;
npth_init ();
}
gpgrt_set_syscall_clamp (npth_unprotect, npth_protect);
/* Now that we have set the syscall clamp we need to tell Libgcrypt
* that it should get them from libgpg-error. Note that Libgcrypt
* has already been initialized but at that point nPth was not
* initialized and thus Libgcrypt could not set its system call
* clamp. */
#if GCRYPT_VERSION_NUMBER >= 0x010800 /* 1.8.0 */
gcry_control (GCRYCTL_REINIT_SYSCALL_CLAMP, 0, 0);
#endif
}
static void
initialize_modules (void)
{
thread_init_once ();
assuan_set_system_hooks (ASSUAN_SYSTEM_NPTH);
initialize_module_cache ();
initialize_module_call_pinentry ();
initialize_module_call_scd ();
initialize_module_trustlist ();
}
/* The main entry point. */
int
main (int argc, char **argv )
{
ARGPARSE_ARGS pargs;
int orig_argc;
char **orig_argv;
FILE *configfp = NULL;
char *configname = NULL;
const char *shell;
unsigned configlineno;
int parse_debug = 0;
int default_config =1;
int pipe_server = 0;
int is_daemon = 0;
int nodetach = 0;
int csh_style = 0;
char *logfile = NULL;
int debug_wait = 0;
int gpgconf_list = 0;
gpg_error_t err;
struct assuan_malloc_hooks malloc_hooks;
early_system_init ();
/* Before we do anything else we save the list of currently open
file descriptors and the signal mask. This info is required to
do the exec call properly. We don't need it on Windows. */
#ifndef HAVE_W32_SYSTEM
startup_fd_list = get_all_open_fds ();
#endif /*!HAVE_W32_SYSTEM*/
#ifdef HAVE_SIGPROCMASK
if (!sigprocmask (SIG_UNBLOCK, NULL, &startup_signal_mask))
startup_signal_mask_valid = 1;
#endif /*HAVE_SIGPROCMASK*/
/* Set program name etc. */
set_strusage (my_strusage);
gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN);
/* Please note that we may running SUID(ROOT), so be very CAREFUL
when adding any stuff between here and the call to INIT_SECMEM()
somewhere after the option parsing */
log_set_prefix (GPG_AGENT_NAME, GPGRT_LOG_WITH_PREFIX|GPGRT_LOG_WITH_PID);
/* Make sure that our subsystems are ready. */
i18n_init ();
init_common_subsystems (&argc, &argv);
malloc_hooks.malloc = gcry_malloc;
malloc_hooks.realloc = gcry_realloc;
malloc_hooks.free = gcry_free;
assuan_set_malloc_hooks (&malloc_hooks);
assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT);
assuan_sock_init ();
assuan_sock_set_system_hooks (ASSUAN_SYSTEM_NPTH);
setup_libassuan_logging (&opt.debug, NULL);
setup_libgcrypt_logging ();
gcry_control (GCRYCTL_USE_SECURE_RNDPOOL);
gcry_set_progress_handler (agent_libgcrypt_progress_cb, NULL);
disable_core_dumps ();
/* Set default options. */
parse_rereadable_options (NULL, 0); /* Reset them to default values. */
shell = getenv ("SHELL");
if (shell && strlen (shell) >= 3 && !strcmp (shell+strlen (shell)-3, "csh") )
csh_style = 1;
/* Record some of the original environment strings. */
{
const char *s;
int idx;
static const char *names[] =
{ "DISPLAY", "TERM", "XAUTHORITY", "PINENTRY_USER_DATA", NULL };
err = 0;
opt.startup_env = session_env_new ();
if (!opt.startup_env)
err = gpg_error_from_syserror ();
for (idx=0; !err && names[idx]; idx++)
{
s = getenv (names[idx]);
if (s)
err = session_env_setenv (opt.startup_env, names[idx], s);
}
if (!err)
{
s = gnupg_ttyname (0);
if (s)
err = session_env_setenv (opt.startup_env, "GPG_TTY", s);
}
if (err)
log_fatal ("error recording startup environment: %s\n",
gpg_strerror (err));
/* Fixme: Better use the locale function here. */
opt.startup_lc_ctype = getenv ("LC_CTYPE");
if (opt.startup_lc_ctype)
opt.startup_lc_ctype = xstrdup (opt.startup_lc_ctype);
opt.startup_lc_messages = getenv ("LC_MESSAGES");
if (opt.startup_lc_messages)
opt.startup_lc_messages = xstrdup (opt.startup_lc_messages);
}
/* 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 option 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)
gnupg_set_homedir (pargs.r.ret_str);
else if (pargs.r_opt == oDebugQuickRandom)
{
gcry_control (GCRYCTL_ENABLE_QUICK_RANDOM, 0);
}
}
/* Initialize the secure memory. */
gcry_control (GCRYCTL_INIT_SECMEM, SECMEM_BUFFER_SIZE, 0);
maybe_setuid = 0;
/*
Now we are now working under our real uid
*/
if (default_config)
configname = make_filename (gnupg_homedir (),
GPG_AGENT_NAME EXTSEP_S "conf", 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 );
/* Save the default conf file name so that
reread_configuration is able to test whether the
config file has been created in the meantime. */
xfree (config_filename);
config_filename = configname;
configname = NULL;
}
else
{
log_error (_("option file '%s': %s\n"),
configname, strerror(errno) );
exit(2);
}
xfree (configname);
configname = NULL;
}
if (parse_debug && configname )
log_info (_("reading options from '%s'\n"), configname );
default_config = 0;
}
while (optfile_parse( configfp, configname, &configlineno, &pargs, opts) )
{
if (parse_rereadable_options (&pargs, 0))
continue; /* Already handled */
switch (pargs.r_opt)
{
case aGPGConfList: gpgconf_list = 1; break;
case aGPGConfTest: gpgconf_list = 2; break;
case aUseStandardSocketP: gpgconf_list = 3; break;
case oBatch: opt.batch=1; break;
case oDebugWait: debug_wait = pargs.r.ret_int; 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 oNoGreeting: /* Dummy option. */ break;
case oNoVerbose: opt.verbose = 0; break;
case oNoOptions: break; /* no-options */
case oHomedir: gnupg_set_homedir (pargs.r.ret_str); break;
case oNoDetach: nodetach = 1; break;
case oLogFile: logfile = pargs.r.ret_str; break;
case oCsh: csh_style = 1; break;
case oSh: csh_style = 0; break;
case oServer: pipe_server = 1; break;
case oDaemon: is_daemon = 1; break;
case oSupervised: is_supervised = 1; break;
case oDisplay: default_display = xstrdup (pargs.r.ret_str); break;
case oTTYname: default_ttyname = xstrdup (pargs.r.ret_str); break;
case oTTYtype: default_ttytype = xstrdup (pargs.r.ret_str); break;
case oLCctype: default_lc_ctype = xstrdup (pargs.r.ret_str); break;
case oLCmessages: default_lc_messages = xstrdup (pargs.r.ret_str);
break;
case oXauthority: default_xauthority = xstrdup (pargs.r.ret_str);
break;
case oUseStandardSocket:
case oNoUseStandardSocket:
obsolete_option (configname, configlineno, "use-standard-socket");
break;
case oFakedSystemTime:
{
time_t faked_time = isotime2epoch (pargs.r.ret_str);
if (faked_time == (time_t)(-1))
faked_time = (time_t)strtoul (pargs.r.ret_str, NULL, 10);
gnupg_set_time (faked_time, 0);
}
break;
case oKeepTTY: opt.keep_tty = 1; break;
case oKeepDISPLAY: opt.keep_display = 1; break;
case oSSHSupport:
ssh_support = 1;
break;
case oPuttySupport:
# ifdef HAVE_W32_SYSTEM
putty_support = 1;
# endif
break;
case oExtraSocket:
opt.extra_socket = 1; /* (1 = points into argv) */
socket_name_extra = pargs.r.ret_str;
break;
case oBrowserSocket:
opt.browser_socket = 1; /* (1 = points into argv) */
socket_name_browser = pargs.r.ret_str;
break;
case oAutoExpandSecmem:
/* Try to enable this option. It will officially only be
* supported by Libgcrypt 1.9 but 1.8.2 already supports it
* on the quiet and thus we use the numeric value value. */
gcry_control (78 /*GCRYCTL_AUTO_EXPAND_SECMEM*/,
(unsigned int)pargs.r.ret_ulong, 0);
break;
case oListenBacklog:
listen_backlog = pargs.r.ret_int;
break;
case oDebugQuickRandom:
/* Only used by the first stage command line parser. */
break;
case oWriteEnvFile:
obsolete_option (configname, configlineno, "write-env-file");
break;
default : pargs.err = configfp? 1:2; break;
}
}
if (configfp)
{
fclose( configfp );
configfp = NULL;
/* Keep a copy of the name so that it can be read on SIGHUP. */
if (config_filename != configname)
{
xfree (config_filename);
config_filename = configname;
}
configname = NULL;
goto next_pass;
}
xfree (configname);
configname = NULL;
if (log_get_errorcount(0))
exit(2);
finalize_rereadable_options ();
/* Print a warning if an argument looks like an option. */
if (!opt.quiet && !(pargs.flags & ARGPARSE_FLAG_STOP_SEEN))
{
int i;
for (i=0; i < argc; i++)
if (argv[i][0] == '-' && argv[i][1] == '-')
log_info (_("Note: '%s' is not considered an option\n"), argv[i]);
}
#ifdef ENABLE_NLS
/* gpg-agent usually does not output any messages because it runs in
the background. For log files it is acceptable to have messages
always encoded in utf-8. We switch here to utf-8, so that
commands like --help still give native messages. It is far
easier to switch only once instead of for every message and it
actually helps when more then one thread is active (avoids an
extra copy step). */
bind_textdomain_codeset (PACKAGE_GT, "UTF-8");
#endif
if (!pipe_server && !is_daemon && !gpgconf_list && !is_supervised)
{
/* We have been called without any command and thus we merely
check whether an agent is already running. We do this right
here so that we don't clobber a logfile with this check but
print the status directly to stderr. */
opt.debug = 0;
set_debug ();
check_for_running_agent (0);
agent_exit (0);
}
if (is_supervised)
;
else if (!opt.extra_socket)
opt.extra_socket = 1;
else if (socket_name_extra
&& (!strcmp (socket_name_extra, "none")
|| !strcmp (socket_name_extra, "/dev/null")))
{
/* User requested not to create this socket. */
opt.extra_socket = 0;
socket_name_extra = NULL;
}
if (is_supervised)
;
else if (!opt.browser_socket)
opt.browser_socket = 1;
else if (socket_name_browser
&& (!strcmp (socket_name_browser, "none")
|| !strcmp (socket_name_browser, "/dev/null")))
{
/* User requested not to create this socket. */
opt.browser_socket = 0;
socket_name_browser = NULL;
}
set_debug ();
if (atexit (cleanup))
{
log_error ("atexit failed\n");
cleanup ();
exit (1);
}
/* Try to create missing directories. */
create_directories ();
if (debug_wait && pipe_server)
{
thread_init_once ();
log_debug ("waiting for debugger - my pid is %u .....\n",
(unsigned int)getpid());
gnupg_sleep (debug_wait);
log_debug ("... okay\n");
}
if (gpgconf_list == 3)
{
/* We now use the standard socket always - return true for
backward compatibility. */
agent_exit (0);
}
else if (gpgconf_list == 2)
agent_exit (0);
else if (gpgconf_list)
{
char *filename;
char *filename_esc;
/* List options and default values in the GPG Conf format. */
filename = make_filename (gnupg_homedir (),
GPG_AGENT_NAME EXTSEP_S "conf", NULL);
filename_esc = percent_escape (filename, NULL);
es_printf ("%s-%s.conf:%lu:\"%s\n",
GPGCONF_NAME, GPG_AGENT_NAME,
GC_OPT_FLAG_DEFAULT, filename_esc);
xfree (filename);
xfree (filename_esc);
es_printf ("verbose:%lu:\n"
"quiet:%lu:\n"
"debug-level:%lu:\"none:\n"
"log-file:%lu:\n",
GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME,
GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME,
GC_OPT_FLAG_DEFAULT|GC_OPT_FLAG_RUNTIME,
GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME );
es_printf ("default-cache-ttl:%lu:%d:\n",
GC_OPT_FLAG_DEFAULT|GC_OPT_FLAG_RUNTIME, DEFAULT_CACHE_TTL );
es_printf ("default-cache-ttl-ssh:%lu:%d:\n",
GC_OPT_FLAG_DEFAULT|GC_OPT_FLAG_RUNTIME, DEFAULT_CACHE_TTL_SSH );
es_printf ("max-cache-ttl:%lu:%d:\n",
GC_OPT_FLAG_DEFAULT|GC_OPT_FLAG_RUNTIME, MAX_CACHE_TTL );
es_printf ("max-cache-ttl-ssh:%lu:%d:\n",
GC_OPT_FLAG_DEFAULT|GC_OPT_FLAG_RUNTIME, MAX_CACHE_TTL_SSH );
es_printf ("enforce-passphrase-constraints:%lu:\n",
GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME);
es_printf ("min-passphrase-len:%lu:%d:\n",
GC_OPT_FLAG_DEFAULT|GC_OPT_FLAG_RUNTIME, MIN_PASSPHRASE_LEN );
es_printf ("min-passphrase-nonalpha:%lu:%d:\n",
GC_OPT_FLAG_DEFAULT|GC_OPT_FLAG_RUNTIME,
MIN_PASSPHRASE_NONALPHA);
es_printf ("check-passphrase-pattern:%lu:\n",
GC_OPT_FLAG_DEFAULT|GC_OPT_FLAG_RUNTIME);
es_printf ("max-passphrase-days:%lu:%d:\n",
GC_OPT_FLAG_DEFAULT|GC_OPT_FLAG_RUNTIME,
MAX_PASSPHRASE_DAYS);
es_printf ("enable-passphrase-history:%lu:\n",
GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME);
es_printf ("no-grab:%lu:\n",
GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME);
es_printf ("ignore-cache-for-signing:%lu:\n",
GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME);
es_printf ("no-allow-external-cache:%lu:\n",
GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME);
es_printf ("no-allow-mark-trusted:%lu:\n",
GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME);
es_printf ("disable-scdaemon:%lu:\n",
GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME);
es_printf ("enable-ssh-support:%lu:\n", GC_OPT_FLAG_NONE);
es_printf ("ssh-fingerprint-digest:%lu:\"%s:\n",
GC_OPT_FLAG_DEFAULT|GC_OPT_FLAG_RUNTIME, "md5");
#ifdef HAVE_W32_SYSTEM
es_printf ("enable-putty-support:%lu:\n", GC_OPT_FLAG_NONE);
#endif
es_printf ("no-allow-loopback-pinentry:%lu:\n",
GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME);
es_printf ("allow-emacs-pinentry:%lu:\n",
GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME);
es_printf ("pinentry-timeout:%lu:0:\n",
GC_OPT_FLAG_DEFAULT|GC_OPT_FLAG_RUNTIME);
es_printf ("grab:%lu:\n",
GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME);
agent_exit (0);
}
/* Now start with logging to a file if this is desired. */
if (logfile)
{
log_set_file (logfile);
log_set_prefix (NULL, (GPGRT_LOG_WITH_PREFIX
| GPGRT_LOG_WITH_TIME
| GPGRT_LOG_WITH_PID));
current_logfile = xstrdup (logfile);
}
/* Make sure that we have a default ttyname. */
if (!default_ttyname && gnupg_ttyname (1))
default_ttyname = xstrdup (gnupg_ttyname (1));
if (!default_ttytype && getenv ("TERM"))
default_ttytype = xstrdup (getenv ("TERM"));
if (pipe_server)
{
/* This is the simple pipe based server */
ctrl_t ctrl;
initialize_modules ();
ctrl = xtrycalloc (1, sizeof *ctrl);
if (!ctrl)
{
log_error ("error allocating connection control data: %s\n",
strerror (errno) );
agent_exit (1);
}
ctrl->session_env = session_env_new ();
if (!ctrl->session_env)
{
log_error ("error allocating session environment block: %s\n",
strerror (errno) );
xfree (ctrl);
agent_exit (1);
}
agent_init_default_ctrl (ctrl);
start_command_handler (ctrl, GNUPG_INVALID_FD, GNUPG_INVALID_FD);
agent_deinit_default_ctrl (ctrl);
xfree (ctrl);
}
else if (is_supervised)
{
#ifndef HAVE_W32_SYSTEM
gnupg_fd_t fd, fd_extra, fd_browser, fd_ssh;
initialize_modules ();
/* when supervised and sending logs to stderr, the process
supervisor should handle log entry metadata (pid, name,
timestamp) */
if (!logfile)
log_set_prefix (NULL, 0);
log_info ("%s %s starting in supervised mode.\n",
strusage(11), strusage(13) );
/* See below in "regular server mode" on why we remove certain
* envvars. */
if (!opt.keep_display)
gnupg_unsetenv ("DISPLAY");
gnupg_unsetenv ("INSIDE_EMACS");
/* Virtually create the sockets. Note that we use -1 here
* because the whole thing works only on Unix. */
map_supervised_sockets (&fd, &fd_extra, &fd_browser, &fd_ssh);
if (fd == -1)
log_fatal ("no standard socket provided\n");
#ifdef HAVE_SIGPROCMASK
if (startup_signal_mask_valid)
{
if (sigprocmask (SIG_SETMASK, &startup_signal_mask, NULL))
log_error ("error restoring signal mask: %s\n",
strerror (errno));
}
else
log_info ("no saved signal mask\n");
#endif /*HAVE_SIGPROCMASK*/
log_info ("listening on: std=%d extra=%d browser=%d ssh=%d\n",
fd, fd_extra, fd_browser, fd_ssh);
handle_connections (fd, fd_extra, fd_browser, fd_ssh);
#endif /*!HAVE_W32_SYSTEM*/
}
else if (!is_daemon)
; /* NOTREACHED */
else
{ /* Regular server mode */
gnupg_fd_t fd;
gnupg_fd_t fd_extra = GNUPG_INVALID_FD;
gnupg_fd_t fd_browser = GNUPG_INVALID_FD;
gnupg_fd_t fd_ssh = GNUPG_INVALID_FD;
#ifndef HAVE_W32_SYSTEM
pid_t pid;
#endif
/* Remove the DISPLAY variable so that a pinentry does not
default to a specific display. There is still a default
display when gpg-agent was started using --display or a
client requested this using an OPTION command. Note, that we
don't do this when running in reverse daemon mode (i.e. when
exec the program given as arguments). */
#ifndef HAVE_W32_SYSTEM
if (!opt.keep_display && !argc)
gnupg_unsetenv ("DISPLAY");
#endif
/* Remove the INSIDE_EMACS variable so that a pinentry does not
always try to interact with Emacs. The variable is set when
a client requested this using an OPTION command. */
gnupg_unsetenv ("INSIDE_EMACS");
/* Create the sockets. */
socket_name = create_socket_name (GPG_AGENT_SOCK_NAME, 1);
fd = create_server_socket (socket_name, 1, 0,
&redir_socket_name, &socket_nonce);
if (opt.extra_socket)
{
if (socket_name_extra)
socket_name_extra = create_socket_name (socket_name_extra, 0);
else
socket_name_extra = create_socket_name
/**/ (GPG_AGENT_EXTRA_SOCK_NAME, 1);
opt.extra_socket = 2; /* Indicate that it has been malloced. */
fd_extra = create_server_socket (socket_name_extra, 0, 0,
&redir_socket_name_extra,
&socket_nonce_extra);
}
if (opt.browser_socket)
{
if (socket_name_browser)
socket_name_browser = create_socket_name (socket_name_browser, 0);
else
socket_name_browser= create_socket_name
/**/ (GPG_AGENT_BROWSER_SOCK_NAME, 1);
opt.browser_socket = 2; /* Indicate that it has been malloced. */
fd_browser = create_server_socket (socket_name_browser, 0, 0,
&redir_socket_name_browser,
&socket_nonce_browser);
}
socket_name_ssh = create_socket_name (GPG_AGENT_SSH_SOCK_NAME, 1);
fd_ssh = create_server_socket (socket_name_ssh, 0, 1,
&redir_socket_name_ssh,
&socket_nonce_ssh);
/* If we are going to exec a program in the parent, we record
the PID, so that the child may check whether the program is
still alive. */
if (argc)
parent_pid = getpid ();
fflush (NULL);
#ifdef HAVE_W32_SYSTEM
(void)csh_style;
(void)nodetach;
initialize_modules ();
#else /*!HAVE_W32_SYSTEM*/
pid = fork ();
if (pid == (pid_t)-1)
{
log_fatal ("fork failed: %s\n", strerror (errno) );
exit (1);
}
else if (pid)
{ /* We are the parent */
char *infostr_ssh_sock, *infostr_ssh_valid;
/* Close the socket FD. */
close (fd);
/* The signal mask might not be correct right now and thus
we restore it. That is not strictly necessary but some
programs falsely assume a cleared signal mask. */
#ifdef HAVE_SIGPROCMASK
if (startup_signal_mask_valid)
{
if (sigprocmask (SIG_SETMASK, &startup_signal_mask, NULL))
log_error ("error restoring signal mask: %s\n",
strerror (errno));
}
else
log_info ("no saved signal mask\n");
#endif /*HAVE_SIGPROCMASK*/
/* Create the SSH info string if enabled. */
if (ssh_support)
{
if (asprintf (&infostr_ssh_sock, "SSH_AUTH_SOCK=%s",
socket_name_ssh) < 0)
{
log_error ("out of core\n");
kill (pid, SIGTERM);
exit (1);
}
if (asprintf (&infostr_ssh_valid, "gnupg_SSH_AUTH_SOCK_by=%lu",
(unsigned long)getpid()) < 0)
{
log_error ("out of core\n");
kill (pid, SIGTERM);
exit (1);
}
}
*socket_name = 0; /* Don't let cleanup() remove the socket -
the child should do this from now on */
if (opt.extra_socket)
*socket_name_extra = 0;
if (opt.browser_socket)
*socket_name_browser = 0;
*socket_name_ssh = 0;
if (argc)
{ /* Run the program given on the commandline. */
if (ssh_support && (putenv (infostr_ssh_sock)
|| putenv (infostr_ssh_valid)))
{
log_error ("failed to set environment: %s\n",
strerror (errno) );
kill (pid, SIGTERM );
exit (1);
}
/* Close all the file descriptors except the standard
ones and those open at startup. We explicitly don't
close 0,1,2 in case something went wrong collecting
them at startup. */
close_all_fds (3, startup_fd_list);
/* Run the command. */
execvp (argv[0], argv);
log_error ("failed to run the command: %s\n", strerror (errno));
kill (pid, SIGTERM);
exit (1);
}
else
{
/* Print the environment string, so that the caller can use
shell's eval to set it */
if (csh_style)
{
if (ssh_support)
{
*strchr (infostr_ssh_sock, '=') = ' ';
es_printf ("setenv %s;\n", infostr_ssh_sock);
}
}
else
{
if (ssh_support)
{
es_printf ("%s; export SSH_AUTH_SOCK;\n",
infostr_ssh_sock);
}
}
if (ssh_support)
{
xfree (infostr_ssh_sock);
xfree (infostr_ssh_valid);
}
exit (0);
}
/*NOTREACHED*/
} /* End parent */
/*
This is the child
*/
initialize_modules ();
/* Detach from tty and put process into a new session */
if (!nodetach )
{
int i;
unsigned int oldflags;
/* Close stdin, stdout and stderr unless it is the log stream */
for (i=0; i <= 2; i++)
{
if (!log_test_fd (i) && i != fd )
{
if ( ! close (i)
&& open ("/dev/null", i? O_WRONLY : O_RDONLY) == -1)
{
log_error ("failed to open '%s': %s\n",
"/dev/null", strerror (errno));
cleanup ();
exit (1);
}
}
}
if (setsid() == -1)
{
log_error ("setsid() failed: %s\n", strerror(errno) );
cleanup ();
exit (1);
}
log_get_prefix (&oldflags);
log_set_prefix (NULL, oldflags | GPGRT_LOG_RUN_DETACHED);
opt.running_detached = 1;
/* Unless we are running with a program given on the command
* line we can assume that the inotify things works and thus
* we can avoid the regular stat calls. */
if (!argc)
reliable_homedir_inotify = 1;
}
{
struct sigaction sa;
sa.sa_handler = SIG_IGN;
sigemptyset (&sa.sa_mask);
sa.sa_flags = 0;
sigaction (SIGPIPE, &sa, NULL);
}
#endif /*!HAVE_W32_SYSTEM*/
if (gnupg_chdir (gnupg_daemon_rootdir ()))
{
log_error ("chdir to '%s' failed: %s\n",
gnupg_daemon_rootdir (), strerror (errno));
exit (1);
}
log_info ("%s %s started\n", strusage(11), strusage(13) );
handle_connections (fd, fd_extra, fd_browser, fd_ssh);
assuan_sock_close (fd);
}
return 0;
}
/* Exit entry point. This function should be called instead of a
plain exit. */
void
agent_exit (int rc)
{
/*FIXME: update_random_seed_file();*/
/* We run our cleanup handler because that may close cipher contexts
stored in secure memory and thus this needs to be done before we
explicitly terminate secure memory. */
cleanup ();
#if 1
/* at this time a bit annoying */
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 );
#endif
gcry_control (GCRYCTL_TERM_SECMEM );
rc = rc? rc : log_get_errorcount(0)? 2 : 0;
exit (rc);
}
/* This is our callback function for gcrypt progress messages. It is
set once at startup and dispatches progress messages to the
corresponding threads of the agent. */
static void
agent_libgcrypt_progress_cb (void *data, const char *what, int printchar,
int current, int total)
{
struct progress_dispatch_s *dispatch;
npth_t mytid = npth_self ();
(void)data;
for (dispatch = progress_dispatch_list; dispatch; dispatch = dispatch->next)
if (dispatch->ctrl && dispatch->tid == mytid)
break;
if (dispatch && dispatch->cb)
dispatch->cb (dispatch->ctrl, what, printchar, current, total);
/* Libgcrypt < 1.8 does not know about nPth and thus when it reads
* from /dev/random this will block the process. To mitigate this
* problem we yield the thread when Libgcrypt tells us that it needs
* more entropy. This way other threads have chance to run. */
#if GCRYPT_VERSION_NUMBER < 0x010800 /* 1.8.0 */
if (what && !strcmp (what, "need_entropy"))
{
#if GPGRT_VERSION_NUMBER < 0x011900 /* 1.25 */
/* In older gpg-error versions gpgrt_yield is buggy for use with
* nPth and thus we need to resort to a sleep call. */
npth_usleep (1000); /* 1ms */
#else
gpgrt_yield ();
#endif
}
#endif
}
/* If a progress dispatcher callback has been associated with the
* current connection unregister it. */
static void
unregister_progress_cb (void)
{
struct progress_dispatch_s *dispatch;
npth_t mytid = npth_self ();
for (dispatch = progress_dispatch_list; dispatch; dispatch = dispatch->next)
if (dispatch->ctrl && dispatch->tid == mytid)
break;
if (dispatch)
{
dispatch->ctrl = NULL;
dispatch->cb = NULL;
}
}
/* Setup a progress callback CB for the current connection. Using a
* CB of NULL disables the callback. */
void
agent_set_progress_cb (void (*cb)(ctrl_t ctrl, const char *what,
int printchar, int current, int total),
ctrl_t ctrl)
{
struct progress_dispatch_s *dispatch, *firstfree;
npth_t mytid = npth_self ();
firstfree = NULL;
for (dispatch = progress_dispatch_list; dispatch; dispatch = dispatch->next)
{
if (dispatch->ctrl && dispatch->tid == mytid)
break;
if (!dispatch->ctrl && !firstfree)
firstfree = dispatch;
}
if (!dispatch) /* None allocated: Reuse or allocate a new one. */
{
if (firstfree)
{
dispatch = firstfree;
}
else if ((dispatch = xtrycalloc (1, sizeof *dispatch)))
{
dispatch->next = progress_dispatch_list;
progress_dispatch_list = dispatch;
}
else
{
log_error ("error allocating new progress dispatcher slot: %s\n",
gpg_strerror (gpg_error_from_syserror ()));
return;
}
dispatch->ctrl = ctrl;
dispatch->tid = mytid;
}
dispatch->cb = cb;
}
/* Each thread has its own local variables conveyed by a control
structure usually identified by an argument named CTRL. This
function is called immediately after allocating the control
structure. Its purpose is to setup the default values for that
structure. Note that some values may have already been set. */
static void
agent_init_default_ctrl (ctrl_t ctrl)
{
- assert (ctrl->session_env);
+ log_assert (ctrl->session_env);
/* Note we ignore malloc errors because we can't do much about it
and the request will fail anyway shortly after this
initialization. */
session_env_setenv (ctrl->session_env, "DISPLAY", default_display);
session_env_setenv (ctrl->session_env, "GPG_TTY", default_ttyname);
session_env_setenv (ctrl->session_env, "TERM", default_ttytype);
session_env_setenv (ctrl->session_env, "XAUTHORITY", default_xauthority);
session_env_setenv (ctrl->session_env, "PINENTRY_USER_DATA", NULL);
if (ctrl->lc_ctype)
xfree (ctrl->lc_ctype);
ctrl->lc_ctype = default_lc_ctype? xtrystrdup (default_lc_ctype) : NULL;
if (ctrl->lc_messages)
xfree (ctrl->lc_messages);
ctrl->lc_messages = default_lc_messages? xtrystrdup (default_lc_messages)
/**/ : NULL;
ctrl->cache_ttl_opt_preset = CACHE_TTL_OPT_PRESET;
}
/* Release all resources allocated by default in the control
structure. This is the counterpart to agent_init_default_ctrl. */
static void
agent_deinit_default_ctrl (ctrl_t ctrl)
{
unregister_progress_cb ();
session_env_release (ctrl->session_env);
if (ctrl->lc_ctype)
xfree (ctrl->lc_ctype);
if (ctrl->lc_messages)
xfree (ctrl->lc_messages);
}
/* Because the ssh protocol does not send us information about the
current TTY setting, we use this function to use those from startup
or those explicitly set. This is also used for the restricted mode
where we ignore requests to change the environment. */
gpg_error_t
agent_copy_startup_env (ctrl_t ctrl)
{
gpg_error_t err = 0;
int iterator = 0;
const char *name, *value;
while (!err && (name = session_env_list_stdenvnames (&iterator, NULL)))
{
if ((value = session_env_getenv (opt.startup_env, name)))
err = session_env_setenv (ctrl->session_env, name, value);
}
if (!err && !ctrl->lc_ctype && opt.startup_lc_ctype)
if (!(ctrl->lc_ctype = xtrystrdup (opt.startup_lc_ctype)))
err = gpg_error_from_syserror ();
if (!err && !ctrl->lc_messages && opt.startup_lc_messages)
if (!(ctrl->lc_messages = xtrystrdup (opt.startup_lc_messages)))
err = gpg_error_from_syserror ();
if (err)
log_error ("error setting default session environment: %s\n",
gpg_strerror (err));
return err;
}
/* Reread parts of the configuration. Note, that this function is
obviously not thread-safe and should only be called from the PTH
signal handler.
Fixme: Due to the way the argument parsing works, we create a
memory leak here for all string type arguments. There is currently
no clean way to tell whether the memory for the argument has been
allocated or points into the process' original arguments. Unless
we have a mechanism to tell this, we need to live on with this. */
static void
reread_configuration (void)
{
ARGPARSE_ARGS pargs;
FILE *fp;
unsigned int configlineno = 0;
int dummy;
if (!config_filename)
return; /* No config file. */
fp = fopen (config_filename, "r");
if (!fp)
{
log_info (_("option file '%s': %s\n"),
config_filename, strerror(errno) );
return;
}
parse_rereadable_options (NULL, 1); /* Start from the default values. */
memset (&pargs, 0, sizeof pargs);
dummy = 0;
pargs.argc = &dummy;
pargs.flags = 1; /* do not remove the args */
while (optfile_parse (fp, config_filename, &configlineno, &pargs, opts) )
{
if (pargs.r_opt < -1)
pargs.err = 1; /* Print a warning. */
else /* Try to parse this option - ignore unchangeable ones. */
parse_rereadable_options (&pargs, 1);
}
fclose (fp);
finalize_rereadable_options ();
set_debug ();
}
/* Return the file name of the socket we are using for native
requests. */
const char *
get_agent_socket_name (void)
{
const char *s = socket_name;
return (s && *s)? s : NULL;
}
/* Return the file name of the socket we are using for SSH
requests. */
const char *
get_agent_ssh_socket_name (void)
{
const char *s = socket_name_ssh;
return (s && *s)? s : NULL;
}
/* Return the number of active connections. */
int
get_agent_active_connection_count (void)
{
return active_connections;
}
/* Under W32, this function returns the handle of the scdaemon
notification event. Calling it the first time creates that
event. */
#if defined(HAVE_W32_SYSTEM) && !defined(HAVE_W32CE_SYSTEM)
void *
get_agent_scd_notify_event (void)
{
static HANDLE the_event = INVALID_HANDLE_VALUE;
if (the_event == INVALID_HANDLE_VALUE)
{
HANDLE h, h2;
SECURITY_ATTRIBUTES sa = { sizeof (SECURITY_ATTRIBUTES), NULL, TRUE};
/* We need to use a manual reset event object due to the way our
w32-pth wait function works: If we would use an automatic
reset event we are not able to figure out which handle has
been signaled because at the time we single out the signaled
handles using WFSO the event has already been reset due to
the WFMO. */
h = CreateEvent (&sa, TRUE, FALSE, NULL);
if (!h)
log_error ("can't create scd notify event: %s\n", w32_strerror (-1) );
else if (!DuplicateHandle (GetCurrentProcess(), h,
GetCurrentProcess(), &h2,
EVENT_MODIFY_STATE|SYNCHRONIZE, TRUE, 0))
{
log_error ("setting synchronize for scd notify event failed: %s\n",
w32_strerror (-1) );
CloseHandle (h);
}
else
{
CloseHandle (h);
the_event = h2;
}
}
return the_event;
}
#endif /*HAVE_W32_SYSTEM && !HAVE_W32CE_SYSTEM*/
/* Create a name for the socket in the home directory as using
STANDARD_NAME. We also check for valid characters as well as
against a maximum allowed length for a unix domain socket is done.
The function terminates the process in case of an error. Returns:
Pointer to an allocated string with the absolute name of the socket
used. */
static char *
create_socket_name (char *standard_name, int with_homedir)
{
char *name;
if (with_homedir)
name = make_filename (gnupg_socketdir (), standard_name, NULL);
else
name = make_filename (standard_name, NULL);
if (strchr (name, PATHSEP_C))
{
log_error (("'%s' are not allowed in the socket name\n"), PATHSEP_S);
agent_exit (2);
}
return name;
}
/* Create a Unix domain socket with NAME. Returns the file descriptor
or terminates the process in case of an error. Note that this
function needs to be used for the regular socket first (indicated
by PRIMARY) and only then for the extra and the ssh sockets. If
the socket has been redirected the name of the real socket is
stored as a malloced string at R_REDIR_NAME. If CYGWIN is set a
Cygwin compatible socket is created (Windows only). */
static gnupg_fd_t
create_server_socket (char *name, int primary, int cygwin,
char **r_redir_name, assuan_sock_nonce_t *nonce)
{
struct sockaddr *addr;
struct sockaddr_un *unaddr;
socklen_t len;
gnupg_fd_t fd;
int rc;
xfree (*r_redir_name);
*r_redir_name = NULL;
fd = assuan_sock_new (AF_UNIX, SOCK_STREAM, 0);
if (fd == ASSUAN_INVALID_FD)
{
log_error (_("can't create socket: %s\n"), strerror (errno));
*name = 0; /* Inhibit removal of the socket by cleanup(). */
agent_exit (2);
}
if (cygwin)
assuan_sock_set_flag (fd, "cygwin", 1);
unaddr = xmalloc (sizeof *unaddr);
addr = (struct sockaddr*)unaddr;
{
int redirected;
if (assuan_sock_set_sockaddr_un (name, addr, &redirected))
{
if (errno == ENAMETOOLONG)
log_error (_("socket name '%s' is too long\n"), name);
else
log_error ("error preparing socket '%s': %s\n",
name, gpg_strerror (gpg_error_from_syserror ()));
*name = 0; /* Inhibit removal of the socket by cleanup(). */
xfree (unaddr);
agent_exit (2);
}
if (redirected)
{
*r_redir_name = xstrdup (unaddr->sun_path);
if (opt.verbose)
log_info ("redirecting socket '%s' to '%s'\n", name, *r_redir_name);
}
}
len = SUN_LEN (unaddr);
rc = assuan_sock_bind (fd, addr, len);
/* Our error code mapping on W32CE returns EEXIST thus we also test
for this. */
if (rc == -1
&& (errno == EADDRINUSE
#ifdef HAVE_W32_SYSTEM
|| errno == EEXIST
#endif
))
{
/* Check whether a gpg-agent is already running. We do this
test only if this is the primary socket. For secondary
sockets we assume that a test for gpg-agent has already been
done and reuse the requested socket. Testing the ssh-socket
is not possible because at this point, though we know the new
Assuan socket, the Assuan server and thus the ssh-agent
server is not yet operational; this would lead to a hang. */
if (primary && !check_for_running_agent (1))
{
log_set_prefix (NULL, GPGRT_LOG_WITH_PREFIX);
log_set_file (NULL);
log_error (_("a gpg-agent is already running - "
"not starting a new one\n"));
*name = 0; /* Inhibit removal of the socket by cleanup(). */
assuan_sock_close (fd);
xfree (unaddr);
agent_exit (2);
}
gnupg_remove (unaddr->sun_path);
rc = assuan_sock_bind (fd, addr, len);
}
if (rc != -1 && (rc=assuan_sock_get_nonce (addr, len, nonce)))
log_error (_("error getting nonce for the socket\n"));
if (rc == -1)
{
/* We use gpg_strerror here because it allows us to get strings
for some W32 socket error codes. */
log_error (_("error binding socket to '%s': %s\n"),
unaddr->sun_path,
gpg_strerror (gpg_error_from_syserror ()));
assuan_sock_close (fd);
*name = 0; /* Inhibit removal of the socket by cleanup(). */
xfree (unaddr);
agent_exit (2);
}
if (gnupg_chmod (unaddr->sun_path, "-rwx"))
log_error (_("can't set permissions of '%s': %s\n"),
unaddr->sun_path, strerror (errno));
if (listen (FD2INT(fd), listen_backlog ) == -1)
{
log_error ("listen(fd,%d) failed: %s\n",
listen_backlog, strerror (errno));
*name = 0; /* Inhibit removal of the socket by cleanup(). */
assuan_sock_close (fd);
xfree (unaddr);
agent_exit (2);
}
if (opt.verbose)
log_info (_("listening on socket '%s'\n"), unaddr->sun_path);
xfree (unaddr);
return fd;
}
/* Check that the directory for storing the private keys exists and
create it if not. This function won't fail as it is only a
convenience function and not strictly necessary. */
static void
create_private_keys_directory (const char *home)
{
char *fname;
struct stat statbuf;
fname = make_filename (home, GNUPG_PRIVATE_KEYS_DIR, NULL);
if (stat (fname, &statbuf) && errno == ENOENT)
{
if (gnupg_mkdir (fname, "-rwx"))
log_error (_("can't create directory '%s': %s\n"),
fname, strerror (errno) );
else if (!opt.quiet)
log_info (_("directory '%s' created\n"), fname);
}
if (gnupg_chmod (fname, "-rwx"))
log_error (_("can't set permissions of '%s': %s\n"),
fname, strerror (errno));
xfree (fname);
}
/* Create the directory only if the supplied directory name is the
same as the default one. This way we avoid to create arbitrary
directories when a non-default home directory is used. To cope
with HOME, we compare only the suffix if we see that the default
homedir does start with a tilde. We don't stop here in case of
problems because other functions will throw an error anyway.*/
static void
create_directories (void)
{
struct stat statbuf;
const char *defhome = standard_homedir ();
char *home;
home = make_filename (gnupg_homedir (), NULL);
if ( stat (home, &statbuf) )
{
if (errno == ENOENT)
{
if (
#ifdef HAVE_W32_SYSTEM
( !compare_filenames (home, defhome) )
#else
(*defhome == '~'
&& (strlen (home) >= strlen (defhome+1)
&& !strcmp (home + strlen(home)
- strlen (defhome+1), defhome+1)))
|| (*defhome != '~' && !strcmp (home, defhome) )
#endif
)
{
if (gnupg_mkdir (home, "-rwx"))
log_error (_("can't create directory '%s': %s\n"),
home, strerror (errno) );
else
{
if (!opt.quiet)
log_info (_("directory '%s' created\n"), home);
create_private_keys_directory (home);
}
}
}
else
log_error (_("stat() failed for '%s': %s\n"), home, strerror (errno));
}
else if ( !S_ISDIR(statbuf.st_mode))
{
log_error (_("can't use '%s' as home directory\n"), home);
}
else /* exists and is a directory. */
{
create_private_keys_directory (home);
}
xfree (home);
}
/* This is the worker for the ticker. It is called every few seconds
and may only do fast operations. */
static void
handle_tick (void)
{
static time_t last_minute;
struct stat statbuf;
if (!last_minute)
last_minute = time (NULL);
/* If we are running as a child of another process, check whether
the parent is still alive and shutdown if not. */
#ifndef HAVE_W32_SYSTEM
if (parent_pid != (pid_t)(-1))
{
if (kill (parent_pid, 0))
{
shutdown_pending = 2;
log_info ("parent process died - shutting down\n");
log_info ("%s %s stopped\n", strusage(11), strusage(13) );
cleanup ();
agent_exit (0);
}
}
#endif /*HAVE_W32_SYSTEM*/
/* Code to be run from time to time. */
#if CHECK_OWN_SOCKET_INTERVAL > 0
if (last_minute + CHECK_OWN_SOCKET_INTERVAL <= time (NULL))
{
check_own_socket ();
last_minute = time (NULL);
}
#endif
/* Need to check for expired cache entries. */
agent_cache_housekeeping ();
/* Check whether the homedir is still available. */
if (!shutdown_pending
&& (!have_homedir_inotify || !reliable_homedir_inotify)
&& stat (gnupg_homedir (), &statbuf) && errno == ENOENT)
{
shutdown_pending = 1;
log_info ("homedir has been removed - shutting down\n");
}
}
/* A global function which allows us to call the reload stuff from
other places too. This is only used when build for W32. */
void
agent_sighup_action (void)
{
log_info ("SIGHUP received - "
"re-reading configuration and flushing cache\n");
agent_flush_cache ();
reread_configuration ();
agent_reload_trustlist ();
/* We flush the module name cache so that after installing a
"pinentry" binary that one can be used in case the
"pinentry-basic" fallback was in use. */
gnupg_module_name_flush_some ();
+
+ if (opt.disable_scdaemon)
+ agent_card_killscd ();
}
/* A helper function to handle SIGUSR2. */
static void
agent_sigusr2_action (void)
{
if (opt.verbose)
log_info ("SIGUSR2 received - updating card event counter\n");
/* Nothing to check right now. We only increment a counter. */
bump_card_eventcounter ();
}
#ifndef HAVE_W32_SYSTEM
/* The signal handler for this program. It is expected to be run in
its own thread and not in the context of a signal handler. */
static void
handle_signal (int signo)
{
switch (signo)
{
#ifndef HAVE_W32_SYSTEM
case SIGHUP:
agent_sighup_action ();
break;
case SIGUSR1:
log_info ("SIGUSR1 received - printing internal information:\n");
/* Fixme: We need to see how to integrate pth dumping into our
logging system. */
/* pth_ctrl (PTH_CTRL_DUMPSTATE, log_get_stream ()); */
agent_query_dump_state ();
agent_scd_dump_state ();
break;
case SIGUSR2:
agent_sigusr2_action ();
break;
case SIGTERM:
if (!shutdown_pending)
log_info ("SIGTERM received - shutting down ...\n");
else
log_info ("SIGTERM received - still %i open connections\n",
active_connections);
shutdown_pending++;
if (shutdown_pending > 2)
{
log_info ("shutdown forced\n");
log_info ("%s %s stopped\n", strusage(11), strusage(13) );
cleanup ();
agent_exit (0);
}
break;
case SIGINT:
log_info ("SIGINT received - immediate shutdown\n");
log_info( "%s %s stopped\n", strusage(11), strusage(13));
cleanup ();
agent_exit (0);
break;
#endif
default:
log_info ("signal %d received - no action defined\n", signo);
}
}
#endif
/* Check the nonce on a new connection. This is a NOP unless we
are using our Unix domain socket emulation under Windows. */
static int
check_nonce (ctrl_t ctrl, assuan_sock_nonce_t *nonce)
{
if (assuan_sock_check_nonce (ctrl->thread_startup.fd, nonce))
{
log_info (_("error reading nonce on fd %d: %s\n"),
FD2INT(ctrl->thread_startup.fd), strerror (errno));
assuan_sock_close (ctrl->thread_startup.fd);
xfree (ctrl);
return -1;
}
else
return 0;
}
#ifdef HAVE_W32_SYSTEM
/* The window message processing function for Putty. Warning: This
code runs as a native Windows thread. Use of our own functions
needs to be bracket with pth_leave/pth_enter. */
static LRESULT CALLBACK
putty_message_proc (HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
int ret = 0;
int w32rc;
COPYDATASTRUCT *cds;
const char *mapfile;
HANDLE maphd;
PSID mysid = NULL;
PSID mapsid = NULL;
void *data = NULL;
PSECURITY_DESCRIPTOR psd = NULL;
ctrl_t ctrl = NULL;
if (msg != WM_COPYDATA)
{
return DefWindowProc (hwnd, msg, wparam, lparam);
}
cds = (COPYDATASTRUCT*)lparam;
if (cds->dwData != PUTTY_IPC_MAGIC)
return 0; /* Ignore data with the wrong magic. */
mapfile = cds->lpData;
if (!cds->cbData || mapfile[cds->cbData - 1])
return 0; /* Ignore empty and non-properly terminated strings. */
if (DBG_IPC)
{
npth_protect ();
log_debug ("ssh map file '%s'", mapfile);
npth_unprotect ();
}
maphd = OpenFileMapping (FILE_MAP_ALL_ACCESS, FALSE, mapfile);
if (DBG_IPC)
{
npth_protect ();
log_debug ("ssh map handle %p\n", maphd);
npth_unprotect ();
}
if (!maphd || maphd == INVALID_HANDLE_VALUE)
return 0;
npth_protect ();
mysid = w32_get_user_sid ();
if (!mysid)
{
log_error ("error getting my sid\n");
goto leave;
}
w32rc = GetSecurityInfo (maphd, SE_KERNEL_OBJECT,
OWNER_SECURITY_INFORMATION,
&mapsid, NULL, NULL, NULL,
&psd);
if (w32rc)
{
log_error ("error getting sid of ssh map file: rc=%d", w32rc);
goto leave;
}
if (DBG_IPC)
{
char *sidstr;
if (!ConvertSidToStringSid (mysid, &sidstr))
sidstr = NULL;
log_debug (" my sid: '%s'", sidstr? sidstr: "[error]");
LocalFree (sidstr);
if (!ConvertSidToStringSid (mapsid, &sidstr))
sidstr = NULL;
log_debug ("ssh map file sid: '%s'", sidstr? sidstr: "[error]");
LocalFree (sidstr);
}
if (!EqualSid (mysid, mapsid))
{
log_error ("ssh map file has a non-matching sid\n");
goto leave;
}
data = MapViewOfFile (maphd, FILE_MAP_ALL_ACCESS, 0, 0, 0);
if (DBG_IPC)
log_debug ("ssh IPC buffer at %p\n", data);
if (!data)
goto leave;
/* log_printhex ("request:", data, 20); */
ctrl = xtrycalloc (1, sizeof *ctrl);
if (!ctrl)
{
log_error ("error allocating connection control data: %s\n",
strerror (errno) );
goto leave;
}
ctrl->session_env = session_env_new ();
if (!ctrl->session_env)
{
log_error ("error allocating session environment block: %s\n",
strerror (errno) );
goto leave;
}
agent_init_default_ctrl (ctrl);
if (!serve_mmapped_ssh_request (ctrl, data, PUTTY_IPC_MAXLEN))
ret = 1; /* Valid ssh message has been constructed. */
agent_deinit_default_ctrl (ctrl);
/* log_printhex (" reply:", data, 20); */
leave:
xfree (ctrl);
if (data)
UnmapViewOfFile (data);
xfree (mapsid);
if (psd)
LocalFree (psd);
xfree (mysid);
CloseHandle (maphd);
npth_unprotect ();
return ret;
}
#endif /*HAVE_W32_SYSTEM*/
#ifdef HAVE_W32_SYSTEM
/* The thread handling Putty's IPC requests. */
static void *
putty_message_thread (void *arg)
{
WNDCLASS wndwclass = {0, putty_message_proc, 0, 0,
NULL, NULL, NULL, NULL, NULL, "Pageant"};
HWND hwnd;
MSG msg;
(void)arg;
if (opt.verbose)
log_info ("putty message loop thread started\n");
/* The message loop runs as thread independent from our nPth system.
This also means that we need to make sure that we switch back to
our system before calling any no-windows function. */
npth_unprotect ();
/* First create a window to make sure that a message queue exists
for this thread. */
if (!RegisterClass (&wndwclass))
{
npth_protect ();
log_error ("error registering Pageant window class");
return NULL;
}
hwnd = CreateWindowEx (0, "Pageant", "Pageant", 0,
0, 0, 0, 0,
HWND_MESSAGE, /* hWndParent */
NULL, /* hWndMenu */
NULL, /* hInstance */
NULL); /* lpParm */
if (!hwnd)
{
npth_protect ();
log_error ("error creating Pageant window");
return NULL;
}
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
/* Back to nPth. */
npth_protect ();
if (opt.verbose)
log_info ("putty message loop thread stopped\n");
return NULL;
}
#endif /*HAVE_W32_SYSTEM*/
static void *
do_start_connection_thread (ctrl_t ctrl)
{
active_connections++;
agent_init_default_ctrl (ctrl);
if (opt.verbose && !DBG_IPC)
log_info (_("handler 0x%lx for fd %d started\n"),
(unsigned long) npth_self(), FD2INT(ctrl->thread_startup.fd));
start_command_handler (ctrl, GNUPG_INVALID_FD, ctrl->thread_startup.fd);
if (opt.verbose && !DBG_IPC)
log_info (_("handler 0x%lx for fd %d terminated\n"),
(unsigned long) npth_self(), FD2INT(ctrl->thread_startup.fd));
agent_deinit_default_ctrl (ctrl);
xfree (ctrl);
active_connections--;
return NULL;
}
/* This is the standard connection thread's main function. */
static void *
start_connection_thread_std (void *arg)
{
ctrl_t ctrl = arg;
if (check_nonce (ctrl, &socket_nonce))
{
log_error ("handler 0x%lx nonce check FAILED\n",
(unsigned long) npth_self());
return NULL;
}
return do_start_connection_thread (ctrl);
}
/* This is the extra socket connection thread's main function. */
static void *
start_connection_thread_extra (void *arg)
{
ctrl_t ctrl = arg;
if (check_nonce (ctrl, &socket_nonce_extra))
{
log_error ("handler 0x%lx nonce check FAILED\n",
(unsigned long) npth_self());
return NULL;
}
ctrl->restricted = 1;
return do_start_connection_thread (ctrl);
}
/* This is the browser socket connection thread's main function. */
static void *
start_connection_thread_browser (void *arg)
{
ctrl_t ctrl = arg;
if (check_nonce (ctrl, &socket_nonce_browser))
{
log_error ("handler 0x%lx nonce check FAILED\n",
(unsigned long) npth_self());
return NULL;
}
ctrl->restricted = 2;
return do_start_connection_thread (ctrl);
}
/* This is the ssh connection thread's main function. */
static void *
start_connection_thread_ssh (void *arg)
{
ctrl_t ctrl = arg;
if (check_nonce (ctrl, &socket_nonce_ssh))
return NULL;
active_connections++;
agent_init_default_ctrl (ctrl);
if (opt.verbose)
log_info (_("ssh handler 0x%lx for fd %d started\n"),
(unsigned long) npth_self(), FD2INT(ctrl->thread_startup.fd));
start_command_handler_ssh (ctrl, ctrl->thread_startup.fd);
if (opt.verbose)
log_info (_("ssh handler 0x%lx for fd %d terminated\n"),
(unsigned long) npth_self(), FD2INT(ctrl->thread_startup.fd));
agent_deinit_default_ctrl (ctrl);
xfree (ctrl);
active_connections--;
return NULL;
}
/* Connection handler loop. Wait for connection requests and spawn a
thread after accepting a connection. */
static void
handle_connections (gnupg_fd_t listen_fd,
gnupg_fd_t listen_fd_extra,
gnupg_fd_t listen_fd_browser,
gnupg_fd_t listen_fd_ssh)
{
gpg_error_t err;
npth_attr_t tattr;
struct sockaddr_un paddr;
socklen_t plen;
fd_set fdset, read_fdset;
int ret;
gnupg_fd_t fd;
int nfd;
int saved_errno;
struct timespec abstime;
struct timespec curtime;
struct timespec timeout;
#ifdef HAVE_W32_SYSTEM
HANDLE events[2];
unsigned int events_set;
#endif
int sock_inotify_fd = -1;
int home_inotify_fd = -1;
struct {
const char *name;
void *(*func) (void *arg);
gnupg_fd_t l_fd;
} listentbl[] = {
{ "std", start_connection_thread_std },
{ "extra", start_connection_thread_extra },
{ "browser", start_connection_thread_browser },
{ "ssh", start_connection_thread_ssh }
};
ret = npth_attr_init(&tattr);
if (ret)
log_fatal ("error allocating thread attributes: %s\n",
strerror (ret));
npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED);
#ifndef HAVE_W32_SYSTEM
npth_sigev_init ();
npth_sigev_add (SIGHUP);
npth_sigev_add (SIGUSR1);
npth_sigev_add (SIGUSR2);
npth_sigev_add (SIGINT);
npth_sigev_add (SIGTERM);
npth_sigev_fini ();
#else
# ifdef HAVE_W32CE_SYSTEM
/* Use a dummy event. */
sigs = 0;
ev = pth_event (PTH_EVENT_SIGS, &sigs, &signo);
# else
events[0] = get_agent_scd_notify_event ();
events[1] = INVALID_HANDLE_VALUE;
# endif
#endif
if (disable_check_own_socket)
sock_inotify_fd = -1;
else if ((err = gnupg_inotify_watch_socket (&sock_inotify_fd, socket_name)))
{
if (gpg_err_code (err) != GPG_ERR_NOT_SUPPORTED)
log_info ("error enabling daemon termination by socket removal: %s\n",
gpg_strerror (err));
}
if (disable_check_own_socket)
home_inotify_fd = -1;
else if ((err = gnupg_inotify_watch_delete_self (&home_inotify_fd,
gnupg_homedir ())))
{
if (gpg_err_code (err) != GPG_ERR_NOT_SUPPORTED)
log_info ("error enabling daemon termination by homedir removal: %s\n",
gpg_strerror (err));
}
else
have_homedir_inotify = 1;
/* On Windows we need to fire up a separate thread to listen for
requests from Putty (an SSH client), so we can replace Putty's
Pageant (its ssh-agent implementation). */
#ifdef HAVE_W32_SYSTEM
if (putty_support)
{
npth_t thread;
ret = npth_create (&thread, &tattr, putty_message_thread, NULL);
if (ret)
{
log_error ("error spawning putty message loop: %s\n", strerror (ret));
}
}
#endif /*HAVE_W32_SYSTEM*/
/* Set a flag to tell call-scd.c that it may enable event
notifications. */
opt.sigusr2_enabled = 1;
FD_ZERO (&fdset);
FD_SET (FD2INT (listen_fd), &fdset);
nfd = FD2INT (listen_fd);
if (listen_fd_extra != GNUPG_INVALID_FD)
{
FD_SET ( FD2INT(listen_fd_extra), &fdset);
if (FD2INT (listen_fd_extra) > nfd)
nfd = FD2INT (listen_fd_extra);
}
if (listen_fd_browser != GNUPG_INVALID_FD)
{
FD_SET ( FD2INT(listen_fd_browser), &fdset);
if (FD2INT (listen_fd_browser) > nfd)
nfd = FD2INT (listen_fd_browser);
}
if (listen_fd_ssh != GNUPG_INVALID_FD)
{
FD_SET ( FD2INT(listen_fd_ssh), &fdset);
if (FD2INT (listen_fd_ssh) > nfd)
nfd = FD2INT (listen_fd_ssh);
}
if (sock_inotify_fd != -1)
{
FD_SET (sock_inotify_fd, &fdset);
if (sock_inotify_fd > nfd)
nfd = sock_inotify_fd;
}
if (home_inotify_fd != -1)
{
FD_SET (home_inotify_fd, &fdset);
if (home_inotify_fd > nfd)
nfd = home_inotify_fd;
}
listentbl[0].l_fd = listen_fd;
listentbl[1].l_fd = listen_fd_extra;
listentbl[2].l_fd = listen_fd_browser;
listentbl[3].l_fd = listen_fd_ssh;
npth_clock_gettime (&abstime);
abstime.tv_sec += TIMERTICK_INTERVAL;
for (;;)
{
/* Shutdown test. */
if (shutdown_pending)
{
if (active_connections == 0)
break; /* ready */
/* Do not accept new connections but keep on running the
* loop to cope with the timer events.
*
* Note that we do not close the listening socket because a
* client trying to connect to that socket would instead
* restart a new dirmngr instance - which is unlikely the
* intention of a shutdown. */
FD_ZERO (&fdset);
nfd = -1;
if (sock_inotify_fd != -1)
{
FD_SET (sock_inotify_fd, &fdset);
nfd = sock_inotify_fd;
}
if (home_inotify_fd != -1)
{
FD_SET (home_inotify_fd, &fdset);
if (home_inotify_fd > nfd)
nfd = home_inotify_fd;
}
}
/* POSIX says that fd_set should be implemented as a structure,
thus a simple assignment is fine to copy the entire set. */
read_fdset = fdset;
npth_clock_gettime (&curtime);
if (!(npth_timercmp (&curtime, &abstime, <)))
{
/* Timeout. */
handle_tick ();
npth_clock_gettime (&abstime);
abstime.tv_sec += TIMERTICK_INTERVAL;
}
npth_timersub (&abstime, &curtime, &timeout);
#ifndef HAVE_W32_SYSTEM
ret = npth_pselect (nfd+1, &read_fdset, NULL, NULL, &timeout,
npth_sigev_sigmask ());
saved_errno = errno;
{
int signo;
while (npth_sigev_get_pending (&signo))
handle_signal (signo);
}
#else
ret = npth_eselect (nfd+1, &read_fdset, NULL, NULL, &timeout,
events, &events_set);
saved_errno = errno;
/* This is valid even if npth_eselect returns an error. */
if (events_set & 1)
agent_sigusr2_action ();
#endif
if (ret == -1 && saved_errno != EINTR)
{
log_error (_("npth_pselect failed: %s - waiting 1s\n"),
strerror (saved_errno));
npth_sleep (1);
continue;
}
if (ret <= 0)
/* Interrupt or timeout. Will be handled when calculating the
next timeout. */
continue;
/* The inotify fds are set even when a shutdown is pending (see
* above). So we must handle them in any case. To avoid that
* they trigger a second time we close them immediately. */
if (sock_inotify_fd != -1
&& FD_ISSET (sock_inotify_fd, &read_fdset)
&& gnupg_inotify_has_name (sock_inotify_fd, GPG_AGENT_SOCK_NAME))
{
shutdown_pending = 1;
close (sock_inotify_fd);
sock_inotify_fd = -1;
log_info ("socket file has been removed - shutting down\n");
}
if (home_inotify_fd != -1
&& FD_ISSET (home_inotify_fd, &read_fdset))
{
shutdown_pending = 1;
close (home_inotify_fd);
home_inotify_fd = -1;
log_info ("homedir has been removed - shutting down\n");
}
if (!shutdown_pending)
{
int idx;
ctrl_t ctrl;
npth_t thread;
for (idx=0; idx < DIM(listentbl); idx++)
{
if (listentbl[idx].l_fd == GNUPG_INVALID_FD)
continue;
if (!FD_ISSET (FD2INT (listentbl[idx].l_fd), &read_fdset))
continue;
plen = sizeof paddr;
fd = INT2FD (npth_accept (FD2INT(listentbl[idx].l_fd),
(struct sockaddr *)&paddr, &plen));
if (fd == GNUPG_INVALID_FD)
{
log_error ("accept failed for %s: %s\n",
listentbl[idx].name, strerror (errno));
}
else if ( !(ctrl = xtrycalloc (1, sizeof *ctrl)))
{
log_error ("error allocating connection data for %s: %s\n",
listentbl[idx].name, strerror (errno) );
assuan_sock_close (fd);
}
else if ( !(ctrl->session_env = session_env_new ()))
{
log_error ("error allocating session env block for %s: %s\n",
listentbl[idx].name, strerror (errno) );
xfree (ctrl);
assuan_sock_close (fd);
}
else
{
ctrl->thread_startup.fd = fd;
ret = npth_create (&thread, &tattr,
listentbl[idx].func, ctrl);
if (ret)
{
log_error ("error spawning connection handler for %s:"
" %s\n", listentbl[idx].name, strerror (ret));
assuan_sock_close (fd);
xfree (ctrl);
}
}
}
}
}
if (sock_inotify_fd != -1)
close (sock_inotify_fd);
if (home_inotify_fd != -1)
close (home_inotify_fd);
cleanup ();
log_info (_("%s %s stopped\n"), strusage(11), strusage(13));
npth_attr_destroy (&tattr);
}
/* Helper for check_own_socket. */
static gpg_error_t
check_own_socket_pid_cb (void *opaque, const void *buffer, size_t length)
{
membuf_t *mb = opaque;
put_membuf (mb, buffer, length);
return 0;
}
/* The thread running the actual check. We need to run this in a
separate thread so that check_own_thread can be called from the
timer tick. */
static void *
check_own_socket_thread (void *arg)
{
int rc;
char *sockname = arg;
assuan_context_t ctx = NULL;
membuf_t mb;
char *buffer;
check_own_socket_running++;
rc = assuan_new (&ctx);
if (rc)
{
log_error ("can't allocate assuan context: %s\n", gpg_strerror (rc));
goto leave;
}
assuan_set_flag (ctx, ASSUAN_NO_LOGGING, 1);
rc = assuan_socket_connect (ctx, sockname, (pid_t)(-1), 0);
if (rc)
{
log_error ("can't connect my own socket: %s\n", gpg_strerror (rc));
goto leave;
}
init_membuf (&mb, 100);
rc = assuan_transact (ctx, "GETINFO pid", check_own_socket_pid_cb, &mb,
NULL, NULL, NULL, NULL);
put_membuf (&mb, "", 1);
buffer = get_membuf (&mb, NULL);
if (rc || !buffer)
{
log_error ("sending command \"%s\" to my own socket failed: %s\n",
"GETINFO pid", gpg_strerror (rc));
rc = 1;
}
else if ( (pid_t)strtoul (buffer, NULL, 10) != getpid ())
{
log_error ("socket is now serviced by another server\n");
rc = 1;
}
else if (opt.verbose > 1)
log_error ("socket is still served by this server\n");
xfree (buffer);
leave:
xfree (sockname);
if (ctx)
assuan_release (ctx);
if (rc)
{
/* We may not remove the socket as it is now in use by another
server. */
inhibit_socket_removal = 1;
shutdown_pending = 2;
log_info ("this process is useless - shutting down\n");
}
check_own_socket_running--;
return NULL;
}
/* Check whether we are still listening on our own socket. In case
another gpg-agent process started after us has taken ownership of
our socket, we would linger around without any real task. Thus we
better check once in a while whether we are really needed. */
static void
check_own_socket (void)
{
char *sockname;
npth_t thread;
npth_attr_t tattr;
int err;
if (disable_check_own_socket)
return;
if (check_own_socket_running || shutdown_pending)
return; /* Still running or already shutting down. */
sockname = make_filename_try (gnupg_socketdir (), GPG_AGENT_SOCK_NAME, NULL);
if (!sockname)
return; /* Out of memory. */
err = npth_attr_init (&tattr);
if (err)
return;
npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED);
err = npth_create (&thread, &tattr, check_own_socket_thread, sockname);
if (err)
log_error ("error spawning check_own_socket_thread: %s\n", strerror (err));
npth_attr_destroy (&tattr);
}
/* Figure out whether an agent is available and running. Prints an
error if not. If SILENT is true, no messages are printed.
Returns 0 if the agent is running. */
static int
check_for_running_agent (int silent)
{
gpg_error_t err;
char *sockname;
assuan_context_t ctx = NULL;
sockname = make_filename_try (gnupg_socketdir (), GPG_AGENT_SOCK_NAME, NULL);
if (!sockname)
return gpg_error_from_syserror ();
err = assuan_new (&ctx);
if (!err)
err = assuan_socket_connect (ctx, sockname, (pid_t)(-1), 0);
xfree (sockname);
if (err)
{
if (!silent)
log_error (_("no gpg-agent running in this session\n"));
if (ctx)
assuan_release (ctx);
return -1;
}
if (!opt.quiet && !silent)
log_info ("gpg-agent running and available\n");
assuan_release (ctx);
return 0;
}
diff --git a/agent/keyformat.txt b/agent/keyformat.txt
index c7426db9d..e2ca05c84 100644
--- a/agent/keyformat.txt
+++ b/agent/keyformat.txt
@@ -1,425 +1,461 @@
keyformat.txt emacs, please switch to -*- org -*- mode
-------------
Some notes on the format of the secret keys used with gpg-agent.
* Location of keys
The secret keys[1] are stored on a per file basis in a directory below
the ~/.gnupg home directory. This directory is named
private-keys-v1.d
and should have permissions 700.
The secret keys are stored in files with a name matching the
hexadecimal representation of the keygrip[2] and suffixed with ".key".
* Extended Private Key Format
-GnuPG 2.3+ will use a new format to store private keys that is both
+** Overview
+GnuPG 2.3+ uses a new format to store private keys that is both
more flexible and easier to read and edit by human beings. The new
format stores name,value-pairs using the common mail and http header
convention. Example (here indented with two spaces):
Description: Key to sign all GnuPG released tarballs.
The key is actually stored on a smart card.
Use-for-ssh: yes
OpenSSH-cert: long base64 encoded string wrapped so that this
key file can be easily edited with a standard editor.
+ Token: D2760001240102000005000011730000 OPENPGP.1
+ Token: FF020001008A77C1 PIV.9C
Key: (shadowed-private-key
(rsa
(n #00AA1AD2A55FD8C8FDE9E1941772D9CC903FA43B268CB1B5A1BAFDC900
2961D8AEA153424DC851EF13B83AC64FBE365C59DC1BD3E83017C90D4365B4
83E02859FC13DB5842A00E969480DB96CE6F7D1C03600392B8E08EF0C01FC7
19F9F9086B25AD39B4F1C2A2DF3E2BE317110CFFF21D4A11455508FE407997
601260816C8422297C0637BB291C3A079B9CB38A92CE9E551F80AA0EBF4F0E
72C3F250461E4D31F23A7087857FC8438324A013634563D34EFDDCBF2EA80D
F9662C9CCD4BEF2522D8BDFED24CEF78DC6B309317407EAC576D889F88ADA0
8C4FFB480981FB68C5C6CA27503381D41018E6CDC52AAAE46B166BDC10637A
E186A02BA2497FDC5D1221#)
(e #00010001#)
(shadowed t1-v1
(#D2760001240102000005000011730000# OPENPGP.1)
)))
GnuPG 2.2 is able to read and update keys using the new format, but
will not create new files using the new format. Furthermore, it only
makes use of the value stored under the name 'Key:'.
Keys in the extended format can be recognized by looking at the first
byte of the file. If it starts with a '(' it is a naked S-expression,
otherwise it is a key in extended format.
-** Names
-
+*** Names
A name must start with a letter and end with a colon. Valid
characters are all ASCII letters, numbers and the hyphen. Comparison
of names is done case insensitively. Names may be used several times
-to represent an array of values.
-
-The name "Key:" is special in that it may occur only once and the
-associated value holds the actual S-expression with the cryptographic
-key. The S-expression is formatted using the 'Advanced Format'
-(GCRYSEXP_FMT_ADVANCED) that avoids non-printable characters so that
-the file can be easily inspected and edited. See section 'Private Key
-Format' below for details.
-
-** Values
+to represent an array of values. Note that the name "Key" is special
+in that it is madandory must occur only once.
+*** Values
Values are UTF-8 encoded strings. Values can be wrapped at any point,
and continued in the next line indicated by leading whitespace. A
continuation line with one leading space does not introduce a blank so
that the lines can be effectively concatenated. A blank line as part
of a continuation line encodes a newline.
-** Comments
-
+*** Comments
Lines containing only whitespace, and lines starting with whitespace
followed by '#' are considered to be comments and are ignored.
+** Well defined names
+
+*** Description
+This is a human readable string describing the key.
+
+*** Key
+The name "Key" is special in that it is mandatory and must occur only
+once. The associated value holds the actual S-expression with the
+cryptographic key. The S-expression is formatted using the 'Advanced
+Format' (GCRYSEXP_FMT_ADVANCED) that avoids non-printable characters
+so that the file can be easily inspected and edited. See section
+'Private Key Format' below for details.
+
+*** Label
+This is a short human readable description for the key which can be
+used by the software to describe the key in a user interface. For
+example as part of the description in a prompt for a PIN or
+passphrase. It is often used instead of a comment element as present
+in the S-expression of the "Key" item.
+
+*** OpenSSH-cert
+This takes a base64 encoded string wrapped so that this
+key file can be easily edited with a standard editor. Several of such
+items can be used.
+
+*** Token
+If such an item exists it overrides the info given by the "shadow"
+parameter in the S-expression. Using this item makes it possible to
+describe a key which is stored on several tokens and also makes it
+easy to update this info using a standard editor. The syntax is the
+same as with the "shadow" parameter:
+
+- Serialnumber of the token
+- Key reference from the token in full format (e.g. "OpenPGP.2")
+- An optional fixed length of the PIN.
+
+*** Use-for-ssh
+If given and the value is "yes" or "1" the key is allowed for use by
+gpg-agent's ssh-agent implementation. This is thus the same as
+putting the keygrip into the 'sshcontrol' file. Only one such item
+should exist.
+
* Private Key Format
** Unprotected Private Key Format
The content of the file is an S-Expression like the ones used with
Libgcrypt. Here is an example of an unprotected file:
(private-key
(rsa
(n #00e0ce9..[some bytes not shown]..51#)
(e #010001#)
(d #046129F..[some bytes not shown]..81#)
(p #00e861b..[some bytes not shown]..f1#)
(q #00f7a7c..[some bytes not shown]..61#)
(u #304559a..[some bytes not shown]..9b#)
)
(created-at timestamp)
(uri http://foo.bar x-foo:whatever_you_want)
(comment whatever)
)
"comment", "created-at" and "uri" are optional. "comment" is
currently used to keep track of ssh key comments. "created-at" is used
to keep track of the creation time stamp used with OpenPGP keys; it is
optional but required for some operations to calculate the fingerprint
of the key. This timestamp should be a string with the number of
seconds since Epoch or an ISO time string (yyyymmddThhmmss).
** Protected Private Key Format
A protected key is like this:
(protected-private-key
(rsa
(n #00e0ce9..[some bytes not shown]..51#)
(e #010001#)
(protected mode (parms) encrypted_octet_string)
(protected-at <isotimestamp>)
)
(uri http://foo.bar x-foo:whatever_you_want)
(comment whatever)
)
In this scheme the encrypted_octet_string is encrypted according to
the algorithm described after the keyword protected; most protection
algorithms need some parameters, which are given in a list before the
encrypted_octet_string. The result of the decryption process is a
list of the secret key parameters. The protected-at expression is
optional; the isotimestamp is 15 bytes long (e.g. "19610711T172000").
The currently defined protection modes are:
*** openpgp-s2k3-sha1-aes-cbc
This describes an algorithm using AES in CBC mode for
encryption, SHA-1 for integrity protection and the String to Key
algorithm 3 from OpenPGP (rfc4880).
Example:
(protected openpgp-s2k3-sha1-aes-cbc
((sha1 16byte_salt no_of_iterations) 16byte_iv)
encrypted_octet_string
)
The encrypted_octet string should yield this S-Exp (in canonical
representation) after decryption:
(
(
(d #046129F..[some bytes not shown]..81#)
(p #00e861b..[some bytes not shown]..f1#)
(q #00f7a7c..[some bytes not shown]..61#)
(u #304559a..[some bytes not shown]..9b#)
)
(hash sha1 #...[hashvalue]...#)
)
For padding reasons, random bytes are appended to this list - they can
easily be stripped by looking for the end of the list.
The hash is calculated on the concatenation of the public key and
secret key parameter lists: i.e. it is required to hash the
concatenation of these 6 canonical encoded lists for RSA, including
the parenthesis, the algorithm keyword and (if used) the protected-at
list.
(rsa
(n #00e0ce9..[some bytes not shown]..51#)
(e #010001#)
(d #046129F..[some bytes not shown]..81#)
(p #00e861b..[some bytes not shown]..f1#)
(q #00f7a7c..[some bytes not shown]..61#)
(u #304559a..[some bytes not shown]..9b#)
(protected-at "18950523T000000")
)
After decryption the hash must be recalculated and compared against
the stored one - If they don't match the integrity of the key is not
given.
*** openpgp-s2k3-ocb-aes
This describes an algorithm using AES-128 in OCB mode, a nonce
of 96 bit, a taglen of 128 bit, and the String to Key algorithm 3
from OpenPGP (rfc4880).
Example:
(protected openpgp-s2k3-ocb-aes
((sha1 16byte_salt no_of_iterations) 12byte_nonce)
encrypted_octet_string
)
The encrypted_octet string should yield this S-Exp (in canonical
representation) after decryption:
(
(
(d #046129F..[some bytes not shown]..81#)
(p #00e861b..[some bytes not shown]..f1#)
(q #00f7a7c..[some bytes not shown]..61#)
(u #304559a..[some bytes not shown]..9b#)
)
)
For padding reasons, random bytes may be appended to this list -
they can easily be stripped by looking for the end of the list.
The associated data required for this protection mode is the list
formiing the public key parameters. For the above example this is
is this canonical encoded S-expression:
(rsa
(n #00e0ce9..[some bytes not shown]..51#)
(e #010001#)
(protected-at "18950523T000000")
)
*** openpgp-native
This is a wrapper around the OpenPGP Private Key Transport format
which resembles the standard OpenPGP format and allows the use of an
existing key without re-encrypting to the default protection format.
Example:
(protected openpgp-native
(openpgp-private-key
(version V)
(algo PUBKEYALGO)
(skey _ P1 _ P2 _ P3 ... e PN)
(csum n)
(protection PROTTYPE PROTALGO IV S2KMODE S2KHASH S2KSALT S2KCOUNT)))
Note that the public key parameters in SKEY are duplicated and
should be identical to their copies in the standard parameter
elements. Here is an example of an entire protected private key
using this format:
(protected-private-key
(rsa
(n #00e0ce9..[some bytes not shown]..51#)
(e #010001#)
(protected openpgp-native
(openpgp-private-key
(version 4)
(algo rsa)
(skey _ #00e0ce9..[some bytes not shown]..51#
_ #010001#
e #.........................#)
(protection sha1 aes #aabbccddeeff00112233445566778899#
3 sha1 #2596f93e85f41e53# 3:190))))
(uri http://foo.bar x-foo:whatever_you_want)
(comment whatever))
** Shadowed Private Key Format
To keep track of keys stored on IC cards we use a third format for
private kyes which are called shadow keys as they are only a reference
to keys stored on a token:
(shadowed-private-key
(rsa
(n #00e0ce9..[some bytes not shown]..51#)
(e #010001#)
(shadowed protocol (info))
)
(uri http://foo.bar x-foo:whatever_you_want)
(comment whatever)
)
The currently used protocol is "t1-v1" (token info version 1). The
second list with the information has this layout:
(card_serial_number id_string_of_key fixed_pin_length)
FIXED_PIN_LENGTH is optional. It can be used to store the length of
the PIN; a value of 0 indicates that this information is not
available. The rationale for this field is that some pinpad equipped
readers don't allow passing a variable length PIN.
More items may be added to the list.
** OpenPGP Private Key Transfer Format
This format is used to transfer keys between gpg and gpg-agent.
(openpgp-private-key
(version V)
(algo PUBKEYALGO)
(curve CURVENAME)
(skey _ P1 _ P2 _ P3 ... e PN)
(csum n)
(protection PROTTYPE PROTALGO IV S2KMODE S2KHASH S2KSALT S2KCOUNT))
* V is the packet version number (3 or 4).
* PUBKEYALGO is a Libgcrypt algo name
* CURVENAME is the name of the curve - only used with ECC.
* P1 .. PN are the parameters; the public parameters are never encrypted
the secrect key parameters are encrypted if the "protection" list is
given. To make this more explicit each parameter is preceded by a
flag "_" for cleartext or "e" for encrypted text.
* CSUM is the deprecated 16 bit checksum as defined by OpenPGP. This
is an optional element.
* If PROTTYPE is "sha1" the new style SHA1 checksum is used if it is "sum"
the old 16 bit checksum (above) is used and if it is "none" no
protection at all is used.
* PROTALGO is a Libgcrypt style cipher algorithm name
* IV is the initialization verctor.
* S2KMODE is the value from RFC-4880.
* S2KHASH is a libgcrypt style hash algorithm identifier.
* S2KSALT is the 8 byte salt
* S2KCOUNT is the count value from RFC-4880.
** Persistent Passphrase Format
Note: That this has not yet been implemented.
To allow persistent storage of cached passphrases we use a scheme
similar to the private-key storage format. This is a master
passphrase format where each file may protect several secrets under
one master passphrase. It is possible to have several of those files
each protected by a dedicated master passphrase. Clear text keywords
allow listing the available protected passphrases.
The name of the files with these protected secrets have this form:
pw-<string>.dat. STRING may be an arbitrary string, as a default name
for the passphrase storage the name "pw-default.dat" is suggested.
(protected-shared-secret
((desc descriptive_text)
(key [key_1] (keyword_1 keyword_2 keyword_n))
(key [key_2] (keyword_21 keyword_22 keyword_2n))
(key [key_n] (keyword_n1 keyword_n2 keyword_nn))
(protected mode (parms) encrypted_octet_string)
(protected-at <isotimestamp>)
)
)
After decryption the encrypted_octet_string yields this S-expression:
(
(
(value key_1 value_1)
(value key_2 value_2)
(value key_n value_n)
)
(hash sha1 #...[hashvalue]...#)
)
The "descriptive_text" is displayed with the prompt to enter the
unprotection passphrase.
KEY_1 to KEY_N are unique identifiers for the shared secret, for
example an URI. In case this information should be kept confidential
as well, they may not appear in the unprotected part; however they are
mandatory in the encrypted_octet_string. The list of keywords is
optional. The order of the "key" lists and the order of the "value"
lists must match, that is the first "key"-list is associated with the
first "value" list in the encrypted_octet_string.
The protection mode etc. is identical to the protection mode as
described for the private key format.
list of the secret key parameters. The protected-at expression is
optional; the isotimestamp is 15 bytes long (e.g. "19610711T172000").
The "hash" in the encrypted_octet_string is calculated on the
concatenation of the key list and value lists: i.e it is required to
hash the concatenation of all these lists, including the
parenthesis and (if used) the protected-at list.
Example:
(protected-shared-secret
((desc "List of system passphrases")
(key "uid-1002" ("Knuth" "Donald Ervin Knuth"))
(key "uid-1001" ("Dijkstra" "Edsger Wybe Dijkstra"))
(key)
(protected mode (parms) encrypted_octet_string)
(protected-at "20100915T111722")
)
)
with "encrypted_octet_string" decoding to:
(
(
(value 4:1002 "signal flags at the lock")
(value 4:1001 "taocp")
(value 1:0 "premature optimization is the root of all evil")
)
(hash sha1 #0102030405060708091011121314151617181920#)
)
To compute the hash this S-expression (in canoncical format) was
hashed:
((desc "List of system passphrases")
(key "uid-1002" ("Knuth" "Donald Ervin Knuth"))
(key "uid-1001" ("Dijkstra" "Edsger Wybe Dijkstra"))
(key)
(value 4:1002 "signal flags at the lock")
(value 4:1001 "taocp")
(value 1:0 "premature optimization is the root of all evil")
(protected-at "20100915T111722")
)
* Notes
[1] I usually use the terms private and secret key exchangeable but prefer the
term secret key because it can be visually be better distinguished
from the term public key.
[2] The keygrip is a unique identifier for a key pair, it is
independent of any protocol, so that the same key can be used with
different protocols. PKCS-15 calls this a subjectKeyHash; it can be
calculated using Libgcrypt's gcry_pk_get_keygrip ().
[3] Even when canonical representation are required we will show the
S-expression here in a more readable representation.
diff --git a/agent/learncard.c b/agent/learncard.c
index f3219ed8f..f40f5ac4d 100644
--- a/agent/learncard.c
+++ b/agent/learncard.c
@@ -1,445 +1,444 @@
/* learncard.c - Handle the LEARN command
* Copyright (C) 2002, 2003, 2004, 2009 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
-#include <assert.h>
#include <unistd.h>
#include <sys/stat.h>
#include "agent.h"
#include <assuan.h>
/* Structures used by the callback mechanism to convey information
pertaining to key pairs. */
struct keypair_info_s
{
struct keypair_info_s *next;
int no_cert;
char *id; /* points into grip */
char hexgrip[1]; /* The keygrip (i.e. a hash over the public key
parameters) formatted as a hex string.
Allocated somewhat large to also act as
memory for the above ID field. */
};
typedef struct keypair_info_s *KEYPAIR_INFO;
struct kpinfo_cb_parm_s
{
ctrl_t ctrl;
int error;
KEYPAIR_INFO info;
};
/* Structures used by the callback mechanism to convey information
pertaining to certificates. */
struct certinfo_s {
struct certinfo_s *next;
int type;
int done;
char id[1];
};
typedef struct certinfo_s *CERTINFO;
struct certinfo_cb_parm_s
{
ctrl_t ctrl;
int error;
CERTINFO info;
};
/* Structures used by the callback mechanism to convey assuan status
lines. */
struct sinfo_s {
struct sinfo_s *next;
char *data; /* Points into keyword. */
char keyword[1];
};
typedef struct sinfo_s *SINFO;
struct sinfo_cb_parm_s {
int error;
SINFO info;
};
/* Destructor for key information objects. */
static void
release_keypair_info (KEYPAIR_INFO info)
{
while (info)
{
KEYPAIR_INFO tmp = info->next;
xfree (info);
info = tmp;
}
}
/* Destructor for certificate information objects. */
static void
release_certinfo (CERTINFO info)
{
while (info)
{
CERTINFO tmp = info->next;
xfree (info);
info = tmp;
}
}
/* Destructor for status information objects. */
static void
release_sinfo (SINFO info)
{
while (info)
{
SINFO tmp = info->next;
xfree (info);
info = tmp;
}
}
/* This callback is used by agent_card_learn and passed the content of
all KEYPAIRINFO lines. It merely stores this data away */
static void
kpinfo_cb (void *opaque, const char *line)
{
struct kpinfo_cb_parm_s *parm = opaque;
KEYPAIR_INFO item;
char *p;
if (parm->error)
return; /* no need to gather data after an error occurred */
if ((parm->error = agent_write_status (parm->ctrl, "PROGRESS",
"learncard", "k", "0", "0", NULL)))
return;
item = xtrycalloc (1, sizeof *item + strlen (line));
if (!item)
{
parm->error = out_of_core ();
return;
}
strcpy (item->hexgrip, line);
for (p = item->hexgrip; hexdigitp (p); p++)
;
if (p == item->hexgrip && *p == 'X' && spacep (p+1))
{
item->no_cert = 1;
p++;
}
else if ((p - item->hexgrip) != 40 || !spacep (p))
{ /* not a 20 byte hex keygrip or not followed by a space */
parm->error = gpg_error (GPG_ERR_INV_RESPONSE);
xfree (item);
return;
}
*p++ = 0;
while (spacep (p))
p++;
item->id = p;
while (*p && !spacep (p))
p++;
if (p == item->id)
{ /* invalid ID string */
parm->error = gpg_error (GPG_ERR_INV_RESPONSE);
xfree (item);
return;
}
*p = 0; /* ignore trailing stuff */
/* store it */
item->next = parm->info;
parm->info = item;
}
/* This callback is used by agent_card_learn and passed the content of
all CERTINFO lines. It merely stores this data away */
static void
certinfo_cb (void *opaque, const char *line)
{
struct certinfo_cb_parm_s *parm = opaque;
CERTINFO item;
int type;
char *p, *pend;
if (parm->error)
return; /* no need to gather data after an error occurred */
if ((parm->error = agent_write_status (parm->ctrl, "PROGRESS",
"learncard", "c", "0", "0", NULL)))
return;
type = strtol (line, &p, 10);
while (spacep (p))
p++;
for (pend = p; *pend && !spacep (pend); pend++)
;
if (p == pend || !*p)
{
parm->error = gpg_error (GPG_ERR_INV_RESPONSE);
return;
}
*pend = 0; /* ignore trailing stuff */
item = xtrycalloc (1, sizeof *item + strlen (p));
if (!item)
{
parm->error = out_of_core ();
return;
}
item->type = type;
strcpy (item->id, p);
/* store it */
item->next = parm->info;
parm->info = item;
}
/* This callback is used by agent_card_learn and passed the content of
all SINFO lines. It merely stores this data away */
static void
sinfo_cb (void *opaque, const char *keyword, size_t keywordlen,
const char *data)
{
struct sinfo_cb_parm_s *sparm = opaque;
SINFO item;
if (sparm->error)
return; /* no need to gather data after an error occurred */
item = xtrycalloc (1, sizeof *item + keywordlen + 1 + strlen (data));
if (!item)
{
sparm->error = out_of_core ();
return;
}
memcpy (item->keyword, keyword, keywordlen);
item->data = item->keyword + keywordlen;
*item->data = 0;
item->data++;
strcpy (item->data, data);
/* store it */
item->next = sparm->info;
sparm->info = item;
}
static int
send_cert_back (ctrl_t ctrl, const char *id, void *assuan_context)
{
int rc;
char *derbuf;
size_t derbuflen;
rc = agent_card_readcert (ctrl, id, &derbuf, &derbuflen);
if (rc)
{
const char *action;
switch (gpg_err_code (rc))
{
case GPG_ERR_INV_ID:
case GPG_ERR_NOT_FOUND:
action = " - ignored";
break;
default:
action = "";
break;
}
if (opt.verbose || !*action)
log_info ("error reading certificate '%s': %s%s\n",
id? id:"?", gpg_strerror (rc), action);
return *action? 0 : rc;
}
rc = assuan_send_data (assuan_context, derbuf, derbuflen);
xfree (derbuf);
if (!rc)
rc = assuan_send_data (assuan_context, NULL, 0);
if (!rc)
rc = assuan_write_line (assuan_context, "END");
if (rc)
{
log_error ("sending certificate failed: %s\n",
gpg_strerror (rc));
return rc;
}
return 0;
}
/* Perform the learn operation. If ASSUAN_CONTEXT is not NULL and
SEND is true all new certificates are send back via Assuan. */
int
agent_handle_learn (ctrl_t ctrl, int send, void *assuan_context, int force)
{
int rc;
struct kpinfo_cb_parm_s parm;
struct certinfo_cb_parm_s cparm;
struct sinfo_cb_parm_s sparm;
const char *serialno = NULL;
KEYPAIR_INFO item;
SINFO sitem;
unsigned char grip[20];
char *p;
int i;
static int certtype_list[] = {
111, /* Root CA */
101, /* trusted */
102, /* useful */
100, /* regular */
/* We don't include 110 here because gpgsm can't handle that
special root CA format. */
-1 /* end of list */
};
memset (&parm, 0, sizeof parm);
memset (&cparm, 0, sizeof cparm);
memset (&sparm, 0, sizeof sparm);
parm.ctrl = ctrl;
cparm.ctrl = ctrl;
/* Now gather all the available info. */
rc = agent_card_learn (ctrl, kpinfo_cb, &parm, certinfo_cb, &cparm,
sinfo_cb, &sparm);
if (!rc && (parm.error || cparm.error || sparm.error))
rc = parm.error? parm.error : cparm.error? cparm.error : sparm.error;
if (rc)
{
log_debug ("agent_card_learn failed: %s\n", gpg_strerror (rc));
goto leave;
}
/* Pass on all the collected status information. */
for (sitem = sparm.info; sitem; sitem = sitem->next)
{
if (!strcmp (sitem->keyword, "SERIALNO"))
serialno = sitem->data;
if (assuan_context)
assuan_write_status (assuan_context, sitem->keyword, sitem->data);
}
if (!serialno)
{
rc = GPG_ERR_NOT_FOUND;
goto leave;
}
log_info ("card has S/N: %s\n", serialno);
/* Write out the certificates in a standard order. */
for (i=0; certtype_list[i] != -1; i++)
{
CERTINFO citem;
for (citem = cparm.info; citem; citem = citem->next)
{
if (certtype_list[i] != citem->type)
continue;
if (opt.verbose)
log_info (" id: %s (type=%d)\n",
citem->id, citem->type);
if (assuan_context && send)
{
rc = send_cert_back (ctrl, citem->id, assuan_context);
if (rc)
goto leave;
citem->done = 1;
}
}
}
for (item = parm.info; item; item = item->next)
{
unsigned char *pubkey;
if (opt.verbose)
log_info (" id: %s (grip=%s)\n", item->id, item->hexgrip);
if (item->no_cert)
continue; /* No public key yet available. */
if (assuan_context)
{
agent_write_status (ctrl, "KEYPAIRINFO",
item->hexgrip, item->id, NULL);
}
for (p=item->hexgrip, i=0; i < 20; p += 2, i++)
grip[i] = xtoi_2 (p);
if (!force && !agent_key_available (grip))
continue; /* The key is already available. */
/* Unknown key - store it. */
rc = agent_card_readkey (ctrl, item->id, &pubkey);
if (rc)
{
log_debug ("agent_card_readkey failed: %s\n", gpg_strerror (rc));
goto leave;
}
rc = agent_write_shadow_key (grip, serialno, item->id, pubkey, force);
xfree (pubkey);
if (rc)
goto leave;
if (opt.verbose)
log_info (" id: %s - shadow key created\n", item->id);
if (assuan_context && send)
{
CERTINFO citem;
/* only send the certificate if we have not done so before */
for (citem = cparm.info; citem; citem = citem->next)
{
if (!strcmp (citem->id, item->id))
break;
}
if (!citem)
{
rc = send_cert_back (ctrl, item->id, assuan_context);
if (rc)
goto leave;
}
}
}
leave:
release_keypair_info (parm.info);
release_certinfo (cparm.info);
release_sinfo (sparm.info);
return rc;
}
diff --git a/agent/pkdecrypt.c b/agent/pkdecrypt.c
index 06a8e0b6f..ec23daf83 100644
--- a/agent/pkdecrypt.c
+++ b/agent/pkdecrypt.c
@@ -1,146 +1,145 @@
/* pkdecrypt.c - public key decryption (well, actually using a secret key)
* 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 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
-#include <assert.h>
#include <unistd.h>
#include <sys/stat.h>
#include "agent.h"
/* DECRYPT the stuff in ciphertext which is expected to be a S-Exp.
Try to get the key from CTRL and write the decoded stuff back to
OUTFP. The padding information is stored at R_PADDING with -1
for not known. */
int
agent_pkdecrypt (ctrl_t ctrl, const char *desc_text,
const unsigned char *ciphertext, size_t ciphertextlen,
membuf_t *outbuf, int *r_padding)
{
gcry_sexp_t s_skey = NULL, s_cipher = NULL, s_plain = NULL;
unsigned char *shadow_info = NULL;
int rc;
char *buf = NULL;
size_t len;
*r_padding = -1;
if (!ctrl->have_keygrip)
{
log_error ("speculative decryption not yet supported\n");
rc = gpg_error (GPG_ERR_NO_SECKEY);
goto leave;
}
rc = gcry_sexp_sscan (&s_cipher, NULL, (char*)ciphertext, ciphertextlen);
if (rc)
{
log_error ("failed to convert ciphertext: %s\n", gpg_strerror (rc));
rc = gpg_error (GPG_ERR_INV_DATA);
goto leave;
}
if (DBG_CRYPTO)
{
log_printhex (ctrl->keygrip, 20, "keygrip:");
log_printhex (ciphertext, ciphertextlen, "cipher: ");
}
rc = agent_key_from_file (ctrl, NULL, desc_text,
ctrl->keygrip, &shadow_info,
CACHE_MODE_NORMAL, NULL, &s_skey, NULL);
if (rc)
{
if (gpg_err_code (rc) != GPG_ERR_NO_SECKEY)
log_error ("failed to read the secret key\n");
goto leave;
}
if (shadow_info)
{ /* divert operation to the smartcard */
if (!gcry_sexp_canon_len (ciphertext, ciphertextlen, NULL, NULL))
{
rc = gpg_error (GPG_ERR_INV_SEXP);
goto leave;
}
- rc = divert_pkdecrypt (ctrl, desc_text, ciphertext, shadow_info,
- &buf, &len, r_padding);
+ rc = divert_pkdecrypt (ctrl, desc_text, ctrl->keygrip, ciphertext,
+ shadow_info, &buf, &len, r_padding);
if (rc)
{
log_error ("smartcard decryption failed: %s\n", gpg_strerror (rc));
goto leave;
}
put_membuf_printf (outbuf, "(5:value%u:", (unsigned int)len);
put_membuf (outbuf, buf, len);
put_membuf (outbuf, ")", 2);
}
else
{ /* No smartcard, but a private key */
/* if (DBG_CRYPTO ) */
/* { */
/* log_debug ("skey: "); */
/* gcry_sexp_dump (s_skey); */
/* } */
rc = gcry_pk_decrypt (&s_plain, s_cipher, s_skey);
if (rc)
{
log_error ("decryption failed: %s\n", gpg_strerror (rc));
goto leave;
}
if (DBG_CRYPTO)
{
log_debug ("plain: ");
gcry_sexp_dump (s_plain);
}
len = gcry_sexp_sprint (s_plain, GCRYSEXP_FMT_CANON, NULL, 0);
- assert (len);
+ log_assert (len);
buf = xmalloc (len);
len = gcry_sexp_sprint (s_plain, GCRYSEXP_FMT_CANON, buf, len);
- assert (len);
+ log_assert (len);
if (*buf == '(')
put_membuf (outbuf, buf, len);
else
{
/* Old style libgcrypt: This is only an S-expression
part. Turn it into a complete S-expression. */
put_membuf (outbuf, "(5:value", 8);
put_membuf (outbuf, buf, len);
put_membuf (outbuf, ")", 2);
}
}
leave:
gcry_sexp_release (s_skey);
gcry_sexp_release (s_plain);
gcry_sexp_release (s_cipher);
xfree (buf);
xfree (shadow_info);
return rc;
}
diff --git a/agent/pksign.c b/agent/pksign.c
index 828e63f58..4a43b09de 100644
--- a/agent/pksign.c
+++ b/agent/pksign.c
@@ -1,567 +1,571 @@
/* pksign.c - public key signing (well, actually using a secret key)
* Copyright (C) 2001-2004, 2010 Free Software Foundation, Inc.
* Copyright (C) 2001-2004, 2010, 2013 Werner Koch
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
-#include <assert.h>
-#include <unistd.h>
#include <sys/stat.h>
#include "agent.h"
#include "../common/i18n.h"
static int
do_encode_md (const byte * md, size_t mdlen, int algo, gcry_sexp_t * r_hash,
int raw_value)
{
gcry_sexp_t hash;
int rc;
if (!raw_value)
{
const char *s;
char tmp[16+1];
int i;
s = gcry_md_algo_name (algo);
- if (s && strlen (s) < 16)
+ if (!s || strlen (s) >= 16)
+ {
+ hash = NULL;
+ rc = gpg_error (GPG_ERR_DIGEST_ALGO);
+ }
+ else
{
- for (i=0; i < strlen (s); i++)
- tmp[i] = tolower (s[i]);
+ for (i=0; s[i]; i++)
+ tmp[i] = ascii_tolower (s[i]);
tmp[i] = '\0';
- }
- rc = gcry_sexp_build (&hash, NULL,
- "(data (flags pkcs1) (hash %s %b))",
- tmp, (int)mdlen, md);
+ rc = gcry_sexp_build (&hash, NULL,
+ "(data (flags pkcs1) (hash %s %b))",
+ tmp, (int)mdlen, md);
+ }
}
else
{
gcry_mpi_t mpi;
rc = gcry_mpi_scan (&mpi, GCRYMPI_FMT_USG, md, mdlen, NULL);
if (!rc)
{
rc = gcry_sexp_build (&hash, NULL,
"(data (flags raw) (value %m))",
mpi);
gcry_mpi_release (mpi);
}
else
hash = NULL;
}
*r_hash = hash;
return rc;
}
/* Return the number of bits of the Q parameter from the DSA key
KEY. */
static unsigned int
get_dsa_qbits (gcry_sexp_t key)
{
gcry_sexp_t l1, l2;
gcry_mpi_t q;
unsigned int nbits;
l1 = gcry_sexp_find_token (key, "private-key", 0);
if (!l1)
l1 = gcry_sexp_find_token (key, "protected-private-key", 0);
if (!l1)
l1 = gcry_sexp_find_token (key, "shadowed-private-key", 0);
if (!l1)
l1 = gcry_sexp_find_token (key, "public-key", 0);
if (!l1)
return 0; /* Does not contain a key object. */
l2 = gcry_sexp_cadr (l1);
gcry_sexp_release (l1);
l1 = gcry_sexp_find_token (l2, "q", 1);
gcry_sexp_release (l2);
if (!l1)
return 0; /* Invalid object. */
q = gcry_sexp_nth_mpi (l1, 1, GCRYMPI_FMT_USG);
gcry_sexp_release (l1);
if (!q)
return 0; /* Missing value. */
nbits = gcry_mpi_get_nbits (q);
gcry_mpi_release (q);
return nbits;
}
/* Return an appropriate hash algorithm to be used with RFC-6979 for a
message digest of length MDLEN. Although a fallback of SHA-256 is
used the current implementation in Libgcrypt will reject a hash
algorithm which does not match the length of the message. */
static const char *
rfc6979_hash_algo_string (size_t mdlen)
{
switch (mdlen)
{
case 20: return "sha1";
case 28: return "sha224";
case 32: return "sha256";
case 48: return "sha384";
case 64: return "sha512";
default: return "sha256";
}
}
/* Encode a message digest for use with the EdDSA algorithm
(i.e. curve Ed25519). */
static gpg_error_t
do_encode_eddsa (const byte *md, size_t mdlen, gcry_sexp_t *r_hash)
{
gpg_error_t err;
gcry_sexp_t hash;
*r_hash = NULL;
err = gcry_sexp_build (&hash, NULL,
"(data(flags eddsa)(hash-algo sha512)(value %b))",
(int)mdlen, md);
if (!err)
*r_hash = hash;
return err;
}
/* Encode a message digest for use with an DSA algorithm. */
static gpg_error_t
do_encode_dsa (const byte *md, size_t mdlen, int pkalgo, gcry_sexp_t pkey,
gcry_sexp_t *r_hash)
{
gpg_error_t err;
gcry_sexp_t hash;
unsigned int qbits;
*r_hash = NULL;
if (pkalgo == GCRY_PK_ECDSA)
qbits = gcry_pk_get_nbits (pkey);
else if (pkalgo == GCRY_PK_DSA)
qbits = get_dsa_qbits (pkey);
else
return gpg_error (GPG_ERR_WRONG_PUBKEY_ALGO);
if (pkalgo == GCRY_PK_DSA && (qbits%8))
{
/* FIXME: We check the QBITS but print a message about the hash
length. */
log_error (_("DSA requires the hash length to be a"
" multiple of 8 bits\n"));
return gpg_error (GPG_ERR_INV_LENGTH);
}
/* Don't allow any Q smaller than 160 bits. We don't want someone
to issue signatures from a key with a 16-bit Q or something like
that, which would look correct but allow trivial forgeries. Yes,
I know this rules out using MD5 with DSA. ;) */
if (qbits < 160)
{
log_error (_("%s key uses an unsafe (%u bit) hash\n"),
gcry_pk_algo_name (pkalgo), qbits);
return gpg_error (GPG_ERR_INV_LENGTH);
}
/* ECDSA 521 is special has it is larger than the largest hash
we have (SHA-512). Thus we change the size for further
processing to 512. */
if (pkalgo == GCRY_PK_ECDSA && qbits > 512)
qbits = 512;
/* Check if we're too short. Too long is safe as we'll
automatically left-truncate. */
if (mdlen < qbits/8)
{
log_error (_("a %zu bit hash is not valid for a %u bit %s key\n"),
mdlen*8,
gcry_pk_get_nbits (pkey),
gcry_pk_algo_name (pkalgo));
return gpg_error (GPG_ERR_INV_LENGTH);
}
/* Truncate. */
if (mdlen > qbits/8)
mdlen = qbits/8;
/* Create the S-expression. */
err = gcry_sexp_build (&hash, NULL,
"(data (flags rfc6979) (hash %s %b))",
rfc6979_hash_algo_string (mdlen),
(int)mdlen, md);
if (!err)
*r_hash = hash;
return err;
}
/* Special version of do_encode_md to take care of pkcs#1 padding.
For TLS-MD5SHA1 we need to do the padding ourself as Libgrypt does
not know about this special scheme. Fixme: We should have a
pkcs1-only-padding flag for Libgcrypt. */
static int
do_encode_raw_pkcs1 (const byte *md, size_t mdlen, unsigned int nbits,
gcry_sexp_t *r_hash)
{
int rc;
gcry_sexp_t hash;
unsigned char *frame;
size_t i, n, nframe;
nframe = (nbits+7) / 8;
if ( !mdlen || mdlen + 8 + 4 > nframe )
{
/* Can't encode this hash into a frame of size NFRAME. */
return gpg_error (GPG_ERR_TOO_SHORT);
}
frame = xtrymalloc (nframe);
if (!frame)
return gpg_error_from_syserror ();
/* Assemble the pkcs#1 block type 1. */
n = 0;
frame[n++] = 0;
frame[n++] = 1; /* Block type. */
i = nframe - mdlen - 3 ;
- assert (i >= 8); /* At least 8 bytes of padding. */
+ log_assert (i >= 8); /* At least 8 bytes of padding. */
memset (frame+n, 0xff, i );
n += i;
frame[n++] = 0;
memcpy (frame+n, md, mdlen );
n += mdlen;
- assert (n == nframe);
+ log_assert (n == nframe);
/* Create the S-expression. */
rc = gcry_sexp_build (&hash, NULL,
"(data (flags raw) (value %b))",
(int)nframe, frame);
xfree (frame);
*r_hash = hash;
return rc;
}
/* SIGN whatever information we have accumulated in CTRL and return
* the signature S-expression. LOOKUP is an optional function to
* provide a way for lower layers to ask for the caching TTL. If a
* CACHE_NONCE is given that cache item is first tried to get a
* passphrase. If OVERRIDEDATA is not NULL, OVERRIDEDATALEN bytes
* from this buffer are used instead of the data in CTRL. The
* override feature is required to allow the use of Ed25519 with ssh
* because Ed25519 does the hashing itself. */
gpg_error_t
agent_pksign_do (ctrl_t ctrl, const char *cache_nonce,
const char *desc_text,
gcry_sexp_t *signature_sexp,
cache_mode_t cache_mode, lookup_ttl_t lookup_ttl,
const void *overridedata, size_t overridedatalen)
{
gpg_error_t err = 0;
gcry_sexp_t s_skey = NULL;
gcry_sexp_t s_sig = NULL;
gcry_sexp_t s_hash = NULL;
gcry_sexp_t s_pkey = NULL;
unsigned char *shadow_info = NULL;
const unsigned char *data;
int datalen;
int check_signature = 0;
if (overridedata)
{
data = overridedata;
datalen = overridedatalen;
}
else
{
data = ctrl->digest.value;
datalen = ctrl->digest.valuelen;
}
if (!ctrl->have_keygrip)
return gpg_error (GPG_ERR_NO_SECKEY);
err = agent_key_from_file (ctrl, cache_nonce, desc_text, ctrl->keygrip,
&shadow_info, cache_mode, lookup_ttl,
&s_skey, NULL);
if (err)
{
if (gpg_err_code (err) != GPG_ERR_NO_SECKEY)
log_error ("failed to read the secret key\n");
goto leave;
}
if (shadow_info)
{
/* Divert operation to the smartcard */
size_t len;
unsigned char *buf = NULL;
int key_type;
int is_RSA = 0;
int is_ECDSA = 0;
int is_EdDSA = 0;
err = agent_public_key_from_file (ctrl, ctrl->keygrip, &s_pkey);
if (err)
{
log_error ("failed to read the public key\n");
goto leave;
}
if (agent_is_eddsa_key (s_skey))
is_EdDSA = 1;
else
{
key_type = agent_is_dsa_key (s_skey);
if (key_type == 0)
is_RSA = 1;
else if (key_type == GCRY_PK_ECDSA)
is_ECDSA = 1;
}
{
char *desc2 = NULL;
if (desc_text)
agent_modify_description (desc_text, NULL, s_skey, &desc2);
err = divert_pksign (ctrl, desc2? desc2 : desc_text,
+ ctrl->keygrip,
data, datalen,
ctrl->digest.algo,
shadow_info, &buf, &len);
xfree (desc2);
}
if (err)
{
log_error ("smartcard signing failed: %s\n", gpg_strerror (err));
goto leave;
}
if (is_RSA)
{
unsigned char *p = buf;
check_signature = 1;
/*
* Smartcard returns fixed-size data, which is good for
* PKCS1. If variable-size unsigned MPI is needed, remove
* zeros.
*/
if (ctrl->digest.algo == MD_USER_TLS_MD5SHA1
|| ctrl->digest.raw_value)
{
int i;
for (i = 0; i < len - 1; i++)
if (p[i])
break;
p += i;
len -= i;
}
err = gcry_sexp_build (&s_sig, NULL, "(sig-val(rsa(s%b)))",
(int)len, p);
}
else if (is_EdDSA)
{
err = gcry_sexp_build (&s_sig, NULL, "(sig-val(eddsa(r%b)(s%b)))",
(int)len/2, buf, (int)len/2, buf + len/2);
}
else if (is_ECDSA)
{
unsigned char *r_buf, *s_buf;
int r_buflen, s_buflen;
int i;
r_buflen = s_buflen = len/2;
/*
* Smartcard returns fixed-size data. For ECDSA signature,
* variable-size unsigned MPI is assumed, thus, remove
* zeros.
*/
r_buf = buf;
for (i = 0; i < r_buflen - 1; i++)
if (r_buf[i])
break;
r_buf += i;
r_buflen -= i;
s_buf = buf + len/2;
for (i = 0; i < s_buflen - 1; i++)
if (s_buf[i])
break;
s_buf += i;
s_buflen -= i;
err = gcry_sexp_build (&s_sig, NULL, "(sig-val(ecdsa(r%b)(s%b)))",
r_buflen, r_buf,
s_buflen, s_buf);
}
else
err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
xfree (buf);
if (err)
{
log_error ("failed to convert sigbuf returned by divert_pksign "
"into S-Exp: %s", gpg_strerror (err));
goto leave;
}
}
else
{
/* No smartcard, but a private key */
int dsaalgo = 0;
/* Put the hash into a sexp */
if (agent_is_eddsa_key (s_skey))
err = do_encode_eddsa (data, datalen,
&s_hash);
else if (ctrl->digest.algo == MD_USER_TLS_MD5SHA1)
err = do_encode_raw_pkcs1 (data, datalen,
gcry_pk_get_nbits (s_skey),
&s_hash);
else if ( (dsaalgo = agent_is_dsa_key (s_skey)) )
err = do_encode_dsa (data, datalen,
dsaalgo, s_skey,
&s_hash);
else
err = do_encode_md (data, datalen,
ctrl->digest.algo,
&s_hash,
ctrl->digest.raw_value);
if (err)
goto leave;
if (dsaalgo == 0 && GCRYPT_VERSION_NUMBER < 0x010700)
{
/* It's RSA and Libgcrypt < 1.7 */
check_signature = 1;
}
if (DBG_CRYPTO)
{
gcry_log_debugsxp ("skey", s_skey);
gcry_log_debugsxp ("hash", s_hash);
}
/* sign */
err = gcry_pk_sign (&s_sig, s_hash, s_skey);
if (err)
{
log_error ("signing failed: %s\n", gpg_strerror (err));
goto leave;
}
if (DBG_CRYPTO)
gcry_log_debugsxp ("rslt", s_sig);
}
/* Check that the signature verification worked and nothing is
* fooling us e.g. by a bug in the signature create code or by
* deliberately introduced faults. Because Libgcrypt 1.7 does this
* for RSA internally there is no need to do it here again. */
if (check_signature)
{
gcry_sexp_t sexp_key = s_pkey? s_pkey: s_skey;
if (s_hash == NULL)
{
if (ctrl->digest.algo == MD_USER_TLS_MD5SHA1)
err = do_encode_raw_pkcs1 (data, datalen,
gcry_pk_get_nbits (sexp_key), &s_hash);
else
err = do_encode_md (data, datalen, ctrl->digest.algo, &s_hash,
ctrl->digest.raw_value);
}
if (!err)
err = gcry_pk_verify (s_sig, s_hash, sexp_key);
if (err)
{
log_error (_("checking created signature failed: %s\n"),
gpg_strerror (err));
gcry_sexp_release (s_sig);
s_sig = NULL;
}
}
leave:
*signature_sexp = s_sig;
gcry_sexp_release (s_pkey);
gcry_sexp_release (s_skey);
gcry_sexp_release (s_hash);
xfree (shadow_info);
return err;
}
/* SIGN whatever information we have accumulated in CTRL and write it
* back to OUTFP. If a CACHE_NONCE is given that cache item is first
* tried to get a passphrase. */
gpg_error_t
agent_pksign (ctrl_t ctrl, const char *cache_nonce, const char *desc_text,
membuf_t *outbuf, cache_mode_t cache_mode)
{
gpg_error_t err;
gcry_sexp_t s_sig = NULL;
char *buf = NULL;
size_t len = 0;
err = agent_pksign_do (ctrl, cache_nonce, desc_text, &s_sig, cache_mode,
NULL, NULL, 0);
if (err)
goto leave;
len = gcry_sexp_sprint (s_sig, GCRYSEXP_FMT_CANON, NULL, 0);
log_assert (len);
buf = xtrymalloc (len);
if (!buf)
{
err = gpg_error_from_syserror ();
goto leave;
}
len = gcry_sexp_sprint (s_sig, GCRYSEXP_FMT_CANON, buf, len);
log_assert (len);
put_membuf (outbuf, buf, len);
leave:
gcry_sexp_release (s_sig);
xfree (buf);
return err;
}
diff --git a/agent/preset-passphrase.c b/agent/preset-passphrase.c
index 7a9ea1b44..e22e9d58d 100644
--- a/agent/preset-passphrase.c
+++ b/agent/preset-passphrase.c
@@ -1,267 +1,266 @@
/* preset-passphrase.c - A tool to preset a passphrase.
* Copyright (C) 2004 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>
-#include <assert.h>
#include <sys/stat.h>
#include <unistd.h>
#ifdef HAVE_LOCALE_H
#include <locale.h>
#endif
#ifdef HAVE_LANGINFO_CODESET
#include <langinfo.h>
#endif
#ifdef HAVE_DOSISH_SYSTEM
#include <fcntl.h> /* for setmode() */
#endif
#ifdef HAVE_W32_SYSTEM
# ifdef HAVE_WINSOCK2_H
# include <winsock2.h>
# endif
# include <windows.h> /* To initialize the sockets. fixme */
#endif
#include "agent.h"
#include "../common/simple-pwquery.h"
#include "../common/i18n.h"
#include "../common/sysutils.h"
#include "../common/init.h"
enum cmd_and_opt_values
{ aNull = 0,
oVerbose = 'v',
oPassphrase = 'P',
oPreset = 'c',
oForget = 'f',
oNoVerbose = 500,
oHomedir,
aTest };
static const char *opt_passphrase;
static ARGPARSE_OPTS opts[] = {
{ 301, NULL, 0, N_("@Options:\n ") },
{ oVerbose, "verbose", 0, "verbose" },
{ oPassphrase, "passphrase", 2, "|STRING|use passphrase STRING" },
{ oPreset, "preset", 256, "preset passphrase"},
{ oForget, "forget", 256, "forget passphrase"},
{ oHomedir, "homedir", 2, "@" },
ARGPARSE_end ()
};
static const char *
my_strusage (int level)
{
const char *p;
switch (level)
{
case 11: p = "gpg-preset-passphrase (@GNUPG@)";
break;
case 13: p = VERSION; break;
case 17: p = PRINTABLE_OS_NAME; break;
case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break;
case 1:
case 40:
p = _("Usage: gpg-preset-passphrase [options] KEYGRIP (-h for help)\n");
break;
case 41:
p = _("Syntax: gpg-preset-passphrase [options] KEYGRIP\n"
"Password cache maintenance\n");
break;
default: p = NULL;
}
return p;
}
static void
preset_passphrase (const char *keygrip)
{
int rc;
char *line;
/* FIXME: Use secure memory. */
char passphrase[500];
char *passphrase_esc;
if (!opt_passphrase)
{
rc = read (0, passphrase, sizeof (passphrase) - 1);
if (rc < 0)
{
log_error ("reading passphrase failed: %s\n",
gpg_strerror (gpg_error_from_syserror ()));
return;
}
passphrase[rc] = '\0';
line = strchr (passphrase, '\n');
if (line)
{
if (line > passphrase && line[-1] == '\r')
line--;
*line = '\0';
}
/* FIXME: How to handle empty passwords? */
}
{
const char *s = opt_passphrase ? opt_passphrase : passphrase;
passphrase_esc = bin2hex (s, strlen (s), NULL);
}
if (!passphrase_esc)
{
log_error ("can not escape string: %s\n",
gpg_strerror (gpg_error_from_syserror ()));
return;
}
rc = asprintf (&line, "PRESET_PASSPHRASE %s -1 %s\n", keygrip,
passphrase_esc);
wipememory (passphrase_esc, strlen (passphrase_esc));
xfree (passphrase_esc);
if (rc < 0)
{
log_error ("caching passphrase failed: %s\n",
gpg_strerror (gpg_error_from_syserror ()));
return;
}
if (!opt_passphrase)
wipememory (passphrase, sizeof (passphrase));
rc = simple_query (line);
if (rc)
{
log_error ("caching passphrase failed: %s\n", gpg_strerror (rc));
return;
}
wipememory (line, strlen (line));
xfree (line);
}
static void
forget_passphrase (const char *keygrip)
{
int rc;
char *line;
rc = asprintf (&line, "CLEAR_PASSPHRASE %s\n", keygrip);
if (rc < 0)
rc = gpg_error_from_syserror ();
else
rc = simple_query (line);
if (rc)
{
log_error ("clearing passphrase failed: %s\n", gpg_strerror (rc));
return;
}
xfree (line);
}
int
main (int argc, char **argv)
{
ARGPARSE_ARGS pargs;
int cmd = 0;
const char *keygrip = NULL;
early_system_init ();
set_strusage (my_strusage);
log_set_prefix ("gpg-preset-passphrase", GPGRT_LOG_WITH_PREFIX);
/* Make sure that our subsystems are ready. */
i18n_init ();
init_common_subsystems (&argc, &argv);
pargs.argc = &argc;
pargs.argv = &argv;
pargs.flags= 1; /* (do not remove the args) */
while (arg_parse (&pargs, opts) )
{
switch (pargs.r_opt)
{
case oVerbose: opt.verbose++; break;
case oHomedir: gnupg_set_homedir (pargs.r.ret_str); break;
case oPreset: cmd = oPreset; break;
case oForget: cmd = oForget; break;
case oPassphrase: opt_passphrase = pargs.r.ret_str; break;
default : pargs.err = 2; break;
}
}
if (log_get_errorcount(0))
exit(2);
if (argc == 1)
keygrip = *argv;
else
usage (1);
/* Tell simple-pwquery about the standard socket name. */
{
char *tmp = make_filename (gnupg_socketdir (), GPG_AGENT_SOCK_NAME, NULL);
simple_pw_set_socket (tmp);
xfree (tmp);
}
if (cmd == oPreset)
preset_passphrase (keygrip);
else if (cmd == oForget)
forget_passphrase (keygrip);
else
log_error ("one of the options --preset or --forget must be given\n");
agent_exit (0);
return 8; /*NOTREACHED*/
}
void
agent_exit (int rc)
{
rc = rc? rc : log_get_errorcount(0)? 2 : 0;
exit (rc);
}
diff --git a/agent/protect-tool.c b/agent/protect-tool.c
index ec7b47695..059a9bdbd 100644
--- a/agent/protect-tool.c
+++ b/agent/protect-tool.c
@@ -1,820 +1,822 @@
/* protect-tool.c - A tool to test the secret key protection
* Copyright (C) 2002, 2003, 2004, 2006 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>
-#include <assert.h>
#include <sys/stat.h>
#include <unistd.h>
#ifdef HAVE_LOCALE_H
#include <locale.h>
#endif
#ifdef HAVE_LANGINFO_CODESET
#include <langinfo.h>
#endif
#ifdef HAVE_DOSISH_SYSTEM
#include <fcntl.h> /* for setmode() */
#endif
#include "agent.h"
#include "../common/i18n.h"
#include "../common/get-passphrase.h"
#include "../common/sysutils.h"
#include "../common/init.h"
enum cmd_and_opt_values
{
aNull = 0,
oVerbose = 'v',
oArmor = 'a',
oPassphrase = 'P',
oProtect = 'p',
oUnprotect = 'u',
oNoVerbose = 500,
oShadow,
oShowShadowInfo,
oShowKeygrip,
oS2Kcalibration,
oCanonical,
oStore,
oForce,
oHaveCert,
oNoFailOnExist,
oHomedir,
oPrompt,
oStatusMsg,
oDebugUseOCB,
oAgentProgram
};
struct rsa_secret_key_s
{
gcry_mpi_t n; /* public modulus */
gcry_mpi_t e; /* public exponent */
gcry_mpi_t d; /* exponent */
gcry_mpi_t p; /* prime p. */
gcry_mpi_t q; /* prime q. */
gcry_mpi_t u; /* inverse of p mod q. */
};
static int opt_armor;
static int opt_canonical;
static int opt_store;
static int opt_force;
static int opt_no_fail_on_exist;
static int opt_have_cert;
static const char *opt_passphrase;
static char *opt_prompt;
static int opt_status_msg;
static const char *opt_agent_program;
static int opt_debug_use_ocb;
static char *get_passphrase (int promptno);
static void release_passphrase (char *pw);
static ARGPARSE_OPTS opts[] = {
ARGPARSE_group (300, N_("@Commands:\n ")),
ARGPARSE_c (oProtect, "protect", "protect a private key"),
ARGPARSE_c (oUnprotect, "unprotect", "unprotect a private key"),
ARGPARSE_c (oShadow, "shadow", "create a shadow entry for a public key"),
ARGPARSE_c (oShowShadowInfo, "show-shadow-info", "return the shadow info"),
ARGPARSE_c (oShowKeygrip, "show-keygrip", "show the \"keygrip\""),
ARGPARSE_c (oS2Kcalibration, "s2k-calibration", "@"),
ARGPARSE_group (301, N_("@\nOptions:\n ")),
ARGPARSE_s_n (oVerbose, "verbose", "verbose"),
ARGPARSE_s_n (oArmor, "armor", "write output in advanced format"),
ARGPARSE_s_n (oCanonical, "canonical", "write output in canonical format"),
ARGPARSE_s_s (oPassphrase, "passphrase", "|STRING|use passphrase STRING"),
ARGPARSE_s_n (oHaveCert, "have-cert",
"certificate to export provided on STDIN"),
ARGPARSE_s_n (oStore, "store",
"store the created key in the appropriate place"),
ARGPARSE_s_n (oForce, "force",
"force overwriting"),
ARGPARSE_s_n (oNoFailOnExist, "no-fail-on-exist", "@"),
ARGPARSE_s_s (oHomedir, "homedir", "@"),
ARGPARSE_s_s (oPrompt, "prompt",
"|ESCSTRING|use ESCSTRING as prompt in pinentry"),
ARGPARSE_s_n (oStatusMsg, "enable-status-msg", "@"),
ARGPARSE_s_s (oAgentProgram, "agent-program", "@"),
ARGPARSE_s_n (oDebugUseOCB, "debug-use-ocb", "@"), /* For hacking only. */
ARGPARSE_end ()
};
static const char *
my_strusage (int level)
{
const char *p;
switch (level)
{
case 11: p = "gpg-protect-tool (" GNUPG_NAME ")";
break;
case 13: p = VERSION; break;
case 17: p = PRINTABLE_OS_NAME; break;
case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break;
case 1:
case 40: p = _("Usage: gpg-protect-tool [options] (-h for help)\n");
break;
case 41: p = _("Syntax: gpg-protect-tool [options] [args]\n"
"Secret key maintenance tool\n");
break;
default: p = NULL;
}
return p;
}
/* static void */
/* print_mpi (const char *text, gcry_mpi_t a) */
/* { */
/* char *buf; */
/* void *bufaddr = &buf; */
/* int rc; */
/* rc = gcry_mpi_aprint (GCRYMPI_FMT_HEX, bufaddr, NULL, a); */
/* if (rc) */
/* log_info ("%s: [error printing number: %s]\n", text, gpg_strerror (rc)); */
/* else */
/* { */
/* log_info ("%s: %s\n", text, buf); */
/* gcry_free (buf); */
/* } */
/* } */
static unsigned char *
make_canonical (const char *fname, const char *buf, size_t buflen)
{
int rc;
size_t erroff, len;
gcry_sexp_t sexp;
unsigned char *result;
rc = gcry_sexp_sscan (&sexp, &erroff, buf, buflen);
if (rc)
{
log_error ("invalid S-Expression in '%s' (off=%u): %s\n",
fname, (unsigned int)erroff, gpg_strerror (rc));
return NULL;
}
len = gcry_sexp_sprint (sexp, GCRYSEXP_FMT_CANON, NULL, 0);
- assert (len);
+ log_assert (len);
result = xmalloc (len);
len = gcry_sexp_sprint (sexp, GCRYSEXP_FMT_CANON, result, len);
- assert (len);
+ log_assert (len);
gcry_sexp_release (sexp);
return result;
}
static char *
make_advanced (const unsigned char *buf, size_t buflen)
{
int rc;
size_t erroff, len;
gcry_sexp_t sexp;
char *result;
rc = gcry_sexp_sscan (&sexp, &erroff, (const char*)buf, buflen);
if (rc)
{
log_error ("invalid canonical S-Expression (off=%u): %s\n",
(unsigned int)erroff, gpg_strerror (rc));
return NULL;
}
len = gcry_sexp_sprint (sexp, GCRYSEXP_FMT_ADVANCED, NULL, 0);
- assert (len);
+ log_assert (len);
result = xmalloc (len);
len = gcry_sexp_sprint (sexp, GCRYSEXP_FMT_ADVANCED, result, len);
- assert (len);
+ log_assert (len);
gcry_sexp_release (sexp);
return result;
}
static char *
read_file (const char *fname, size_t *r_length)
{
FILE *fp;
char *buf;
size_t buflen;
if (!strcmp (fname, "-"))
{
size_t nread, bufsize = 0;
fp = stdin;
#ifdef HAVE_DOSISH_SYSTEM
setmode ( fileno(fp) , O_BINARY );
#endif
buf = NULL;
buflen = 0;
#define NCHUNK 8192
do
{
bufsize += NCHUNK;
if (!buf)
buf = xmalloc (bufsize);
else
buf = xrealloc (buf, bufsize);
nread = fread (buf+buflen, 1, NCHUNK, fp);
if (nread < NCHUNK && ferror (fp))
{
log_error ("error reading '[stdin]': %s\n", strerror (errno));
xfree (buf);
return NULL;
}
buflen += nread;
}
while (nread == NCHUNK);
#undef NCHUNK
}
else
{
struct stat st;
fp = fopen (fname, "rb");
if (!fp)
{
log_error ("can't open '%s': %s\n", fname, strerror (errno));
return NULL;
}
if (fstat (fileno(fp), &st))
{
log_error ("can't stat '%s': %s\n", fname, strerror (errno));
fclose (fp);
return NULL;
}
buflen = st.st_size;
buf = xmalloc (buflen+1);
if (fread (buf, buflen, 1, fp) != 1)
{
log_error ("error reading '%s': %s\n", fname, strerror (errno));
fclose (fp);
xfree (buf);
return NULL;
}
fclose (fp);
}
*r_length = buflen;
return buf;
}
static unsigned char *
read_key (const char *fname)
{
char *buf;
size_t buflen;
unsigned char *key;
buf = read_file (fname, &buflen);
if (!buf)
return NULL;
key = make_canonical (fname, buf, buflen);
xfree (buf);
return key;
}
static void
read_and_protect (const char *fname)
{
int rc;
unsigned char *key;
unsigned char *result;
size_t resultlen;
char *pw;
key = read_key (fname);
if (!key)
return;
pw = get_passphrase (1);
rc = agent_protect (key, pw, &result, &resultlen, 0,
opt_debug_use_ocb? 1 : -1);
release_passphrase (pw);
xfree (key);
if (rc)
{
log_error ("protecting the key failed: %s\n", gpg_strerror (rc));
return;
}
if (opt_armor)
{
char *p = make_advanced (result, resultlen);
xfree (result);
if (!p)
return;
result = (unsigned char*)p;
resultlen = strlen (p);
}
fwrite (result, resultlen, 1, stdout);
xfree (result);
}
static void
read_and_unprotect (ctrl_t ctrl, const char *fname)
{
int rc;
unsigned char *key;
unsigned char *result;
size_t resultlen;
char *pw;
gnupg_isotime_t protected_at;
key = read_key (fname);
if (!key)
return;
rc = agent_unprotect (ctrl, key, (pw=get_passphrase (1)),
protected_at, &result, &resultlen);
release_passphrase (pw);
xfree (key);
if (rc)
{
if (opt_status_msg)
log_info ("[PROTECT-TOOL:] bad-passphrase\n");
log_error ("unprotecting the key failed: %s\n", gpg_strerror (rc));
return;
}
if (opt.verbose)
{
if (*protected_at)
log_info ("key protection done at %.4s-%.2s-%.2s %.2s:%.2s:%s\n",
protected_at, protected_at+4, protected_at+6,
protected_at+9, protected_at+11, protected_at+13);
else
log_info ("key protection done at [unknown]\n");
}
if (opt_armor)
{
char *p = make_advanced (result, resultlen);
xfree (result);
if (!p)
return;
result = (unsigned char*)p;
resultlen = strlen (p);
}
fwrite (result, resultlen, 1, stdout);
xfree (result);
}
static void
read_and_shadow (const char *fname)
{
int rc;
unsigned char *key;
unsigned char *result;
size_t resultlen;
unsigned char dummy_info[] = "(8:313233342:43)";
key = read_key (fname);
if (!key)
return;
rc = agent_shadow_key (key, dummy_info, &result);
xfree (key);
if (rc)
{
log_error ("shadowing the key failed: %s\n", gpg_strerror (rc));
return;
}
resultlen = gcry_sexp_canon_len (result, 0, NULL,NULL);
- assert (resultlen);
+ log_assert (resultlen);
if (opt_armor)
{
char *p = make_advanced (result, resultlen);
xfree (result);
if (!p)
return;
result = (unsigned char*)p;
resultlen = strlen (p);
}
fwrite (result, resultlen, 1, stdout);
xfree (result);
}
static void
show_shadow_info (const char *fname)
{
int rc;
unsigned char *key;
const unsigned char *info;
size_t infolen;
key = read_key (fname);
if (!key)
return;
rc = agent_get_shadow_info (key, &info);
xfree (key);
if (rc)
{
log_error ("get_shadow_info failed: %s\n", gpg_strerror (rc));
return;
}
infolen = gcry_sexp_canon_len (info, 0, NULL,NULL);
- assert (infolen);
+ log_assert (infolen);
if (opt_armor)
{
char *p = make_advanced (info, infolen);
if (!p)
return;
fwrite (p, strlen (p), 1, stdout);
xfree (p);
}
else
fwrite (info, infolen, 1, stdout);
}
static void
show_file (const char *fname)
{
unsigned char *key;
size_t keylen;
char *p;
key = read_key (fname);
if (!key)
return;
keylen = gcry_sexp_canon_len (key, 0, NULL,NULL);
- assert (keylen);
+ log_assert (keylen);
if (opt_canonical)
{
fwrite (key, keylen, 1, stdout);
}
else
{
p = make_advanced (key, keylen);
if (p)
{
fwrite (p, strlen (p), 1, stdout);
xfree (p);
}
}
xfree (key);
}
static void
show_keygrip (const char *fname)
{
unsigned char *key;
gcry_sexp_t private;
unsigned char grip[20];
int i;
key = read_key (fname);
if (!key)
return;
if (gcry_sexp_new (&private, key, 0, 0))
{
log_error ("gcry_sexp_new failed\n");
return;
}
xfree (key);
if (!gcry_pk_get_keygrip (private, grip))
{
log_error ("can't calculate keygrip\n");
return;
}
gcry_sexp_release (private);
for (i=0; i < 20; i++)
printf ("%02X", grip[i]);
putchar ('\n');
}
int
main (int argc, char **argv )
{
ARGPARSE_ARGS pargs;
int cmd = 0;
const char *fname;
ctrl_t ctrl;
early_system_init ();
set_strusage (my_strusage);
gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN);
log_set_prefix ("gpg-protect-tool", GPGRT_LOG_WITH_PREFIX);
/* Make sure that our subsystems are ready. */
i18n_init ();
init_common_subsystems (&argc, &argv);
setup_libgcrypt_logging ();
gcry_control (GCRYCTL_INIT_SECMEM, 16384, 0);
pargs.argc = &argc;
pargs.argv = &argv;
pargs.flags= 1; /* (do not remove the args) */
while (arg_parse (&pargs, opts) )
{
switch (pargs.r_opt)
{
case oVerbose: opt.verbose++; break;
case oArmor: opt_armor=1; break;
case oCanonical: opt_canonical=1; break;
case oHomedir: gnupg_set_homedir (pargs.r.ret_str); break;
case oAgentProgram: opt_agent_program = pargs.r.ret_str; break;
case oProtect: cmd = oProtect; break;
case oUnprotect: cmd = oUnprotect; break;
case oShadow: cmd = oShadow; break;
case oShowShadowInfo: cmd = oShowShadowInfo; break;
case oShowKeygrip: cmd = oShowKeygrip; break;
case oS2Kcalibration: cmd = oS2Kcalibration; break;
case oPassphrase: opt_passphrase = pargs.r.ret_str; break;
case oStore: opt_store = 1; break;
case oForce: opt_force = 1; break;
case oNoFailOnExist: opt_no_fail_on_exist = 1; break;
case oHaveCert: opt_have_cert = 1; break;
case oPrompt: opt_prompt = pargs.r.ret_str; break;
case oStatusMsg: opt_status_msg = 1; break;
case oDebugUseOCB: opt_debug_use_ocb = 1; break;
default: pargs.err = ARGPARSE_PRINT_ERROR; break;
}
}
if (log_get_errorcount (0))
exit (2);
fname = "-";
if (argc == 1)
fname = *argv;
else if (argc > 1)
usage (1);
/* Allocate an CTRL object. An empty object should be sufficient. */
ctrl = xtrycalloc (1, sizeof *ctrl);
if (!ctrl)
{
log_error ("error allocating connection control data: %s\n",
strerror (errno));
agent_exit (1);
}
/* Set the information which can't be taken from envvars. */
gnupg_prepare_get_passphrase (GPG_ERR_SOURCE_DEFAULT,
opt.verbose,
opt_agent_program,
NULL, NULL, NULL);
if (opt_prompt)
opt_prompt = percent_plus_unescape (opt_prompt, 0);
if (cmd == oProtect)
read_and_protect (fname);
else if (cmd == oUnprotect)
read_and_unprotect (ctrl, fname);
else if (cmd == oShadow)
read_and_shadow (fname);
else if (cmd == oShowShadowInfo)
show_shadow_info (fname);
else if (cmd == oShowKeygrip)
show_keygrip (fname);
else if (cmd == oS2Kcalibration)
{
if (!opt.verbose)
opt.verbose++; /* We need to see something. */
get_standard_s2k_count ();
}
else
show_file (fname);
xfree (ctrl);
agent_exit (0);
return 8; /*NOTREACHED*/
}
void
agent_exit (int rc)
{
rc = rc? rc : log_get_errorcount(0)? 2 : 0;
exit (rc);
}
/* Return the passphrase string and ask the agent if it has not been
set from the command line PROMPTNO select the prompt to display:
0 = default
1 = taken from the option --prompt
2 = for unprotecting a pkcs#12 object
3 = for protecting a new pkcs#12 object
4 = for protecting an imported pkcs#12 in our system
*/
static char *
get_passphrase (int promptno)
{
char *pw;
int err;
const char *desc;
char *orig_codeset;
int repeat = 0;
if (opt_passphrase)
return xstrdup (opt_passphrase);
orig_codeset = i18n_switchto_utf8 ();
if (promptno == 1 && opt_prompt)
{
desc = opt_prompt;
}
else if (promptno == 2)
{
desc = _("Please enter the passphrase to unprotect the "
"PKCS#12 object.");
}
else if (promptno == 3)
{
desc = _("Please enter the passphrase to protect the "
"new PKCS#12 object.");
repeat = 1;
}
else if (promptno == 4)
{
desc = _("Please enter the passphrase to protect the "
"imported object within the GnuPG system.");
repeat = 1;
}
else
desc = _("Please enter the passphrase or the PIN\n"
"needed to complete this operation.");
i18n_switchback (orig_codeset);
err = gnupg_get_passphrase (NULL, NULL, _("Passphrase:"), desc,
repeat, repeat, 1, &pw);
if (err)
{
if (gpg_err_code (err) == GPG_ERR_CANCELED
|| gpg_err_code (err) == GPG_ERR_FULLY_CANCELED)
log_info (_("cancelled\n"));
else
log_error (_("error while asking for the passphrase: %s\n"),
gpg_strerror (err));
agent_exit (0);
}
- assert (pw);
+ log_assert (pw);
return pw;
}
static void
release_passphrase (char *pw)
{
if (pw)
{
wipememory (pw, strlen (pw));
xfree (pw);
}
}
/* Stub function. */
int
agent_key_available (const unsigned char *grip)
{
(void)grip;
return -1; /* Not available. */
}
char *
agent_get_cache (ctrl_t ctrl, const char *key, cache_mode_t cache_mode)
{
(void)ctrl;
(void)key;
(void)cache_mode;
return NULL;
}
gpg_error_t
agent_askpin (ctrl_t ctrl,
const char *desc_text, const char *prompt_text,
const char *initial_errtext,
struct pin_entry_info_s *pininfo,
const char *keyinfo, cache_mode_t cache_mode)
{
gpg_error_t err;
unsigned char *passphrase;
size_t size;
(void)ctrl;
(void)desc_text;
(void)prompt_text;
(void)initial_errtext;
(void)keyinfo;
(void)cache_mode;
*pininfo->pin = 0; /* Reset the PIN. */
passphrase = get_passphrase (0);
size = strlen (passphrase);
if (size >= pininfo->max_length)
return gpg_error (GPG_ERR_TOO_LARGE);
memcpy (&pininfo->pin, passphrase, size);
xfree (passphrase);
pininfo->pin[size] = 0;
if (pininfo->check_cb)
{
/* More checks by utilizing the optional callback. */
pininfo->cb_errtext = NULL;
err = pininfo->check_cb (pininfo);
}
else
err = 0;
return err;
}
/* Replacement for the function in findkey.c. Here we write the key
* to stdout. */
int
agent_write_private_key (const unsigned char *grip,
- const void *buffer, size_t length, int force)
+ const void *buffer, size_t length, int force,
+ const char *serialno, const char *keyref)
{
char hexgrip[40+4+1];
char *p;
(void)force;
+ (void)serialno;
+ (void)keyref;
bin2hex (grip, 20, hexgrip);
strcpy (hexgrip+40, ".key");
p = make_advanced (buffer, length);
if (p)
{
printf ("# Begin dump of %s\n%s%s# End dump of %s\n",
hexgrip, p, (*p && p[strlen(p)-1] == '\n')? "":"\n", hexgrip);
xfree (p);
}
return 0;
}
diff --git a/agent/protect.c b/agent/protect.c
index 61fb8f45d..e3bbf3ed5 100644
--- a/agent/protect.c
+++ b/agent/protect.c
@@ -1,1758 +1,1759 @@
/* protect.c - Un/Protect a secret key
* Copyright (C) 1998-2003, 2007, 2009, 2011 Free Software Foundation, Inc.
* Copyright (C) 1998-2003, 2007, 2009, 2011, 2013-2015 Werner Koch
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <ctype.h>
#include <assert.h>
#include <unistd.h>
#include <sys/stat.h>
#ifdef HAVE_W32_SYSTEM
# ifdef HAVE_WINSOCK2_H
# include <winsock2.h>
# endif
# include <windows.h>
#else
# include <sys/times.h>
#endif
#include "agent.h"
#include "cvt-openpgp.h"
#include "../common/sexp-parse.h"
#include "../common/openpgpdefs.h" /* For s2k functions. */
/* The protection mode for encryption. The supported modes for
decryption are listed in agent_unprotect(). */
#define PROT_CIPHER GCRY_CIPHER_AES128
#define PROT_CIPHER_STRING "aes"
#define PROT_CIPHER_KEYLEN (128/8)
/* A table containing the information needed to create a protected
private key. */
static const struct {
const char *algo;
const char *parmlist;
int prot_from, prot_to;
int ecc_hack;
} protect_info[] = {
{ "rsa", "nedpqu", 2, 5 },
{ "dsa", "pqgyx", 4, 4 },
{ "elg", "pgyx", 3, 3 },
{ "ecdsa","pabgnqd", 6, 6, 1 },
{ "ecdh", "pabgnqd", 6, 6, 1 },
{ "ecc", "pabgnqd", 6, 6, 1 },
{ NULL }
};
/* The number of milliseconds we use in the S2K function and the
* calibrated count value. A count value of zero indicates that the
* calibration has not yet been done or needs to be done again. */
static unsigned int s2k_calibration_time = AGENT_S2K_CALIBRATION;
static unsigned long s2k_calibrated_count;
/* A helper object for time measurement. */
struct calibrate_time_s
{
#ifdef HAVE_W32_SYSTEM
FILETIME creation_time, exit_time, kernel_time, user_time;
#else
clock_t ticks;
#endif
};
static int
hash_passphrase (const char *passphrase, int hashalgo,
int s2kmode,
const unsigned char *s2ksalt, unsigned long s2kcount,
unsigned char *key, size_t keylen);
/* Get the process time and store it in DATA. */
static void
calibrate_get_time (struct calibrate_time_s *data)
{
#ifdef HAVE_W32_SYSTEM
# ifdef HAVE_W32CE_SYSTEM
GetThreadTimes (GetCurrentThread (),
&data->creation_time, &data->exit_time,
&data->kernel_time, &data->user_time);
# else
GetProcessTimes (GetCurrentProcess (),
&data->creation_time, &data->exit_time,
&data->kernel_time, &data->user_time);
# endif
#elif defined (CLOCK_THREAD_CPUTIME_ID)
struct timespec tmp;
clock_gettime (CLOCK_THREAD_CPUTIME_ID, &tmp);
data->ticks = (clock_t)(((unsigned long long)tmp.tv_sec * 1000000000 +
tmp.tv_nsec) * CLOCKS_PER_SEC / 1000000000);
#else
data->ticks = clock ();
#endif
}
static unsigned long
calibrate_elapsed_time (struct calibrate_time_s *starttime)
{
struct calibrate_time_s stoptime;
calibrate_get_time (&stoptime);
#ifdef HAVE_W32_SYSTEM
{
unsigned long long t1, t2;
t1 = (((unsigned long long)starttime->kernel_time.dwHighDateTime << 32)
+ starttime->kernel_time.dwLowDateTime);
t1 += (((unsigned long long)starttime->user_time.dwHighDateTime << 32)
+ starttime->user_time.dwLowDateTime);
t2 = (((unsigned long long)stoptime.kernel_time.dwHighDateTime << 32)
+ stoptime.kernel_time.dwLowDateTime);
t2 += (((unsigned long long)stoptime.user_time.dwHighDateTime << 32)
+ stoptime.user_time.dwLowDateTime);
return (unsigned long)((t2 - t1)/10000);
}
#else
return (unsigned long)((((double) (stoptime.ticks - starttime->ticks))
/CLOCKS_PER_SEC)*1000);
#endif
}
/* Run a test hashing for COUNT and return the time required in
milliseconds. */
static unsigned long
calibrate_s2k_count_one (unsigned long count)
{
int rc;
char keybuf[PROT_CIPHER_KEYLEN];
struct calibrate_time_s starttime;
calibrate_get_time (&starttime);
rc = hash_passphrase ("123456789abcdef0", GCRY_MD_SHA1,
3, "saltsalt", count, keybuf, sizeof keybuf);
if (rc)
BUG ();
return calibrate_elapsed_time (&starttime);
}
/* Measure the time we need to do the hash operations and deduce an
S2K count which requires roughly some targeted amount of time. */
static unsigned long
calibrate_s2k_count (void)
{
unsigned long count;
unsigned long ms;
for (count = 65536; count; count *= 2)
{
ms = calibrate_s2k_count_one (count);
if (opt.verbose > 1)
log_info ("S2K calibration: %lu -> %lums\n", count, ms);
if (ms > s2k_calibration_time)
break;
}
count = (unsigned long)(((double)count / ms) * s2k_calibration_time);
count /= 1024;
count *= 1024;
if (count < 65536)
count = 65536;
if (opt.verbose)
{
ms = calibrate_s2k_count_one (count);
log_info ("S2K calibration: %lu -> %lums\n", count, ms);
}
return count;
}
/* Set the calibration time. This may be called early at startup or
* at any time. Thus it should one set variables. */
void
set_s2k_calibration_time (unsigned int milliseconds)
{
if (!milliseconds)
milliseconds = AGENT_S2K_CALIBRATION;
else if (milliseconds > 60 * 1000)
milliseconds = 60 * 1000; /* Cap at 60 seconds. */
s2k_calibration_time = milliseconds;
s2k_calibrated_count = 0; /* Force re-calibration. */
}
/* Return the calibrated S2K count. This is only public for the use
* of the Assuan getinfo s2k_count_cal command. */
unsigned long
get_calibrated_s2k_count (void)
{
if (!s2k_calibrated_count)
s2k_calibrated_count = calibrate_s2k_count ();
/* Enforce a lower limit. */
return s2k_calibrated_count < 65536 ? 65536 : s2k_calibrated_count;
}
/* Return the standard S2K count. */
unsigned long
get_standard_s2k_count (void)
{
if (opt.s2k_count)
return opt.s2k_count < 65536 ? 65536 : opt.s2k_count;
return get_calibrated_s2k_count ();
}
/* Return the milliseconds required for the standard S2K
* operation. */
unsigned long
get_standard_s2k_time (void)
{
return calibrate_s2k_count_one (get_standard_s2k_count ());
}
/* Same as get_standard_s2k_count but return the count in the encoding
as described by rfc4880. */
unsigned char
get_standard_s2k_count_rfc4880 (void)
{
unsigned long iterations;
unsigned int count;
unsigned char result;
unsigned char c=0;
iterations = get_standard_s2k_count ();
if (iterations >= 65011712)
return 255;
/* Need count to be in the range 16-31 */
for (count=iterations>>6; count>=32; count>>=1)
c++;
result = (c<<4)|(count-16);
if (S2K_DECODE_COUNT(result) < iterations)
result++;
return result;
}
/* Calculate the MIC for a private key or shared secret S-expression.
SHA1HASH should point to a 20 byte buffer. This function is
suitable for all algorithms. */
static gpg_error_t
calculate_mic (const unsigned char *plainkey, unsigned char *sha1hash)
{
const unsigned char *hash_begin, *hash_end;
const unsigned char *s;
size_t n;
int is_shared_secret;
s = plainkey;
if (*s != '(')
return gpg_error (GPG_ERR_INV_SEXP);
s++;
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
if (smatch (&s, n, "private-key"))
is_shared_secret = 0;
else if (smatch (&s, n, "shared-secret"))
is_shared_secret = 1;
else
return gpg_error (GPG_ERR_UNKNOWN_SEXP);
if (*s != '(')
return gpg_error (GPG_ERR_UNKNOWN_SEXP);
hash_begin = s;
if (!is_shared_secret)
{
s++;
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
s += n; /* Skip the algorithm name. */
}
while (*s == '(')
{
s++;
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
s += n;
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
s += n;
if ( *s != ')' )
return gpg_error (GPG_ERR_INV_SEXP);
s++;
}
if (*s != ')')
return gpg_error (GPG_ERR_INV_SEXP);
s++;
hash_end = s;
gcry_md_hash_buffer (GCRY_MD_SHA1, sha1hash,
hash_begin, hash_end - hash_begin);
return 0;
}
/* Encrypt the parameter block starting at PROTBEGIN with length
PROTLEN using the utf8 encoded key PASSPHRASE and return the entire
encrypted block in RESULT or return with an error code. SHA1HASH
is the 20 byte SHA-1 hash required for the integrity code.
The parameter block is expected to be an incomplete canonical
encoded S-Expression of the form (example in advanced format):
(d #046129F..[some bytes not shown]..81#)
(p #00e861b..[some bytes not shown]..f1#)
(q #00f7a7c..[some bytes not shown]..61#)
(u #304559a..[some bytes not shown]..9b#)
the returned block is the S-Expression:
(protected mode (parms) encrypted_octet_string)
*/
static int
do_encryption (const unsigned char *hashbegin, size_t hashlen,
const unsigned char *protbegin, size_t protlen,
const char *passphrase,
const char *timestamp_exp, size_t timestamp_exp_len,
unsigned char **result, size_t *resultlen,
unsigned long s2k_count, int use_ocb)
{
gcry_cipher_hd_t hd;
const char *modestr;
unsigned char hashvalue[20];
int blklen, enclen, outlen;
unsigned char *iv = NULL;
unsigned int ivsize; /* Size of the buffer allocated for IV. */
const unsigned char *s2ksalt; /* Points into IV. */
int rc;
char *outbuf = NULL;
char *p;
int saltpos, ivpos, encpos;
s2ksalt = iv; /* Silence compiler warning. */
*resultlen = 0;
*result = NULL;
modestr = (use_ocb? "openpgp-s2k3-ocb-aes"
/* */: "openpgp-s2k3-sha1-" PROT_CIPHER_STRING "-cbc");
rc = gcry_cipher_open (&hd, PROT_CIPHER,
use_ocb? GCRY_CIPHER_MODE_OCB :
GCRY_CIPHER_MODE_CBC,
GCRY_CIPHER_SECURE);
if (rc)
return rc;
/* We need to work on a copy of the data because this makes it
* easier to add the trailer and the padding and more important we
* have to prefix the text with 2 parenthesis. In CBC mode we
* have to allocate enough space for:
*
* ((<parameter_list>)(4:hash4:sha120:<hashvalue>)) + padding
*
* we always append a full block of random bytes as padding but
* encrypt only what is needed for a full blocksize. In OCB mode we
* have to allocate enough space for just:
*
* ((<parameter_list>))
*/
blklen = gcry_cipher_get_algo_blklen (PROT_CIPHER);
if (use_ocb)
{
/* (( )) */
outlen = 2 + protlen + 2 ;
enclen = outlen + 16 /* taglen */;
outbuf = gcry_malloc_secure (enclen);
}
else
{
/* (( )( 4:hash 4:sha1 20:<hash> )) <padding> */
outlen = 2 + protlen + 2 + 6 + 6 + 23 + 2 + blklen;
enclen = outlen/blklen * blklen;
outbuf = gcry_malloc_secure (outlen);
}
if (!outbuf)
{
rc = out_of_core ();
goto leave;
}
/* Allocate a buffer for the nonce and the salt. */
if (!rc)
{
/* Allocate random bytes to be used as IV, padding and s2k salt
* or in OCB mode for a nonce and the s2k salt. The IV/nonce is
* set later because for OCB we need to set the key first. */
ivsize = (use_ocb? 12 : (blklen*2)) + 8;
iv = xtrymalloc (ivsize);
if (!iv)
rc = gpg_error_from_syserror ();
else
{
gcry_create_nonce (iv, ivsize);
s2ksalt = iv + ivsize - 8;
}
}
/* Hash the passphrase and set the key. */
if (!rc)
{
unsigned char *key;
size_t keylen = PROT_CIPHER_KEYLEN;
key = gcry_malloc_secure (keylen);
if (!key)
rc = out_of_core ();
else
{
rc = hash_passphrase (passphrase, GCRY_MD_SHA1,
3, s2ksalt,
s2k_count? s2k_count:get_standard_s2k_count(),
key, keylen);
if (!rc)
rc = gcry_cipher_setkey (hd, key, keylen);
xfree (key);
}
}
if (rc)
goto leave;
/* Set the IV/nonce. */
rc = gcry_cipher_setiv (hd, iv, use_ocb? 12 : blklen);
if (rc)
goto leave;
if (use_ocb)
{
/* In OCB Mode we use only the public key parameters as AAD. */
rc = gcry_cipher_authenticate (hd, hashbegin, protbegin - hashbegin);
if (!rc)
rc = gcry_cipher_authenticate (hd, timestamp_exp, timestamp_exp_len);
if (!rc)
rc = gcry_cipher_authenticate
(hd, protbegin+protlen, hashlen - (protbegin+protlen - hashbegin));
}
else
{
/* Hash the entire expression for CBC mode. Because
* TIMESTAMP_EXP won't get protected, we can't simply hash a
* continuous buffer but need to call md_write several times. */
gcry_md_hd_t md;
rc = gcry_md_open (&md, GCRY_MD_SHA1, 0 );
if (!rc)
{
gcry_md_write (md, hashbegin, protbegin - hashbegin);
gcry_md_write (md, protbegin, protlen);
gcry_md_write (md, timestamp_exp, timestamp_exp_len);
gcry_md_write (md, protbegin+protlen,
hashlen - (protbegin+protlen - hashbegin));
memcpy (hashvalue, gcry_md_read (md, GCRY_MD_SHA1), 20);
gcry_md_close (md);
}
}
/* Encrypt. */
if (!rc)
{
p = outbuf;
*p++ = '(';
*p++ = '(';
memcpy (p, protbegin, protlen);
p += protlen;
if (use_ocb)
{
*p++ = ')';
*p++ = ')';
}
else
{
memcpy (p, ")(4:hash4:sha120:", 17);
p += 17;
memcpy (p, hashvalue, 20);
p += 20;
*p++ = ')';
*p++ = ')';
memcpy (p, iv+blklen, blklen); /* Add padding. */
p += blklen;
}
- assert ( p - outbuf == outlen);
+ log_assert ( p - outbuf == outlen);
if (use_ocb)
{
gcry_cipher_final (hd);
rc = gcry_cipher_encrypt (hd, outbuf, outlen, NULL, 0);
if (!rc)
{
log_assert (outlen + 16 == enclen);
rc = gcry_cipher_gettag (hd, outbuf + outlen, 16);
}
}
else
{
rc = gcry_cipher_encrypt (hd, outbuf, enclen, NULL, 0);
}
}
if (rc)
goto leave;
/* Release cipher handle and check for errors. */
gcry_cipher_close (hd);
/* Now allocate the buffer we want to return. This is
(protected openpgp-s2k3-sha1-aes-cbc
((sha1 salt no_of_iterations) 16byte_iv)
encrypted_octet_string)
in canoncical format of course. We use asprintf and %n modifier
and dummy values as placeholders. */
{
char countbuf[35];
snprintf (countbuf, sizeof countbuf, "%lu",
s2k_count ? s2k_count : get_standard_s2k_count ());
p = xtryasprintf
("(9:protected%d:%s((4:sha18:%n_8bytes_%u:%s)%d:%n%*s)%d:%n%*s)",
(int)strlen (modestr), modestr,
&saltpos,
(unsigned int)strlen (countbuf), countbuf,
use_ocb? 12 : blklen, &ivpos, use_ocb? 12 : blklen, "",
enclen, &encpos, enclen, "");
if (!p)
{
gpg_error_t tmperr = out_of_core ();
xfree (iv);
xfree (outbuf);
return tmperr;
}
}
*resultlen = strlen (p);
*result = (unsigned char*)p;
memcpy (p+saltpos, s2ksalt, 8);
memcpy (p+ivpos, iv, use_ocb? 12 : blklen);
memcpy (p+encpos, outbuf, enclen);
xfree (iv);
xfree (outbuf);
return 0;
leave:
gcry_cipher_close (hd);
xfree (iv);
xfree (outbuf);
return rc;
}
/* Protect the key encoded in canonical format in PLAINKEY. We assume
a valid S-Exp here. With USE_UCB set to -1 the default scheme is
used (ie. either CBC or OCB), set to 0 the old CBC mode is used,
and set to 1 OCB is used. */
int
agent_protect (const unsigned char *plainkey, const char *passphrase,
unsigned char **result, size_t *resultlen,
unsigned long s2k_count, int use_ocb)
{
int rc;
const char *parmlist;
int prot_from_idx, prot_to_idx;
const unsigned char *s;
const unsigned char *hash_begin, *hash_end;
const unsigned char *prot_begin, *prot_end, *real_end;
size_t n;
int c, infidx, i;
char timestamp_exp[35];
unsigned char *protected;
size_t protectedlen;
int depth = 0;
unsigned char *p;
int have_curve = 0;
if (use_ocb == -1)
use_ocb = !!opt.enable_extended_key_format;
/* Create an S-expression with the protected-at timestamp. */
memcpy (timestamp_exp, "(12:protected-at15:", 19);
gnupg_get_isotime (timestamp_exp+19);
timestamp_exp[19+15] = ')';
/* Parse original key. */
s = plainkey;
if (*s != '(')
return gpg_error (GPG_ERR_INV_SEXP);
depth++;
s++;
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
if (!smatch (&s, n, "private-key"))
return gpg_error (GPG_ERR_UNKNOWN_SEXP);
if (*s != '(')
return gpg_error (GPG_ERR_UNKNOWN_SEXP);
depth++;
hash_begin = s;
s++;
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
for (infidx=0; protect_info[infidx].algo
&& !smatch (&s, n, protect_info[infidx].algo); infidx++)
;
if (!protect_info[infidx].algo)
return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM);
/* The parser below is a complete mess: To make it robust for ECC
use we should reorder the s-expression to include only what we
really need and thus guarantee the right order for saving stuff.
This should be done before calling this function and maybe with
the help of the new gcry_sexp_extract_param. */
parmlist = protect_info[infidx].parmlist;
prot_from_idx = protect_info[infidx].prot_from;
prot_to_idx = protect_info[infidx].prot_to;
prot_begin = prot_end = NULL;
for (i=0; (c=parmlist[i]); i++)
{
if (i == prot_from_idx)
prot_begin = s;
if (*s != '(')
return gpg_error (GPG_ERR_INV_SEXP);
depth++;
s++;
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
if (n != 1 || c != *s)
{
if (n == 5 && !memcmp (s, "curve", 5)
&& !i && protect_info[infidx].ecc_hack)
{
/* This is a private ECC key but the first parameter is
the name of the curve. We change the parameter list
here to the one we expect in this case. */
have_curve = 1;
parmlist = "?qd";
prot_from_idx = 2;
prot_to_idx = 2;
}
else if (n == 5 && !memcmp (s, "flags", 5)
&& i == 1 && have_curve)
{
/* "curve" followed by "flags": Change again. */
parmlist = "??qd";
prot_from_idx = 3;
prot_to_idx = 3;
}
else
return gpg_error (GPG_ERR_INV_SEXP);
}
s += n;
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
s +=n; /* skip value */
if (*s != ')')
return gpg_error (GPG_ERR_INV_SEXP);
depth--;
if (i == prot_to_idx)
prot_end = s;
s++;
}
if (*s != ')' || !prot_begin || !prot_end )
return gpg_error (GPG_ERR_INV_SEXP);
depth--;
hash_end = s;
s++;
/* Skip to the end of the S-expression. */
- assert (depth == 1);
+ log_assert (depth == 1);
rc = sskip (&s, &depth);
if (rc)
return rc;
- assert (!depth);
+ log_assert (!depth);
real_end = s-1;
rc = do_encryption (hash_begin, hash_end - hash_begin + 1,
prot_begin, prot_end - prot_begin + 1,
passphrase, timestamp_exp, sizeof (timestamp_exp),
&protected, &protectedlen, s2k_count, use_ocb);
if (rc)
return rc;
/* Now create the protected version of the key. Note that the 10
extra bytes are for the inserted "protected-" string (the
beginning of the plaintext reads: "((11:private-key(" ). The 35
term is the space for (12:protected-at15:<timestamp>). */
*resultlen = (10
+ (prot_begin-plainkey)
+ protectedlen
+ 35
+ (real_end-prot_end));
*result = p = xtrymalloc (*resultlen);
if (!p)
{
gpg_error_t tmperr = out_of_core ();
xfree (protected);
return tmperr;
}
memcpy (p, "(21:protected-", 14);
p += 14;
memcpy (p, plainkey+4, prot_begin - plainkey - 4);
p += prot_begin - plainkey - 4;
memcpy (p, protected, protectedlen);
p += protectedlen;
memcpy (p, timestamp_exp, 35);
p += 35;
memcpy (p, prot_end+1, real_end - prot_end);
p += real_end - prot_end;
- assert ( p - *result == *resultlen);
+ log_assert ( p - *result == *resultlen);
xfree (protected);
return 0;
}
/* Do the actual decryption and check the return list for consistency. */
static gpg_error_t
do_decryption (const unsigned char *aad_begin, size_t aad_len,
const unsigned char *aadhole_begin, size_t aadhole_len,
const unsigned char *protected, size_t protectedlen,
const char *passphrase,
const unsigned char *s2ksalt, unsigned long s2kcount,
const unsigned char *iv, size_t ivlen,
int prot_cipher, int prot_cipher_keylen, int is_ocb,
unsigned char **result)
{
int rc;
int blklen;
gcry_cipher_hd_t hd;
unsigned char *outbuf;
size_t reallen;
blklen = gcry_cipher_get_algo_blklen (prot_cipher);
if (is_ocb)
{
/* OCB does not require a multiple of the block length but we
* check that it is long enough for the 128 bit tag and that we
* have the 96 bit nonce. */
if (protectedlen < (4 + 16) || ivlen != 12)
return gpg_error (GPG_ERR_CORRUPTED_PROTECTION);
}
else
{
if (protectedlen < 4 || (protectedlen%blklen))
return gpg_error (GPG_ERR_CORRUPTED_PROTECTION);
}
rc = gcry_cipher_open (&hd, prot_cipher,
is_ocb? GCRY_CIPHER_MODE_OCB :
GCRY_CIPHER_MODE_CBC,
GCRY_CIPHER_SECURE);
if (rc)
return rc;
outbuf = gcry_malloc_secure (protectedlen);
if (!outbuf)
rc = out_of_core ();
/* Hash the passphrase and set the key. */
if (!rc)
{
unsigned char *key;
key = gcry_malloc_secure (prot_cipher_keylen);
if (!key)
rc = out_of_core ();
else
{
rc = hash_passphrase (passphrase, GCRY_MD_SHA1,
3, s2ksalt, s2kcount, key, prot_cipher_keylen);
if (!rc)
rc = gcry_cipher_setkey (hd, key, prot_cipher_keylen);
xfree (key);
}
}
/* Set the IV/nonce. */
if (!rc)
{
rc = gcry_cipher_setiv (hd, iv, ivlen);
}
/* Decrypt. */
if (!rc)
{
if (is_ocb)
{
rc = gcry_cipher_authenticate (hd, aad_begin,
aadhole_begin - aad_begin);
if (!rc)
rc = gcry_cipher_authenticate
(hd, aadhole_begin + aadhole_len,
aad_len - (aadhole_begin+aadhole_len - aad_begin));
if (!rc)
{
gcry_cipher_final (hd);
rc = gcry_cipher_decrypt (hd, outbuf, protectedlen - 16,
protected, protectedlen - 16);
}
if (!rc)
{
rc = gcry_cipher_checktag (hd, protected + protectedlen - 16, 16);
if (gpg_err_code (rc) == GPG_ERR_CHECKSUM)
{
/* Return Bad Passphrase instead of checksum error */
rc = gpg_error (GPG_ERR_BAD_PASSPHRASE);
}
}
}
else
{
rc = gcry_cipher_decrypt (hd, outbuf, protectedlen,
protected, protectedlen);
}
}
/* Release cipher handle and check for errors. */
gcry_cipher_close (hd);
if (rc)
{
xfree (outbuf);
return rc;
}
/* Do a quick check on the data structure. */
if (*outbuf != '(' && outbuf[1] != '(')
{
xfree (outbuf);
return gpg_error (GPG_ERR_BAD_PASSPHRASE);
}
/* Check that we have a consistent S-Exp. */
reallen = gcry_sexp_canon_len (outbuf, protectedlen, NULL, NULL);
if (!reallen || (reallen + blklen < protectedlen) )
{
xfree (outbuf);
return gpg_error (GPG_ERR_BAD_PASSPHRASE);
}
*result = outbuf;
return 0;
}
/* Merge the parameter list contained in CLEARTEXT with the original
* protect lists PROTECTEDKEY by replacing the list at REPLACEPOS.
* Return the new list in RESULT and the MIC value in the 20 byte
* buffer SHA1HASH; if SHA1HASH is NULL no MIC will be computed.
* CUTOFF and CUTLEN will receive the offset and the length of the
* resulting list which should go into the MIC calculation but then be
* removed. */
static gpg_error_t
merge_lists (const unsigned char *protectedkey,
size_t replacepos,
const unsigned char *cleartext,
unsigned char *sha1hash,
unsigned char **result, size_t *resultlen,
size_t *cutoff, size_t *cutlen)
{
size_t n, newlistlen;
unsigned char *newlist, *p;
const unsigned char *s;
const unsigned char *startpos, *endpos;
int i, rc;
*result = NULL;
*resultlen = 0;
*cutoff = 0;
*cutlen = 0;
if (replacepos < 26)
return gpg_error (GPG_ERR_BUG);
/* Estimate the required size of the resulting list. We have a large
safety margin of >20 bytes (FIXME: MIC hash from CLEARTEXT and the
removed "protected-" */
newlistlen = gcry_sexp_canon_len (protectedkey, 0, NULL, NULL);
if (!newlistlen)
return gpg_error (GPG_ERR_BUG);
n = gcry_sexp_canon_len (cleartext, 0, NULL, NULL);
if (!n)
return gpg_error (GPG_ERR_BUG);
newlistlen += n;
newlist = gcry_malloc_secure (newlistlen);
if (!newlist)
return out_of_core ();
/* Copy the initial segment */
strcpy ((char*)newlist, "(11:private-key");
p = newlist + 15;
memcpy (p, protectedkey+15+10, replacepos-15-10);
p += replacepos-15-10;
/* Copy the cleartext. */
s = cleartext;
if (*s != '(' && s[1] != '(')
return gpg_error (GPG_ERR_BUG); /*we already checked this */
s += 2;
startpos = s;
while ( *s == '(' )
{
s++;
n = snext (&s);
if (!n)
goto invalid_sexp;
s += n;
n = snext (&s);
if (!n)
goto invalid_sexp;
s += n;
if ( *s != ')' )
goto invalid_sexp;
s++;
}
if ( *s != ')' )
goto invalid_sexp;
endpos = s;
s++;
/* Intermezzo: Get the MIC if requested. */
if (sha1hash)
{
if (*s != '(')
goto invalid_sexp;
s++;
n = snext (&s);
if (!smatch (&s, n, "hash"))
goto invalid_sexp;
n = snext (&s);
if (!smatch (&s, n, "sha1"))
goto invalid_sexp;
n = snext (&s);
if (n != 20)
goto invalid_sexp;
memcpy (sha1hash, s, 20);
s += n;
if (*s != ')')
goto invalid_sexp;
}
/* Append the parameter list. */
memcpy (p, startpos, endpos - startpos);
p += endpos - startpos;
/* Skip over the protected list element in the original list. */
s = protectedkey + replacepos;
- assert (*s == '(');
+ log_assert (*s == '(');
s++;
i = 1;
rc = sskip (&s, &i);
if (rc)
goto failure;
/* Record the position of the optional protected-at expression. */
if (*s == '(')
{
const unsigned char *save_s = s;
s++;
n = snext (&s);
if (smatch (&s, n, "protected-at"))
{
i = 1;
rc = sskip (&s, &i);
if (rc)
goto failure;
*cutlen = s - save_s;
}
s = save_s;
}
startpos = s;
i = 2; /* we are inside this level */
rc = sskip (&s, &i);
if (rc)
goto failure;
- assert (s[-1] == ')');
+ log_assert (s[-1] == ')');
endpos = s; /* one behind the end of the list */
/* Append the rest. */
if (*cutlen)
*cutoff = p - newlist;
memcpy (p, startpos, endpos - startpos);
p += endpos - startpos;
/* ready */
*result = newlist;
*resultlen = newlistlen;
return 0;
failure:
wipememory (newlist, newlistlen);
xfree (newlist);
return rc;
invalid_sexp:
wipememory (newlist, newlistlen);
xfree (newlist);
return gpg_error (GPG_ERR_INV_SEXP);
}
/* Unprotect the key encoded in canonical format. We assume a valid
S-Exp here. If a protected-at item is available, its value will
be stored at protected_at unless this is NULL. */
gpg_error_t
agent_unprotect (ctrl_t ctrl,
const unsigned char *protectedkey, const char *passphrase,
gnupg_isotime_t protected_at,
unsigned char **result, size_t *resultlen)
{
static const struct {
const char *name; /* Name of the protection method. */
int algo; /* (A zero indicates the "openpgp-native" hack.) */
int keylen; /* Used key length in bytes. */
unsigned int is_ocb:1;
} algotable[] = {
{ "openpgp-s2k3-sha1-aes-cbc", GCRY_CIPHER_AES128, (128/8)},
{ "openpgp-s2k3-sha1-aes256-cbc", GCRY_CIPHER_AES256, (256/8)},
{ "openpgp-s2k3-ocb-aes", GCRY_CIPHER_AES128, (128/8), 1},
{ "openpgp-native", 0, 0 }
};
int rc;
const unsigned char *s;
const unsigned char *protect_list;
size_t n;
int infidx, i;
unsigned char sha1hash[20], sha1hash2[20];
const unsigned char *s2ksalt;
unsigned long s2kcount;
const unsigned char *iv;
int prot_cipher, prot_cipher_keylen;
int is_ocb;
const unsigned char *aad_begin, *aad_end, *aadhole_begin, *aadhole_end;
const unsigned char *prot_begin;
unsigned char *cleartext;
unsigned char *final;
size_t finallen;
size_t cutoff, cutlen;
if (protected_at)
*protected_at = 0;
s = protectedkey;
if (*s != '(')
return gpg_error (GPG_ERR_INV_SEXP);
s++;
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
if (!smatch (&s, n, "protected-private-key"))
return gpg_error (GPG_ERR_UNKNOWN_SEXP);
if (*s != '(')
return gpg_error (GPG_ERR_UNKNOWN_SEXP);
{
aad_begin = aad_end = s;
aad_end++;
i = 1;
rc = sskip (&aad_end, &i);
if (rc)
return rc;
}
s++;
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
for (infidx=0; protect_info[infidx].algo
&& !smatch (&s, n, protect_info[infidx].algo); infidx++)
;
if (!protect_info[infidx].algo)
return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM);
/* See whether we have a protected-at timestamp. */
protect_list = s; /* Save for later. */
if (protected_at)
{
while (*s == '(')
{
prot_begin = s;
s++;
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
if (smatch (&s, n, "protected-at"))
{
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
if (n != 15)
return gpg_error (GPG_ERR_UNKNOWN_SEXP);
memcpy (protected_at, s, 15);
protected_at[15] = 0;
break;
}
s += n;
i = 1;
rc = sskip (&s, &i);
if (rc)
return rc;
}
}
/* Now find the list with the protected information. Here is an
example for such a list:
(protected openpgp-s2k3-sha1-aes-cbc
((sha1 <salt> <count>) <Initialization_Vector>)
<encrypted_data>)
*/
s = protect_list;
for (;;)
{
if (*s != '(')
return gpg_error (GPG_ERR_INV_SEXP);
prot_begin = s;
s++;
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
if (smatch (&s, n, "protected"))
break;
s += n;
i = 1;
rc = sskip (&s, &i);
if (rc)
return rc;
}
/* found */
{
aadhole_begin = aadhole_end = prot_begin;
aadhole_end++;
i = 1;
rc = sskip (&aadhole_end, &i);
if (rc)
return rc;
}
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
/* Lookup the protection algo. */
prot_cipher = 0; /* (avoid gcc warning) */
prot_cipher_keylen = 0; /* (avoid gcc warning) */
is_ocb = 0;
for (i=0; i < DIM (algotable); i++)
if (smatch (&s, n, algotable[i].name))
{
prot_cipher = algotable[i].algo;
prot_cipher_keylen = algotable[i].keylen;
is_ocb = algotable[i].is_ocb;
break;
}
if (i == DIM (algotable))
return gpg_error (GPG_ERR_UNSUPPORTED_PROTECTION);
if (!prot_cipher) /* This is "openpgp-native". */
{
gcry_sexp_t s_prot_begin;
rc = gcry_sexp_sscan (&s_prot_begin, NULL,
prot_begin,
gcry_sexp_canon_len (prot_begin, 0,NULL,NULL));
if (rc)
return rc;
rc = convert_from_openpgp_native (ctrl, s_prot_begin, passphrase, &final);
gcry_sexp_release (s_prot_begin);
if (!rc)
{
*result = final;
*resultlen = gcry_sexp_canon_len (final, 0, NULL, NULL);
}
return rc;
}
if (*s != '(' || s[1] != '(')
return gpg_error (GPG_ERR_INV_SEXP);
s += 2;
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
if (!smatch (&s, n, "sha1"))
return gpg_error (GPG_ERR_UNSUPPORTED_PROTECTION);
n = snext (&s);
if (n != 8)
return gpg_error (GPG_ERR_CORRUPTED_PROTECTION);
s2ksalt = s;
s += n;
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_CORRUPTED_PROTECTION);
/* We expect a list close as next, so we can simply use strtoul()
here. We might want to check that we only have digits - but this
is nothing we should worry about */
if (s[n] != ')' )
return gpg_error (GPG_ERR_INV_SEXP);
/* Old versions of gpg-agent used the funny floating point number in
a byte encoding as specified by OpenPGP. However this is not
needed and thus we now store it as a plain unsigned integer. We
can easily distinguish the old format by looking at its value:
Less than 256 is an old-style encoded number; other values are
plain integers. In any case we check that they are at least
65536 because we never used a lower value in the past and we
should have a lower limit. */
s2kcount = strtoul ((const char*)s, NULL, 10);
if (!s2kcount)
return gpg_error (GPG_ERR_CORRUPTED_PROTECTION);
if (s2kcount < 256)
s2kcount = (16ul + (s2kcount & 15)) << ((s2kcount >> 4) + 6);
if (s2kcount < 65536)
return gpg_error (GPG_ERR_CORRUPTED_PROTECTION);
s += n;
s++; /* skip list end */
n = snext (&s);
if (is_ocb)
{
if (n != 12) /* Wrong size of the nonce. */
return gpg_error (GPG_ERR_CORRUPTED_PROTECTION);
}
else
{
if (n != 16) /* Wrong blocksize for IV (we support only 128 bit). */
return gpg_error (GPG_ERR_CORRUPTED_PROTECTION);
}
iv = s;
s += n;
if (*s != ')' )
return gpg_error (GPG_ERR_INV_SEXP);
s++;
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
cleartext = NULL; /* Avoid cc warning. */
rc = do_decryption (aad_begin, aad_end - aad_begin,
aadhole_begin, aadhole_end - aadhole_begin,
s, n,
passphrase, s2ksalt, s2kcount,
iv, is_ocb? 12:16,
prot_cipher, prot_cipher_keylen, is_ocb,
&cleartext);
if (rc)
return rc;
rc = merge_lists (protectedkey, prot_begin-protectedkey, cleartext,
is_ocb? NULL : sha1hash,
&final, &finallen, &cutoff, &cutlen);
/* Albeit cleartext has been allocated in secure memory and thus
xfree will wipe it out, we do an extra wipe just in case
somethings goes badly wrong. */
wipememory (cleartext, n);
xfree (cleartext);
if (rc)
return rc;
if (!is_ocb)
{
rc = calculate_mic (final, sha1hash2);
if (!rc && memcmp (sha1hash, sha1hash2, 20))
rc = gpg_error (GPG_ERR_CORRUPTED_PROTECTION);
if (rc)
{
wipememory (final, finallen);
xfree (final);
return rc;
}
}
/* Now remove the part which is included in the MIC but should not
go into the final thing. */
if (cutlen)
{
memmove (final+cutoff, final+cutoff+cutlen, finallen-cutoff-cutlen);
finallen -= cutlen;
}
*result = final;
*resultlen = gcry_sexp_canon_len (final, 0, NULL, NULL);
return 0;
}
/* Check the type of the private key, this is one of the constants:
PRIVATE_KEY_UNKNOWN if we can't figure out the type (this is the
value 0), PRIVATE_KEY_CLEAR for an unprotected private key.
PRIVATE_KEY_PROTECTED for an protected private key or
PRIVATE_KEY_SHADOWED for a sub key where the secret parts are
stored elsewhere. Finally PRIVATE_KEY_OPENPGP_NONE may be returned
is the key is still in the openpgp-native format but without
protection. */
int
agent_private_key_type (const unsigned char *privatekey)
{
const unsigned char *s;
size_t n;
int i;
s = privatekey;
if (*s != '(')
return PRIVATE_KEY_UNKNOWN;
s++;
n = snext (&s);
if (!n)
return PRIVATE_KEY_UNKNOWN;
if (smatch (&s, n, "protected-private-key"))
{
/* We need to check whether this is openpgp-native protected
with the protection method "none". In that case we return a
different key type so that the caller knows that there is no
need to ask for a passphrase. */
if (*s != '(')
return PRIVATE_KEY_PROTECTED; /* Unknown sexp - assume protected. */
s++;
n = snext (&s);
if (!n)
return PRIVATE_KEY_UNKNOWN; /* Invalid sexp. */
s += n; /* Skip over the algo */
/* Find the (protected ...) list. */
for (;;)
{
if (*s != '(')
return PRIVATE_KEY_UNKNOWN; /* Invalid sexp. */
s++;
n = snext (&s);
if (!n)
return PRIVATE_KEY_UNKNOWN; /* Invalid sexp. */
if (smatch (&s, n, "protected"))
break;
s += n;
i = 1;
if (sskip (&s, &i))
return PRIVATE_KEY_UNKNOWN; /* Invalid sexp. */
}
/* Found - Is this openpgp-native? */
n = snext (&s);
if (!n)
return PRIVATE_KEY_UNKNOWN; /* Invalid sexp. */
if (smatch (&s, n, "openpgp-native")) /* Yes. */
{
if (*s != '(')
return PRIVATE_KEY_UNKNOWN; /* Unknown sexp. */
s++;
n = snext (&s);
if (!n)
return PRIVATE_KEY_UNKNOWN; /* Invalid sexp. */
s += n; /* Skip over "openpgp-private-key". */
/* Find the (protection ...) list. */
for (;;)
{
if (*s != '(')
return PRIVATE_KEY_UNKNOWN; /* Invalid sexp. */
s++;
n = snext (&s);
if (!n)
return PRIVATE_KEY_UNKNOWN; /* Invalid sexp. */
if (smatch (&s, n, "protection"))
break;
s += n;
i = 1;
if (sskip (&s, &i))
return PRIVATE_KEY_UNKNOWN; /* Invalid sexp. */
}
/* Found - Is the mode "none"? */
n = snext (&s);
if (!n)
return PRIVATE_KEY_UNKNOWN; /* Invalid sexp. */
if (smatch (&s, n, "none"))
return PRIVATE_KEY_OPENPGP_NONE; /* Yes. */
}
return PRIVATE_KEY_PROTECTED;
}
if (smatch (&s, n, "shadowed-private-key"))
return PRIVATE_KEY_SHADOWED;
if (smatch (&s, n, "private-key"))
return PRIVATE_KEY_CLEAR;
return PRIVATE_KEY_UNKNOWN;
}
/* Transform a passphrase into a suitable key of length KEYLEN and
store this key in the caller provided buffer KEY. The caller must
provide an HASHALGO, a valid S2KMODE (see rfc-2440) and depending on
that mode an S2KSALT of 8 random bytes and an S2KCOUNT.
Returns an error code on failure. */
static int
hash_passphrase (const char *passphrase, int hashalgo,
int s2kmode,
const unsigned char *s2ksalt,
unsigned long s2kcount,
unsigned char *key, size_t keylen)
{
/* The key derive function does not support a zero length string for
the passphrase in the S2K modes. Return a better suited error
code than GPG_ERR_INV_DATA. */
if (!passphrase || !*passphrase)
return gpg_error (GPG_ERR_NO_PASSPHRASE);
return gcry_kdf_derive (passphrase, strlen (passphrase),
s2kmode == 3? GCRY_KDF_ITERSALTED_S2K :
s2kmode == 1? GCRY_KDF_SALTED_S2K :
s2kmode == 0? GCRY_KDF_SIMPLE_S2K : GCRY_KDF_NONE,
hashalgo, s2ksalt, 8, s2kcount,
keylen, key);
}
gpg_error_t
s2k_hash_passphrase (const char *passphrase, int hashalgo,
int s2kmode,
const unsigned char *s2ksalt,
unsigned int s2kcount,
unsigned char *key, size_t keylen)
{
return hash_passphrase (passphrase, hashalgo, s2kmode, s2ksalt,
S2K_DECODE_COUNT (s2kcount),
key, keylen);
}
/* Create an canonical encoded S-expression with the shadow info from
a card's SERIALNO and the IDSTRING. */
unsigned char *
make_shadow_info (const char *serialno, const char *idstring)
{
const char *s;
char *info, *p;
char numbuf[20];
size_t n;
for (s=serialno, n=0; *s && s[1]; s += 2)
n++;
info = p = xtrymalloc (1 + sizeof numbuf + n
+ sizeof numbuf + strlen (idstring) + 1 + 1);
if (!info)
return NULL;
*p++ = '(';
p = stpcpy (p, smklen (numbuf, sizeof numbuf, n, NULL));
for (s=serialno; *s && s[1]; s += 2)
*(unsigned char *)p++ = xtoi_2 (s);
p = stpcpy (p, smklen (numbuf, sizeof numbuf, strlen (idstring), NULL));
p = stpcpy (p, idstring);
*p++ = ')';
*p = 0;
return (unsigned char *)info;
}
/* Create a shadow key from a public key. We use the shadow protocol
"t1-v1" and insert the S-expressionn SHADOW_INFO. The resulting
S-expression is returned in an allocated buffer RESULT will point
to. The input parameters are expected to be valid canonicalized
S-expressions */
int
agent_shadow_key (const unsigned char *pubkey,
const unsigned char *shadow_info,
unsigned char **result)
{
const unsigned char *s;
const unsigned char *point;
size_t n;
int depth = 0;
char *p;
size_t pubkey_len = gcry_sexp_canon_len (pubkey, 0, NULL,NULL);
size_t shadow_info_len = gcry_sexp_canon_len (shadow_info, 0, NULL,NULL);
if (!pubkey_len || !shadow_info_len)
return gpg_error (GPG_ERR_INV_VALUE);
s = pubkey;
if (*s != '(')
return gpg_error (GPG_ERR_INV_SEXP);
depth++;
s++;
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
if (!smatch (&s, n, "public-key"))
return gpg_error (GPG_ERR_UNKNOWN_SEXP);
if (*s != '(')
return gpg_error (GPG_ERR_UNKNOWN_SEXP);
depth++;
s++;
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
s += n; /* skip over the algorithm name */
while (*s != ')')
{
if (*s != '(')
return gpg_error (GPG_ERR_INV_SEXP);
depth++;
s++;
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
s += n;
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
s +=n; /* skip value */
if (*s != ')')
return gpg_error (GPG_ERR_INV_SEXP);
depth--;
s++;
}
point = s; /* insert right before the point */
depth--;
s++;
- assert (depth == 1);
+ log_assert (depth == 1);
/* Calculate required length by taking in account: the "shadowed-"
prefix, the "shadowed", "t1-v1" as well as some parenthesis */
n = 12 + pubkey_len + 1 + 3+8 + 2+5 + shadow_info_len + 1;
*result = xtrymalloc (n);
p = (char*)*result;
if (!p)
return out_of_core ();
p = stpcpy (p, "(20:shadowed-private-key");
/* (10:public-key ...)*/
memcpy (p, pubkey+14, point - (pubkey+14));
p += point - (pubkey+14);
p = stpcpy (p, "(8:shadowed5:t1-v1");
memcpy (p, shadow_info, shadow_info_len);
p += shadow_info_len;
*p++ = ')';
memcpy (p, point, pubkey_len - (point - pubkey));
p += pubkey_len - (point - pubkey);
return 0;
}
/* Parse a canonical encoded shadowed key and return a pointer to the
inner list with the shadow_info */
gpg_error_t
agent_get_shadow_info (const unsigned char *shadowkey,
unsigned char const **shadow_info)
{
const unsigned char *s;
size_t n;
int depth = 0;
s = shadowkey;
if (*s != '(')
return gpg_error (GPG_ERR_INV_SEXP);
depth++;
s++;
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
if (!smatch (&s, n, "shadowed-private-key"))
return gpg_error (GPG_ERR_UNKNOWN_SEXP);
if (*s != '(')
return gpg_error (GPG_ERR_UNKNOWN_SEXP);
depth++;
s++;
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
s += n; /* skip over the algorithm name */
for (;;)
{
if (*s == ')')
return gpg_error (GPG_ERR_UNKNOWN_SEXP);
if (*s != '(')
return gpg_error (GPG_ERR_INV_SEXP);
depth++;
s++;
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
if (smatch (&s, n, "shadowed"))
break;
s += n;
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
s +=n; /* skip value */
if (*s != ')')
return gpg_error (GPG_ERR_INV_SEXP);
depth--;
s++;
}
/* Found the shadowed list, S points to the protocol */
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
if (smatch (&s, n, "t1-v1"))
{
if (*s != '(')
return gpg_error (GPG_ERR_INV_SEXP);
*shadow_info = s;
}
else
return gpg_error (GPG_ERR_UNSUPPORTED_PROTOCOL);
return 0;
}
/* Parse the canonical encoded SHADOW_INFO S-expression. On success
the hex encoded serial number is returned as a malloced strings at
R_HEXSN and the Id string as a malloced string at R_IDSTR. On
error an error code is returned and NULL is stored at the result
parameters addresses. If the serial number or the ID string is not
- required, NULL may be passed for them. */
+ required, NULL may be passed for them. Note that R_PINLEN is
+ currently not used by any caller. */
gpg_error_t
parse_shadow_info (const unsigned char *shadow_info,
char **r_hexsn, char **r_idstr, int *r_pinlen)
{
const unsigned char *s;
size_t n;
if (r_hexsn)
*r_hexsn = NULL;
if (r_idstr)
*r_idstr = NULL;
if (r_pinlen)
*r_pinlen = 0;
s = shadow_info;
if (*s != '(')
return gpg_error (GPG_ERR_INV_SEXP);
s++;
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
if (r_hexsn)
{
*r_hexsn = bin2hex (s, n, NULL);
if (!*r_hexsn)
return gpg_error_from_syserror ();
}
s += n;
n = snext (&s);
if (!n)
{
if (r_hexsn)
{
xfree (*r_hexsn);
*r_hexsn = NULL;
}
return gpg_error (GPG_ERR_INV_SEXP);
}
if (r_idstr)
{
*r_idstr = xtrymalloc (n+1);
if (!*r_idstr)
{
if (r_hexsn)
{
xfree (*r_hexsn);
*r_hexsn = NULL;
}
return gpg_error_from_syserror ();
}
memcpy (*r_idstr, s, n);
(*r_idstr)[n] = 0;
}
/* Parse the optional PINLEN. */
n = snext (&s);
if (!n)
return 0;
if (r_pinlen)
{
char *tmpstr = xtrymalloc (n+1);
if (!tmpstr)
{
if (r_hexsn)
{
xfree (*r_hexsn);
*r_hexsn = NULL;
}
if (r_idstr)
{
xfree (*r_idstr);
*r_idstr = NULL;
}
return gpg_error_from_syserror ();
}
memcpy (tmpstr, s, n);
tmpstr[n] = 0;
*r_pinlen = (int)strtol (tmpstr, NULL, 10);
xfree (tmpstr);
}
return 0;
}
diff --git a/agent/trans.c b/agent/trans.c
index ff1a34e68..9d090ff86 100644
--- a/agent/trans.c
+++ b/agent/trans.c
@@ -1,41 +1,40 @@
/* trans.c - translatable strings
* 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 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
/* To avoid any problems with the gettext implementation (there used
to be some vulnerabilities in the last years and the use of
external files is a minor security problem in itself), we use our
own simple translation stuff */
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
-#include <assert.h>
#include <unistd.h>
#include <sys/stat.h>
#include "agent.h"
const char *
trans (const char *text)
{
return text;
}
diff --git a/agent/trustlist.c b/agent/trustlist.c
index af177b2e2..d91e92e07 100644
--- a/agent/trustlist.c
+++ b/agent/trustlist.c
@@ -1,825 +1,824 @@
/* trustlist.c - Maintain the list of trusted keys
* Copyright (C) 2002, 2004, 2006, 2007, 2009,
* 2012 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
-#include <assert.h>
#include <unistd.h>
#include <sys/stat.h>
#include <npth.h>
#include "agent.h"
#include <assuan.h> /* fixme: need a way to avoid assuan calls here */
#include "../common/i18n.h"
/* A structure to store the information from the trust file. */
struct trustitem_s
{
struct
{
int disabled:1; /* This entry is disabled. */
int for_pgp:1; /* Set by '*' or 'P' as first flag. */
int for_smime:1; /* Set by '*' or 'S' as first flag. */
int relax:1; /* Relax checking of root certificate
constraints. */
int cm:1; /* Use chain model for validation. */
} flags;
unsigned char fpr[20]; /* The binary fingerprint. */
};
typedef struct trustitem_s trustitem_t;
/* Malloced table and its allocated size with all trust items. */
static trustitem_t *trusttable;
static size_t trusttablesize;
/* A mutex used to protect the table. */
static npth_mutex_t trusttable_lock;
static const char headerblurb[] =
"# This is the list of trusted keys. Comment lines, like this one, as\n"
"# well as empty lines are ignored. Lines have a length limit but this\n"
"# is not a serious limitation as the format of the entries is fixed and\n"
"# checked by gpg-agent. A non-comment line starts with optional white\n"
"# space, followed by the SHA-1 fingerpint in hex, followed by a flag\n"
"# which may be one of 'P', 'S' or '*' and optionally followed by a list of\n"
"# other flags. The fingerprint may be prefixed with a '!' to mark the\n"
"# key as not trusted. You should give the gpg-agent a HUP or run the\n"
"# command \"gpgconf --reload gpg-agent\" after changing this file.\n"
"\n\n"
"# Include the default trust list\n"
"include-default\n"
"\n";
/* This function must be called once to initialize this module. This
has to be done before a second thread is spawned. We can't do the
static initialization because Pth emulation code might not be able
to do a static init; in particular, it is not possible for W32. */
void
initialize_module_trustlist (void)
{
static int initialized;
int err;
if (!initialized)
{
err = npth_mutex_init (&trusttable_lock, NULL);
if (err)
log_fatal ("failed to init mutex in %s: %s\n", __FILE__,strerror (err));
initialized = 1;
}
}
static void
lock_trusttable (void)
{
int err;
err = npth_mutex_lock (&trusttable_lock);
if (err)
log_fatal ("failed to acquire mutex in %s: %s\n", __FILE__, strerror (err));
}
static void
unlock_trusttable (void)
{
int err;
err = npth_mutex_unlock (&trusttable_lock);
if (err)
log_fatal ("failed to release mutex in %s: %s\n", __FILE__, strerror (err));
}
/* Clear the trusttable. The caller needs to make sure that the
trusttable is locked. */
static inline void
clear_trusttable (void)
{
xfree (trusttable);
trusttable = NULL;
trusttablesize = 0;
}
static gpg_error_t
read_one_trustfile (const char *fname, int allow_include,
trustitem_t **addr_of_table,
size_t *addr_of_tablesize,
int *addr_of_tableidx)
{
gpg_error_t err = 0;
estream_t fp;
int n, c;
char *p, line[256];
trustitem_t *table, *ti;
int tableidx;
size_t tablesize;
int lnr = 0;
table = *addr_of_table;
tablesize = *addr_of_tablesize;
tableidx = *addr_of_tableidx;
fp = es_fopen (fname, "r");
if (!fp)
{
err = gpg_error_from_syserror ();
log_error (_("error opening '%s': %s\n"), fname, gpg_strerror (err));
goto leave;
}
while (es_fgets (line, DIM(line)-1, fp))
{
lnr++;
n = strlen (line);
if (!n || line[n-1] != '\n')
{
/* Eat until end of line. */
while ( (c=es_getc (fp)) != EOF && c != '\n')
;
err = gpg_error (*line? GPG_ERR_LINE_TOO_LONG
: GPG_ERR_INCOMPLETE_LINE);
log_error (_("file '%s', line %d: %s\n"),
fname, lnr, gpg_strerror (err));
continue;
}
line[--n] = 0; /* Chop the LF. */
if (n && line[n-1] == '\r')
line[--n] = 0; /* Chop an optional CR. */
/* Allow for empty lines and spaces */
for (p=line; spacep (p); p++)
;
if (!*p || *p == '#')
continue;
if (!strncmp (p, "include-default", 15)
&& (!p[15] || spacep (p+15)))
{
char *etcname;
gpg_error_t err2;
if (!allow_include)
{
log_error (_("statement \"%s\" ignored in '%s', line %d\n"),
"include-default", fname, lnr);
continue;
}
/* fixme: Should check for trailing garbage. */
etcname = make_filename (gnupg_sysconfdir (), "trustlist.txt", NULL);
if ( !strcmp (etcname, fname) ) /* Same file. */
log_info (_("statement \"%s\" ignored in '%s', line %d\n"),
"include-default", fname, lnr);
else if ( access (etcname, F_OK) && errno == ENOENT )
{
/* A non existent system trustlist is not an error.
Just print a note. */
log_info (_("system trustlist '%s' not available\n"), etcname);
}
else
{
err2 = read_one_trustfile (etcname, 0,
&table, &tablesize, &tableidx);
if (err2)
err = err2;
}
xfree (etcname);
continue;
}
if (tableidx == tablesize) /* Need more space. */
{
trustitem_t *tmp;
size_t tmplen;
tmplen = tablesize + 20;
tmp = xtryrealloc (table, tmplen * sizeof *table);
if (!tmp)
{
err = gpg_error_from_syserror ();
goto leave;
}
table = tmp;
tablesize = tmplen;
}
ti = table + tableidx;
memset (&ti->flags, 0, sizeof ti->flags);
if (*p == '!')
{
ti->flags.disabled = 1;
p++;
while (spacep (p))
p++;
}
n = hexcolon2bin (p, ti->fpr, 20);
if (n < 0)
{
log_error (_("bad fingerprint in '%s', line %d\n"), fname, lnr);
err = gpg_error (GPG_ERR_BAD_DATA);
continue;
}
p += n;
for (; spacep (p); p++)
;
/* Process the first flag which needs to be the first for
backward compatibility. */
if (!*p || *p == '*' )
{
ti->flags.for_smime = 1;
ti->flags.for_pgp = 1;
}
else if ( *p == 'P' || *p == 'p')
{
ti->flags.for_pgp = 1;
}
else if ( *p == 'S' || *p == 's')
{
ti->flags.for_smime = 1;
}
else
{
log_error (_("invalid keyflag in '%s', line %d\n"), fname, lnr);
err = gpg_error (GPG_ERR_BAD_DATA);
continue;
}
p++;
if ( *p && !spacep (p) )
{
log_error (_("invalid keyflag in '%s', line %d\n"), fname, lnr);
err = gpg_error (GPG_ERR_BAD_DATA);
continue;
}
/* Now check for more key-value pairs of the form NAME[=VALUE]. */
while (*p)
{
for (; spacep (p); p++)
;
if (!*p)
break;
n = strcspn (p, "= \t");
if (p[n] == '=')
{
log_error ("assigning a value to a flag is not yet supported; "
"in '%s', line %d\n", fname, lnr);
err = gpg_error (GPG_ERR_BAD_DATA);
p++;
}
else if (n == 5 && !memcmp (p, "relax", 5))
ti->flags.relax = 1;
else if (n == 2 && !memcmp (p, "cm", 2))
ti->flags.cm = 1;
else
log_error ("flag '%.*s' in '%s', line %d ignored\n",
n, p, fname, lnr);
p += n;
}
tableidx++;
}
if ( !err && !es_feof (fp) )
{
err = gpg_error_from_syserror ();
log_error (_("error reading '%s', line %d: %s\n"),
fname, lnr, gpg_strerror (err));
}
leave:
es_fclose (fp);
*addr_of_table = table;
*addr_of_tablesize = tablesize;
*addr_of_tableidx = tableidx;
return err;
}
/* Read the trust files and update the global table on success. The
trusttable is assumed to be locked. */
static gpg_error_t
read_trustfiles (void)
{
gpg_error_t err;
trustitem_t *table, *ti;
int tableidx;
size_t tablesize;
char *fname;
int allow_include = 1;
tablesize = 20;
table = xtrycalloc (tablesize, sizeof *table);
if (!table)
return gpg_error_from_syserror ();
tableidx = 0;
fname = make_filename_try (gnupg_homedir (), "trustlist.txt", NULL);
if (!fname)
{
err = gpg_error_from_syserror ();
xfree (table);
return err;
}
if ( access (fname, F_OK) )
{
if ( errno == ENOENT )
; /* Silently ignore a non-existing trustfile. */
else
{
err = gpg_error_from_syserror ();
log_error (_("error opening '%s': %s\n"), fname, gpg_strerror (err));
}
xfree (fname);
fname = make_filename (gnupg_sysconfdir (), "trustlist.txt", NULL);
allow_include = 0;
}
err = read_one_trustfile (fname, allow_include,
&table, &tablesize, &tableidx);
xfree (fname);
if (err)
{
xfree (table);
if (gpg_err_code (err) == GPG_ERR_ENOENT)
{
/* Take a missing trustlist as an empty one. */
clear_trusttable ();
err = 0;
}
return err;
}
/* Fixme: we should drop duplicates and sort the table. */
ti = xtryrealloc (table, (tableidx?tableidx:1) * sizeof *table);
if (!ti)
{
err = gpg_error_from_syserror ();
xfree (table);
return err;
}
/* Replace the trusttable. */
xfree (trusttable);
trusttable = ti;
trusttablesize = tableidx;
return 0;
}
/* Check whether the given fpr is in our trustdb. We expect FPR to be
an all uppercase hexstring of 40 characters. If ALREADY_LOCKED is
true the function assumes that the trusttable is already locked. */
static gpg_error_t
istrusted_internal (ctrl_t ctrl, const char *fpr, int *r_disabled,
int already_locked)
{
gpg_error_t err = 0;
int locked = already_locked;
trustitem_t *ti;
size_t len;
unsigned char fprbin[20];
if (r_disabled)
*r_disabled = 0;
if ( hexcolon2bin (fpr, fprbin, 20) < 0 )
{
err = gpg_error (GPG_ERR_INV_VALUE);
goto leave;
}
if (!already_locked)
{
lock_trusttable ();
locked = 1;
}
if (!trusttable)
{
err = read_trustfiles ();
if (err)
{
log_error (_("error reading list of trusted root certificates\n"));
goto leave;
}
}
if (trusttable)
{
for (ti=trusttable, len = trusttablesize; len; ti++, len--)
if (!memcmp (ti->fpr, fprbin, 20))
{
if (ti->flags.disabled && r_disabled)
*r_disabled = 1;
/* Print status messages only if we have not been called
in a locked state. */
if (already_locked)
;
else if (ti->flags.relax)
{
unlock_trusttable ();
locked = 0;
err = agent_write_status (ctrl, "TRUSTLISTFLAG", "relax", NULL);
}
else if (ti->flags.cm)
{
unlock_trusttable ();
locked = 0;
err = agent_write_status (ctrl, "TRUSTLISTFLAG", "cm", NULL);
}
if (!err)
err = ti->flags.disabled? gpg_error (GPG_ERR_NOT_TRUSTED) : 0;
goto leave;
}
}
err = gpg_error (GPG_ERR_NOT_TRUSTED);
leave:
if (locked && !already_locked)
unlock_trusttable ();
return err;
}
/* Check whether the given fpr is in our trustdb. We expect FPR to be
an all uppercase hexstring of 40 characters. */
gpg_error_t
agent_istrusted (ctrl_t ctrl, const char *fpr, int *r_disabled)
{
return istrusted_internal (ctrl, fpr, r_disabled, 0);
}
/* Write all trust entries to FP. */
gpg_error_t
agent_listtrusted (void *assuan_context)
{
trustitem_t *ti;
char key[51];
gpg_error_t err;
size_t len;
lock_trusttable ();
if (!trusttable)
{
err = read_trustfiles ();
if (err)
{
unlock_trusttable ();
log_error (_("error reading list of trusted root certificates\n"));
return err;
}
}
if (trusttable)
{
for (ti=trusttable, len = trusttablesize; len; ti++, len--)
{
if (ti->flags.disabled)
continue;
bin2hex (ti->fpr, 20, key);
key[40] = ' ';
key[41] = ((ti->flags.for_smime && ti->flags.for_pgp)? '*'
: ti->flags.for_smime? 'S': ti->flags.for_pgp? 'P':' ');
key[42] = '\n';
assuan_send_data (assuan_context, key, 43);
assuan_send_data (assuan_context, NULL, 0); /* flush */
}
}
unlock_trusttable ();
return 0;
}
/* Create a copy of string with colons inserted after each two bytes.
Caller needs to release the string. In case of a memory failure,
NULL is returned. */
static char *
insert_colons (const char *string)
{
char *buffer, *p;
size_t n = strlen (string);
size_t nnew = n + (n+1)/2;
p = buffer = xtrymalloc ( nnew + 1 );
if (!buffer)
return NULL;
while (*string)
{
*p++ = *string++;
if (*string)
{
*p++ = *string++;
if (*string)
*p++ = ':';
}
}
*p = 0;
- assert (strlen (buffer) <= nnew);
+ log_assert (strlen (buffer) <= nnew);
return buffer;
}
/* To pretty print DNs in the Pinentry, we replace slashes by
REPLSTRING. The caller needs to free the returned string. NULL is
returned on error with ERRNO set. */
static char *
reformat_name (const char *name, const char *replstring)
{
const char *s;
char *newname;
char *d;
size_t count;
size_t replstringlen = strlen (replstring);
/* If the name does not start with a slash it is not a preformatted
DN and thus we don't bother to reformat it. */
if (*name != '/')
return xtrystrdup (name);
/* Count the names. Note that a slash contained in a DN part is
expected to be C style escaped and thus the slashes we see here
are the actual part delimiters. */
for (s=name+1, count=0; *s; s++)
if (*s == '/')
count++;
newname = xtrymalloc (strlen (name) + count*replstringlen + 1);
if (!newname)
return NULL;
for (s=name+1, d=newname; *s; s++)
if (*s == '/')
d = stpcpy (d, replstring);
else
*d++ = *s;
*d = 0;
return newname;
}
/* Insert the given fpr into our trustdb. We expect FPR to be an all
uppercase hexstring of 40 characters. FLAG is either 'P' or 'C'.
This function does first check whether that key has already been
put into the trustdb and returns success in this case. Before a
FPR actually gets inserted, the user is asked by means of the
Pinentry whether this is actual what he wants to do. */
gpg_error_t
agent_marktrusted (ctrl_t ctrl, const char *name, const char *fpr, int flag)
{
gpg_error_t err = 0;
char *desc;
char *fname;
estream_t fp;
char *fprformatted;
char *nameformatted;
int is_disabled;
int yes_i_trust;
/* Check whether we are at all allowed to modify the trustlist.
This is useful so that the trustlist may be a symlink to a global
trustlist with only admin privileges to modify it. Of course
this is not a secure way of denying access, but it avoids the
usual clicking on an Okay button most users are used to. */
fname = make_filename_try (gnupg_homedir (), "trustlist.txt", NULL);
if (!fname)
return gpg_error_from_syserror ();
if ( access (fname, W_OK) && errno != ENOENT)
{
xfree (fname);
return gpg_error (GPG_ERR_EPERM);
}
xfree (fname);
if (!agent_istrusted (ctrl, fpr, &is_disabled))
{
return 0; /* We already got this fingerprint. Silently return
success. */
}
/* This feature must explicitly been enabled. */
if (!opt.allow_mark_trusted)
return gpg_error (GPG_ERR_NOT_SUPPORTED);
if (is_disabled)
{
/* There is an disabled entry in the trustlist. Return an error
so that the user won't be asked again for that one. Changing
this flag with the integrated marktrusted feature is and will
not be made possible. */
return gpg_error (GPG_ERR_NOT_TRUSTED);
}
/* Insert a new one. */
nameformatted = reformat_name (name, "%0A ");
if (!nameformatted)
return gpg_error_from_syserror ();
/* First a general question whether this is trusted. */
desc = xtryasprintf (
/* TRANSLATORS: This prompt is shown by the Pinentry
and has one special property: A "%%0A" is used by
Pinentry to insert a line break. The double
percent sign is actually needed because it is also
a printf format string. If you need to insert a
plain % sign, you need to encode it as "%%25". The
"%s" gets replaced by the name as stored in the
certificate. */
L_("Do you ultimately trust%%0A"
" \"%s\"%%0A"
"to correctly certify user certificates?"),
nameformatted);
if (!desc)
{
xfree (nameformatted);
return out_of_core ();
}
err = agent_get_confirmation (ctrl, desc, L_("Yes"), L_("No"), 1);
xfree (desc);
if (!err)
yes_i_trust = 1;
else if (gpg_err_code (err) == GPG_ERR_NOT_CONFIRMED)
yes_i_trust = 0;
else
{
xfree (nameformatted);
return err;
}
fprformatted = insert_colons (fpr);
if (!fprformatted)
{
xfree (nameformatted);
return out_of_core ();
}
/* If the user trusts this certificate he has to verify the
fingerprint of course. */
if (yes_i_trust)
{
desc = xtryasprintf
(
/* TRANSLATORS: This prompt is shown by the Pinentry and has
one special property: A "%%0A" is used by Pinentry to
insert a line break. The double percent sign is actually
needed because it is also a printf format string. If you
need to insert a plain % sign, you need to encode it as
"%%25". The second "%s" gets replaced by a hexdecimal
fingerprint string whereas the first one receives the name
as stored in the certificate. */
L_("Please verify that the certificate identified as:%%0A"
" \"%s\"%%0A"
"has the fingerprint:%%0A"
" %s"), nameformatted, fprformatted);
if (!desc)
{
xfree (fprformatted);
xfree (nameformatted);
return out_of_core ();
}
/* TRANSLATORS: "Correct" is the label of a button and intended
to be hit if the fingerprint matches the one of the CA. The
other button is "the default "Cancel" of the Pinentry. */
err = agent_get_confirmation (ctrl, desc, L_("Correct"), L_("Wrong"), 1);
xfree (desc);
if (gpg_err_code (err) == GPG_ERR_NOT_CONFIRMED)
yes_i_trust = 0;
else if (err)
{
xfree (fprformatted);
xfree (nameformatted);
return err;
}
}
/* Now check again to avoid duplicates. We take the lock to make
sure that nobody else plays with our file and force a reread. */
lock_trusttable ();
clear_trusttable ();
if (!istrusted_internal (ctrl, fpr, &is_disabled, 1) || is_disabled)
{
unlock_trusttable ();
xfree (fprformatted);
xfree (nameformatted);
return is_disabled? gpg_error (GPG_ERR_NOT_TRUSTED) : 0;
}
fname = make_filename_try (gnupg_homedir (), "trustlist.txt", NULL);
if (!fname)
{
err = gpg_error_from_syserror ();
unlock_trusttable ();
xfree (fprformatted);
xfree (nameformatted);
return err;
}
if ( access (fname, F_OK) && errno == ENOENT)
{
fp = es_fopen (fname, "wx,mode=-rw-r");
if (!fp)
{
err = gpg_error_from_syserror ();
log_error ("can't create '%s': %s\n", fname, gpg_strerror (err));
xfree (fname);
unlock_trusttable ();
xfree (fprformatted);
xfree (nameformatted);
return err;
}
es_fputs (headerblurb, fp);
es_fclose (fp);
}
fp = es_fopen (fname, "a+,mode=-rw-r");
if (!fp)
{
err = gpg_error_from_syserror ();
log_error ("can't open '%s': %s\n", fname, gpg_strerror (err));
xfree (fname);
unlock_trusttable ();
xfree (fprformatted);
xfree (nameformatted);
return err;
}
/* Append the key. */
es_fputs ("\n# ", fp);
xfree (nameformatted);
nameformatted = reformat_name (name, "\n# ");
if (!nameformatted || strchr (name, '\n'))
{
/* Note that there should never be a LF in NAME but we better
play safe and print a sanitized version in this case. */
es_write_sanitized (fp, name, strlen (name), NULL, NULL);
}
else
es_fputs (nameformatted, fp);
es_fprintf (fp, "\n%s%s %c%s\n", yes_i_trust?"":"!", fprformatted, flag,
flag == 'S'? " relax":"");
if (es_ferror (fp))
err = gpg_error_from_syserror ();
if (es_fclose (fp))
err = gpg_error_from_syserror ();
clear_trusttable ();
xfree (fname);
unlock_trusttable ();
xfree (fprformatted);
xfree (nameformatted);
if (!err)
bump_key_eventcounter ();
return err;
}
/* This function may be called to force reloading of the
trustlist. */
void
agent_reload_trustlist (void)
{
/* All we need to do is to delete the trusttable. At the next
access it will get re-read. */
lock_trusttable ();
clear_trusttable ();
unlock_trusttable ();
bump_key_eventcounter ();
}
diff --git a/build-aux/speedo/w32/inst.nsi b/build-aux/speedo/w32/inst.nsi
index fb452d513..f130a4eda 100644
--- a/build-aux/speedo/w32/inst.nsi
+++ b/build-aux/speedo/w32/inst.nsi
@@ -1,1653 +1,1655 @@
# inst.nsi - Installer for GnuPG on Windows. -*- coding: latin-1; -*-
# Copyright (C) 2005, 2014 g10 Code GmbH
# 2017 Intevation GmbH
#
# This file is part of GnuPG.
#
# GnuPG is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# GnuPG is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, see <http://www.gnu.org/licenses/>.
# Macros to provide for invocation:
# INST_DIR
# INST6_DIR
# BUILD_DIR
# TOP_SRCDIR
# W32_SRCDIR
# BUILD_ISODATE - the build date, e.g. "2014-10-31"
# BUILD_DATESTR - ditto w/o '-', e.g. "20141031"
# NAME
# VERSION
# PROD_VERSION
#
# WITH_GUI - Include the GPA GUI
!cd "${INST_DIR}"
!addincludedir "${W32_SRCDIR}"
!addplugindir "${BUILD_DIR}"
# The package name and version. PRETTY_PACKAGE is a user visible name
# only while PACKAGE is useful for filenames etc. PROD_VERSION is the
# product version and needs to be in the format "MAJ.MIN.MIC.BUILDNR".
!define PACKAGE "gnupg"
!define PACKAGE_SHORT "gnupg"
!define PRETTY_PACKAGE "GNU Privacy Guard"
!define PRETTY_PACKAGE_SHORT "GnuPG"
!define COMPANY "The GnuPG Project"
!define COPYRIGHT "Copyright (C) 2017 The GnuPG Project"
!define DESCRIPTION "GnuPG: The GNU Privacy Guard for Windows"
!define INSTALL_DIR "GnuPG"
!define WELCOME_TITLE_ENGLISH \
"Welcome to the installation of GnuPG"
!define WELCOME_TITLE_GERMAN \
"Willkommen bei der Installation von GnuPG"
!define ABOUT_ENGLISH \
"GnuPG is the mostly used software for mail and data encryption. \
GnuPG can be used to encrypt data and to create digital signatures. \
GnuPG includes an advanced key management facility and is compliant \
with the OpenPGP Internet standard as described in RFC-4880. \
\r\n\r\n$_CLICK \
\r\n\r\n\r\n\r\n\r\nThis is GnuPG version ${VERSION}.\r\n\
File version: ${PROD_VERSION}\r\n\
Release date: ${BUILD_ISODATE}"
!define ABOUT_GERMAN \
"GnuPG is die häufigst verwendete Software zur Mail- und Datenverschlüsselung.\
\r\n\r\n$_CLICK \
\r\n\r\n\r\n\r\n\r\nDies ist GnuPG Version ${VERSION}.\r\n\
Dateiversion: ${PROD_VERSION}\r\n\
Releasedatum: ${BUILD_ISODATE}"
# The copyright license of the package. Define only one of these.
!define LICENSE_GPL
# Select the best compression algorithm available. The dictionary
# size is the default (8 MB).
!ifndef SOURCES
SetCompressor lzma
# SetCompressorDictSize 8
!endif
# We use the modern UI.
!include "MUI.nsh"
# Some helper some
!include "LogicLib.nsh"
!include "x64.nsh"
# We support user mode installation but prefer system wide
!define MULTIUSER_EXECUTIONLEVEL Highest
!define MULTIUSER_MUI
!define MULTIUSER_INSTALLMODE_COMMANDLINE
!define MULTIUSER_INSTALLMODE_DEFAULT_REGISTRY_KEY "Software\${PACKAGE_SHORT}"
!define MULTIUSER_INSTALLMODE_DEFAULT_REGISTRY_VALUENAME ""
!define MULTIUSER_INSTALLMODE_INSTDIR_REGISTRY_KEY "Software\${PACKAGE_SHORT}"
!define MULTIUSER_INSTALLMODE_INSTDIR_REGISTRY_VALUENAME "Install Directory"
!define MULTIUSER_INSTALLMODE_INSTDIR "${PACKAGE_SHORT}"
!include "MultiUser.nsh"
# Set the package name. Note that this name should not be suffixed
# with the version because this would get displayed in the start menu.
# Given that a slash in the name troubles Windows startmenu creation
# we set the Startmenu explicit below.
Name "${PRETTY_PACKAGE}"
# Set the output filename.
OutFile "${NAME}-${VERSION}_${BUILD_DATESTR}.exe"
#Fixme: Do we need a logo?
#Icon "${TOP_SRCDIR}/doc/logo/gnupg-logo-icon.ico"
#UninstallIcon "${TOP_SRCDIR}/doc/logo/gnupg-logo-icon.ico"
# Set the installation directory.
!ifndef INSTALL_DIR
!define INSTALL_DIR "GnuPG"
!endif
InstallDir "$PROGRAMFILES\${INSTALL_DIR}"
# Add version information to the file properties.
VIProductVersion "${PROD_VERSION}"
VIAddVersionKey "ProductName" "${PRETTY_PACKAGE_SHORT} (${VERSION})"
VIAddVersionKey "Comments" \
"GnuPG is Free Software; you can redistribute it \
and/or modify it under the terms of the GNU General Public License. \
You should have received a copy of the GNU General Public License \
along with this software; if not, write to the Free Software \
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, \
MA 02110-1301, USA"
VIAddVersionKey "CompanyName" "${COMPANY}"
VIAddVersionKey "LegalTrademarks" ""
VIAddVersionKey "LegalCopyright" "${COPYRIGHT}"
VIAddVersionKey "FileDescription" "${DESCRIPTION}"
VIAddVersionKey "FileVersion" "${PROD_VERSION}"
# Interface Settings
# !define MUI_ABORTWARNING
!define MUI_FINISHPAGE_NOAUTOCLOSE
!define MUI_UNFINISHPAGE_NOAUTOCLOSE
!define MUI_HEADERIMAGE
!define MUI_HEADERIMAGE_BITMAP "${W32_SRCDIR}\gnupg-logo-150x57.bmp"
!define MUI_WELCOMEFINISHPAGE_BITMAP "${W32_SRCDIR}\gnupg-logo-164x314.bmp"
# Remember the installer language
!define MUI_LANGDLL_REGISTRY_ROOT "HKCU"
!define MUI_LANGDLL_REGISTRY_KEY "Software\GnuPG"
!define MUI_LANGDLL_REGISTRY_VALUENAME "Installer Language"
#
# The list of wizard pages.
#
!define MUI_WELCOMEPAGE_TITLE "$(T_WelcomeTitle)"
!define MUI_WELCOMEPAGE_TEXT "$(T_About)"
!insertmacro MUI_PAGE_WELCOME
!define MUI_LICENSEPAGE_BUTTON "$(^NextBtn)"
!define MUI_PAGE_HEADER_SUBTEXT "$(T_GPLHeader)"
!define MUI_LICENSEPAGE_TEXT_BOTTOM "$(T_GPLShort)"
!insertmacro MUI_PAGE_LICENSE "${TOP_SRCDIR}/COPYING"
!define MUI_PAGE_CUSTOMFUNCTION_SHOW PrintNonAdminWarning
!define MUI_PAGE_CUSTOMFUNCTION_LEAVE CheckExistingVersion
!insertmacro MUI_PAGE_COMPONENTS
# We don't have MUI_PAGE_DIRECTORY
!ifdef WITH_GUI
Page custom CustomPageOptions
Var STARTMENU_FOLDER
!define MUI_PAGE_CUSTOMFUNCTION_PRE CheckIfStartMenuWanted
!define MUI_STARTMENUPAGE_NODISABLE
!define MUI_STARTMENUPAGE_REGISTRY_ROOT "SHCTX"
!define MUI_STARTMENUPAGE_REGISTRY_KEY "Software\GnuPG"
!define MUI_STARTMENUPAGE_REGISTRY_VALUENAME "Start Menu Folder"
# We need to set the Startmenu name explicitly because a slash in the
# name is not possible.
!define MUI_STARTMENUPAGE_DEFAULTFOLDER "GnuPG"
!insertmacro MUI_PAGE_STARTMENU Application $STARTMENU_FOLDER
!endif
!define MUI_PAGE_CUSTOMFUNCTION_PRE PrintCloseOtherApps
!insertmacro MUI_PAGE_INSTFILES
#!define MUI_PAGE_CUSTOMFUNCTION_PRE ShowFinalWarnings
!define MUI_FINISHPAGE_SHOWREADME "README.txt"
!define MUI_FINISHPAGE_SHOWREADME_TEXT "$(T_ShowReadme)"
#!define MUI_FINISHPAGE_RUN
#!define MUI_FINISHPAGE_RUN_FUNCTION RunOnFinish
#!define MUI_FINISHPAGE_RUN_TEXT "$(T_RunKeyManager)"
#!define MUI_FINISHPAGE_RUN_NOTCHECKED
!define MUI_FINISHPAGE_LINK "$(T_MoreInfo)"
!define MUI_FINISHPAGE_LINK_LOCATION "$(T_MoreInfoURL)"
!insertmacro MUI_PAGE_FINISH
# Uninstaller pages.
!insertmacro MUI_UNPAGE_CONFIRM
!insertmacro MUI_UNPAGE_INSTFILES
#Page license
#Page components
#Page directory
#Page instfiles
#UninstPage uninstConfirm
#UninstPage instfiles
# Language support. This has to be done after defining the pages, but
# before defining the translation strings. Confusing.
!insertmacro MUI_LANGUAGE "English"
!insertmacro MUI_LANGUAGE "German"
!insertmacro MUI_RESERVEFILE_LANGDLL
!insertmacro MUI_RESERVEFILE_INSTALLOPTIONS
ReserveFile "${BUILD_DIR}\g4wihelp.dll"
ReserveFile "${W32_SRCDIR}\gnupg-logo-150x57.bmp"
ReserveFile "${W32_SRCDIR}\gnupg-logo-164x314.bmp"
ReserveFile "${TOP_SRCDIR}\COPYING"
ReserveFile "${W32_SRCDIR}\inst-options.ini"
# Language support
LangString T_LangCode ${LANG_ENGLISH} "en"
LangString T_LangCode ${LANG_GERMAN} "de"
# The WelcomeTitle is displayed on the first page.
LangString T_WelcomeTitle ${LANG_ENGLISH} "${WELCOME_TITLE_ENGLISH}"
LangString T_WelcomeTitle ${LANG_GERMAN} "${WELCOME_TITLE_GERMAN}"
# The About string as displayed on the first page.
LangString T_About ${LANG_ENGLISH} "${ABOUT_ENGLISH}"
LangString T_About ${LANG_GERMAN} "${ABOUT_GERMAN}"
# Startup page
LangString T_GPLHeader ${LANG_ENGLISH} \
"This software is licensed under the terms of the GNU General Public \
License (GNU GPL)."
LangString T_GPLHeader ${LANG_GERMAN}} \
"Diese Software ist unter der GNU General Public License \
(GNU GPL) lizensiert."
LangString T_GPLShort ${LANG_ENGLISH} \
"In short: You are allowed to run this software for any purpose. \
You may distribute it as long as you give the recipients the same \
rights you have received."
LangString T_GPLShort ${LANG_GERMAN} \
"In aller Kürze: Sie haben das Recht, die Software zu jedem Zweck \
einzusetzen. Sie können die Software weitergeben, sofern Sie dem \
Empfänger dieselben Rechte einräumen, die auch Sie erhalten haben."
LangString T_RunKeyManager ${LANG_ENGLISH} \
"Run the key manager"
LangString T_RunKeyManager ${LANG_GERMAN} \
"Die Schlüsselverwaltung aufrufen"
LangString T_MoreInfo ${LANG_ENGLISH} \
"Click here to see how to help the GnuPG Project"
LangString T_MoreInfo ${LANG_GERMAN} \
"Hier klicken um dem GnuPG Projekt zu zu helfen"
LangString T_MoreInfoURL ${LANG_ENGLISH} "https://gnupg.org/donate"
LangString T_MoreInfoURL ${LANG_GERMAN} "https://gnupg.org/donate"
LangString T_ShowReadme ${LANG_ENGLISH} \
"Show the README file"
LangString T_ShowReadme ${LANG_GERMAN} \
"Die README Datei anzeigen"
LangString T_NoKeyManager ${LANG_ENGLISH} \
"No key manager has been installed, thus we can't run one now."
LangString T_NoKeyManager ${LANG_GERMAN} \
"Es wurde keine Schlüsselverwaltung installiert. \
Deswegen kann sie jetzt auch nicht ausgeführt werden."
# Functions
# Custom functions and macros for this installer.
LangString T_AlreadyRunning ${LANG_ENGLISH} \
"An instance of this installer is already running."
LangString T_AlreadyRunning ${LANG_GERMAN} \
"Ein Exemplar dieses Installers läuft bereits."
Function G4wRunOnce
Push $R0
StrCpy $R0 "gnupg"
g4wihelp::runonce
StrCmp $R0 0 +3
MessageBox MB_OK $(T_AlreadyRunning)
Abort
Pop $R0
FunctionEnd
#
# Control function for the Custom page to select special
# install options.
#
Function CustomPageOptions
!insertmacro MUI_HEADER_TEXT "$(T_InstallOptions)" "$(T_InstallOptLinks)"
# Note, that the default selection is done in the ini file
!insertmacro MUI_INSTALLOPTIONS_WRITE "${W32_SRCDIR}/inst-options.ini" \
"Field 1" "Text" "$(T_InstOptLabelA)"
!insertmacro MUI_INSTALLOPTIONS_WRITE "${W32_SRCDIR}/inst-options.ini" \
"Field 2" "Text" "$(T_InstOptFieldA)"
!insertmacro MUI_INSTALLOPTIONS_WRITE "${W32_SRCDIR}/inst-options.ini" \
"Field 3" "Text" "$(T_InstOptFieldB)"
!insertmacro MUI_INSTALLOPTIONS_WRITE "${W32_SRCDIR}/inst-options.ini" \
"Field 4" "Text" "$(T_InstOptFieldC)"
!insertmacro MUI_INSTALLOPTIONS_WRITE "${W32_SRCDIR}/inst-options.ini" \
"Field 5" "Text" "$(T_InstOptLabelB)"
!insertmacro MUI_INSTALLOPTIONS_DISPLAY "${W32_SRCDIR}/inst-options.ini"
FunctionEnd
# Check whether GnuPG has already been installed. This is called as
# a leave function from the components page. A call to abort will get
# back to the components selection.
Function CheckExistingVersion
ClearErrors
FileOpen $0 "$INSTDIR\VERSION" r
IfErrors nexttest
FileRead $0 $R0
FileRead $0 $R1
FileClose $0
Push $R1
Call TrimNewLines
Pop $R1
MessageBox MB_YESNO "$(T_FoundExistingVersion)" IDYES leave
Abort
nexttest:
ClearErrors
ReadRegStr $0 HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\GnuPG" "DisplayVersion"
IfErrors leave 0
MessageBox MB_YESNO "$(T_FoundExistingVersionB)" IDYES leave
Abort
leave:
FunctionEnd
# PrintNonAdminWarning
# Check whether the current user is in the Administrator group or an
# OS version without the need for an Administrator is in use. Print a
# diagnostic if this is not the case and abort installation.
Function PrintNonAdminWarning
ClearErrors
UserInfo::GetName
IfErrors leave
Pop $0
UserInfo::GetAccountType
Pop $1
StrCmp $1 "Admin" leave +1
MessageBox MB_YESNO "$(T_AdminWanted)" IDNO exit
goto leave
exit:
Quit
leave:
FunctionEnd
# Check whether the start menu is actually wanted.
Function CheckIfStartMenuWanted
!insertmacro MUI_INSTALLOPTIONS_READ $R0 "${W32_SRCDIR}/inst-options.ini" \
"Field 2" "State"
IntCmp $R0 1 +2
Abort
FunctionEnd
# Check whether this is a reinstall and popup a message box to explain
# that it is better to close other apps before continuing
Function PrintCloseOtherApps
IfFileExists $INSTDIR\bin\gpg.exe print_warning
IfFileExists $INSTDIR\bin\gpa.exe print_warning
Return
print_warning:
MessageBox MB_OK|MB_ICONEXCLAMATION "$(T_CloseOtherApps)"
FunctionEnd
# Called right before the final page to show more warnings.
#Function ShowFinalWarnings
# leave:
#FunctionEnd
#-----------------------------------------------
# Strings pertaining to the install options page
#-----------------------------------------------
# Installation options title
LangString T_InstallOptions ${LANG_ENGLISH} "Install Options"
LangString T_InstallOptions ${LANG_GERMAN} "Installationsoptionen"
# Installation options subtitle 1
LangString T_InstallOptLinks ${LANG_ENGLISH} "Start links"
LangString T_InstallOptLinks ${LANG_GERMAN} "Startlinks"
LangString T_InstOptLabelA ${LANG_ENGLISH} \
"Please select where GnuPG shall install links:"
LangString T_InstOptLabelA ${LANG_GERMAN} \
"Bitte wählen Sie, welche Verknüpfungen angelegt werden sollen:"
LangString T_InstOptLabelB ${LANG_ENGLISH} \
"(Only programs will be linked into the quick launch bar.)"
LangString T_InstOptLabelB ${LANG_GERMAN} \
"(In die Schnellstartleiste werden nur Verknüpfungen für \
Programme angelegt.) "
LangString T_InstOptFieldA ${LANG_ENGLISH} \
"Start Menu"
LangString T_InstOptFieldA ${LANG_GERMAN} \
"Startmenü"
LangString T_InstOptFieldB ${LANG_ENGLISH} \
"Desktop"
LangString T_InstOptFieldB ${LANG_GERMAN} \
"Arbeitsfläche"
LangString T_InstOptFieldC ${LANG_ENGLISH} \
"Quick Launch Bar"
LangString T_InstOptFieldC ${LANG_GERMAN} \
"Schnellstartleiste"
#------------------------------------------------
# String pertaining to the existing version check
#------------------------------------------------
LangString T_FoundExistingVersion ${LANG_ENGLISH} \
"Version $R1 has already been installed. $\r$\n\
Do you want to overwrite it with version ${VERSION}?"
LangString T_FoundExistingVersion ${LANG_GERMAN} \
"Version $R1 ist hier bereits installiert. $\r$\n\
Möchten Sie diese mit Version ${VERSION} überschreiben? $\r$\n\
$\r$\n\
(Sie können in jedem Fall mit JA antworten, falls es sich um \
eine neuere oder dieselbe Version handelt.)"
LangString T_FoundExistingVersionB ${LANG_ENGLISH} \
"A version of GnuPG has already been installed on the system. \
$\r$\n\
$\r$\n\
Do you want to continue installing GnuPG?"
LangString T_FoundExistingVersionB ${LANG_GERMAN} \
"Eine Version von GnuPG ist hier bereits installiert. \
$\r$\n\
$\r$\n\
Möchten die die Installation von GnuPG fortführen?"
# From Function PrintNonAdminWarning
LangString T_AdminWanted ${LANG_ENGLISH} \
"Warning: It is recommended to install GnuPG system-wide with \
administrator rights. \
$\r$\n\
$\r$\n\
Do you want to continue installing GnuPG without administrator rights?"
LangString T_AdminWanted ${LANG_GERMAN} \
"Achtung: Es wird empfohlen GnuPG systemweit mit \
Administratorrechten zu installieren. \
$\r$\n\
$\r$\n\
Möchten die die Installation von GnuPG ohne Administratorrechte fortführen?"
# From Function PrintCloseOtherApps
LangString T_CloseOtherApps ${LANG_ENGLISH} \
"Please make sure that other applications are not running. \
GnuPG will try to install anyway but a reboot may be required."
LangString T_CloseOtherApps ${LANG_GERMAN} \
"Bitte stellen Sie sicher, daß alle anderen Anwendugen geschlossen \
sind. GnuPG wird auf jeden Fall versuchen, eine Installation \
durchzuführen; es ist dann aber u.U. notwendig, das System neu zu starten."
# TrimNewlines - taken from the NSIS reference
# input, top of stack (e.g. whatever$\r$\n)
# output, top of stack (replaces, with e.g. whatever)
# modifies no other variables.
Function TrimNewlines
Exch $R0
Push $R1
Push $R2
StrCpy $R1 0
loop:
IntOp $R1 $R1 - 1
StrCpy $R2 $R0 1 $R1
StrCmp $R2 "$\r" loop
StrCmp $R2 "$\n" loop
IntOp $R1 $R1 + 1
IntCmp $R1 0 no_trim_needed
StrCpy $R0 $R0 $R1
no_trim_needed:
Pop $R2
Pop $R1
Exch $R0
FunctionEnd
# AddToPath - Adds the given dir to the search path.
# Input - head of the stack
Function AddToPath
ClearErrors
UserInfo::GetName
IfErrors add_admin
Pop $0
UserInfo::GetAccountType
Pop $1
StrCmp $1 "Admin" add_admin add_user
add_admin:
Exch $0
g4wihelp::path_add "$0" "0"
goto add_done
add_user:
Exch $0
g4wihelp::path_add "$0" "1"
goto add_done
add_done:
StrCmp $R5 "0" add_to_path_done
SendMessage ${HWND_BROADCAST} ${WM_WININICHANGE} 0 "STR:Environment" /TIMEOUT=5000
add_to_path_done:
Pop $0
FunctionEnd
# RemoveFromPath - Remove a given dir from the path
# Input: head of the stack
Function un.RemoveFromPath
ClearErrors
UserInfo::GetName
IfErrors remove_admin
Pop $0
UserInfo::GetAccountType
Pop $1
StrCmp $1 "Admin" remove_admin remove_user
remove_admin:
Exch $0
g4wihelp::path_remove "$0" "0"
goto remove_done
remove_user:
Exch $0
g4wihelp::path_remove "$0" "1"
goto remove_done
remove_done:
StrCmp $R5 "0" remove_from_path_done
SendMessage ${HWND_BROADCAST} ${WM_WININICHANGE} 0 "STR:Environment" /TIMEOUT=5000
remove_from_path_done:
Pop $0
FunctionEnd
#
# Define the installer sections.
#
Section "-gnupginst"
SetOutPath "$INSTDIR"
File "${BUILD_DIR}/README.txt"
# Write a version file.
FileOpen $0 "$INSTDIR\VERSION" w
FileWrite $0 "${PACKAGE}$\r$\n"
FileWrite $0 "${VERSION}$\r$\n"
FileClose $0
WriteRegStr SHCTX "Software\GnuPG" "Install Directory" $INSTDIR
# If we are reinstalling, try to kill a possible running gpa using
# an already installed gpa.
ifFileExists "$INSTDIR\bin\launch-gpa.exe" 0 no_uiserver
nsExec::ExecToLog '"$INSTDIR\bin\launch-gpa" "--stop-server"'
no_uiserver:
# If we are reinstalling, try to kill a possible running agent using
# an already installed gpgconf.
ifFileExists "$INSTDIR\bin\gpgconf.exe" 0 no_gpgconf
nsExec::ExecToLog '"$INSTDIR\bin\gpgconf" "--kill" "dirmngr"'
nsExec::ExecToLog '"$INSTDIR\bin\gpgconf" "--kill" "gpg-agent"'
no_gpgconf:
# Add the bin directory to the PATH
Push "$INSTDIR\bin"
Call AddToPath
DetailPrint "Added $INSTDIR\bin to PATH"
SectionEnd
LangString DESC_Menu_gnupg_readme ${LANG_ENGLISH} \
"General information on GnuPG"
LangString DESC_Menu_gnupg_readme ${LANG_GERMAN} \
"Allgemeine Informationen zu GnuPG"
Section "GnuPG" SEC_gnupg
SectionIn RO
SetOutPath "$INSTDIR\bin"
File "bin/gpg.exe"
File "bin/gpgv.exe"
File "bin/gpgsm.exe"
File "bin/gpgconf.exe"
File "bin/gpg-connect-agent.exe"
+ File "bin/gpg-card.exe"
File "bin/gpgtar.exe"
File "libexec/dirmngr_ldap.exe"
File "libexec/gpg-preset-passphrase.exe"
File "libexec/gpg-wks-client.exe"
ClearErrors
SetOverwrite try
File "bin/gpg-agent.exe"
SetOverwrite lastused
ifErrors 0 +3
File /oname=gpg-agent.exe.tmp "bin/gpg-agent.exe"
Rename /REBOOTOK gpg-agent.exe.tmp gpg-agent.exe
ClearErrors
SetOverwrite try
File "bin/dirmngr.exe"
SetOverwrite lastused
ifErrors 0 +3
File /oname=dirmngr.exe.tmp "bin/dirmngr.exe"
Rename /REBOOTOK dirmngr.exe.tmp dirmngr.exe
ClearErrors
SetOverwrite try
File "libexec/scdaemon.exe"
SetOverwrite lastused
ifErrors 0 +3
File /oname=scdaemon.exe.tmp "libexec/scdaemon.exe"
Rename /REBOOTOK scdaemon.exe.tmp scdaemon.exe
SetOutPath "$INSTDIR\share\gnupg"
File "share/gnupg/distsigkey.gpg"
File "share/gnupg/sks-keyservers.netCA.pem"
SetOutPath "$INSTDIR\share\locale\ca\LC_MESSAGES"
File share/locale/ca/LC_MESSAGES/gnupg2.mo
SetOutPath "$INSTDIR\share\locale\cs\LC_MESSAGES"
File share/locale/cs/LC_MESSAGES/gnupg2.mo
SetOutPath "$INSTDIR\share\locale\da\LC_MESSAGES"
File share/locale/da/LC_MESSAGES/gnupg2.mo
SetOutPath "$INSTDIR\share\locale\de\LC_MESSAGES"
File share/locale/de/LC_MESSAGES/gnupg2.mo
SetOutPath "$INSTDIR\share\locale\el\LC_MESSAGES"
File share/locale/el/LC_MESSAGES/gnupg2.mo
SetOutPath "$INSTDIR\share\locale\en@boldquot\LC_MESSAGES"
File share/locale/en@boldquot/LC_MESSAGES/gnupg2.mo
SetOutPath "$INSTDIR\share\locale\en@quot\LC_MESSAGES"
File share/locale/en@quot/LC_MESSAGES/gnupg2.mo
SetOutPath "$INSTDIR\share\locale\eo\LC_MESSAGES"
File share/locale/eo/LC_MESSAGES/gnupg2.mo
SetOutPath "$INSTDIR\share\locale\es\LC_MESSAGES"
File share/locale/es/LC_MESSAGES/gnupg2.mo
SetOutPath "$INSTDIR\share\locale\et\LC_MESSAGES"
File share/locale/et/LC_MESSAGES/gnupg2.mo
SetOutPath "$INSTDIR\share\locale\fi\LC_MESSAGES"
File share/locale/fi/LC_MESSAGES/gnupg2.mo
SetOutPath "$INSTDIR\share\locale\fr\LC_MESSAGES"
File share/locale/fr/LC_MESSAGES/gnupg2.mo
SetOutPath "$INSTDIR\share\locale\gl\LC_MESSAGES"
File share/locale/gl/LC_MESSAGES/gnupg2.mo
SetOutPath "$INSTDIR\share\locale\hu\LC_MESSAGES"
File share/locale/hu/LC_MESSAGES/gnupg2.mo
SetOutPath "$INSTDIR\share\locale\id\LC_MESSAGES"
File share/locale/id/LC_MESSAGES/gnupg2.mo
SetOutPath "$INSTDIR\share\locale\it\LC_MESSAGES"
File share/locale/it/LC_MESSAGES/gnupg2.mo
SetOutPath "$INSTDIR\share\locale\ja\LC_MESSAGES"
File share/locale/ja/LC_MESSAGES/gnupg2.mo
SetOutPath "$INSTDIR\share\locale\nb\LC_MESSAGES"
File share/locale/nb/LC_MESSAGES/gnupg2.mo
SetOutPath "$INSTDIR\share\locale\pl\LC_MESSAGES"
File share/locale/pl/LC_MESSAGES/gnupg2.mo
SetOutPath "$INSTDIR\share\locale\pt\LC_MESSAGES"
File share/locale/pt/LC_MESSAGES/gnupg2.mo
SetOutPath "$INSTDIR\share\locale\ro\LC_MESSAGES"
File share/locale/ro/LC_MESSAGES/gnupg2.mo
SetOutPath "$INSTDIR\share\locale\ru\LC_MESSAGES"
File share/locale/ru/LC_MESSAGES/gnupg2.mo
SetOutPath "$INSTDIR\share\locale\sk\LC_MESSAGES"
File share/locale/sk/LC_MESSAGES/gnupg2.mo
SetOutPath "$INSTDIR\share\locale\sv\LC_MESSAGES"
File share/locale/sv/LC_MESSAGES/gnupg2.mo
SetOutPath "$INSTDIR\share\locale\tr\LC_MESSAGES"
File share/locale/tr/LC_MESSAGES/gnupg2.mo
SetOutPath "$INSTDIR\share\locale\uk\LC_MESSAGES"
File share/locale/uk/LC_MESSAGES/gnupg2.mo
SetOutPath "$INSTDIR\share\locale\zh_CN\LC_MESSAGES"
File share/locale/zh_CN/LC_MESSAGES/gnupg2.mo
SetOutPath "$INSTDIR\share\locale\zh_TW\LC_MESSAGES"
File share/locale/zh_TW/LC_MESSAGES/gnupg2.mo
SectionEnd
LangString DESC_SEC_gnupg ${LANG_ENGLISH} \
"The GnuPG Core is the actual encrypt core and a set of command \
line utilities."
LangString DESC_SEC_gnupg ${LANG_GERMAN} \
"Der GnuPG Core ist, wie der Name schon sagt, der Kernbestandteil \
dieser Software. Der GnuPG Core stellt die eigentliche \
Verschlüsselung sowie die Verwaltung der Schlüssel bereit."
LangString DESC_Menu_gnupg_manual ${LANG_ENGLISH} \
"Show the manual for the GnuPG Core"
LangString DESC_Menu_gnupg_manual ${LANG_GERMAN} \
"Das Handbuch zum GnuPG Kern anzeigen"
Section "-libgpg-error" SEC_libgpg_error
SetOutPath "$INSTDIR\bin"
File bin/libgpg-error-0.dll
SetOutPath "$INSTDIR\lib"
File /oname=libgpg-error.imp lib/libgpg-error.dll.a
SetOutPath "$INSTDIR\include"
File include/gpg-error.h
SetOutPath "$INSTDIR\share\locale\cs\LC_MESSAGES"
File share/locale/cs/LC_MESSAGES/libgpg-error.mo
SetOutPath "$INSTDIR\share\locale\da\LC_MESSAGES"
File share/locale/da/LC_MESSAGES/libgpg-error.mo
SetOutPath "$INSTDIR\share\locale\de\LC_MESSAGES"
File share/locale/de/LC_MESSAGES/libgpg-error.mo
SetOutPath "$INSTDIR\share\locale\eo\LC_MESSAGES"
File share/locale/eo/LC_MESSAGES/libgpg-error.mo
SetOutPath "$INSTDIR\share\locale\es\LC_MESSAGES"
File share/locale/es/LC_MESSAGES/libgpg-error.mo
SetOutPath "$INSTDIR\share\locale\fr\LC_MESSAGES"
File share/locale/fr/LC_MESSAGES/libgpg-error.mo
SetOutPath "$INSTDIR\share\locale\hu\LC_MESSAGES"
File share/locale/hu/LC_MESSAGES/libgpg-error.mo
SetOutPath "$INSTDIR\share\locale\it\LC_MESSAGES"
File share/locale/it/LC_MESSAGES/libgpg-error.mo
SetOutPath "$INSTDIR\share\locale\ja\LC_MESSAGES"
File share/locale/ja/LC_MESSAGES/libgpg-error.mo
SetOutPath "$INSTDIR\share\locale\nl\LC_MESSAGES"
File share/locale/nl/LC_MESSAGES/libgpg-error.mo
SetOutPath "$INSTDIR\share\locale\pl\LC_MESSAGES"
File share/locale/pl/LC_MESSAGES/libgpg-error.mo
SetOutPath "$INSTDIR\share\locale\pt\LC_MESSAGES"
File share/locale/pt/LC_MESSAGES/libgpg-error.mo
SetOutPath "$INSTDIR\share\locale\ro\LC_MESSAGES"
File share/locale/ro/LC_MESSAGES/libgpg-error.mo
SetOutPath "$INSTDIR\share\locale\ru\LC_MESSAGES"
File share/locale/ru/LC_MESSAGES/libgpg-error.mo
SetOutPath "$INSTDIR\share\locale\sr\LC_MESSAGES"
File share/locale/sr/LC_MESSAGES/libgpg-error.mo
SetOutPath "$INSTDIR\share\locale\sv\LC_MESSAGES"
File share/locale/sv/LC_MESSAGES/libgpg-error.mo
SetOutPath "$INSTDIR\share\locale\uk\LC_MESSAGES"
File share/locale/uk/LC_MESSAGES/libgpg-error.mo
SetOutPath "$INSTDIR\share\locale\vi\LC_MESSAGES"
File share/locale/vi/LC_MESSAGES/libgpg-error.mo
SetOutPath "$INSTDIR\share\locale\zh_CN\LC_MESSAGES"
File share/locale/zh_CN/LC_MESSAGES/libgpg-error.mo
SetOutPath "$INSTDIR\share\locale\zh_TW\LC_MESSAGES"
File share/locale/zh_TW/LC_MESSAGES/libgpg-error.mo
SectionEnd
Section "-zlib" SEC_zlib
SetOutPath "$INSTDIR\bin"
File bin/zlib1.dll
SectionEnd
Section "-npth" SEC_npth
SetOutPath "$INSTDIR\bin"
File bin/libnpth-0.dll
SetOutPath "$INSTDIR\lib"
File /oname=libnpth.imp lib/libnpth.dll.a
SetOutPath "$INSTDIR\include"
File include/npth.h
SectionEnd
Section "-gcrypt" SEC_gcrypt
SetOutPath "$INSTDIR\bin"
File bin/libgcrypt-20.dll
SetOutPath "$INSTDIR\lib"
File /oname=libgcrypt.imp lib/libgcrypt.dll.a
SetOutPath "$INSTDIR\include"
File include/gcrypt.h
SectionEnd
Section "-assuan" SEC_assuan
SetOutPath "$INSTDIR\bin"
File bin/libassuan-0.dll
SetOutPath "$INSTDIR\lib"
File /oname=libassuan.imp lib/libassuan.dll.a
SetOutPath "$INSTDIR\include"
File include/assuan.h
SectionEnd
Section "-ksba" SEC_ksba
SetOutPath "$INSTDIR\bin"
File bin/libksba-8.dll
SetOutPath "$INSTDIR\lib"
File /oname=libksba.imp lib/libksba.dll.a
SetOutPath "$INSTDIR\include"
File include/ksba.h
SectionEnd
Section "-gpgme" SEC_gpgme
SetOutPath "$INSTDIR\bin"
File bin/libgpgme-11.dll
File /nonfatal bin/libgpgme-glib-11.dll
File libexec/gpgme-w32spawn.exe
SetOutPath "$INSTDIR\lib"
File /oname=libgpgme.imp lib/libgpgme.dll.a
File /nonfatal /oname=libgpgme-glib.imp lib/libgpgme-glib.dll.a
SetOutPath "$INSTDIR\include"
File include/gpgme.h
SectionEnd
Section "-sqlite" SEC_sqlite
SetOutPath "$INSTDIR\bin"
File bin/libsqlite3-0.dll
SectionEnd
!ifdef WITH_GUI
Section "-libiconv" SEC_libiconv
SetOutPath "$INSTDIR\bin"
File bin/libiconv-2.dll
SectionEnd
Section "-gettext" SEC_gettext
SetOutPath "$INSTDIR\bin"
File bin/libintl-8.dll
SectionEnd
Section "-glib" SEC_glib
SetOutPath "$INSTDIR\bin"
File bin/libgio-2.0-0.dll
File bin/libglib-2.0-0.dll
File bin/libgmodule-2.0-0.dll
File bin/libgobject-2.0-0.dll
File bin/libgthread-2.0-0.dll
File bin/gspawn-win32-helper.exe
File bin/gspawn-win32-helper-console.exe
File bin/libffi-6.dll
SectionEnd
Section "-libpng" SEC_libpng
SetOutPath "$INSTDIR\bin"
File bin/libpng14-14.dll
SectionEnd
#Section "-jpeg" SEC_jpeg
# SetOutPath "$INSTDIR"
# File bin/jpeg62.dll
#SectionEnd
Section "-cairo" SEC_cairo
SetOutPath "$INSTDIR\bin"
File bin/libcairo-gobject-2.dll
File bin/libpangocairo-1.0-0.dll
File bin/libcairo-2.dll
File bin/libcairo-script-interpreter-2.dll
SectionEnd
Section "-pixman" SEC_pixman
SetOutPath "$INSTDIR\bin"
File bin/libpixman-1-0.dll
SectionEnd
Section "-pango" SEC_pango
SetOutPath "$INSTDIR\bin"
File bin/pango-querymodules.exe
File bin/libpango-1.0-0.dll
File bin/libpangowin32-1.0-0.dll
SetOutPath "$INSTDIR\lib\pango\1.6.0\modules"
File lib/pango/1.6.0/modules/pango-basic-win32.dll
File lib/pango/1.6.0/modules/pango-arabic-lang.dll
File lib/pango/1.6.0/modules/pango-indic-lang.dll
SetOutPath "$INSTDIR\etc\pango"
File ${W32_SRCDIR}/pango.modules
SectionEnd
Section "-atk" SEC_atk
SetOutPath "$INSTDIR\bin"
File bin/libatk-1.0-0.dll
SectionEnd
Section "-gtk+" SEC_gtk_
SetOutPath "$INSTDIR\bin"
File bin/libgdk_pixbuf-2.0-0.dll
File bin/libgdk-win32-2.0-0.dll
File bin/libgtk-win32-2.0-0.dll
SetOutPath "$INSTDIR\lib\gdk-pixbuf-2.0\2.10.0"
File /oname=loaders.cache ${W32_SRCDIR}/gdk-pixbuf-loaders.cache
SetOutPath "$INSTDIR\lib\gdk-pixbuf-2.0\2.10.0\loaders"
File lib/gdk-pixbuf-2.0/2.10.0/loaders/libpixbufloader-ani.dll
File lib/gdk-pixbuf-2.0/2.10.0/loaders/libpixbufloader-gdip-bmp.dll
File lib/gdk-pixbuf-2.0/2.10.0/loaders/libpixbufloader-gdip-emf.dll
File lib/gdk-pixbuf-2.0/2.10.0/loaders/libpixbufloader-gdip-gif.dll
File lib/gdk-pixbuf-2.0/2.10.0/loaders/libpixbufloader-gdip-ico.dll
File lib/gdk-pixbuf-2.0/2.10.0/loaders/libpixbufloader-gdip-jpeg.dll
File lib/gdk-pixbuf-2.0/2.10.0/loaders/libpixbufloader-gdip-tiff.dll
File lib/gdk-pixbuf-2.0/2.10.0/loaders/libpixbufloader-gdip-wmf.dll
File lib/gdk-pixbuf-2.0/2.10.0/loaders/libpixbufloader-icns.dll
File lib/gdk-pixbuf-2.0/2.10.0/loaders/libpixbufloader-pcx.dll
File lib/gdk-pixbuf-2.0/2.10.0/loaders/libpixbufloader-png.dll
File lib/gdk-pixbuf-2.0/2.10.0/loaders/libpixbufloader-pnm.dll
File lib/gdk-pixbuf-2.0/2.10.0/loaders/libpixbufloader-qtif.dll
File lib/gdk-pixbuf-2.0/2.10.0/loaders/libpixbufloader-ras.dll
File lib/gdk-pixbuf-2.0/2.10.0/loaders/libpixbufloader-tga.dll
File lib/gdk-pixbuf-2.0/2.10.0/loaders/libpixbufloader-wbmp.dll
File lib/gdk-pixbuf-2.0/2.10.0/loaders/libpixbufloader-xbm.dll
File lib/gdk-pixbuf-2.0/2.10.0/loaders/libpixbufloader-xpm.dll
SetOutPath "$INSTDIR\lib\gtk-2.0\2.10.0\engines"
File lib/gtk-2.0/2.10.0/engines/libwimp.dll
File lib/gtk-2.0/2.10.0/engines/libpixmap.dll
SetOutPath "$INSTDIR\lib\gtk-2.0\2.10.0\immodules"
File lib/gtk-2.0/2.10.0/immodules/im-thai.dll
File lib/gtk-2.0/2.10.0/immodules/im-cyrillic-translit.dll
File lib/gtk-2.0/2.10.0/immodules/im-multipress.dll
File lib/gtk-2.0/2.10.0/immodules/im-ti-er.dll
File lib/gtk-2.0/2.10.0/immodules/im-am-et.dll
File lib/gtk-2.0/2.10.0/immodules/im-cedilla.dll
File lib/gtk-2.0/2.10.0/immodules/im-inuktitut.dll
File lib/gtk-2.0/2.10.0/immodules/im-viqr.dll
File lib/gtk-2.0/2.10.0/immodules/im-ti-et.dll
File lib/gtk-2.0/2.10.0/immodules/im-ipa.dll
File lib/gtk-2.0/2.10.0/immodules/im-ime.dll
SetOutPath "$INSTDIR\share\themes\Default\gtk-2.0-key"
File share/themes/Default/gtk-2.0-key/gtkrc
SetOutPath "$INSTDIR\share\themes\MS-Windows\gtk-2.0"
File share/themes/MS-Windows/gtk-2.0/gtkrc
SetOutPath "$INSTDIR\etc\gtk-2.0"
File etc/gtk-2.0/im-multipress.conf
SectionEnd
!endif
Section "-pinentry" SEC_pinentry
SetOutPath "$INSTDIR\bin"
File /oname=pinentry-basic.exe "bin/pinentry-w32.exe"
SectionEnd
!ifdef WITH_GUI
Section "gpa" SEC_gpa
SectionIn RO
SetOutPath "$INSTDIR\bin"
File bin/gpa.exe
File bin/launch-gpa.exe
SectionEnd
LangString DESC_SEC_gpa ${LANG_ENGLISH} \
"The GnuPG Assistant is the graphical interface of GnuPG"
LangString DESC_SEC_gpa ${LANG_GERMAN} \
"Der GnuPG Assistent ist die graphische Oberfläche von GnuPG."
LangString DESC_Menu_gpa ${LANG_ENGLISH} \
"Run the GnuGP Assistant."
LangString DESC_Menu_gpa ${LANG_GERMAN} \
"Den GnuPG Assistenten starten."
Section "gpgex" SEC_gpgex
SetOutPath "$INSTDIR\bin"
ClearErrors
SetOverwrite try
File bin/gpgex.dll
SetOverwrite lastused
ifErrors 0 do_reg
File /oname=gpgex.dll.tmp bin/gpgex.dll
Rename /REBOOTOK gpgex.dll.tmp gpgex.dll
do_reg:
ClearErrors
RegDLL "$INSTDIR\bin\gpgex.dll"
ifErrors 0 +2
MessageBox MB_OK "$(T_GPGEX_RegFailed)"
${If} ${RunningX64}
# Install the 64 bit version of the plugin.
# Note that we install this in addition to the 32 bit version so that
# the 32 bit version can be used by file dialogs of 32 bit programs.
ClearErrors
SetOverwrite try
File /oname=gpgex6.dll "${INST6_DIR}/bin/gpgex.dll"
SetOverwrite lastused
ifErrors 0 do_reg64
File /oname=gpgex6.dll.tmp "${INST6_DIR}/bin/gpgex.dll"
Rename /REBOOTOK gpgex6.dll.tmp gpgex6.dll
do_reg64:
# Register the DLL. We need to register both versions. However
# RegDLL can't be used for 64 bit and InstallLib seems to be a
# registry hack.
ClearErrors
nsExec::ExecToLog '"$SYSDIR\regsvr32" "/s" "$INSTDIR\bin\gpgex6.dll"'
ifErrors 0 +2
MessageBox MB_OK "$(T_GPGEX_RegFailed) (64 bit)"
# Note: There is no need to install the help an mo files because
# they are identical to those installed by the 32 bit version.
${EndIf}
SectionEnd
LangString T_GPGEX_RegFailed ${LANG_ENGLISH} \
"Warning: Registration of the Explorer plugin failed."
LangString DESC_SEC_gpgex ${LANG_ENGLISH} \
"GnuPG Explorer Extension"
!endif
Section "-gnupglast" SEC_gnupglast
SetOutPath "$INSTDIR"
SectionEnd
#
# Define the uninstaller sections.
#
# (reverse order of the installer sections!)
#
Section "-un.gnupglast"
ifFileExists "$INSTDIR\bin\launch-gpa.exe" 0 no_uiserver
nsExec::ExecToLog '"$INSTDIR\bin\launch-gpa" "--stop-server"'
no_uiserver:
ifFileExists "$INSTDIR\bin\gpgconf.exe" 0 no_gpgconf
nsExec::ExecToLog '"$INSTDIR\bin\gpgconf" "--kill" "gpg-agent"'
nsExec::ExecToLog '"$INSTDIR\bin\gpgconf" "--kill" "dirmngr"'
no_gpgconf:
SectionEnd
Section "-un.gpgex"
UnRegDLL "$INSTDIR\bin\gpgex.dll"
Delete /REBOOTOK "$INSTDIR\bin\gpgex.dll"
${If} ${RunningX64}
nsExec::ExecToLog '"$SYSDIR\regsvr32" "/u" "/s" "$INSTDIR\bin\gpgex6.dll"'
Delete /REBOOTOK "$INSTDIR\bin\gpgex6.dll"
${EndIf}
SectionEnd
!ifdef WITH_GUI
Section "-un.gpa"
Delete "$INSTDIR\bin\gpa.exe"
Delete "$INSTDIR\bin\launch-gpa.exe"
RMDir "$INSTDIR\share\gpa"
SectionEnd
!endif
Section "-un.pinentry"
Delete "$INSTDIR\bin\pinentry-basic.exe"
SectionEnd
!ifdef WITH_GUI
Section "-un.gtk+"
Delete "$INSTDIR\bin\libgdk_pixbuf-2.0-0.dll"
Delete "$INSTDIR\bin\libgdk-win32-2.0-0.dll"
Delete "$INSTDIR\bin\libgtk-win32-2.0-0.dll"
Delete "$INSTDIR\lib\gdk-pixbuf-2.0\2.10.0\loaders.cache"
Delete "$INSTDIR\lib\gdk-pixbuf-2.0\2.10.0\loaders\libpixbufloader-ani.dll"
Delete "$INSTDIR\lib\gdk-pixbuf-2.0\2.10.0\loaders\libpixbufloader-gdip-bmp.dll"
Delete "$INSTDIR\lib\gdk-pixbuf-2.0\2.10.0\loaders\libpixbufloader-gdip-emf.dll"
Delete "$INSTDIR\lib\gdk-pixbuf-2.0\2.10.0\loaders\libpixbufloader-gdip-gif.dll"
Delete "$INSTDIR\lib\gdk-pixbuf-2.0\2.10.0\loaders\libpixbufloader-gdip-ico.dll"
Delete "$INSTDIR\lib\gdk-pixbuf-2.0\2.10.0\loaders\libpixbufloader-gdip-jpeg.dll"
Delete "$INSTDIR\lib\gdk-pixbuf-2.0\2.10.0\loaders\libpixbufloader-gdip-tiff.dll"
Delete "$INSTDIR\lib\gdk-pixbuf-2.0\2.10.0\loaders\libpixbufloader-gdip-wmf.dll"
Delete "$INSTDIR\lib\gdk-pixbuf-2.0\2.10.0\loaders\libpixbufloader-icns.dll"
Delete "$INSTDIR\lib\gdk-pixbuf-2.0\2.10.0\loaders\libpixbufloader-pcx.dll"
Delete "$INSTDIR\lib\gdk-pixbuf-2.0\2.10.0\loaders\libpixbufloader-png.dll"
Delete "$INSTDIR\lib\gdk-pixbuf-2.0\2.10.0\loaders\libpixbufloader-pnm.dll"
Delete "$INSTDIR\lib\gdk-pixbuf-2.0\2.10.0\loaders\libpixbufloader-qtif.dll"
Delete "$INSTDIR\lib\gdk-pixbuf-2.0\2.10.0\loaders\libpixbufloader-ras.dll"
Delete "$INSTDIR\lib\gdk-pixbuf-2.0\2.10.0\loaders\libpixbufloader-tga.dll"
Delete "$INSTDIR\lib\gdk-pixbuf-2.0\2.10.0\loaders\libpixbufloader-wbmp.dll"
Delete "$INSTDIR\lib\gdk-pixbuf-2.0\2.10.0\loaders\libpixbufloader-xbm.dll"
Delete "$INSTDIR\lib\gdk-pixbuf-2.0\2.10.0\loaders\libpixbufloader-xpm.dll"
RMDir "$INSTDIR\lib\gdk-pixbuf-2.0\2.10.0\loaders"
RMDir "$INSTDIR\lib\gdk-pixbuf-2.0\2.10.0"
RMDir "$INSTDIR\lib\gdk-pixbuf-2.0"
Delete "$INSTDIR\lib\gtk-2.0\2.10.0\engines\libwimp.dll"
Delete "$INSTDIR\lib\gtk-2.0\2.10.0\engines\libpixmap.dll"
RMDir "$INSTDIR\lib\gtk-2.0\2.10.0\engines"
Delete "$INSTDIR\lib\gtk-2.0\2.10.0\immodules\im-thai.dll"
Delete "$INSTDIR\lib\gtk-2.0\2.10.0\immodules\im-cyrillic-translit.dll"
Delete "$INSTDIR\lib\gtk-2.0\2.10.0\immodules\im-multipress.dll"
Delete "$INSTDIR\lib\gtk-2.0\2.10.0\immodules\im-ti-er.dll"
Delete "$INSTDIR\lib\gtk-2.0\2.10.0\immodules\im-am-et.dll"
Delete "$INSTDIR\lib\gtk-2.0\2.10.0\immodules\im-cedilla.dll"
Delete "$INSTDIR\lib\gtk-2.0\2.10.0\immodules\im-inuktitut.dll"
Delete "$INSTDIR\lib\gtk-2.0\2.10.0\immodules\im-viqr.dll"
Delete "$INSTDIR\lib\gtk-2.0\2.10.0\immodules\im-ti-et.dll"
Delete "$INSTDIR\lib\gtk-2.0\2.10.0\immodules\im-ipa.dll"
Delete "$INSTDIR\lib\gtk-2.0\2.10.0\immodules\im-ime.dll"
RMDir "$INSTDIR\lib\gtk-2.0\2.10.0\immodules"
RMDir "$INSTDIR\lib\gtk-2.0\2.10.0"
RMDir "$INSTDIR\lib\gtk-2.0"
Delete "$INSTDIR\share\themes\Default\gtk-2.0-key\gtkrc"
RMDir "$INSTDIR\share\themes\Default\gtk-2.0-key"
RMDir "$INSTDIR\share\themes\Default"
Delete "$INSTDIR\share\themes\MS-Windows\gtk-2.0\gtkrc"
RMDir "$INSTDIR\share\themes\MS-Windows\gtk-2.0"
RMDir "$INSTDIR\share\themes\MS-Windows"
RMDir "$INSTDIR\share\themes"
Delete "$INSTDIR\etc\gtk-2.0\im-multipress.conf"
RMDir "$INSTDIR\etc\gtk-2.0"
SectionEnd
Section "-un.atk"
Delete "$INSTDIR\bin\libatk-1.0-0.dll"
SectionEnd
Section "-un.pango"
Delete "$INSTDIR\bin\pango-querymodules.exe"
Delete "$INSTDIR\bin\libpango-1.0-0.dll"
Delete "$INSTDIR\bin\libpangowin32-1.0-0.dll"
Delete "$INSTDIR\lib\pango\1.6.0\modules\pango-basic-win32.dll"
Delete "$INSTDIR\lib\pango\1.6.0\modules\pango-arabic-lang.dll"
Delete "$INSTDIR\lib\pango\1.6.0\modules\pango-indic-lang.dll"
RMDir "$INSTDIR\lib\pango\1.6.0\modules"
RMDir "$INSTDIR\lib\pango\1.6.0"
RMDir "$INSTDIR\lib\pango"
Delete "$INSTDIR\etc\pango\pango.modules"
RMDir "$INSTDIR\etc\pango"
SectionEnd
Section "-un.pixman"
Delete "$INSTDIR\bin\libpixman-1-0.dll"
SectionEnd
Section "-un.cairo"
Delete "$INSTDIR\bin\libcairo-gobject-2.dll"
Delete "$INSTDIR\bin\libpangocairo-1.0-0.dll"
Delete "$INSTDIR\bin\libcairo-2.dll"
Delete "$INSTDIR\bin\libcairo-script-interpreter-2.dll"
SectionEnd
Section "-un.libpng"
Delete "$INSTDIR\bin\libpng14-14.dll"
SectionEnd
Section "-un.glib"
Delete "$INSTDIR\bin\libgio-2.0-0.dll"
Delete "$INSTDIR\bin\libglib-2.0-0.dll"
Delete "$INSTDIR\bin\libgmodule-2.0-0.dll"
Delete "$INSTDIR\bin\libgobject-2.0-0.dll"
Delete "$INSTDIR\bin\libgthread-2.0-0.dll"
Delete "$INSTDIR\bin\gspawn-win32-helper.exe"
Delete "$INSTDIR\bin\gspawn-win32-helper-console.exe"
Delete "$INSTDIR\bin\libffi-6.dll"
SectionEnd
!endif
Section "-un.gettext"
Delete "$INSTDIR\bin\libintl-8.dll"
SectionEnd
Section "-un.libiconv"
Delete "$INSTDIR\bin\libiconv-2.dll"
SectionEnd
Section "-un.gpgme"
Delete "$INSTDIR\bin\libgpgme-11.dll"
Delete "$INSTDIR\bin\libgpgme-glib-11.dll"
Delete "$INSTDIR\bin\gpgme-w32spawn.exe"
Delete "$INSTDIR\lib\libgpgme.imp"
Delete "$INSTDIR\lib\libgpgme-glib.imp"
Delete "$INSTDIR\include\gpgme.h"
SectionEnd
Section "-un.ksba"
Delete "$INSTDIR\bin\libksba-8.dll"
Delete "$INSTDIR\lib\libksba.imp"
Delete "$INSTDIR\include\ksba.h"
SectionEnd
Section "-un.assuan"
Delete "$INSTDIR\bin\libassuan-0.dll"
Delete "$INSTDIR\lib\libassuan.imp"
Delete "$INSTDIR\include\assuan.h"
SectionEnd
Section "-un.gcrypt"
Delete "$INSTDIR\bin\libgcrypt-20.dll"
Delete "$INSTDIR\lib\libgcrypt.imp"
Delete "$INSTDIR\include\gcrypt.h"
SectionEnd
Section "-un.npth"
Delete "$INSTDIR\bin\libnpth-0.dll"
Delete "$INSTDIR\lib\libnpth.imp"
Delete "$INSTDIR\include\npth.h"
SectionEnd
Section "-un.zlib"
Delete "$INSTDIR\bin\zlib1.dll"
SectionEnd
Section "-un.libgpg-error"
Delete "$INSTDIR\bin\libgpg-error-0.dll"
Delete "$INSTDIR\lib\libgpg-error.imp"
Delete "$INSTDIR\include\gpg-error.h"
Delete "$INSTDIR\share\locale\cs\LC_MESSAGES\libgpg-error.mo"
RMDir "$INSTDIR\share\locale\cs\LC_MESSAGES"
RMDir "$INSTDIR\share\locale\cs"
Delete "$INSTDIR\share\locale\da\LC_MESSAGES\libgpg-error.mo"
RMDir "$INSTDIR\share\locale\da\LC_MESSAGES"
RMDir "$INSTDIR\share\locale\da"
Delete "$INSTDIR\share\locale\de\LC_MESSAGES\libgpg-error.mo"
RMDir "$INSTDIR\share\locale\de\LC_MESSAGES"
RMDir "$INSTDIR\share\locale\de"
Delete "$INSTDIR\share\locale\eo\LC_MESSAGES\libgpg-error.mo"
RMDir "$INSTDIR\share\locale\eo\LC_MESSAGES"
RMDir "$INSTDIR\share\locale\eo"
Delete "$INSTDIR\share\locale\es\LC_MESSAGES\libgpg-error.mo"
RMDir "$INSTDIR\share\locale\es\LC_MESSAGES"
RMDir "$INSTDIR\share\locale\es"
Delete "$INSTDIR\share\locale\fr\LC_MESSAGES\libgpg-error.mo"
RMDir "$INSTDIR\share\locale\fr\LC_MESSAGES"
RMDir "$INSTDIR\share\locale\fr"
Delete "$INSTDIR\share\locale\hu\LC_MESSAGES\libgpg-error.mo"
RMDir "$INSTDIR\share\locale\hu\LC_MESSAGES"
RMDir "$INSTDIR\share\locale\hu"
Delete "$INSTDIR\share\locale\it\LC_MESSAGES\libgpg-error.mo"
RMDir "$INSTDIR\share\locale\it\LC_MESSAGES"
RMDir "$INSTDIR\share\locale\it"
Delete "$INSTDIR\share\locale\ja\LC_MESSAGES\libgpg-error.mo"
RMDir "$INSTDIR\share\locale\ja\LC_MESSAGES"
RMDir "$INSTDIR\share\locale\ja"
Delete "$INSTDIR\share\locale\nl\LC_MESSAGES\libgpg-error.mo"
RMDir "$INSTDIR\share\locale\nl\LC_MESSAGES"
RMDir "$INSTDIR\share\locale\nl"
Delete "$INSTDIR\share\locale\pl\LC_MESSAGES\libgpg-error.mo"
RMDir "$INSTDIR\share\locale\pl\LC_MESSAGES"
RMDir "$INSTDIR\share\locale\pl"
Delete "$INSTDIR\share\locale\pt\LC_MESSAGES\libgpg-error.mo"
RMDir "$INSTDIR\share\locale\pt\LC_MESSAGES"
RMDir "$INSTDIR\share\locale\pt"
Delete "$INSTDIR\share\locale\ro\LC_MESSAGES\libgpg-error.mo"
RMDir "$INSTDIR\share\locale\ro\LC_MESSAGES"
RMDir "$INSTDIR\share\locale\ro"
Delete "$INSTDIR\share\locale\ru\LC_MESSAGES\libgpg-error.mo"
RMDir "$INSTDIR\share\locale\ru\LC_MESSAGES"
RMDir "$INSTDIR\share\locale\ru"
Delete "$INSTDIR\share\locale\sr\LC_MESSAGES\libgpg-error.mo"
RMDir "$INSTDIR\share\locale\sr\LC_MESSAGES"
RMDir "$INSTDIR\share\locale\sr"
Delete "$INSTDIR\share\locale\sv\LC_MESSAGES\libgpg-error.mo"
RMDir "$INSTDIR\share\locale\sv\LC_MESSAGES"
RMDir "$INSTDIR\share\locale\sv"
Delete "$INSTDIR\share\locale\uk\LC_MESSAGES\libgpg-error.mo"
RMDir "$INSTDIR\share\locale\uk\LC_MESSAGES"
RMDir "$INSTDIR\share\locale\uk"
Delete "$INSTDIR\share\locale\vi\LC_MESSAGES\libgpg-error.mo"
RMDir "$INSTDIR\share\locale\vi\LC_MESSAGES"
RMDir "$INSTDIR\share\locale\vi"
Delete "$INSTDIR\share\locale\zh_CN\LC_MESSAGES\libgpg-error.mo"
RMDir "$INSTDIR\share\locale\zh_CN\LC_MESSAGES"
RMDir "$INSTDIR\share\locale\zh_CN"
Delete "$INSTDIR\share\locale\zh_TW\LC_MESSAGES\libgpg-error.mo"
RMDir "$INSTDIR\share\locale\zh_TW\LC_MESSAGES"
RMDir "$INSTDIR\share\locale\zh_TW"
RMDir "$INSTDIR\share\locale"
SectionEnd
Section "-un.gnupg"
Delete "$INSTDIR\bin\gpg.exe"
Delete "$INSTDIR\bin\gpgv.exe"
Delete "$INSTDIR\bin\gpgsm.exe"
Delete "$INSTDIR\bin\gpg-agent.exe"
Delete "$INSTDIR\bin\scdaemon.exe"
Delete "$INSTDIR\bin\dirmngr.exe"
Delete "$INSTDIR\bin\gpgconf.exe"
Delete "$INSTDIR\bin\gpg-connect-agent.exe"
Delete "$INSTDIR\bin\gpgtar.exe"
+ Delete "$INSTDIR\bin\gpg-card.exe"
Delete "$INSTDIR\bin\dirmngr_ldap.exe"
Delete "$INSTDIR\bin\gpg-preset-passphrase.exe"
Delete "$INSTDIR\bin\gpg-wks-client.exe"
Delete "$INSTDIR\share\gnupg\sks-keyservers.netCA.pem"
Delete "$INSTDIR\share\gnupg\dirmngr-conf.skel"
Delete "$INSTDIR\share\gnupg\distsigkey.gpg"
Delete "$INSTDIR\share\gnupg\gpg-conf.skel"
RMDir "$INSTDIR\share\gnupg"
Delete "$INSTDIR\share\locale\ca\LC_MESSAGES\gnupg2.mo"
RMDir "$INSTDIR\share\locale\ca\LC_MESSAGES"
RMDir "$INSTDIR\share\locale\ca"
Delete "$INSTDIR\share\locale\cs\LC_MESSAGES\gnupg2.mo"
RMDir "$INSTDIR\share\locale\cs\LC_MESSAGES"
RMDir "$INSTDIR\share\locale\cs"
Delete "$INSTDIR\share\locale\da\LC_MESSAGES\gnupg2.mo"
RMDir "$INSTDIR\share\locale\da\LC_MESSAGES"
RMDir "$INSTDIR\share\locale\da"
Delete "$INSTDIR\share\locale\de\LC_MESSAGES\gnupg2.mo"
RMDir "$INSTDIR\share\locale\de\LC_MESSAGES"
RMDir "$INSTDIR\share\locale\de"
Delete "$INSTDIR\share\locale\el\LC_MESSAGES\gnupg2.mo"
RMDir "$INSTDIR\share\locale\el\LC_MESSAGES"
RMDir "$INSTDIR\share\locale\el"
Delete "$INSTDIR\share\locale\en@boldquot\LC_MESSAGES\gnupg2.mo"
RMDir "$INSTDIR\share\locale\en@boldquot\LC_MESSAGES"
RMDir "$INSTDIR\share\locale\en@boldquot"
Delete "$INSTDIR\share\locale\en@quot\LC_MESSAGES\gnupg2.mo"
RMDir "$INSTDIR\share\locale\en@quot\LC_MESSAGES"
RMDir "$INSTDIR\share\locale\en@quot"
Delete "$INSTDIR\share\locale\eo\LC_MESSAGES\gnupg2.mo"
RMDir "$INSTDIR\share\locale\eo\LC_MESSAGES"
RMDir "$INSTDIR\share\locale\eo"
Delete "$INSTDIR\share\locale\es\LC_MESSAGES\gnupg2.mo"
RMDir "$INSTDIR\share\locale\es\LC_MESSAGES"
RMDir "$INSTDIR\share\locale\es"
Delete "$INSTDIR\share\locale\et\LC_MESSAGES\gnupg2.mo"
RMDir "$INSTDIR\share\locale\et\LC_MESSAGES"
RMDir "$INSTDIR\share\locale\et"
Delete "$INSTDIR\share\locale\fi\LC_MESSAGES\gnupg2.mo"
RMDir "$INSTDIR\share\locale\fi\LC_MESSAGES"
RMDir "$INSTDIR\share\locale\fi"
Delete "$INSTDIR\share\locale\fr\LC_MESSAGES\gnupg2.mo"
RMDir "$INSTDIR\share\locale\fr\LC_MESSAGES"
RMDir "$INSTDIR\share\locale\fr"
Delete "$INSTDIR\share\locale\gl\LC_MESSAGES\gnupg2.mo"
RMDir "$INSTDIR\share\locale\gl\LC_MESSAGES"
RMDir "$INSTDIR\share\locale\gl"
Delete "$INSTDIR\share\locale\hu\LC_MESSAGES\gnupg2.mo"
RMDir "$INSTDIR\share\locale\hu\LC_MESSAGES"
RMDir "$INSTDIR\share\locale\hu"
Delete "$INSTDIR\share\locale\id\LC_MESSAGES\gnupg2.mo"
RMDir "$INSTDIR\share\locale\id\LC_MESSAGES"
RMDir "$INSTDIR\share\locale\id"
Delete "$INSTDIR\share\locale\it\LC_MESSAGES\gnupg2.mo"
RMDir "$INSTDIR\share\locale\it\LC_MESSAGES"
RMDir "$INSTDIR\share\locale\it"
Delete "$INSTDIR\share\locale\ja\LC_MESSAGES\gnupg2.mo"
RMDir "$INSTDIR\share\locale\ja\LC_MESSAGES"
RMDir "$INSTDIR\share\locale\ja"
Delete "$INSTDIR\share\locale\nb\LC_MESSAGES\gnupg2.mo"
RMDir "$INSTDIR\share\locale\nb\LC_MESSAGES"
RMDir "$INSTDIR\share\locale\nb"
Delete "$INSTDIR\share\locale\pl\LC_MESSAGES\gnupg2.mo"
RMDir "$INSTDIR\share\locale\pl\LC_MESSAGES"
RMDir "$INSTDIR\share\locale\pl"
Delete "$INSTDIR\share\locale\pt\LC_MESSAGES\gnupg2.mo"
RMDir "$INSTDIR\share\locale\pt\LC_MESSAGES"
RMDir "$INSTDIR\share\locale\pt"
Delete "$INSTDIR\share\locale\ro\LC_MESSAGES\gnupg2.mo"
RMDir "$INSTDIR\share\locale\ro\LC_MESSAGES"
RMDir "$INSTDIR\share\locale\ro"
Delete "$INSTDIR\share\locale\ru\LC_MESSAGES\gnupg2.mo"
RMDir "$INSTDIR\share\locale\ru\LC_MESSAGES"
RMDir "$INSTDIR\share\locale\ru"
Delete "$INSTDIR\share\locale\sk\LC_MESSAGES\gnupg2.mo"
RMDir "$INSTDIR\share\locale\sk\LC_MESSAGES"
RMDir "$INSTDIR\share\locale\sk"
Delete "$INSTDIR\share\locale\sv\LC_MESSAGES\gnupg2.mo"
RMDir "$INSTDIR\share\locale\sv\LC_MESSAGES"
RMDir "$INSTDIR\share\locale\sv"
Delete "$INSTDIR\share\locale\tr\LC_MESSAGES\gnupg2.mo"
RMDir "$INSTDIR\share\locale\tr\LC_MESSAGES"
RMDir "$INSTDIR\share\locale\tr"
Delete "$INSTDIR\share\locale\uk\LC_MESSAGES\gnupg2.mo"
RMDir "$INSTDIR\share\locale\uk\LC_MESSAGES"
RMDir "$INSTDIR\share\locale\uk"
Delete "$INSTDIR\share\locale\zh_CN\LC_MESSAGES\gnupg2.mo"
RMDir "$INSTDIR\share\locale\zh_CN\LC_MESSAGES"
RMDir "$INSTDIR\share\locale\zh_CN"
Delete "$INSTDIR\share\locale\zh_TW\LC_MESSAGES\gnupg2.mo"
RMDir "$INSTDIR\share\locale\zh_TW\LC_MESSAGES"
RMDir "$INSTDIR\share\locale\zh_TW"
RMDir "$INSTDIR\share\locale"
SectionEnd
Section "-un.sqlite"
Delete "$INSTDIR\bin\libsqlite3-0.dll"
SectionEnd
Section "-un.gnupginst"
# Delete standard stuff.
Delete "$INSTDIR\README.txt"
Delete "$INSTDIR\VERSION"
# Remove the bin directory from the PATH
Push "$INSTDIR\bin"
Call un.RemoveFromPath
# Try to remove the top level directories.
RMDir "$INSTDIR\bin"
RMDir "$INSTDIR\lib"
RMDir "$INSTDIR\include"
RMDir "$INSTDIR\share"
RMDir "$INSTDIR\etc"
RMDir "$INSTDIR"
# Clean the registry.
DeleteRegValue SHCTX "Software\GNU\GnuPG" "Install Directory"
SectionEnd
Function .onInit
;;!define MUI_LANGDLL_ALWAYSSHOW
!insertmacro MUI_LANGDLL_DISPLAY
Call G4wRunOnce
SetOutPath $TEMP
#!ifdef SOURCES
# File /oname=gpgspltmp.bmp "${TOP_SRCDIR}/doc/logo/gnupg-logo-400px.bmp"
# # We play the tune only for the soruce installer
# File /oname=gpgspltmp.wav "${TOP_SRCDIR}/src/gnupg-splash.wav"
# g4wihelp::playsound $TEMP\gpgspltmp.wav
# g4wihelp::showsplash 2500 $TEMP\gpgspltmp.bmp
# Delete $TEMP\gpgspltmp.bmp
# # Note that we delete gpgspltmp.wav in .onInst{Failed,Success}
#!endif
# We can't use TOP_SRCDIR dir as the name of the file needs to be
# the same while building and running the installer. Thus we
# generate the file from a template.
!insertmacro MUI_INSTALLOPTIONS_EXTRACT "${W32_SRCDIR}/inst-options.ini"
#Call CalcDepends
Var /GLOBAL changed_dir
# Check if the install directory was modified on the command line
StrCmp "$INSTDIR" "$PROGRAMFILES\${INSTALL_DIR}" unmodified 0
# It is modified. Save that value.
StrCpy $changed_dir "$INSTDIR"
# MULITUSER_INIT overwrites directory setting from command line
!insertmacro MULTIUSER_INIT
StrCpy $INSTDIR "$changed_dir"
goto initDone
unmodified:
!insertmacro MULTIUSER_INIT
initDone:
FunctionEnd
Function "un.onInit"
!insertmacro MULTIUSER_UNINIT
FunctionEnd
#Function .onInstFailed
# Delete $TEMP\gpgspltmp.wav
#FunctionEnd
#Function .onInstSuccess
# Delete $TEMP\gpgspltmp.wav
#FunctionEnd
#Function .onSelChange
# Call CalcDepends
#FunctionEnd
# This must be in a central place. Urgs.
!insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN
!insertmacro MUI_DESCRIPTION_TEXT ${SEC_gnupg} $(DESC_SEC_gnupg)
!insertmacro MUI_DESCRIPTION_TEXT ${SEC_gpa} $(DESC_SEC_gpa)
!insertmacro MUI_DESCRIPTION_TEXT ${SEC_gpgex} $(DESC_SEC_gpgex)
!insertmacro MUI_FUNCTION_DESCRIPTION_END
# This also must be in a central place. Also Urgs.
!ifdef WITH_GUI
Section "-startmenu"
# Check if the start menu entries where requested.
!insertmacro MUI_INSTALLOPTIONS_READ $R0 "${W32_SRCDIR}/inst-options.ini" \
"Field 2" "State"
IntCmp $R0 0 no_start_menu
!insertmacro MUI_STARTMENU_WRITE_BEGIN Application
CreateDirectory "$SMPROGRAMS\$STARTMENU_FOLDER"
SectionGetFlags ${SEC_gpa} $R0
IntOp $R0 $R0 & ${SF_SELECTED}
IntCmp $R0 ${SF_SELECTED} 0 no_gpa_menu
CreateShortCut "$SMPROGRAMS\$STARTMENU_FOLDER\GPA.lnk" \
"$INSTDIR\bin\launch-gpa.exe" \
"" "" "" SW_SHOWNORMAL "" $(DESC_Menu_gpa)
no_gpa_menu:
CreateShortCut "$SMPROGRAMS\$STARTMENU_FOLDER\GnuPG Manual.lnk" \
"$INSTDIR\share\gnupg\gnupg.html" \
"" "" "" SW_SHOWNORMAL "" $(DESC_Menu_gnupg_manual)
CreateShortCut "$SMPROGRAMS\$STARTMENU_FOLDER\GnuPG README.lnk" \
"$INSTDIR\README.txt" \
"" "" "" SW_SHOWNORMAL "" $(DESC_Menu_gnupg_readme)
!insertmacro MUI_STARTMENU_WRITE_END
no_start_menu:
# Check if the desktop entries where requested.
!insertmacro MUI_INSTALLOPTIONS_READ $R0 "${W32_SRCDIR}/inst-options.ini" \
"Field 3" "State"
IntCmp $R0 0 no_desktop
SectionGetFlags ${SEC_gpa} $R0
IntOp $R0 $R0 & ${SF_SELECTED}
IntCmp $R0 ${SF_SELECTED} 0 no_gpa_desktop
CreateShortCut "$DESKTOP\GPA.lnk" \
"$INSTDIR\bin\launch-gpa.exe" \
"" "" "" SW_SHOWNORMAL "" $(DESC_Menu_gpa)
no_gpa_desktop:
CreateShortCut "$DESKTOP\GPA Manual.lnk" \
"$INSTDIR\share\gpa\gpa.html" \
"" "" "" SW_SHOWNORMAL "" $(DESC_Menu_gpa_manual)
no_desktop:
# Check if the quick launch bar entries where requested.
!insertmacro MUI_INSTALLOPTIONS_READ $R0 "${W32_SRCDIR}/inst-options.ini" \
"Field 4" "State"
IntCmp $R0 0 no_quick_launch
StrCmp $QUICKLAUNCH $TEMP no_quick_launch
SectionGetFlags ${SEC_gpa} $R0
IntOp $R0 $R0 & ${SF_SELECTED}
IntCmp $R0 ${SF_SELECTED} 0 no_gpa_quicklaunch
CreateShortCut "$QUICKLAUNCH\GPA.lnk" \
"$INSTDIR\bin\launch-gpa.exe" \
"" "" "" SW_SHOWNORMAL "" $(DESC_Menu_gpa)
no_gpa_quicklaunch:
no_quick_launch:
SectionEnd
!endif
#
# Now for the generic parts to end the installation.
#
Var MYTMP
# Last section is a hidden one.
Section
WriteUninstaller "$INSTDIR\gnupg-uninstall.exe"
# Windows Add/Remove Programs support
StrCpy $MYTMP "Software\Microsoft\Windows\CurrentVersion\Uninstall\GnuPG"
WriteRegExpandStr SHCTX $MYTMP "UninstallString" '"$INSTDIR\gnupg-uninstall.exe"'
WriteRegExpandStr SHCTX $MYTMP "InstallLocation" "$INSTDIR"
WriteRegStr SHCTX $MYTMP "DisplayName" "${PRETTY_PACKAGE}"
!ifdef WITH_GUI
WriteRegStr SHCTX $MYTMP "DisplayIcon" "$INSTDIR\bin\gpa.exe,0"
!else
WriteRegStr SHCTX $MYTMP "DisplayIcon" "$INSTDIR\bin\gpg.exe,0"
!endif
WriteRegStr SHCTX $MYTMP "DisplayVersion" "${VERSION}"
WriteRegStr SHCTX $MYTMP "Publisher" "The GnuPG Project"
WriteRegStr SHCTX $MYTMP "URLInfoAbout" "https://gnupg.org"
WriteRegDWORD SHCTX $MYTMP "NoModify" "1"
WriteRegDWORD SHCTX $MYTMP "NoRepair" "1"
SectionEnd
Section Uninstall
!ifdef WITH_GUI
#---------------------------------------------------
# Delete the menu entries and any empty parent menus
#---------------------------------------------------
!insertmacro MUI_STARTMENU_GETFOLDER Application $MYTMP
Delete "$SMPROGRAMS\$MYTMP\GPA.lnk"
Delete "$SMPROGRAMS\$MYTMP\GnuPG Manual.lnk"
Delete "$SMPROGRAMS\$MYTMP\GnuPG README.lnk"
Delete "$SMPROGRAMS\$MYTMP\*.lnk"
StrCpy $MYTMP "$SMPROGRAMS\$MYTMP"
startMenuDeleteLoop:
ClearErrors
RMDir $MYTMP
GetFullPathName $MYTMP "$MYTMP\.."
IfErrors startMenuDeleteLoopDone
StrCmp $MYTMP $SMPROGRAMS startMenuDeleteLoopDone startMenuDeleteLoop
startMenuDeleteLoopDone:
DeleteRegValue SHCTX "Software\GNU\GnuPG" "Start Menu Folder"
# Delete Desktop links.
Delete "$DESKTOP\GPA.lnk"
Delete "$DESKTOP\GnuPG Manual.lnk"
Delete "$DESKTOP\GnuPG README.lnk"
# Delete Quick Launch Bar links.
StrCmp $QUICKLAUNCH $TEMP no_quick_launch_uninstall
Delete "$QUICKLAUNCH\GPA.lnk"
no_quick_launch_uninstall:
!endif
Delete "$INSTDIR\gnupg-uninstall.exe"
RMDir "$INSTDIR"
# Clean the registry.
DeleteRegValue SHCTX "Software\GnuPG" "Install Directory"
DeleteRegKey /ifempty SHCTX "Software\GnuPG"
# Remove Windows Add/Remove Programs support.
DeleteRegKey SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\GnuPG"
SectionEnd
diff --git a/common/Makefile.am b/common/Makefile.am
index b6a6605f1..9e0f10917 100644
--- a/common/Makefile.am
+++ b/common/Makefile.am
@@ -1,229 +1,229 @@
# Makefile for common gnupg modules
# Copyright (C) 2001, 2003, 2007, 2010 Free Software Foundation, Inc.
#
# This file is part of GnuPG.
#
# GnuPG is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# GnuPG is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, see <https://www.gnu.org/licenses/>.
## Process this file with automake to produce Makefile.in
EXTRA_DIST = mkstrtable.awk exaudit.awk exstatus.awk ChangeLog-2011 \
audit-events.h status-codes.h ChangeLog.jnlib \
ChangeLog-2011.include w32info-rc.h.in gnupg.ico \
all-tests.scm
noinst_LIBRARIES = libcommon.a libcommonpth.a libgpgrl.a
if !HAVE_W32CE_SYSTEM
noinst_LIBRARIES += libsimple-pwquery.a
endif
noinst_PROGRAMS = $(module_tests) $(module_maint_tests)
TESTS = $(module_tests)
BUILT_SOURCES = audit-events.h status-codes.h
MAINTAINERCLEANFILES = audit-events.h status-codes.h
AM_CPPFLAGS =
AM_CFLAGS = $(LIBGCRYPT_CFLAGS) $(LIBASSUAN_CFLAGS) $(KSBA_CFLAGS)
include $(top_srcdir)/am/cmacros.am
common_sources = \
common-defs.h \
util.h utilproto.h fwddecl.h i18n.c i18n.h \
types.h host2net.h dynload.h w32help.h \
mapstrings.c stringhelp.c stringhelp.h \
strlist.c strlist.h \
utf8conv.c utf8conv.h \
argparse.c argparse.h \
logging.h \
dotlock.c dotlock.h \
mischelp.c mischelp.h \
status.c status.h\
shareddefs.h \
openpgpdefs.h \
gc-opt-flags.h \
keyserver.h \
sexp-parse.h \
tlv.c tlv.h \
init.c init.h \
sexputil.c \
sysutils.c sysutils.h \
homedir.c \
gettime.c gettime.h \
yesno.c \
b64enc.c b64dec.c zb32.c zb32.h \
convert.c \
percent.c \
mbox-util.c mbox-util.h \
miscellaneous.c \
xasprintf.c \
xreadline.c \
membuf.c membuf.h \
ccparray.c ccparray.h \
iobuf.c iobuf.h \
ttyio.c ttyio.h \
asshelp.c asshelp2.c asshelp.h \
exechelp.h \
signal.c \
audit.c audit.h \
localename.c \
session-env.c session-env.h \
userids.c userids.h \
openpgp-oid.c openpgp-s2k.c \
ssh-utils.c ssh-utils.h \
agent-opt.c \
helpfile.c \
mkdir_p.c mkdir_p.h \
strlist.c strlist.h \
exectool.c exectool.h \
server-help.c server-help.h \
name-value.c name-value.h \
recsel.c recsel.h \
ksba-io-support.c ksba-io-support.h \
compliance.c compliance.h \
pkscreening.c pkscreening.h
if HAVE_W32_SYSTEM
common_sources += w32-reg.c
endif
# To make the code easier to read we have split home some code into
# separate source files.
if HAVE_W32_SYSTEM
if HAVE_W32CE_SYSTEM
common_sources += exechelp-w32ce.c
else
common_sources += exechelp-w32.c
endif
else
common_sources += exechelp-posix.c
endif
# Sources only useful without NPTH.
without_npth_sources = \
get-passphrase.c get-passphrase.h
# Sources only useful with NPTH.
with_npth_sources = \
call-gpg.c call-gpg.h
libcommon_a_SOURCES = $(common_sources) $(without_npth_sources)
libcommon_a_CFLAGS = $(AM_CFLAGS) $(LIBASSUAN_CFLAGS) -DWITHOUT_NPTH=1
libcommonpth_a_SOURCES = $(common_sources) $(with_npth_sources)
libcommonpth_a_CFLAGS = $(AM_CFLAGS) $(LIBASSUAN_CFLAGS) $(NPTH_CFLAGS)
if !HAVE_W32CE_SYSTEM
libsimple_pwquery_a_SOURCES = \
simple-pwquery.c simple-pwquery.h asshelp.c asshelp.h
libsimple_pwquery_a_CFLAGS = $(AM_CFLAGS) $(LIBASSUAN_CFLAGS)
endif
libgpgrl_a_SOURCES = \
gpgrlhelp.c
if MAINTAINER_MODE
# Note: Due to the dependency on Makefile, the file will always be
# rebuilt, so we allow this only in maintainer mode.
# Create the audit-events.h include file from audit.h
# Note: We create the target file in the source directory because it
# is a distributed built source. If we would not do that we may end
# up with two files and then it is not clear which version of the
# files will be picked up.
audit-events.h: Makefile.am mkstrtable.awk exaudit.awk audit.h
$(AWK) -f $(srcdir)/exaudit.awk $(srcdir)/audit.h \
| $(AWK) -f $(srcdir)/mkstrtable.awk -v textidx=3 -v nogettext=1 \
- -v namespace=eventstr_ > $(srcdir)/audit-events.h
+ -v pkg_namespace=eventstr_ > $(srcdir)/audit-events.h
# Create the status-codes.h include file from status.h
status-codes.h: Makefile.am mkstrtable.awk exstatus.awk status.h
$(AWK) -f $(srcdir)/exstatus.awk $(srcdir)/status.h \
| $(AWK) -f $(srcdir)/mkstrtable.awk -v textidx=3 -v nogettext=1 \
- -v namespace=statusstr_ > $(srcdir)/status-codes.h
+ -v pkg_namespace=statusstr_ > $(srcdir)/status-codes.h
endif
#
# Module tests
#
module_tests = t-stringhelp t-timestuff \
t-convert t-percent t-gettime t-sysutils t-sexputil \
t-session-env t-openpgp-oid t-ssh-utils \
t-mapstrings t-zb32 t-mbox-util t-iobuf t-strlist \
t-name-value t-ccparray t-recsel
if !HAVE_W32CE_SYSTEM
module_tests += t-exechelp t-exectool
endif
if HAVE_W32_SYSTEM
module_tests += t-w32-reg
endif
if MAINTAINER_MODE
module_maint_tests = t-helpfile t-b64
else
module_maint_tests =
endif
t_extra_src = t-support.h
t_common_cflags = $(KSBA_CFLAGS) $(LIBGCRYPT_CFLAGS) \
$(LIBASSUAN_CFLAGS) $(GPG_ERROR_CFLAGS) $(INCICONV)
t_common_ldadd = libcommon.a \
$(LIBGCRYPT_LIBS) $(LIBASSUAN_LIBS) $(GPG_ERROR_LIBS) \
$(LIBINTL) $(LIBICONV)
# Common tests
t_stringhelp_SOURCES = t-stringhelp.c $(t_extra_src)
t_stringhelp_LDADD = $(t_common_ldadd)
t_timestuff_SOURCES = t-timestuff.c $(t_extra_src)
t_timestuff_LDADD = $(t_common_ldadd)
t_convert_LDADD = $(t_common_ldadd)
t_percent_LDADD = $(t_common_ldadd)
t_gettime_LDADD = $(t_common_ldadd)
t_sysutils_LDADD = $(t_common_ldadd)
t_helpfile_LDADD = $(t_common_ldadd)
t_sexputil_LDADD = $(t_common_ldadd)
t_b64_LDADD = $(t_common_ldadd)
t_exechelp_LDADD = $(t_common_ldadd)
t_exectool_LDADD = $(t_common_ldadd)
t_session_env_LDADD = $(t_common_ldadd)
t_openpgp_oid_LDADD = $(t_common_ldadd)
t_ssh_utils_LDADD = $(t_common_ldadd)
t_mapstrings_LDADD = $(t_common_ldadd)
t_zb32_SOURCES = t-zb32.c $(t_extra_src)
t_zb32_LDADD = $(t_common_ldadd)
t_mbox_util_LDADD = $(t_common_ldadd)
t_iobuf_LDADD = $(t_common_ldadd)
t_strlist_LDADD = $(t_common_ldadd)
t_name_value_LDADD = $(t_common_ldadd)
t_ccparray_LDADD = $(t_common_ldadd)
t_recsel_LDADD = $(t_common_ldadd)
# System specific test
if HAVE_W32_SYSTEM
t_w32_reg_SOURCES = t-w32-reg.c $(t_extra_src)
t_w32_reg_LDADD = $(t_common_ldadd)
endif
# All programs should depend on the created libs.
$(PROGRAMS) : libcommon.a libcommonpth.a
diff --git a/common/exechelp-w32.c b/common/exechelp-w32.c
index 86b1d6869..ea158a33f 100644
--- a/common/exechelp-w32.c
+++ b/common/exechelp-w32.c
@@ -1,933 +1,978 @@
/* exechelp-w32.c - Fork and exec helpers for W32.
* Copyright (C) 2004, 2007-2009, 2010 Free Software Foundation, Inc.
* Copyright (C) 2004, 2006-2012, 2014-2017 g10 Code GmbH
*
* This file is part of GnuPG.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of either
*
* - the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 3 of the License, or (at
* your option) any later version.
*
* or
*
* - 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.
*
* or both in parallel, as here.
*
* This file is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
* SPDX-License-Identifier: (LGPL-3.0+ OR GPL-2.0+)
*/
#include <config.h>
#if !defined(HAVE_W32_SYSTEM) || defined (HAVE_W32CE_SYSTEM)
#error This code is only used on W32.
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <assert.h>
#ifdef HAVE_SIGNAL_H
# include <signal.h>
#endif
#include <unistd.h>
#include <fcntl.h>
#ifdef WITHOUT_NPTH /* Give the Makefile a chance to build without Pth. */
#undef HAVE_NPTH
#undef USE_NPTH
#endif
#ifdef HAVE_NPTH
#include <npth.h>
#endif
#ifdef HAVE_STAT
# include <sys/stat.h>
#endif
#include "util.h"
#include "i18n.h"
#include "sysutils.h"
#include "exechelp.h"
/* Define to 1 do enable debugging. */
#define DEBUG_W32_SPAWN 0
/* It seems Vista doesn't grok X_OK and so fails access() tests.
Previous versions interpreted X_OK as F_OK anyway, so we'll just
use F_OK directly. */
#undef X_OK
#define X_OK F_OK
/* We assume that a HANDLE can be represented by an int which should
be true for all i386 systems (HANDLE is defined as void *) and
these are the only systems for which Windows is available. Further
we assume that -1 denotes an invalid handle. */
# define fd_to_handle(a) ((HANDLE)(a))
# define handle_to_fd(a) ((int)(a))
# define pid_to_handle(a) ((HANDLE)(a))
# define handle_to_pid(a) ((int)(a))
/* Helper */
static inline gpg_error_t
my_error_from_syserror (void)
{
return gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
}
static inline gpg_error_t
my_error (int errcode)
{
return gpg_err_make (default_errsource, errcode);
}
/* Return the maximum number of currently allowed open file
descriptors. Only useful on POSIX systems but returns a value on
other systems too. */
int
get_max_fds (void)
{
int max_fds = -1;
#ifdef OPEN_MAX
if (max_fds == -1)
max_fds = OPEN_MAX;
#endif
if (max_fds == -1)
max_fds = 256; /* Arbitrary limit. */
return max_fds;
}
/* Under Windows this is a dummy function. */
void
close_all_fds (int first, int *except)
{
(void)first;
(void)except;
}
/* Returns an array with all currently open file descriptors. The end
* of the array is marked by -1. The caller needs to release this
* array using the *standard free* and not with xfree. This allow the
* use of this function right at startup even before libgcrypt has
* been initialized. Returns NULL on error and sets ERRNO
* accordingly. Note that fstat prints a warning to DebugView for all
* invalid fds which is a bit annoying. We actually do not need this
* function in real code (close_all_fds is a dummy anyway) but we keep
* it for use by t-exechelp.c. */
int *
get_all_open_fds (void)
{
int *array;
size_t narray;
int fd, max_fd, idx;
#ifndef HAVE_STAT
array = calloc (1, sizeof *array);
if (array)
array[0] = -1;
#else /*HAVE_STAT*/
struct stat statbuf;
max_fd = get_max_fds ();
narray = 32; /* If you change this change also t-exechelp.c. */
array = calloc (narray, sizeof *array);
if (!array)
return NULL;
/* Note: The list we return is ordered. */
for (idx=0, fd=0; fd < max_fd; fd++)
if (!(fstat (fd, &statbuf) == -1 && errno == EBADF))
{
if (idx+1 >= narray)
{
int *tmp;
narray += (narray < 256)? 32:256;
tmp = realloc (array, narray * sizeof *array);
if (!tmp)
{
free (array);
return NULL;
}
array = tmp;
}
array[idx++] = fd;
}
array[idx] = -1;
#endif /*HAVE_STAT*/
return array;
}
/* Helper function to build_w32_commandline. */
static char *
build_w32_commandline_copy (char *buffer, const char *string)
{
char *p = buffer;
const char *s;
if (!*string) /* Empty string. */
p = stpcpy (p, "\"\"");
else if (strpbrk (string, " \t\n\v\f\""))
{
/* Need to do some kind of quoting. */
p = stpcpy (p, "\"");
for (s=string; *s; s++)
{
*p++ = *s;
if (*s == '\"')
*p++ = *s;
}
*p++ = '\"';
*p = 0;
}
else
p = stpcpy (p, string);
return p;
}
/* Build a command line for use with W32's CreateProcess. On success
CMDLINE gets the address of a newly allocated string. */
static gpg_error_t
build_w32_commandline (const char *pgmname, const char * const *argv,
char **cmdline)
{
int i, n;
const char *s;
char *buf, *p;
*cmdline = NULL;
n = 0;
s = pgmname;
n += strlen (s) + 1 + 2; /* (1 space, 2 quoting */
for (; *s; s++)
if (*s == '\"')
n++; /* Need to double inner quotes. */
for (i=0; (s=argv[i]); i++)
{
n += strlen (s) + 1 + 2; /* (1 space, 2 quoting */
for (; *s; s++)
if (*s == '\"')
n++; /* Need to double inner quotes. */
}
n++;
buf = p = xtrymalloc (n);
if (!buf)
return my_error_from_syserror ();
p = build_w32_commandline_copy (p, pgmname);
for (i=0; argv[i]; i++)
{
*p++ = ' ';
p = build_w32_commandline_copy (p, argv[i]);
}
*cmdline= buf;
return 0;
}
#define INHERIT_READ 1
#define INHERIT_WRITE 2
#define INHERIT_BOTH (INHERIT_READ|INHERIT_WRITE)
/* Create pipe. FLAGS indicates which ends are inheritable. */
static int
create_inheritable_pipe (HANDLE filedes[2], int flags)
{
HANDLE r, w;
SECURITY_ATTRIBUTES sec_attr;
memset (&sec_attr, 0, sizeof sec_attr );
sec_attr.nLength = sizeof sec_attr;
sec_attr.bInheritHandle = TRUE;
if (!CreatePipe (&r, &w, &sec_attr, 0))
return -1;
if ((flags & INHERIT_READ) == 0)
if (! SetHandleInformation (r, HANDLE_FLAG_INHERIT, 0))
goto fail;
if ((flags & INHERIT_WRITE) == 0)
if (! SetHandleInformation (w, HANDLE_FLAG_INHERIT, 0))
goto fail;
filedes[0] = r;
filedes[1] = w;
return 0;
fail:
log_error ("SetHandleInformation failed: %s\n", w32_strerror (-1));
CloseHandle (r);
CloseHandle (w);
return -1;
}
static HANDLE
w32_open_null (int for_write)
{
HANDLE hfile;
hfile = CreateFileW (L"nul",
for_write? GENERIC_WRITE : GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL, OPEN_EXISTING, 0, NULL);
if (hfile == INVALID_HANDLE_VALUE)
log_debug ("can't open 'nul': %s\n", w32_strerror (-1));
return hfile;
}
static gpg_error_t
create_pipe_and_estream (int filedes[2], int flags,
estream_t *r_fp, int outbound, int nonblock)
{
gpg_error_t err = 0;
HANDLE fds[2];
es_syshd_t syshd;
filedes[0] = filedes[1] = -1;
err = my_error (GPG_ERR_GENERAL);
if (!create_inheritable_pipe (fds, flags))
{
filedes[0] = _open_osfhandle (handle_to_fd (fds[0]), O_RDONLY);
if (filedes[0] == -1)
{
log_error ("failed to translate osfhandle %p\n", fds[0]);
CloseHandle (fds[1]);
}
else
{
filedes[1] = _open_osfhandle (handle_to_fd (fds[1]), O_APPEND);
if (filedes[1] == -1)
{
log_error ("failed to translate osfhandle %p\n", fds[1]);
close (filedes[0]);
filedes[0] = -1;
CloseHandle (fds[1]);
}
else
err = 0;
}
}
if (! err && r_fp)
{
syshd.type = ES_SYSHD_HANDLE;
if (!outbound)
{
syshd.u.handle = fds[0];
*r_fp = es_sysopen (&syshd, nonblock? "r,nonblock" : "r");
}
else
{
syshd.u.handle = fds[1];
*r_fp = es_sysopen (&syshd, nonblock? "w,nonblock" : "w");
}
if (!*r_fp)
{
err = my_error_from_syserror ();
log_error (_("error creating a stream for a pipe: %s\n"),
gpg_strerror (err));
close (filedes[0]);
close (filedes[1]);
filedes[0] = filedes[1] = -1;
return err;
}
}
return err;
}
/* Portable function to create a pipe. Under Windows the write end is
inheritable. If R_FP is not NULL, an estream is created for the
read end and stored at R_FP. */
gpg_error_t
gnupg_create_inbound_pipe (int filedes[2], estream_t *r_fp, int nonblock)
{
return create_pipe_and_estream (filedes, INHERIT_WRITE,
r_fp, 0, nonblock);
}
/* Portable function to create a pipe. Under Windows the read end is
inheritable. If R_FP is not NULL, an estream is created for the
write end and stored at R_FP. */
gpg_error_t
gnupg_create_outbound_pipe (int filedes[2], estream_t *r_fp, int nonblock)
{
return create_pipe_and_estream (filedes, INHERIT_READ,
r_fp, 1, nonblock);
}
/* Portable function to create a pipe. Under Windows both ends are
inheritable. */
gpg_error_t
gnupg_create_pipe (int filedes[2])
{
return create_pipe_and_estream (filedes, INHERIT_BOTH,
NULL, 0, 0);
}
/* Fork and exec the PGMNAME, see exechelp.h for details. */
gpg_error_t
gnupg_spawn_process (const char *pgmname, const char *argv[],
int *except, void (*preexec)(void), unsigned int flags,
estream_t *r_infp,
estream_t *r_outfp,
estream_t *r_errfp,
pid_t *pid)
{
gpg_error_t err;
SECURITY_ATTRIBUTES sec_attr;
PROCESS_INFORMATION pi =
{
NULL, /* Returns process handle. */
0, /* Returns primary thread handle. */
0, /* Returns pid. */
0 /* Returns tid. */
};
STARTUPINFO si;
int cr_flags;
char *cmdline;
HANDLE inpipe[2] = {INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE};
HANDLE outpipe[2] = {INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE};
HANDLE errpipe[2] = {INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE};
estream_t infp = NULL;
estream_t outfp = NULL;
estream_t errfp = NULL;
HANDLE nullhd[3] = {INVALID_HANDLE_VALUE,
INVALID_HANDLE_VALUE,
INVALID_HANDLE_VALUE};
int i;
es_syshd_t syshd;
gpg_err_source_t errsource = default_errsource;
int nonblock = !!(flags & GNUPG_SPAWN_NONBLOCK);
(void)except; /* Not yet used. */
if (r_infp)
*r_infp = NULL;
if (r_outfp)
*r_outfp = NULL;
if (r_errfp)
*r_errfp = NULL;
*pid = (pid_t)(-1); /* Always required. */
if (r_infp)
{
if (create_inheritable_pipe (inpipe, INHERIT_READ))
{
err = gpg_err_make (errsource, GPG_ERR_GENERAL);
log_error (_("error creating a pipe: %s\n"), gpg_strerror (err));
return err;
}
syshd.type = ES_SYSHD_HANDLE;
syshd.u.handle = inpipe[1];
infp = es_sysopen (&syshd, nonblock? "w,nonblock" : "w");
if (!infp)
{
err = gpg_err_make (errsource, gpg_err_code_from_syserror ());
log_error (_("error creating a stream for a pipe: %s\n"),
gpg_strerror (err));
CloseHandle (inpipe[0]);
CloseHandle (inpipe[1]);
inpipe[0] = inpipe[1] = INVALID_HANDLE_VALUE;
return err;
}
}
if (r_outfp)
{
if (create_inheritable_pipe (outpipe, INHERIT_WRITE))
{
err = gpg_err_make (errsource, GPG_ERR_GENERAL);
log_error (_("error creating a pipe: %s\n"), gpg_strerror (err));
return err;
}
syshd.type = ES_SYSHD_HANDLE;
syshd.u.handle = outpipe[0];
outfp = es_sysopen (&syshd, nonblock? "r,nonblock" : "r");
if (!outfp)
{
err = gpg_err_make (errsource, gpg_err_code_from_syserror ());
log_error (_("error creating a stream for a pipe: %s\n"),
gpg_strerror (err));
CloseHandle (outpipe[0]);
CloseHandle (outpipe[1]);
outpipe[0] = outpipe[1] = INVALID_HANDLE_VALUE;
if (infp)
es_fclose (infp);
else if (inpipe[1] != INVALID_HANDLE_VALUE)
CloseHandle (inpipe[1]);
if (inpipe[0] != INVALID_HANDLE_VALUE)
CloseHandle (inpipe[0]);
return err;
}
}
if (r_errfp)
{
if (create_inheritable_pipe (errpipe, INHERIT_WRITE))
{
err = gpg_err_make (errsource, GPG_ERR_GENERAL);
log_error (_("error creating a pipe: %s\n"), gpg_strerror (err));
return err;
}
syshd.type = ES_SYSHD_HANDLE;
syshd.u.handle = errpipe[0];
errfp = es_sysopen (&syshd, nonblock? "r,nonblock" : "r");
if (!errfp)
{
err = gpg_err_make (errsource, gpg_err_code_from_syserror ());
log_error (_("error creating a stream for a pipe: %s\n"),
gpg_strerror (err));
CloseHandle (errpipe[0]);
CloseHandle (errpipe[1]);
errpipe[0] = errpipe[1] = INVALID_HANDLE_VALUE;
if (outfp)
es_fclose (outfp);
else if (outpipe[0] != INVALID_HANDLE_VALUE)
CloseHandle (outpipe[0]);
if (outpipe[1] != INVALID_HANDLE_VALUE)
CloseHandle (outpipe[1]);
if (infp)
es_fclose (infp);
else if (inpipe[1] != INVALID_HANDLE_VALUE)
CloseHandle (inpipe[1]);
if (inpipe[0] != INVALID_HANDLE_VALUE)
CloseHandle (inpipe[0]);
return err;
}
}
/* Prepare security attributes. */
memset (&sec_attr, 0, sizeof sec_attr );
sec_attr.nLength = sizeof sec_attr;
sec_attr.bInheritHandle = FALSE;
/* Build the command line. */
err = build_w32_commandline (pgmname, argv, &cmdline);
if (err)
return err;
if (inpipe[0] == INVALID_HANDLE_VALUE)
nullhd[0] = w32_open_null (0);
if (outpipe[1] == INVALID_HANDLE_VALUE)
nullhd[1] = w32_open_null (1);
if (errpipe[1] == INVALID_HANDLE_VALUE)
nullhd[2] = w32_open_null (1);
/* Start the process. Note that we can't run the PREEXEC function
because this might change our own environment. */
(void)preexec;
memset (&si, 0, sizeof si);
si.cb = sizeof (si);
si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
si.wShowWindow = DEBUG_W32_SPAWN? SW_SHOW : SW_HIDE;
si.hStdInput = inpipe[0] == INVALID_HANDLE_VALUE? nullhd[0] : inpipe[0];
si.hStdOutput = outpipe[1] == INVALID_HANDLE_VALUE? nullhd[1] : outpipe[1];
si.hStdError = errpipe[1] == INVALID_HANDLE_VALUE? nullhd[2] : errpipe[1];
cr_flags = (CREATE_DEFAULT_ERROR_MODE
| ((flags & GNUPG_SPAWN_DETACHED)? DETACHED_PROCESS : 0)
| GetPriorityClass (GetCurrentProcess ())
| CREATE_SUSPENDED);
/* log_debug ("CreateProcess, path='%s' cmdline='%s'\n", pgmname, cmdline); */
if (!CreateProcess (pgmname, /* Program to start. */
cmdline, /* Command line arguments. */
&sec_attr, /* Process security attributes. */
&sec_attr, /* Thread security attributes. */
TRUE, /* Inherit handles. */
cr_flags, /* Creation flags. */
NULL, /* Environment. */
NULL, /* Use current drive/directory. */
&si, /* Startup information. */
&pi /* Returns process information. */
))
{
log_error ("CreateProcess failed: %s\n", w32_strerror (-1));
xfree (cmdline);
if (infp)
es_fclose (infp);
else if (inpipe[1] != INVALID_HANDLE_VALUE)
CloseHandle (outpipe[1]);
if (inpipe[0] != INVALID_HANDLE_VALUE)
CloseHandle (inpipe[0]);
if (outfp)
es_fclose (outfp);
else if (outpipe[0] != INVALID_HANDLE_VALUE)
CloseHandle (outpipe[0]);
if (outpipe[1] != INVALID_HANDLE_VALUE)
CloseHandle (outpipe[1]);
if (errfp)
es_fclose (errfp);
else if (errpipe[0] != INVALID_HANDLE_VALUE)
CloseHandle (errpipe[0]);
if (errpipe[1] != INVALID_HANDLE_VALUE)
CloseHandle (errpipe[1]);
return gpg_err_make (errsource, GPG_ERR_GENERAL);
}
xfree (cmdline);
cmdline = NULL;
/* Close the inherited handles to /dev/null. */
for (i=0; i < DIM (nullhd); i++)
if (nullhd[i] != INVALID_HANDLE_VALUE)
CloseHandle (nullhd[i]);
/* Close the inherited ends of the pipes. */
if (inpipe[0] != INVALID_HANDLE_VALUE)
CloseHandle (inpipe[0]);
if (outpipe[1] != INVALID_HANDLE_VALUE)
CloseHandle (outpipe[1]);
if (errpipe[1] != INVALID_HANDLE_VALUE)
CloseHandle (errpipe[1]);
/* log_debug ("CreateProcess ready: hProcess=%p hThread=%p" */
/* " dwProcessID=%d dwThreadId=%d\n", */
/* pi.hProcess, pi.hThread, */
/* (int) pi.dwProcessId, (int) pi.dwThreadId); */
/* log_debug (" outfp=%p errfp=%p\n", outfp, errfp); */
/* Fixme: For unknown reasons AllowSetForegroundWindow returns an
invalid argument error if we pass it the correct processID. As a
workaround we use -1 (ASFW_ANY). */
if ((flags & GNUPG_SPAWN_RUN_ASFW))
gnupg_allow_set_foregound_window ((pid_t)(-1)/*pi.dwProcessId*/);
/* Process has been created suspended; resume it now. */
ResumeThread (pi.hThread);
CloseHandle (pi.hThread);
if (r_infp)
*r_infp = infp;
if (r_outfp)
*r_outfp = outfp;
if (r_errfp)
*r_errfp = errfp;
*pid = handle_to_pid (pi.hProcess);
return 0;
}
/* Simplified version of gnupg_spawn_process. This function forks and
then execs PGMNAME, while connecting INFD to stdin, OUTFD to stdout
and ERRFD to stderr (any of them may be -1 to connect them to
/dev/null). The arguments for the process are expected in the NULL
terminated array ARGV. The program name itself should not be
included there. Calling gnupg_wait_process is required.
Returns 0 on success or an error code. */
gpg_error_t
gnupg_spawn_process_fd (const char *pgmname, const char *argv[],
int infd, int outfd, int errfd, pid_t *pid)
{
gpg_error_t err;
SECURITY_ATTRIBUTES sec_attr;
PROCESS_INFORMATION pi = { NULL, 0, 0, 0 };
STARTUPINFO si;
char *cmdline;
int i;
HANDLE stdhd[3];
/* Setup return values. */
*pid = (pid_t)(-1);
/* Prepare security attributes. */
memset (&sec_attr, 0, sizeof sec_attr );
sec_attr.nLength = sizeof sec_attr;
sec_attr.bInheritHandle = FALSE;
/* Build the command line. */
err = build_w32_commandline (pgmname, argv, &cmdline);
if (err)
return err;
memset (&si, 0, sizeof si);
si.cb = sizeof (si);
si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
si.wShowWindow = DEBUG_W32_SPAWN? SW_SHOW : SW_MINIMIZE;
stdhd[0] = infd == -1? w32_open_null (0) : INVALID_HANDLE_VALUE;
stdhd[1] = outfd == -1? w32_open_null (1) : INVALID_HANDLE_VALUE;
stdhd[2] = errfd == -1? w32_open_null (1) : INVALID_HANDLE_VALUE;
si.hStdInput = infd == -1? stdhd[0] : (void*)_get_osfhandle (infd);
si.hStdOutput = outfd == -1? stdhd[1] : (void*)_get_osfhandle (outfd);
si.hStdError = errfd == -1? stdhd[2] : (void*)_get_osfhandle (errfd);
/* log_debug ("CreateProcess, path='%s' cmdline='%s'\n", pgmname, cmdline); */
if (!CreateProcess (pgmname, /* Program to start. */
cmdline, /* Command line arguments. */
&sec_attr, /* Process security attributes. */
&sec_attr, /* Thread security attributes. */
TRUE, /* Inherit handles. */
(CREATE_DEFAULT_ERROR_MODE
| GetPriorityClass (GetCurrentProcess ())
| CREATE_SUSPENDED | DETACHED_PROCESS),
NULL, /* Environment. */
NULL, /* Use current drive/directory. */
&si, /* Startup information. */
&pi /* Returns process information. */
))
{
log_error ("CreateProcess failed: %s\n", w32_strerror (-1));
err = my_error (GPG_ERR_GENERAL);
}
else
err = 0;
xfree (cmdline);
for (i=0; i < 3; i++)
if (stdhd[i] != INVALID_HANDLE_VALUE)
CloseHandle (stdhd[i]);
if (err)
return err;
/* log_debug ("CreateProcess ready: hProcess=%p hThread=%p" */
/* " dwProcessID=%d dwThreadId=%d\n", */
/* pi.hProcess, pi.hThread, */
/* (int) pi.dwProcessId, (int) pi.dwThreadId); */
/* Process has been created suspended; resume it now. */
ResumeThread (pi.hThread);
CloseHandle (pi.hThread);
*pid = handle_to_pid (pi.hProcess);
return 0;
}
/* See exechelp.h for a description. */
gpg_error_t
gnupg_wait_process (const char *pgmname, pid_t pid, int hang, int *r_exitcode)
{
return gnupg_wait_processes (&pgmname, &pid, 1, hang, r_exitcode);
}
/* See exechelp.h for a description. */
gpg_error_t
gnupg_wait_processes (const char **pgmnames, pid_t *pids, size_t count,
int hang, int *r_exitcodes)
{
gpg_err_code_t ec = 0;
size_t i;
HANDLE *procs;
int code;
procs = xtrycalloc (count, sizeof *procs);
if (procs == NULL)
return my_error_from_syserror ();
for (i = 0; i < count; i++)
{
if (r_exitcodes)
r_exitcodes[i] = -1;
if (pids[i] == (pid_t)(-1))
return my_error (GPG_ERR_INV_VALUE);
procs[i] = fd_to_handle (pids[i]);
}
/* FIXME: We should do a pth_waitpid here. However this has not yet
been implemented. A special W32 pth system call would even be
better. */
code = WaitForMultipleObjects (count, procs, TRUE, hang? INFINITE : 0);
switch (code)
{
case WAIT_TIMEOUT:
ec = GPG_ERR_TIMEOUT;
goto leave;
case WAIT_FAILED:
log_error (_("waiting for processes to terminate failed: %s\n"),
w32_strerror (-1));
ec = GPG_ERR_GENERAL;
goto leave;
case WAIT_OBJECT_0:
for (i = 0; i < count; i++)
{
DWORD exc;
if (! GetExitCodeProcess (procs[i], &exc))
{
log_error (_("error getting exit code of process %d: %s\n"),
(int) pids[i], w32_strerror (-1) );
ec = GPG_ERR_GENERAL;
}
else if (exc)
{
if (!r_exitcodes)
log_error (_("error running '%s': exit status %d\n"),
pgmnames[i], (int)exc);
else
r_exitcodes[i] = (int)exc;
ec = GPG_ERR_GENERAL;
}
else
{
if (r_exitcodes)
r_exitcodes[i] = 0;
}
}
break;
default:
log_error ("WaitForMultipleObjects returned unexpected "
"code %d\n", code);
ec = GPG_ERR_GENERAL;
break;
}
leave:
return gpg_err_make (GPG_ERR_SOURCE_DEFAULT, ec);
}
void
gnupg_release_process (pid_t pid)
{
if (pid != (pid_t)INVALID_HANDLE_VALUE)
{
HANDLE process = (HANDLE)pid;
CloseHandle (process);
}
}
/* Spawn a new process and immediately detach from it. The name of
the program to exec is PGMNAME and its arguments are in ARGV (the
programname is automatically passed as first argument).
Environment strings in ENVP are set. An error is returned if
pgmname is not executable; to make this work it is necessary to
provide an absolute file name. All standard file descriptors are
connected to /dev/null. */
gpg_error_t
gnupg_spawn_process_detached (const char *pgmname, const char *argv[],
const char *envp[] )
{
gpg_error_t err;
SECURITY_ATTRIBUTES sec_attr;
PROCESS_INFORMATION pi =
{
NULL, /* Returns process handle. */
0, /* Returns primary thread handle. */
0, /* Returns pid. */
0 /* Returns tid. */
};
STARTUPINFO si;
int cr_flags;
char *cmdline;
+ BOOL in_job = FALSE;
/* We don't use ENVP. */
(void)envp;
if (access (pgmname, X_OK))
return my_error_from_syserror ();
/* Prepare security attributes. */
memset (&sec_attr, 0, sizeof sec_attr );
sec_attr.nLength = sizeof sec_attr;
sec_attr.bInheritHandle = FALSE;
/* Build the command line. */
err = build_w32_commandline (pgmname, argv, &cmdline);
if (err)
return err;
/* Start the process. */
memset (&si, 0, sizeof si);
si.cb = sizeof (si);
si.dwFlags = STARTF_USESHOWWINDOW;
si.wShowWindow = DEBUG_W32_SPAWN? SW_SHOW : SW_MINIMIZE;
cr_flags = (CREATE_DEFAULT_ERROR_MODE
| GetPriorityClass (GetCurrentProcess ())
| CREATE_NEW_PROCESS_GROUP
| DETACHED_PROCESS);
+
+ /* Check if we were spawned as part of a Job.
+ * In a job we need to add CREATE_BREAKAWAY_FROM_JOB
+ * to the cr_flags, otherwise our child processes
+ * are killed when we terminate. */
+ if (!IsProcessInJob (GetCurrentProcess(), NULL, &in_job))
+ {
+ log_error ("IsProcessInJob() failed: %s\n", w32_strerror (-1));
+ in_job = FALSE;
+ }
+
+ if (in_job)
+ {
+ /* Only try to break away from job if it is allowed, otherwise
+ * CreateProcess() would fail with an "Access is denied" error. */
+ JOBOBJECT_EXTENDED_LIMIT_INFORMATION info;
+ if (!QueryInformationJobObject (NULL, JobObjectExtendedLimitInformation,
+ &info, sizeof info, NULL))
+ {
+ log_error ("QueryInformationJobObject() failed: %s\n",
+ w32_strerror (-1));
+ }
+ else if ((info.BasicLimitInformation.LimitFlags &
+ JOB_OBJECT_LIMIT_BREAKAWAY_OK))
+ {
+ log_debug ("Using CREATE_BREAKAWAY_FROM_JOB flag\n");
+ cr_flags |= CREATE_BREAKAWAY_FROM_JOB;
+ }
+ else if ((info.BasicLimitInformation.LimitFlags &
+ JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK))
+ {
+ /* The child process should automatically detach from the job. */
+ log_debug ("Not using CREATE_BREAKAWAY_FROM_JOB flag; "
+ "JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK is set\n");
+ }
+ else
+ {
+ /* It seems that the child process must remain in the job.
+ * This is not necessarily an error, although it can cause premature
+ * termination of the child process when the job is closed. */
+ log_debug ("Not using CREATE_BREAKAWAY_FROM_JOB flag\n");
+ }
+ }
+
/* log_debug ("CreateProcess(detached), path='%s' cmdline='%s'\n", */
/* pgmname, cmdline); */
if (!CreateProcess (pgmname, /* Program to start. */
cmdline, /* Command line arguments. */
&sec_attr, /* Process security attributes. */
&sec_attr, /* Thread security attributes. */
FALSE, /* Inherit handles. */
cr_flags, /* Creation flags. */
NULL, /* Environment. */
NULL, /* Use current drive/directory. */
&si, /* Startup information. */
&pi /* Returns process information. */
))
{
log_error ("CreateProcess(detached) failed: %s\n", w32_strerror (-1));
xfree (cmdline);
return my_error (GPG_ERR_GENERAL);
}
xfree (cmdline);
cmdline = NULL;
/* log_debug ("CreateProcess(detached) ready: hProcess=%p hThread=%p" */
/* " dwProcessID=%d dwThreadId=%d\n", */
/* pi.hProcess, pi.hThread, */
/* (int) pi.dwProcessId, (int) pi.dwThreadId); */
CloseHandle (pi.hThread);
CloseHandle (pi.hProcess);
return 0;
}
/* Kill a process; that is send an appropriate signal to the process.
gnupg_wait_process must be called to actually remove the process
from the system. An invalid PID is ignored. */
void
gnupg_kill_process (pid_t pid)
{
if (pid != (pid_t) INVALID_HANDLE_VALUE)
{
HANDLE process = (HANDLE) pid;
/* Arbitrary error code. */
TerminateProcess (process, 1);
}
}
diff --git a/common/mkstrtable.awk b/common/mkstrtable.awk
index b5d4ef07a..60efce8a3 100644
--- a/common/mkstrtable.awk
+++ b/common/mkstrtable.awk
@@ -1,185 +1,185 @@
# mkstrtable.awk
# Copyright (C) 2003, 2004 g10 Code GmbH
#
# This program 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.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, see <http://www.gnu.org/licenses/>.
#
# As a special exception, g10 Code GmbH gives unlimited permission to
# copy, distribute and modify the C source files that are the output
# of mkstrtable.awk. You need not follow the terms of the GNU General
# Public License when using or distributing such scripts, even though
# portions of the text of mkstrtable.awk appear in them. The GNU
# General Public License (GPL) does govern all other use of the material
# that constitutes the mkstrtable.awk program.
#
# Certain portions of the mkstrtable.awk source text are designed to be
# copied (in certain cases, depending on the input) into the output of
# mkstrtable.awk. We call these the "data" portions. The rest of the
# mkstrtable.awk source text consists of comments plus executable code
# that decides which of the data portions to output in any given case.
# We call these comments and executable code the "non-data" portions.
# mkstrtable.h never copies any of the non-data portions into its output.
#
# This special exception to the GPL applies to versions of mkstrtable.awk
# released by g10 Code GmbH. When you make and distribute a modified version
# of mkstrtable.awk, you may extend this special exception to the GPL to
# apply to your modified version as well, *unless* your modified version
# has the potential to copy into its output some of the text that was the
# non-data portion of the version that you started with. (In other words,
# unless your change moves or copies text from the non-data portions to the
# data portions.) If your modification has such potential, you must delete
# any notice of this special exception to the GPL from your modified version.
# This script outputs a source file that does define the following
# symbols:
#
# static const char msgstr[];
# A string containing all messages in the list.
#
# static const int msgidx[];
# A list of index numbers, one for each message, that points to the
# beginning of the string in msgstr.
#
# msgidxof (code);
# A macro that maps code numbers to idx numbers. If a DEFAULT MESSAGE
# is provided (see below), its index will be returned for unknown codes.
# Otherwise -1 is returned for codes that do not appear in the list.
# You can lookup the message with code CODE with:
# msgstr + msgidx[msgidxof (code)].
#
# The input file has the following format:
# CODE1 ... MESSAGE1 (code nr, <tab>, something, <tab>, msg)
# CODE2 ... MESSAGE2 (code nr, <tab>, something, <tab>, msg)
# ...
# CODEn ... MESSAGEn (code nr, <tab>, something, <tab>, msg)
# ... DEFAULT-MESSAGE (<tab>, something, <tab>, fall-back msg)
#
# Comments (starting with # and ending at the end of the line) are removed,
# as is trailing whitespace. The last line is optional; if no DEFAULT
# MESSAGE is given, msgidxof will return the number -1 for unknown
# index numbers.
#
# The field to be used is specified with the variable "textidx" on
# the command line. It defaults to 2.
#
# The variable nogettext can be set to 1 to suppress gettext markers.
#
# The variable prefix can be used to prepend a string to each message.
#
-# The variable namespace can be used to prepend a string to each
+# The variable pkg_namespace can be used to prepend a string to each
# variable and macro name.
BEGIN {
FS = "[\t]+";
# cpos holds the current position in the message string.
cpos = 0;
# msg holds the number of messages.
msg = 0;
print "/* Output of mkstrtable.awk. DO NOT EDIT. */";
print "";
header = 1;
if (textidx == 0)
textidx = 2;
# nogettext can be set to 1 to suppress gettext noop markers.
}
/^#/ { next; }
header {
if ($1 ~ /^[0123456789]+$/)
{
print "/* The purpose of this complex string table is to produce";
print " optimal code with a minimum of relocations. */";
print "";
- print "static const char " namespace "msgstr[] = ";
+ print "static const char " pkg_namespace "msgstr[] = ";
header = 0;
}
else
print;
}
!header {
- sub (/\#.+/, "");
+ sub (/#.+/, "");
sub (/[ ]+$/, ""); # Strip trailing space and tab characters.
if (/^$/)
next;
# Print the string msgstr line by line. We delay output by one line to be able
# to treat the last line differently (see END).
if (last_msgstr)
{
if (nogettext)
print " \"" last_msgstr "\" \"\\0\"";
else
print " gettext_noop (\"" last_msgstr "\") \"\\0\"";
}
last_msgstr = prefix $textidx;
# Remember the error code and msgidx of each error message.
code[msg] = $1;
pos[msg] = cpos;
cpos += length (last_msgstr) + 1;
msg++;
if ($1 == "")
{
has_default = 1;
exit;
}
}
END {
if (has_default)
coded_msgs = msg - 1;
else
coded_msgs = msg;
if (nogettext)
print " \"" prefix last_msgstr "\";";
else
print " gettext_noop (\"" prefix last_msgstr "\");";
print "";
- print "static const int " namespace "msgidx[] =";
+ print "static const int " pkg_namespace "msgidx[] =";
print " {";
for (i = 0; i < coded_msgs; i++)
print " " pos[i] ",";
print " " pos[coded_msgs];
print " };";
print "";
- print "#define " namespace "msgidxof(code) (0 ? -1 \\";
+ print "#define " pkg_namespace "msgidxof(code) (0 ? -1 \\";
# Gather the ranges.
skip = code[0];
start = code[0];
stop = code[0];
for (i = 1; i < coded_msgs; i++)
{
if (code[i] == stop + 1)
stop++;
else
{
print " : ((code >= " start ") && (code <= " stop ")) ? (code - " \
skip ") \\";
skip += code[i] - stop - 1;
start = code[i];
stop = code[i];
}
}
print " : ((code >= " start ") && (code <= " stop ")) ? (code - " \
skip ") \\";
if (has_default)
print " : " stop + 1 " - " skip ")";
else
print " : -1)";
}
diff --git a/common/name-value.c b/common/name-value.c
index 5094acd03..989a5b111 100644
--- a/common/name-value.c
+++ b/common/name-value.c
@@ -1,806 +1,867 @@
/* name-value.c - Parser and writer for a name-value format.
* Copyright (C) 2016 g10 Code GmbH
*
* This file is part of GnuPG.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of either
*
* - the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 3 of the License, or (at
* your option) any later version.
*
* or
*
* - 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.
*
* or both in parallel, as here.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
/*
* This module aso provides features for the extended private key
* format of gpg-agent.
*/
#include <config.h>
#include <assert.h>
#include <gcrypt.h>
#include <gpg-error.h>
#include <string.h>
#include "mischelp.h"
#include "strlist.h"
#include "util.h"
#include "name-value.h"
struct name_value_container
{
struct name_value_entry *first;
struct name_value_entry *last;
unsigned int private_key_mode:1;
};
struct name_value_entry
{
struct name_value_entry *prev;
struct name_value_entry *next;
/* The name. Comments and blank lines have NAME set to NULL. */
char *name;
/* The value as stored in the file. We store it when we parse
a file so that we can reproduce it. */
strlist_t raw_value;
/* The decoded value. */
char *value;
};
/* Helper */
static inline gpg_error_t
my_error_from_syserror (void)
{
return gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
}
static inline gpg_error_t
my_error (gpg_err_code_t ec)
{
return gpg_err_make (default_errsource, ec);
}
/* Allocation and deallocation. */
/* Allocate a private key container structure. */
nvc_t
nvc_new (void)
{
return xtrycalloc (1, sizeof (struct name_value_container));
}
/* Allocate a private key container structure for use with private keys. */
nvc_t
nvc_new_private_key (void)
{
nvc_t nvc = nvc_new ();
if (nvc)
nvc->private_key_mode = 1;
return nvc;
}
static void
nve_release (nve_t entry, int private_key_mode)
{
if (entry == NULL)
return;
xfree (entry->name);
if (entry->value && private_key_mode)
wipememory (entry->value, strlen (entry->value));
xfree (entry->value);
if (private_key_mode)
free_strlist_wipe (entry->raw_value);
else
free_strlist (entry->raw_value);
xfree (entry);
}
/* Release a private key container structure. */
void
nvc_release (nvc_t pk)
{
nve_t e, next;
if (pk == NULL)
return;
for (e = pk->first; e; e = next)
{
next = e->next;
nve_release (e, pk->private_key_mode);
}
xfree (pk);
}
/* Dealing with names and values. */
/* Check whether the given name is valid. Valid names start with a
letter, end with a colon, and contain only alphanumeric characters
and the hyphen. */
static int
valid_name (const char *name)
{
size_t i, len = strlen (name);
if (! alphap (name) || len == 0 || name[len - 1] != ':')
return 0;
for (i = 1; i < len - 1; i++)
if (! alnump (&name[i]) && name[i] != '-')
return 0;
return 1;
}
/* Makes sure that ENTRY has a RAW_VALUE. */
static gpg_error_t
assert_raw_value (nve_t entry)
{
gpg_error_t err = 0;
size_t len, offset;
#define LINELEN 70
char buf[LINELEN+3];
if (entry->raw_value)
return 0;
len = strlen (entry->value);
offset = 0;
while (len)
{
size_t amount, linelen = LINELEN;
/* On the first line we need to subtract space for the name. */
if (entry->raw_value == NULL && strlen (entry->name) < linelen)
linelen -= strlen (entry->name);
/* See if the rest of the value fits in this line. */
if (len <= linelen)
amount = len;
else
{
size_t i;
/* Find a suitable space to break on. */
for (i = linelen - 1; linelen - i < 30 && linelen - i > offset; i--)
if (ascii_isspace (entry->value[i]))
break;
if (ascii_isspace (entry->value[i]))
{
/* Found one. */
amount = i;
}
else
{
/* Just induce a hard break. */
amount = linelen;
}
}
snprintf (buf, sizeof buf, " %.*s\n", (int) amount,
&entry->value[offset]);
if (append_to_strlist_try (&entry->raw_value, buf) == NULL)
{
err = my_error_from_syserror ();
goto leave;
}
offset += amount;
len -= amount;
}
leave:
if (err)
{
free_strlist_wipe (entry->raw_value);
entry->raw_value = NULL;
}
return err;
#undef LINELEN
}
/* Computes the length of the value encoded as continuation. If
*SWALLOW_WS is set, all whitespace at the beginning of S is
swallowed. If START is given, a pointer to the beginning of the
value is stored there. */
static size_t
continuation_length (const char *s, int *swallow_ws, const char **start)
{
size_t len;
if (*swallow_ws)
{
/* The previous line was a blank line and we inserted a newline.
Swallow all whitespace at the beginning of this line. */
while (ascii_isspace (*s))
s++;
}
else
{
/* Iff a continuation starts with more than one space, it
encodes a space. */
if (ascii_isspace (*s))
s++;
}
/* Strip whitespace at the end. */
len = strlen (s);
while (len > 0 && ascii_isspace (s[len-1]))
len--;
if (len == 0)
{
/* Blank lines encode newlines. */
len = 1;
s = "\n";
*swallow_ws = 1;
}
else
*swallow_ws = 0;
if (start)
*start = s;
return len;
}
/* Makes sure that ENTRY has a VALUE. */
static gpg_error_t
assert_value (nve_t entry)
{
size_t len;
int swallow_ws;
strlist_t s;
char *p;
if (entry->value)
return 0;
len = 0;
swallow_ws = 0;
for (s = entry->raw_value; s; s = s->next)
len += continuation_length (s->d, &swallow_ws, NULL);
/* Add one for the terminating zero. */
len += 1;
entry->value = p = xtrymalloc (len);
if (entry->value == NULL)
return my_error_from_syserror ();
swallow_ws = 0;
for (s = entry->raw_value; s; s = s->next)
{
const char *start;
size_t l = continuation_length (s->d, &swallow_ws, &start);
memcpy (p, start, l);
p += l;
}
*p++ = 0;
assert (p - entry->value == len);
return 0;
}
/* Get the name. */
char *
nve_name (nve_t pke)
{
return pke->name;
}
/* Get the value. */
char *
nve_value (nve_t pke)
{
if (assert_value (pke))
return NULL;
return pke->value;
}
/* Adding and modifying values. */
/* Add (NAME, VALUE, RAW_VALUE) to PK. NAME may be NULL for comments
and blank lines. At least one of VALUE and RAW_VALUE must be
given. If PRESERVE_ORDER is not given, entries with the same name
are grouped. NAME, VALUE and RAW_VALUE is consumed. */
static gpg_error_t
_nvc_add (nvc_t pk, char *name, char *value, strlist_t raw_value,
int preserve_order)
{
gpg_error_t err = 0;
nve_t e;
assert (value || raw_value);
if (name && ! valid_name (name))
{
err = my_error (GPG_ERR_INV_NAME);
goto leave;
}
if (name
&& pk->private_key_mode
&& !ascii_strcasecmp (name, "Key:")
&& nvc_lookup (pk, "Key:"))
{
err = my_error (GPG_ERR_INV_NAME);
goto leave;
}
e = xtrycalloc (1, sizeof *e);
if (e == NULL)
{
err = my_error_from_syserror ();
goto leave;
}
e->name = name;
e->value = value;
e->raw_value = raw_value;
if (pk->first)
{
nve_t last;
if (preserve_order || name == NULL)
last = pk->last;
else
{
/* See if there is already an entry with NAME. */
last = nvc_lookup (pk, name);
/* If so, find the last in that block. */
if (last)
{
while (last->next)
{
nve_t next = last->next;
if (next->name && ascii_strcasecmp (next->name, name) == 0)
last = next;
else
break;
}
}
else /* Otherwise, just find the last entry. */
last = pk->last;
}
if (last->next)
{
e->prev = last;
e->next = last->next;
last->next = e;
e->next->prev = e;
}
else
{
e->prev = last;
last->next = e;
pk->last = e;
}
}
else
pk->first = pk->last = e;
leave:
if (err)
{
xfree (name);
if (value)
wipememory (value, strlen (value));
xfree (value);
free_strlist_wipe (raw_value);
}
return err;
}
/* Add (NAME, VALUE) to PK. If an entry with NAME already exists, it
is not updated but the new entry is appended. */
gpg_error_t
nvc_add (nvc_t pk, const char *name, const char *value)
{
char *k, *v;
k = xtrystrdup (name);
if (k == NULL)
return my_error_from_syserror ();
v = xtrystrdup (value);
if (v == NULL)
{
xfree (k);
return my_error_from_syserror ();
}
return _nvc_add (pk, k, v, NULL, 0);
}
/* Add (NAME, VALUE) to PK. If an entry with NAME already exists, it
is updated with VALUE. If multiple entries with NAME exist, the
first entry is updated. */
gpg_error_t
nvc_set (nvc_t pk, const char *name, const char *value)
{
nve_t e;
if (! valid_name (name))
return GPG_ERR_INV_NAME;
e = nvc_lookup (pk, name);
if (e)
{
char *v;
v = xtrystrdup (value);
if (v == NULL)
return my_error_from_syserror ();
free_strlist_wipe (e->raw_value);
e->raw_value = NULL;
if (e->value)
wipememory (e->value, strlen (e->value));
xfree (e->value);
e->value = v;
return 0;
}
else
return nvc_add (pk, name, value);
}
/* Delete the given entry from PK. */
void
nvc_delete (nvc_t pk, nve_t entry)
{
if (entry->prev)
entry->prev->next = entry->next;
else
pk->first = entry->next;
if (entry->next)
entry->next->prev = entry->prev;
else
pk->last = entry->prev;
nve_release (entry, pk->private_key_mode);
}
+
+/* Delete the entries with NAME from PK. */
+void
+nvc_delete_named (nvc_t pk, const char *name)
+{
+ nve_t e;
+
+ if (!valid_name (name))
+ return;
+
+ while ((e = nvc_lookup (pk, name)))
+ nvc_delete (pk, e);
+}
+
+
/* Lookup and iteration. */
/* Get the first non-comment entry. */
nve_t
nvc_first (nvc_t pk)
{
nve_t entry;
for (entry = pk->first; entry; entry = entry->next)
if (entry->name)
return entry;
return NULL;
}
/* Get the first entry with the given name. */
nve_t
nvc_lookup (nvc_t pk, const char *name)
{
nve_t entry;
for (entry = pk->first; entry; entry = entry->next)
if (entry->name && ascii_strcasecmp (entry->name, name) == 0)
return entry;
return NULL;
}
/* Get the next non-comment entry. */
nve_t
nve_next (nve_t entry)
{
for (entry = entry->next; entry; entry = entry->next)
if (entry->name)
return entry;
return NULL;
}
/* Get the next entry with the given name. */
nve_t
nve_next_value (nve_t entry, const char *name)
{
for (entry = entry->next; entry; entry = entry->next)
if (entry->name && ascii_strcasecmp (entry->name, name) == 0)
return entry;
return NULL;
}
+
+/* Return the string for the first entry in NVC with NAME. If an
+ * entry with NAME is missing in NVC or its value is the empty string
+ * NULL is returned. Note that the The returned string is a pointer
+ * into NVC. */
+const char *
+nvc_get_string (nvc_t nvc, const char *name)
+{
+ nve_t item;
+
+ if (!nvc)
+ return NULL;
+ item = nvc_lookup (nvc, name);
+ if (!item)
+ return NULL;
+ return nve_value (item);
+}
+
+
/* Private key handling. */
/* Get the private key. */
gpg_error_t
nvc_get_private_key (nvc_t pk, gcry_sexp_t *retsexp)
{
gpg_error_t err;
nve_t e;
e = pk->private_key_mode? nvc_lookup (pk, "Key:") : NULL;
if (e == NULL)
return my_error (GPG_ERR_MISSING_KEY);
err = assert_value (e);
if (err)
return err;
return gcry_sexp_sscan (retsexp, NULL, e->value, strlen (e->value));
}
/* Set the private key. */
gpg_error_t
nvc_set_private_key (nvc_t pk, gcry_sexp_t sexp)
{
gpg_error_t err;
char *raw, *clean, *p;
size_t len, i;
if (!pk->private_key_mode)
return my_error (GPG_ERR_MISSING_KEY);
len = gcry_sexp_sprint (sexp, GCRYSEXP_FMT_ADVANCED, NULL, 0);
raw = xtrymalloc (len);
if (raw == NULL)
return my_error_from_syserror ();
clean = xtrymalloc (len);
if (clean == NULL)
{
xfree (raw);
return my_error_from_syserror ();
}
gcry_sexp_sprint (sexp, GCRYSEXP_FMT_ADVANCED, raw, len);
/* Strip any whitespace at the end. */
i = strlen (raw) - 1;
while (i && ascii_isspace (raw[i]))
{
raw[i] = 0;
i--;
}
/* Replace any newlines with spaces, remove superfluous whitespace. */
len = strlen (raw);
for (p = clean, i = 0; i < len; i++)
{
char c = raw[i];
/* Collapse contiguous and superfluous spaces. */
if (ascii_isspace (c) && i > 0
&& (ascii_isspace (raw[i-1]) || raw[i-1] == '(' || raw[i-1] == ')'))
continue;
if (c == '\n')
c = ' ';
*p++ = c;
}
*p = 0;
err = nvc_set (pk, "Key:", clean);
xfree (raw);
xfree (clean);
return err;
}
/* Parsing and serialization. */
static gpg_error_t
do_nvc_parse (nvc_t *result, int *errlinep, estream_t stream,
int for_private_key)
{
gpg_error_t err = 0;
gpgrt_ssize_t len;
char *buf = NULL;
size_t buf_len = 0;
char *name = NULL;
strlist_t raw_value = NULL;
*result = for_private_key? nvc_new_private_key () : nvc_new ();
if (*result == NULL)
return my_error_from_syserror ();
if (errlinep)
*errlinep = 0;
while ((len = es_read_line (stream, &buf, &buf_len, NULL)) > 0)
{
char *p;
if (errlinep)
*errlinep += 1;
/* Skip any whitespace. */
for (p = buf; *p && ascii_isspace (*p); p++)
/* Do nothing. */;
if (name && (spacep (buf) || *p == 0))
{
/* A continuation. */
if (append_to_strlist_try (&raw_value, buf) == NULL)
{
err = my_error_from_syserror ();
goto leave;
}
continue;
}
/* No continuation. Add the current entry if any. */
if (raw_value)
{
err = _nvc_add (*result, name, NULL, raw_value, 1);
if (err)
goto leave;
}
/* And prepare for the next one. */
name = NULL;
raw_value = NULL;
if (*p != 0 && *p != '#')
{
char *colon, *value, tmp;
colon = strchr (buf, ':');
if (colon == NULL)
{
err = my_error (GPG_ERR_INV_VALUE);
goto leave;
}
value = colon + 1;
tmp = *value;
*value = 0;
name = xtrystrdup (p);
*value = tmp;
if (name == NULL)
{
err = my_error_from_syserror ();
goto leave;
}
if (append_to_strlist_try (&raw_value, value) == NULL)
{
err = my_error_from_syserror ();
goto leave;
}
continue;
}
if (append_to_strlist_try (&raw_value, buf) == NULL)
{
err = my_error_from_syserror ();
goto leave;
}
}
if (len < 0)
{
err = gpg_error_from_syserror ();
goto leave;
}
/* Add the final entry. */
if (raw_value)
err = _nvc_add (*result, name, NULL, raw_value, 1);
leave:
gpgrt_free (buf);
if (err)
{
nvc_release (*result);
*result = NULL;
}
return err;
}
/* Parse STREAM and return a newly allocated name value container
structure in RESULT. If ERRLINEP is given, the line number the
parser was last considering is stored there. */
gpg_error_t
nvc_parse (nvc_t *result, int *errlinep, estream_t stream)
{
return do_nvc_parse (result, errlinep, stream, 0);
}
/* Parse STREAM and return a newly allocated name value container
structure in RESULT - assuming the extended private key format. If
ERRLINEP is given, the line number the parser was last considering
is stored there. */
gpg_error_t
nvc_parse_private_key (nvc_t *result, int *errlinep, estream_t stream)
{
return do_nvc_parse (result, errlinep, stream, 1);
}
+/* Helper fpr nvc_write. */
+static gpg_error_t
+write_one_entry (nve_t entry, estream_t stream)
+{
+ gpg_error_t err;
+ strlist_t sl;
+
+ if (entry->name)
+ es_fputs (entry->name, stream);
+
+ err = assert_raw_value (entry);
+ if (err)
+ return err;
+
+ for (sl = entry->raw_value; sl; sl = sl->next)
+ es_fputs (sl->d, stream);
+
+ if (es_ferror (stream))
+ return my_error_from_syserror ();
+
+ return 0;
+}
+
+
/* Write a representation of PK to STREAM. */
gpg_error_t
nvc_write (nvc_t pk, estream_t stream)
{
- gpg_error_t err;
+ gpg_error_t err = 0;
nve_t entry;
- strlist_t s;
+ nve_t keyentry = NULL;
for (entry = pk->first; entry; entry = entry->next)
{
- if (entry->name)
- es_fputs (entry->name, stream);
-
- err = assert_raw_value (entry);
+ if (pk->private_key_mode
+ && entry->name && !ascii_strcasecmp (entry->name, "Key:"))
+ {
+ if (!keyentry)
+ keyentry = entry;
+ continue;
+ }
+
+ err = write_one_entry (entry, stream);
if (err)
return err;
-
- for (s = entry->raw_value; s; s = s->next)
- es_fputs (s->d, stream);
-
- if (es_ferror (stream))
- return my_error_from_syserror ();
}
- return 0;
+ /* In private key mode we write the Key always last. */
+ if (keyentry)
+ err = write_one_entry (keyentry, stream);
+
+ return err;
}
diff --git a/common/name-value.h b/common/name-value.h
index 5c24b8db1..a6283a649 100644
--- a/common/name-value.h
+++ b/common/name-value.h
@@ -1,120 +1,126 @@
/* name-value.h - Parser and writer for a name-value format.
* Copyright (C) 2016 g10 Code GmbH
*
* This file is part of GnuPG.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of either
*
* - the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 3 of the License, or (at
* your option) any later version.
*
* or
*
* - 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.
*
* or both in parallel, as here.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#ifndef GNUPG_COMMON_NAME_VALUE_H
#define GNUPG_COMMON_NAME_VALUE_H
struct name_value_container;
typedef struct name_value_container *nvc_t;
struct name_value_entry;
typedef struct name_value_entry *nve_t;
/* Memory management, and dealing with entries. */
/* Allocate a name value container structure. */
nvc_t nvc_new (void);
/* Allocate a name value container structure for use with the extended
* private key format. */
nvc_t nvc_new_private_key (void);
/* Release a name value container structure. */
void nvc_release (nvc_t pk);
/* Get the name. */
char *nve_name (nve_t pke);
/* Get the value. */
char *nve_value (nve_t pke);
/* Lookup and iteration. */
/* Get the first non-comment entry. */
nve_t nvc_first (nvc_t pk);
/* Get the first entry with the given name. */
nve_t nvc_lookup (nvc_t pk, const char *name);
/* Get the next non-comment entry. */
nve_t nve_next (nve_t entry);
/* Get the next entry with the given name. */
nve_t nve_next_value (nve_t entry, const char *name);
+/* Return the string for the first entry in NVC with NAME or NULL. */
+const char *nvc_get_string (nvc_t nvc, const char *name);
+
/* Adding and modifying values. */
/* Add (NAME, VALUE) to PK. If an entry with NAME already exists, it
is not updated but the new entry is appended. */
gpg_error_t nvc_add (nvc_t pk, const char *name, const char *value);
/* Add (NAME, VALUE) to PK. If an entry with NAME already exists, it
is updated with VALUE. If multiple entries with NAME exist, the
first entry is updated. */
gpg_error_t nvc_set (nvc_t pk, const char *name, const char *value);
/* Delete the given entry from PK. */
void nvc_delete (nvc_t pk, nve_t pke);
+/* Delete the entries with NAME from PK. */
+void nvc_delete_named (nvc_t pk, const char *name);
+
/* Private key handling. */
/* Get the private key. */
gpg_error_t nvc_get_private_key (nvc_t pk, gcry_sexp_t *retsexp);
/* Set the private key. */
gpg_error_t nvc_set_private_key (nvc_t pk, gcry_sexp_t sexp);
/* Parsing and serialization. */
/* Parse STREAM and return a newly allocated name-value container
structure in RESULT. If ERRLINEP is given, the line number the
parser was last considering is stored there. */
gpg_error_t nvc_parse (nvc_t *result, int *errlinep, estream_t stream);
/* Parse STREAM and return a newly allocated name value container
structure in RESULT - assuming the extended private key format. If
ERRLINEP is given, the line number the parser was last considering
is stored there. */
gpg_error_t nvc_parse_private_key (nvc_t *result, int *errlinep,
estream_t stream);
/* Write a representation of PK to STREAM. */
gpg_error_t nvc_write (nvc_t pk, estream_t stream);
#endif /* GNUPG_COMMON_NAME_VALUE_H */
diff --git a/common/openpgp-s2k.c b/common/openpgp-s2k.c
index 2b0ba604b..69de76329 100644
--- a/common/openpgp-s2k.c
+++ b/common/openpgp-s2k.c
@@ -1,67 +1,67 @@
/* openpgp-s2ks.c - OpenPGP S2K helper functions
* Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004,
* 2005, 2006 Free Software Foundation, Inc.
* Copyright (C) 2010, 2019 g10 Code GmbH
*
* This file is part of GnuPG.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of either
*
* - the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 3 of the License, or (at
* your option) any later version.
*
* or
*
* - 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.
*
* or both in parallel, as here.
*
* This file is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdlib.h>
#include <errno.h>
#include <ctype.h>
#include <assert.h>
#include "util.h"
#include "openpgpdefs.h"
-/* Pack an s2k iteration count into the form specified in RFC-48800.
+/* Pack an s2k iteration count into the form specified in RFC-4880.
* If we're in between valid values, round up. */
unsigned char
encode_s2k_iterations (int iterations)
{
unsigned char c=0;
unsigned char result;
unsigned int count;
if (iterations <= 1024)
return 0; /* Command line arg compatibility. */
if (iterations >= 65011712)
return 255;
/* Need count to be in the range 16-31 */
for (count=iterations>>6; count>=32; count>>=1)
c++;
result = (c<<4)|(count-16);
if (S2K_DECODE_COUNT(result) < iterations)
result++;
return result;
}
diff --git a/common/sexputil.c b/common/sexputil.c
index d3020e169..f99bc3b18 100644
--- a/common/sexputil.c
+++ b/common/sexputil.c
@@ -1,637 +1,642 @@
/* sexputil.c - Utility functions for S-expressions.
* Copyright (C) 2005, 2007, 2009 Free Software Foundation, Inc.
* Copyright (C) 2013 Werner Koch
*
* This file is part of GnuPG.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of either
*
* - the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 3 of the License, or (at
* your option) any later version.
*
* or
*
* - 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.
*
* or both in parallel, as here.
*
* This file is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
/* This file implements a few utility functions useful when working
with canonical encrypted S-expressions (i.e. not the S-exprssion
objects from libgcrypt). */
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#ifdef HAVE_LOCALE_H
#include <locale.h>
#endif
#include "util.h"
#include "tlv.h"
#include "sexp-parse.h"
#include "openpgpdefs.h" /* for pubkey_algo_t */
/* Return a malloced string with the S-expression CANON in advanced
format. Returns NULL on error. */
static char *
sexp_to_string (gcry_sexp_t sexp)
{
size_t n;
char *result;
if (!sexp)
return NULL;
n = gcry_sexp_sprint (sexp, GCRYSEXP_FMT_ADVANCED, NULL, 0);
if (!n)
return NULL;
result = xtrymalloc (n);
if (!result)
return NULL;
n = gcry_sexp_sprint (sexp, GCRYSEXP_FMT_ADVANCED, result, n);
if (!n)
BUG ();
return result;
}
/* Return a malloced string with the S-expression CANON in advanced
format. Returns NULL on error. */
char *
canon_sexp_to_string (const unsigned char *canon, size_t canonlen)
{
size_t n;
gcry_sexp_t sexp;
char *result;
n = gcry_sexp_canon_len (canon, canonlen, NULL, NULL);
if (!n)
return NULL;
if (gcry_sexp_sscan (&sexp, NULL, canon, n))
return NULL;
result = sexp_to_string (sexp);
gcry_sexp_release (sexp);
return result;
}
/* Print the canonical encoded S-expression in SEXP in advanced
format. SEXPLEN may be passed as 0 is SEXP is known to be valid.
With TEXT of NULL print just the raw S-expression, with TEXT just
an empty string, print a trailing linefeed, otherwise print an
entire debug line. */
void
log_printcanon (const char *text, const unsigned char *sexp, size_t sexplen)
{
if (text && *text)
log_debug ("%s ", text);
if (sexp)
{
char *buf = canon_sexp_to_string (sexp, sexplen);
log_printf ("%s", buf? buf : "[invalid S-expression]");
xfree (buf);
}
if (text)
log_printf ("\n");
}
/* Print the gcryp S-expression in SEXP in advanced format. With TEXT
of NULL print just the raw S-expression, with TEXT just an empty
string, print a trailing linefeed, otherwise print an entire debug
line. */
void
log_printsexp (const char *text, gcry_sexp_t sexp)
{
if (text && *text)
log_debug ("%s ", text);
if (sexp)
{
char *buf = sexp_to_string (sexp);
log_printf ("%s", buf? buf : "[invalid S-expression]");
xfree (buf);
}
if (text)
log_printf ("\n");
}
/* Helper function to create a canonical encoded S-expression from a
Libgcrypt S-expression object. The function returns 0 on success
and the malloced canonical S-expression is stored at R_BUFFER and
the allocated length at R_BUFLEN. On error an error code is
returned and (NULL, 0) stored at R_BUFFER and R_BUFLEN. If the
allocated buffer length is not required, NULL by be used for
R_BUFLEN. */
gpg_error_t
make_canon_sexp (gcry_sexp_t sexp, unsigned char **r_buffer, size_t *r_buflen)
{
size_t len;
unsigned char *buf;
*r_buffer = NULL;
if (r_buflen)
*r_buflen = 0;;
len = gcry_sexp_sprint (sexp, GCRYSEXP_FMT_CANON, NULL, 0);
if (!len)
return gpg_error (GPG_ERR_BUG);
buf = xtrymalloc (len);
if (!buf)
return gpg_error_from_syserror ();
len = gcry_sexp_sprint (sexp, GCRYSEXP_FMT_CANON, buf, len);
if (!len)
return gpg_error (GPG_ERR_BUG);
*r_buffer = buf;
if (r_buflen)
*r_buflen = len;
return 0;
}
/* Same as make_canon_sexp but pad the buffer to multiple of 64
bits. If SECURE is set, secure memory will be allocated. */
gpg_error_t
make_canon_sexp_pad (gcry_sexp_t sexp, int secure,
unsigned char **r_buffer, size_t *r_buflen)
{
size_t len;
unsigned char *buf;
*r_buffer = NULL;
if (r_buflen)
*r_buflen = 0;;
len = gcry_sexp_sprint (sexp, GCRYSEXP_FMT_CANON, NULL, 0);
if (!len)
return gpg_error (GPG_ERR_BUG);
len += (8 - len % 8) % 8;
buf = secure? xtrycalloc_secure (1, len) : xtrycalloc (1, len);
if (!buf)
return gpg_error_from_syserror ();
if (!gcry_sexp_sprint (sexp, GCRYSEXP_FMT_CANON, buf, len))
return gpg_error (GPG_ERR_BUG);
*r_buffer = buf;
if (r_buflen)
*r_buflen = len;
return 0;
}
/* Return the so called "keygrip" which is the SHA-1 hash of the
public key parameters expressed in a way depended on the algorithm.
KEY is expected to be an canonical encoded S-expression with a
public or private key. KEYLEN is the length of that buffer.
GRIP must be at least 20 bytes long. On success 0 is returned, on
error an error code. */
gpg_error_t
keygrip_from_canon_sexp (const unsigned char *key, size_t keylen,
unsigned char *grip)
{
gpg_error_t err;
gcry_sexp_t sexp;
if (!grip)
return gpg_error (GPG_ERR_INV_VALUE);
err = gcry_sexp_sscan (&sexp, NULL, (const char *)key, keylen);
if (err)
return err;
if (!gcry_pk_get_keygrip (sexp, grip))
err = gpg_error (GPG_ERR_INTERNAL);
gcry_sexp_release (sexp);
return err;
}
/* Compare two simple S-expressions like "(3:foo)". Returns 0 if they
are identical or !0 if they are not. Note that this function can't
be used for sorting. */
int
cmp_simple_canon_sexp (const unsigned char *a_orig,
const unsigned char *b_orig)
{
const char *a = (const char *)a_orig;
const char *b = (const char *)b_orig;
unsigned long n1, n2;
char *endp;
if (!a && !b)
return 0; /* Both are NULL, they are identical. */
if (!a || !b)
return 1; /* One is NULL, they are not identical. */
if (*a != '(' || *b != '(')
log_bug ("invalid S-exp in cmp_simple_canon_sexp\n");
a++;
n1 = strtoul (a, &endp, 10);
a = endp;
b++;
n2 = strtoul (b, &endp, 10);
b = endp;
if (*a != ':' || *b != ':' )
log_bug ("invalid S-exp in cmp_simple_canon_sexp\n");
if (n1 != n2)
return 1; /* Not the same. */
for (a++, b++; n1; n1--, a++, b++)
if (*a != *b)
return 1; /* Not the same. */
return 0;
}
/* Create a simple S-expression from the hex string at LINE. Returns
a newly allocated buffer with that canonical encoded S-expression
or NULL in case of an error. On return the number of characters
scanned in LINE will be stored at NSCANNED. This functions stops
converting at the first character not representing a hexdigit. Odd
numbers of hex digits are allowed; a leading zero is then
assumed. If no characters have been found, NULL is returned.*/
unsigned char *
make_simple_sexp_from_hexstr (const char *line, size_t *nscanned)
{
size_t n, len;
const char *s;
unsigned char *buf;
unsigned char *p;
char numbuf[50], *numbufp;
size_t numbuflen;
for (n=0, s=line; hexdigitp (s); s++, n++)
;
if (nscanned)
*nscanned = n;
if (!n)
return NULL;
len = ((n+1) & ~0x01)/2;
numbufp = smklen (numbuf, sizeof numbuf, len, &numbuflen);
buf = xtrymalloc (1 + numbuflen + len + 1 + 1);
if (!buf)
return NULL;
buf[0] = '(';
p = (unsigned char *)stpcpy ((char *)buf+1, numbufp);
s = line;
if ((n&1))
{
*p++ = xtoi_1 (s);
s++;
n--;
}
for (; n > 1; n -=2, s += 2)
*p++ = xtoi_2 (s);
*p++ = ')';
*p = 0; /* (Not really needed.) */
return buf;
}
/* Return the hash algorithm from a KSBA sig-val. SIGVAL is a
canonical encoded S-expression. Return 0 if the hash algorithm is
not encoded in SIG-VAL or it is not supported by libgcrypt. */
int
hash_algo_from_sigval (const unsigned char *sigval)
{
const unsigned char *s = sigval;
size_t n;
int depth;
char buffer[50];
if (!s || *s != '(')
return 0; /* Invalid S-expression. */
s++;
n = snext (&s);
if (!n)
return 0; /* Invalid S-expression. */
if (!smatch (&s, n, "sig-val"))
return 0; /* Not a sig-val. */
if (*s != '(')
return 0; /* Invalid S-expression. */
s++;
/* Skip over the algo+parameter list. */
depth = 1;
if (sskip (&s, &depth) || depth)
return 0; /* Invalid S-expression. */
if (*s != '(')
return 0; /* No further list. */
/* Check whether this is (hash ALGO). */
s++;
n = snext (&s);
if (!n)
return 0; /* Invalid S-expression. */
if (!smatch (&s, n, "hash"))
return 0; /* Not a "hash" keyword. */
n = snext (&s);
if (!n || n+1 >= sizeof (buffer))
return 0; /* Algorithm string is missing or too long. */
memcpy (buffer, s, n);
buffer[n] = 0;
return gcry_md_map_name (buffer);
}
/* Create a public key S-expression for an RSA public key from the
modulus M with length MLEN and the public exponent E with length
ELEN. Returns a newly allocated buffer of NULL in case of a memory
allocation problem. If R_LEN is not NULL, the length of the
canonical S-expression is stored there. */
unsigned char *
make_canon_sexp_from_rsa_pk (const void *m_arg, size_t mlen,
const void *e_arg, size_t elen,
size_t *r_len)
{
const unsigned char *m = m_arg;
const unsigned char *e = e_arg;
int m_extra = 0;
int e_extra = 0;
char mlen_str[35];
char elen_str[35];
unsigned char *keybuf, *p;
const char part1[] = "(10:public-key(3:rsa(1:n";
const char part2[] = ")(1:e";
const char part3[] = ")))";
/* Remove leading zeroes. */
for (; mlen && !*m; mlen--, m++)
;
for (; elen && !*e; elen--, e++)
;
/* Insert a leading zero if the number would be zero or interpreted
as negative. */
if (!mlen || (m[0] & 0x80))
m_extra = 1;
if (!elen || (e[0] & 0x80))
e_extra = 1;
/* Build the S-expression. */
snprintf (mlen_str, sizeof mlen_str, "%u:", (unsigned int)mlen+m_extra);
snprintf (elen_str, sizeof elen_str, "%u:", (unsigned int)elen+e_extra);
keybuf = xtrymalloc (strlen (part1) + strlen (mlen_str) + mlen + m_extra
+ strlen (part2) + strlen (elen_str) + elen + e_extra
+ strlen (part3) + 1);
if (!keybuf)
return NULL;
p = stpcpy (keybuf, part1);
p = stpcpy (p, mlen_str);
if (m_extra)
*p++ = 0;
memcpy (p, m, mlen);
p += mlen;
p = stpcpy (p, part2);
p = stpcpy (p, elen_str);
if (e_extra)
*p++ = 0;
memcpy (p, e, elen);
p += elen;
p = stpcpy (p, part3);
if (r_len)
*r_len = p - keybuf;
return keybuf;
}
/* Return the parameters of a public RSA key expressed as an
canonical encoded S-expression. */
gpg_error_t
get_rsa_pk_from_canon_sexp (const unsigned char *keydata, size_t keydatalen,
unsigned char const **r_n, size_t *r_nlen,
unsigned char const **r_e, size_t *r_elen)
{
gpg_error_t err;
const unsigned char *buf, *tok;
size_t buflen, toklen;
int depth, last_depth1, last_depth2;
const unsigned char *rsa_n = NULL;
const unsigned char *rsa_e = NULL;
size_t rsa_n_len, rsa_e_len;
*r_n = NULL;
*r_nlen = 0;
*r_e = NULL;
*r_elen = 0;
buf = keydata;
buflen = keydatalen;
depth = 0;
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
return err;
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
return err;
if (!tok || toklen != 10 || memcmp ("public-key", tok, toklen))
return gpg_error (GPG_ERR_BAD_PUBKEY);
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
return err;
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
return err;
if (!tok || toklen != 3 || memcmp ("rsa", tok, toklen))
return gpg_error (GPG_ERR_WRONG_PUBKEY_ALGO);
last_depth1 = depth;
while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))
&& depth && depth >= last_depth1)
{
if (tok)
return gpg_error (GPG_ERR_UNKNOWN_SEXP);
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
return err;
if (tok && toklen == 1)
{
const unsigned char **mpi;
size_t *mpi_len;
switch (*tok)
{
case 'n': mpi = &rsa_n; mpi_len = &rsa_n_len; break;
case 'e': mpi = &rsa_e; mpi_len = &rsa_e_len; break;
default: mpi = NULL; mpi_len = NULL; break;
}
if (mpi && *mpi)
return gpg_error (GPG_ERR_DUP_VALUE);
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
return err;
if (tok && mpi)
{
/* Strip off leading zero bytes and save. */
for (;toklen && !*tok; toklen--, tok++)
;
*mpi = tok;
*mpi_len = toklen;
}
}
/* Skip to the end of the list. */
last_depth2 = depth;
while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))
&& depth && depth >= last_depth2)
;
if (err)
return err;
}
if (err)
return err;
if (!rsa_n || !rsa_n_len || !rsa_e || !rsa_e_len)
return gpg_error (GPG_ERR_BAD_PUBKEY);
*r_n = rsa_n;
*r_nlen = rsa_n_len;
*r_e = rsa_e;
*r_elen = rsa_e_len;
return 0;
}
/* Return the algo of a public KEY of SEXP. */
int
get_pk_algo_from_key (gcry_sexp_t key)
{
gcry_sexp_t list;
const char *s;
size_t n;
char algoname[6];
int algo = 0;
list = gcry_sexp_nth (key, 1);
if (!list)
goto out;
s = gcry_sexp_nth_data (list, 0, &n);
if (!s)
goto out;
if (n >= sizeof (algoname))
goto out;
memcpy (algoname, s, n);
algoname[n] = 0;
algo = gcry_pk_map_name (algoname);
if (algo == GCRY_PK_ECC)
{
gcry_sexp_t l1 = gcry_sexp_find_token (list, "flags", 0);
int i;
for (i = l1 ? gcry_sexp_length (l1)-1 : 0; i > 0; i--)
{
s = gcry_sexp_nth_data (l1, i, &n);
if (!s)
continue; /* Not a data element. */
if (n == 5 && !memcmp (s, "eddsa", 5))
{
algo = GCRY_PK_EDDSA;
break;
}
}
gcry_sexp_release (l1);
}
out:
gcry_sexp_release (list);
return algo;
}
/* This is a variant of get_pk_algo_from_key but takes an canonical
* encoded S-expression as input. Returns a GCRYPT public key
* identiier or 0 on error. */
int
get_pk_algo_from_canon_sexp (const unsigned char *keydata, size_t keydatalen)
{
gcry_sexp_t sexp;
int algo;
if (gcry_sexp_sscan (&sexp, NULL, keydata, keydatalen))
return 0;
algo = get_pk_algo_from_key (sexp);
gcry_sexp_release (sexp);
return algo;
}
/* Given the public key S_PKEY, return a new buffer with a descriptive
* string for its algorithm. This function may return NULL on memory
- * error. */
+ * error. If R_ALGOID is not NULL the gcrypt algo id is stored there. */
char *
-pubkey_algo_string (gcry_sexp_t s_pkey)
+pubkey_algo_string (gcry_sexp_t s_pkey, enum gcry_pk_algos *r_algoid)
{
const char *prefix;
gcry_sexp_t l1;
char *algoname;
int algo;
char *result;
+ if (r_algoid)
+ *r_algoid = 0;
+
l1 = gcry_sexp_find_token (s_pkey, "public-key", 0);
if (!l1)
return xtrystrdup ("E_no_key");
{
gcry_sexp_t l_tmp = gcry_sexp_cadr (l1);
gcry_sexp_release (l1);
l1 = l_tmp;
}
algoname = gcry_sexp_nth_string (l1, 0);
gcry_sexp_release (l1);
if (!algoname)
return xtrystrdup ("E_no_algo");
algo = gcry_pk_map_name (algoname);
switch (algo)
{
case GCRY_PK_RSA: prefix = "rsa"; break;
case GCRY_PK_ELG: prefix = "elg"; break;
case GCRY_PK_DSA: prefix = "dsa"; break;
case GCRY_PK_ECC: prefix = ""; break;
default: prefix = NULL; break;
}
if (prefix && *prefix)
result = xtryasprintf ("%s%u", prefix, gcry_pk_get_nbits (s_pkey));
else if (prefix)
{
const char *curve = gcry_pk_get_curve (s_pkey, 0, NULL);
const char *name = openpgp_oid_to_curve
(openpgp_curve_to_oid (curve, NULL), 0);
if (name)
result = xtrystrdup (name);
else if (curve)
result = xtryasprintf ("X_%s", curve);
else
result = xtrystrdup ("E_unknown");
}
else
result = xtryasprintf ("X_algo_%d", algo);
+ if (r_algoid)
+ *r_algoid = algo;
xfree (algoname);
return result;
}
diff --git a/common/t-name-value.c b/common/t-name-value.c
index 57f685ffb..13a383ddb 100644
--- a/common/t-name-value.c
+++ b/common/t-name-value.c
@@ -1,593 +1,618 @@
/* t-name-value.c - Module test for name-value.c
* Copyright (C) 2016 g10 Code GmbH
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <assert.h>
#include <unistd.h>
#include <sys/stat.h>
#include "util.h"
#include "name-value.h"
static int verbose;
static int private_key_mode;
static nvc_t
my_nvc_new (void)
{
if (private_key_mode)
return nvc_new_private_key ();
else
return nvc_new ();
}
void
test_getting_values (nvc_t pk)
{
nve_t e;
e = nvc_lookup (pk, "Comment:");
assert (e);
/* Names are case-insensitive. */
e = nvc_lookup (pk, "comment:");
assert (e);
e = nvc_lookup (pk, "COMMENT:");
assert (e);
e = nvc_lookup (pk, "SomeOtherName:");
assert (e);
}
void
test_key_extraction (nvc_t pk)
{
gpg_error_t err;
gcry_sexp_t key;
if (private_key_mode)
{
err = nvc_get_private_key (pk, &key);
assert (err == 0);
assert (key);
if (verbose)
gcry_sexp_dump (key);
gcry_sexp_release (key);
}
else
{
err = nvc_get_private_key (pk, &key);
assert (gpg_err_code (err) == GPG_ERR_MISSING_KEY);
}
}
void
test_iteration (nvc_t pk)
{
int i;
nve_t e;
i = 0;
for (e = nvc_first (pk); e; e = nve_next (e))
i++;
assert (i == 4);
i = 0;
for (e = nvc_lookup (pk, "Comment:");
e;
e = nve_next_value (e, "Comment:"))
i++;
assert (i == 3);
}
void
test_whitespace (nvc_t pk)
{
nve_t e;
e = nvc_lookup (pk, "One:");
assert (e);
assert (strcmp (nve_value (e), "WithoutWhitespace") == 0);
e = nvc_lookup (pk, "Two:");
assert (e);
assert (strcmp (nve_value (e), "With Whitespace") == 0);
e = nvc_lookup (pk, "Three:");
assert (e);
assert (strcmp (nve_value (e),
"Blank lines in continuations encode newlines.\n"
"Next paragraph.") == 0);
}
struct
{
char *value;
void (*test_func) (nvc_t);
} tests[] =
{
{
"# This is a comment followed by an empty line\n"
"\n",
NULL,
},
{
"# This is a comment followed by two empty lines, Windows style\r\n"
"\r\n"
"\r\n",
NULL,
},
{
"# Some name,value pairs\n"
"Comment: Some comment.\n"
"SomeOtherName: Some value.\n",
test_getting_values,
},
{
" # Whitespace is preserved as much as possible\r\n"
"Comment:Some comment.\n"
"SomeOtherName: Some value. \n",
test_getting_values,
},
{
"# Values may be continued in the next line as indicated by leading\n"
"# space\n"
"Comment: Some rather long\n"
" comment that is continued in the next line.\n"
"\n"
" Blank lines with or without whitespace are allowed within\n"
" continuations to allow paragraphs.\n"
"SomeOtherName: Some value.\n",
test_getting_values,
},
{
"# Names may be given multiple times forming an array of values\n"
"Comment: Some comment, element 0.\n"
"Comment: Some comment, element 1.\n"
"Comment: Some comment, element 2.\n"
"SomeOtherName: Some value.\n",
test_iteration,
},
{
"# One whitespace at the beginning of a continuation is swallowed.\n"
"One: Without\n"
" Whitespace\n"
"Two: With\n"
" Whitespace\n"
"Three: Blank lines in continuations encode newlines.\n"
"\n"
" Next paragraph.\n",
test_whitespace,
},
{
"Description: Key to sign all GnuPG released tarballs.\n"
" The key is actually stored on a smart card.\n"
"Use-for-ssh: yes\n"
"OpenSSH-cert: long base64 encoded string wrapped so that this\n"
" key file can be easily edited with a standard editor.\n"
"Key: (shadowed-private-key\n"
" (rsa\n"
" (n #00AA1AD2A55FD8C8FDE9E1941772D9CC903FA43B268CB1B5A1BAFDC900\n"
" 2961D8AEA153424DC851EF13B83AC64FBE365C59DC1BD3E83017C90D4365B4\n"
" 83E02859FC13DB5842A00E969480DB96CE6F7D1C03600392B8E08EF0C01FC7\n"
" 19F9F9086B25AD39B4F1C2A2DF3E2BE317110CFFF21D4A11455508FE407997\n"
" 601260816C8422297C0637BB291C3A079B9CB38A92CE9E551F80AA0EBF4F0E\n"
" 72C3F250461E4D31F23A7087857FC8438324A013634563D34EFDDCBF2EA80D\n"
" F9662C9CCD4BEF2522D8BDFED24CEF78DC6B309317407EAC576D889F88ADA0\n"
" 8C4FFB480981FB68C5C6CA27503381D41018E6CDC52AAAE46B166BDC10637A\n"
" E186A02BA2497FDC5D1221#)\n"
" (e #00010001#)\n"
" (shadowed t1-v1\n"
" (#D2760001240102000005000011730000# OPENPGP.1)\n"
" )))\n",
test_key_extraction,
},
};
static char *
nvc_to_string (nvc_t pk)
{
gpg_error_t err;
char *buf;
size_t len;
estream_t sink;
sink = es_fopenmem (0, "rw");
assert (sink);
err = nvc_write (pk, sink);
assert (err == 0);
len = es_ftell (sink);
buf = xmalloc (len+1);
assert (buf);
es_fseek (sink, 0, SEEK_SET);
es_read (sink, buf, len, NULL);
buf[len] = 0;
es_fclose (sink);
return buf;
}
void dummy_free (void *p) { (void) p; }
void *dummy_realloc (void *p, size_t s) { (void) s; return p; }
void
run_tests (void)
{
gpg_error_t err;
nvc_t pk;
int i;
for (i = 0; i < DIM (tests); i++)
{
estream_t source;
char *buf;
size_t len;
len = strlen (tests[i].value);
source = es_mopen (tests[i].value, len, len,
0, dummy_realloc, dummy_free, "r");
assert (source);
if (private_key_mode)
err = nvc_parse_private_key (&pk, NULL, source);
else
err = nvc_parse (&pk, NULL, source);
assert (err == 0);
assert (pk);
if (verbose)
{
err = nvc_write (pk, es_stderr);
assert (err == 0);
}
buf = nvc_to_string (pk);
assert (memcmp (tests[i].value, buf, len) == 0);
es_fclose (source);
xfree (buf);
if (tests[i].test_func)
tests[i].test_func (pk);
nvc_release (pk);
}
}
void
run_modification_tests (void)
{
gpg_error_t err;
nvc_t pk;
+ nve_t e;
gcry_sexp_t key;
char *buf;
pk = my_nvc_new ();
assert (pk);
nvc_set (pk, "Foo:", "Bar");
buf = nvc_to_string (pk);
assert (strcmp (buf, "Foo: Bar\n") == 0);
xfree (buf);
nvc_set (pk, "Foo:", "Baz");
buf = nvc_to_string (pk);
assert (strcmp (buf, "Foo: Baz\n") == 0);
xfree (buf);
nvc_set (pk, "Bar:", "Bazzel");
buf = nvc_to_string (pk);
assert (strcmp (buf, "Foo: Baz\nBar: Bazzel\n") == 0);
xfree (buf);
nvc_add (pk, "Foo:", "Bar");
buf = nvc_to_string (pk);
assert (strcmp (buf, "Foo: Baz\nFoo: Bar\nBar: Bazzel\n") == 0);
xfree (buf);
nvc_add (pk, "DontExistYet:", "Bar");
buf = nvc_to_string (pk);
assert (strcmp (buf, "Foo: Baz\nFoo: Bar\nBar: Bazzel\nDontExistYet: Bar\n")
== 0);
xfree (buf);
nvc_delete (pk, nvc_lookup (pk, "DontExistYet:"));
buf = nvc_to_string (pk);
assert (strcmp (buf, "Foo: Baz\nFoo: Bar\nBar: Bazzel\n") == 0);
xfree (buf);
nvc_delete (pk, nve_next_value (nvc_lookup (pk, "Foo:"), "Foo:"));
buf = nvc_to_string (pk);
assert (strcmp (buf, "Foo: Baz\nBar: Bazzel\n") == 0);
xfree (buf);
nvc_delete (pk, nvc_lookup (pk, "Foo:"));
buf = nvc_to_string (pk);
assert (strcmp (buf, "Bar: Bazzel\n") == 0);
xfree (buf);
nvc_delete (pk, nvc_first (pk));
buf = nvc_to_string (pk);
assert (strcmp (buf, "") == 0);
xfree (buf);
+ /* Test whether we can delete an entry by name. */
+ err = nvc_add (pk, "Key:", "(3:foo)");
+ assert (!err);
+ e = nvc_lookup (pk, "Key:");
+ assert (e);
+ nvc_delete_named (pk, "Kez:"); /* Delete an inexistant name. */
+ e = nvc_lookup (pk, "Key:");
+ assert (e);
+ nvc_delete_named (pk, "Key:");
+ e = nvc_lookup (pk, "Key:");
+ assert (!e);
+
+ /* Ditto but now whether it deletes all entries with that name. We
+ * don't use "Key" because that name is special in private key mode. */
+ err = nvc_add (pk, "AKey:", "A-value");
+ assert (!err);
+ err = nvc_add (pk, "AKey:", "B-value");
+ assert (!err);
+ e = nvc_lookup (pk, "AKey:");
+ assert (e);
+ nvc_delete_named (pk, "AKey:");
+ e = nvc_lookup (pk, "AKey:");
+ assert (!e);
+
nvc_set (pk, "Foo:", "A really long value spanning across multiple lines"
" that has to be wrapped at a convenient space.");
buf = nvc_to_string (pk);
assert (strcmp (buf, "Foo: A really long value spanning across multiple"
" lines that has to be\n wrapped at a convenient space.\n")
== 0);
xfree (buf);
nvc_set (pk, "Foo:", "XA really long value spanning across multiple lines"
" that has to be wrapped at a convenient space.");
buf = nvc_to_string (pk);
assert (strcmp (buf, "Foo: XA really long value spanning across multiple"
" lines that has to\n be wrapped at a convenient space.\n")
== 0);
xfree (buf);
nvc_set (pk, "Foo:", "XXXXA really long value spanning across multiple lines"
" that has to be wrapped at a convenient space.");
buf = nvc_to_string (pk);
assert (strcmp (buf, "Foo: XXXXA really long value spanning across multiple"
" lines that has\n to be wrapped at a convenient space.\n")
== 0);
xfree (buf);
nvc_set (pk, "Foo:", "Areallylongvaluespanningacrossmultiplelines"
"thathastobewrappedataconvenientspacethatisnotthere.");
buf = nvc_to_string (pk);
assert (strcmp (buf, "Foo: Areallylongvaluespanningacrossmultiplelinesthat"
"hastobewrappedataco\n nvenientspacethatisnotthere.\n")
== 0);
xfree (buf);
nvc_release (pk);
pk = my_nvc_new ();
assert (pk);
err = gcry_sexp_build (&key, NULL, "(hello world)");
assert (err == 0);
assert (key);
if (private_key_mode)
{
err = nvc_set_private_key (pk, key);
assert (err == 0);
buf = nvc_to_string (pk);
assert (strcmp (buf, "Key: (hello world)\n") == 0);
xfree (buf);
}
else
{
err = nvc_set_private_key (pk, key);
assert (gpg_err_code (err) == GPG_ERR_MISSING_KEY);
}
gcry_sexp_release (key);
nvc_release (pk);
}
void
convert (const char *fname)
{
gpg_error_t err;
estream_t source;
gcry_sexp_t key;
char *buf;
size_t buflen;
struct stat st;
nvc_t pk;
source = es_fopen (fname, "rb");
if (source == NULL)
goto leave;
if (fstat (es_fileno (source), &st))
goto leave;
buflen = st.st_size;
buf = xtrymalloc (buflen+1);
assert (buf);
if (es_fread (buf, buflen, 1, source) != 1)
goto leave;
err = gcry_sexp_sscan (&key, NULL, buf, buflen);
if (err)
{
fprintf (stderr, "malformed s-expression in %s\n", fname);
exit (1);
}
pk = my_nvc_new ();
assert (pk);
err = nvc_set_private_key (pk, key);
assert (err == 0);
err = nvc_write (pk, es_stdout);
assert (err == 0);
return;
leave:
perror (fname);
exit (1);
}
void
parse (const char *fname)
{
gpg_error_t err;
estream_t source;
char *buf;
nvc_t pk_a, pk_b;
nve_t e;
int line;
source = es_fopen (fname, "rb");
if (source == NULL)
{
perror (fname);
exit (1);
}
if (private_key_mode)
err = nvc_parse_private_key (&pk_a, &line, source);
else
err = nvc_parse (&pk_a, &line, source);
if (err)
{
fprintf (stderr, "failed to parse %s line %d: %s\n",
fname, line, gpg_strerror (err));
exit (1);
}
buf = nvc_to_string (pk_a);
xfree (buf);
pk_b = my_nvc_new ();
assert (pk_b);
for (e = nvc_first (pk_a); e; e = nve_next (e))
{
gcry_sexp_t key = NULL;
if (private_key_mode && !strcasecmp (nve_name (e), "Key:"))
{
err = nvc_get_private_key (pk_a, &key);
if (err)
key = NULL;
}
if (key)
{
err = nvc_set_private_key (pk_b, key);
assert (err == 0);
}
else
{
err = nvc_add (pk_b, nve_name (e), nve_value (e));
assert (err == 0);
}
}
buf = nvc_to_string (pk_b);
if (verbose)
fprintf (stdout, "%s", buf);
xfree (buf);
}
void
print_usage (void)
{
fprintf (stderr,
"usage: t-private-keys [--verbose]"
" [--convert <private-key-file>"
" || --parse-key <extended-private-key-file>"
" || --parse <file> ]\n");
exit (2);
}
int
main (int argc, char **argv)
{
enum { TEST, CONVERT, PARSE, PARSEKEY } command = TEST;
if (argc)
{ argc--; argv++; }
if (argc && !strcmp (argv[0], "--verbose"))
{
verbose = 1;
argc--; argv++;
}
if (argc && !strcmp (argv[0], "--convert"))
{
command = CONVERT;
argc--; argv++;
if (argc != 1)
print_usage ();
}
if (argc && !strcmp (argv[0], "--parse-key"))
{
command = PARSEKEY;
argc--; argv++;
if (argc != 1)
print_usage ();
}
if (argc && !strcmp (argv[0], "--parse"))
{
command = PARSE;
argc--; argv++;
if (argc != 1)
print_usage ();
}
switch (command)
{
case TEST:
run_tests ();
run_modification_tests ();
private_key_mode = 1;
run_tests ();
run_modification_tests ();
break;
case CONVERT:
convert (*argv);
break;
case PARSEKEY:
private_key_mode = 1;
parse (*argv);
break;
case PARSE:
parse (*argv);
break;
}
return 0;
}
diff --git a/common/userids.c b/common/userids.c
index 181b48866..55bd85546 100644
--- a/common/userids.c
+++ b/common/userids.c
@@ -1,474 +1,475 @@
/* userids.c - Utility functions for user ids.
* Copyright (C) 2001, 2003, 2004, 2006,
* 2009 Free Software Foundation, Inc.
* Copyright (C) 2015 g10 Code GmbH
*
* This file is part of GnuPG.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of either
*
* - the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 3 of the License, or (at
* your option) any later version.
*
* or
*
* - 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.
*
* or both in parallel, as here.
*
* This file is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "util.h"
#include "userids.h"
/* Parse the user-id NAME and build a search description for it.
* Returns 0 on success or an error code. DESC may be NULL to merely
* check the validity of a user-id.
*
* Some used rules:
* - If the username starts with 8,9,16 or 17 hex-digits (the first one
* must be in the range 0..9), this is considered a keyid; depending
* on the length a short or complete one.
* - If the username starts with 32,33,40 or 41 hex-digits (the first one
* must be in the range 0..9), this is considered a fingerprint.
* - If the username starts with a left angle, we assume it is a complete
* email address and look only at this part.
* - If the username starts with a colon we assume it is a unified
* key specfification.
* - If the username starts with a '.', we assume it is the ending
* part of an email address
* - If the username starts with an '@', we assume it is a part of an
* email address
* - If the userid start with an '=' an exact compare is done.
* - If the userid starts with a '*' a case insensitive substring search is
* done (This is the default).
* - If the userid starts with a '+' we will compare individual words
* and a match requires that all the words are in the userid.
* Words are delimited by white space or "()<>[]{}.@-+_,;/&!"
* (note that you can't search for these characters). Compare
* is not case sensitive.
* - If the userid starts with a '&' a 40 hex digits keygrip is expected.
*/
gpg_error_t
classify_user_id (const char *name, KEYDB_SEARCH_DESC *desc, int openpgp_hack)
{
const char *s;
char *s2 = NULL;
int rc = 0;
int hexprefix = 0;
int hexlength;
int mode = 0;
KEYDB_SEARCH_DESC dummy_desc;
if (!desc)
desc = &dummy_desc;
/* 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);
/* Skip leading and trailing spaces. */
for(s = name; *s && spacep (s); s++ )
;
if (*s && spacep (s + strlen(s) - 1))
{
s2 = xtrystrdup (s);
if (!s2)
{
rc = gpg_error_from_syserror ();
goto out;
}
trim_trailing_spaces (s2);
s = s2;
}
switch (*s)
{
case 0: /* Empty string is an error. */
rc = gpg_error (GPG_ERR_INV_USER_ID);
goto out;
case '.': /* An email address, compare from end. Note that this
has not yet been implemented in the search code. */
mode = KEYDB_SEARCH_MODE_MAILEND;
s++;
desc->u.name = s;
break;
case '<': /* An email address. */
mode = KEYDB_SEARCH_MODE_MAIL;
/* FIXME: The keyring code in g10 assumes that the mail name is
prefixed with an '<'. However the keybox code used for sm/
assumes it has been removed. For now we use this simple hack
to overcome the problem. */
if (!openpgp_hack)
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. Note that this has not
yet been implemented in the search code. */
mode = KEYDB_SEARCH_MODE_WORDS;
s++;
desc->u.name = s;
break;
case '/': /* Subject's DN. */
s++;
if (!*s || spacep (s)) /* No DN or prefixed with a space. */
{
rc = gpg_error (GPG_ERR_INV_USER_ID);
goto out;
}
desc->u.name = s;
mode = KEYDB_SEARCH_MODE_SUBJECT;
break;
case '#': /* S/N with optional issuer id or just issuer id. */
{
const char *si;
s++;
if ( *s == '/')
{ /* "#/" indicates an issuer's DN. */
s++;
if (!*s || spacep (s)) /* No DN or prefixed with a space. */
{
rc = gpg_error (GPG_ERR_INV_USER_ID);
goto out;
}
desc->u.name = s;
mode = KEYDB_SEARCH_MODE_ISSUER;
}
else
{ /* Serialnumber + optional issuer ID. */
for (si=s; *si && *si != '/'; si++)
{
/* Check for an invalid digit in the serial number. */
if (!strchr("01234567890abcdefABCDEF", *si))
{
rc = gpg_error (GPG_ERR_INV_USER_ID);
goto out;
}
}
desc->sn = (const unsigned char*)s;
desc->snlen = -1;
if (!*si)
mode = KEYDB_SEARCH_MODE_SN;
else
{
s = si+1;
if (!*s || spacep (s)) /* No DN or prefixed with a space. */
{
rc = gpg_error (GPG_ERR_INV_USER_ID);
goto out;
}
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)
{
rc = gpg_error (GPG_ERR_INV_USER_ID);
goto out;
}
for (i=0,si=s; si < se; si++, i++ )
{
if (!strchr("01234567890abcdefABCDEF", *si))
{
rc = gpg_error (GPG_ERR_INV_USER_ID); /* Invalid digit. */
goto out;
}
}
if (i != 32 && i != 40 && i != 64)
{
rc = gpg_error (GPG_ERR_INV_USER_ID); /* Invalid length of fpr. */
goto out;
}
for (i=0,si=s; si < se; i++, si +=2)
desc->u.fpr[i] = hextobyte(si);
desc->fprlen = i;
for (; i < 32; i++)
desc->u.fpr[i]= 0;
mode = KEYDB_SEARCH_MODE_FPR;
}
break;
case '&': /* Keygrip*/
{
if (hex2bin (s+1, desc->u.grip, 20) < 0)
{
rc = gpg_error (GPG_ERR_INV_USER_ID); /* Invalid. */
goto out;
}
mode = KEYDB_SEARCH_MODE_KEYGRIP;
}
break;
default:
if (s[0] == '0' && s[1] == 'x')
{
hexprefix = 1;
s += 2;
}
hexlength = strspn(s, "0123456789abcdefABCDEF");
if (hexlength >= 8 && s[hexlength] =='!')
{
desc->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 a correct
termination is an error. */
{
rc = gpg_error (GPG_ERR_INV_USER_ID);
goto out;
}
/* The first characters looked like a hex number, but the
entire string is not. */
hexlength = 0;
}
if (desc->exact)
hexlength--; /* Remove the bang. */
if ((hexlength == 8
&& (s[hexlength] == 0
|| (s[hexlength] == '!' && s[hexlength + 1] == 0)))
|| (!hexprefix && hexlength == 9 && *s == '0'))
{
/* Short keyid. */
if (hexlength == 9)
s++;
desc->u.kid[1] = strtoul( s, NULL, 16 );
mode = KEYDB_SEARCH_MODE_SHORT_KID;
}
else if ((hexlength == 16
&& (s[hexlength] == 0
|| (s[hexlength] == '!' && s[hexlength + 1] == 0)))
|| (!hexprefix && hexlength == 17 && *s == '0'))
{
/* Long keyid. */
char buf[9];
if (hexlength == 17)
s++;
mem2str (buf, s, 9);
desc->u.kid[0] = strtoul (buf, NULL, 16);
desc->u.kid[1] = strtoul (s+8, NULL, 16);
mode = KEYDB_SEARCH_MODE_LONG_KID;
}
else if ((hexlength == 32
&& (s[hexlength] == 0
|| (s[hexlength] == '!' && s[hexlength + 1] == 0)))
|| (!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)
{
rc = gpg_error (GPG_ERR_INV_USER_ID);
goto out;
}
desc->u.fpr[i] = c;
}
desc->fprlen = 16;
for (; i < 32; i++)
desc->u.fpr[i]= 0;
mode = KEYDB_SEARCH_MODE_FPR;
}
else if ((hexlength == 40
&& (s[hexlength] == 0
|| (s[hexlength] == '!' && s[hexlength + 1] == 0)))
|| (!hexprefix && hexlength == 41 && *s == '0'))
{
/* SHA1 fingerprint. */
int i;
if (hexlength == 41)
s++;
for (i=0; i < 20; i++, s+=2)
{
int c = hextobyte(s);
if (c == -1)
{
rc = gpg_error (GPG_ERR_INV_USER_ID);
goto out;
}
desc->u.fpr[i] = c;
}
desc->fprlen = 20;
for (; i < 32; i++)
desc->u.fpr[i]= 0;
mode = KEYDB_SEARCH_MODE_FPR;
}
else if ((hexlength == 64
&& (s[hexlength] == 0
|| (s[hexlength] == '!' && s[hexlength + 1] == 0)))
|| (!hexprefix && hexlength == 65 && *s == '0'))
{
/* SHA256 fingerprint. */
int i;
if (hexlength == 65)
s++;
for (i=0; i < 32; i++, s+=2)
{
int c = hextobyte(s);
if (c == -1)
{
rc = gpg_error (GPG_ERR_INV_USER_ID);
goto out;
}
desc->u.fpr[i] = c;
}
desc->fprlen = 32;
mode = KEYDB_SEARCH_MODE_FPR;
}
else if (!hexprefix)
{
- /* The fingerprint in an X.509 listing is often delimited by
- colons, so we try to single this case out. */
+ /* The fingerprint of an X.509 listing is often delimited by
+ * colons, so we try to single this case out. Note that the
+ * OpenPGP bang suffix is not supported here. */
+ desc->exact = 0;
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)
{
desc->fprlen = 20;
mode = KEYDB_SEARCH_MODE_FPR;
}
for (; i < 32; i++)
desc->u.fpr[i]= 0;
}
if (!mode)
{
/* Still not found. Now check for a space separated
* OpenPGP v4 fingerprint like:
* 8061 5870 F5BA D690 3336 86D0 F2AD 85AC 1E42 B367
* or
* 8061 5870 F5BA D690 3336 86D0 F2AD 85AC 1E42 B367
* FIXME: Support OpenPGP v5 fingerprint
*/
hexlength = strspn (s, " 0123456789abcdefABCDEF");
if (s[hexlength] && s[hexlength] != ' ')
hexlength = 0; /* Followed by non-space. */
while (hexlength && s[hexlength-1] == ' ')
hexlength--; /* Trim trailing spaces. */
if ((hexlength == 49 || hexlength == 50)
&& (!s[hexlength] || s[hexlength] == ' '))
{
int i, c;
for (i=0; i < 20; i++)
{
if (i && !(i % 2))
{
if (*s != ' ')
break;
s++;
/* Skip the double space in the middle but
don't require it to help copying
fingerprints from sources which fold
multiple space to one. */
if (i == 10 && *s == ' ')
s++;
}
c = hextobyte(s);
if (c == -1)
break;
desc->u.fpr[i] = c;
s += 2;
}
if (i == 20)
{
desc->fprlen = 20;
mode = KEYDB_SEARCH_MODE_FPR;
}
for (; i < 32; i++)
desc->u.fpr[i]= 0;
}
}
if (!mode) /* Default to substring search. */
{
- desc->exact = 0;
desc->u.name = s;
mode = KEYDB_SEARCH_MODE_SUBSTR;
}
}
else
{
/* Hex number with a prefix but with a wrong length. */
rc = gpg_error (GPG_ERR_INV_USER_ID);
goto out;
}
}
desc->mode = mode;
out:
xfree (s2);
return rc;
}
diff --git a/common/util.h b/common/util.h
index 8895137ec..bd6cd1ff5 100644
--- a/common/util.h
+++ b/common/util.h
@@ -1,387 +1,387 @@
/* util.h - Utility functions for GnuPG
* Copyright (C) 2001, 2002, 2003, 2004, 2009 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute and/or modify this
* part of GnuPG under the terms of either
*
* - the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 3 of the License, or (at
* your option) any later version.
*
* or
*
* - 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.
*
* or both in parallel, as here.
*
* 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 copies of the GNU General Public License
* and the GNU Lesser General Public License along with this program;
* if not, see <https://www.gnu.org/licenses/>.
*/
#ifndef GNUPG_COMMON_UTIL_H
#define GNUPG_COMMON_UTIL_H
#include <gcrypt.h> /* We need this for the memory function protos. */
#include <errno.h> /* We need errno. */
#include <gpg-error.h> /* We need gpg_error_t and estream. */
/* These error codes are used but not defined in the required
* libgpg-error version. Define them here.
* Example: (#if GPG_ERROR_VERSION_NUMBER < 0x011500 // 1.21)
*/
#if GPG_ERROR_VERSION_NUMBER < 0x012400 /* 1.36 */
#define GPG_ERR_NO_AUTH 314
#define GPG_ERR_BAD_AUTH 315
#endif /*GPG_ERROR_VERSION_NUMBER*/
/* Hash function used with libksba. */
#define HASH_FNC ((void (*)(void *, const void*,size_t))gcry_md_write)
/* The length of the keygrip. This is a SHA-1 hash of the key
* parameters as generated by gcry_pk_get_keygrip. */
#define KEYGRIP_LEN 20
/* Get all the stuff from jnlib. */
#include "../common/logging.h"
#include "../common/argparse.h"
#include "../common/stringhelp.h"
#include "../common/mischelp.h"
#include "../common/strlist.h"
#include "../common/dotlock.h"
#include "../common/utf8conv.h"
#include "../common/dynload.h"
#include "../common/fwddecl.h"
#include "../common/utilproto.h"
#include "gettime.h"
/* Redefine asprintf by our estream version which uses our own memory
allocator.. */
#define asprintf gpgrt_asprintf
#define vasprintf gpgrt_vasprintf
/* Due to a bug in mingw32's snprintf related to the 'l' modifier and
for increased portability we use our snprintf on all systems. */
#undef snprintf
#define snprintf gpgrt_snprintf
/* Replacements for macros not available with libgpg-error < 1.20. */
/* We need this type even if we are not using libreadline and or we
did not include libreadline in the current file. */
#ifndef GNUPG_LIBREADLINE_H_INCLUDED
typedef char **rl_completion_func_t (const char *, int, int);
#endif /*!GNUPG_LIBREADLINE_H_INCLUDED*/
/* Handy malloc macros - please use only them. */
#define xtrymalloc(a) gcry_malloc ((a))
#define xtrymalloc_secure(a) gcry_malloc_secure ((a))
#define xtrycalloc(a,b) gcry_calloc ((a),(b))
#define xtrycalloc_secure(a,b) gcry_calloc_secure ((a),(b))
#define xtryrealloc(a,b) gcry_realloc ((a),(b))
#define xtrystrdup(a) gcry_strdup ((a))
#define xfree(a) gcry_free ((a))
#define xfree_fnc gcry_free
#define xmalloc(a) gcry_xmalloc ((a))
#define xmalloc_secure(a) gcry_xmalloc_secure ((a))
#define xcalloc(a,b) gcry_xcalloc ((a),(b))
#define xcalloc_secure(a,b) gcry_xcalloc_secure ((a),(b))
#define xrealloc(a,b) gcry_xrealloc ((a),(b))
#define xstrdup(a) gcry_xstrdup ((a))
/* For compatibility with gpg 1.4 we also define these: */
#define xmalloc_clear(a) gcry_xcalloc (1, (a))
#define xmalloc_secure_clear(a) gcry_xcalloc_secure (1, (a))
/* The default error source of the application. This is different
from GPG_ERR_SOURCE_DEFAULT in that it does not depend on the
source file and thus is usable in code shared by applications.
Defined by init.c. */
extern gpg_err_source_t default_errsource;
/* Convenience function to return a gpg-error code for memory
allocation failures. This function makes sure that an error will
be returned even if accidentally ERRNO is not set. */
static inline gpg_error_t
out_of_core (void)
{
return gpg_error_from_syserror ();
}
/*-- yesno.c --*/
int answer_is_yes (const char *s);
int answer_is_yes_no_default (const char *s, int def_answer);
int answer_is_yes_no_quit (const char *s);
int answer_is_okay_cancel (const char *s, int def_answer);
/*-- xreadline.c --*/
ssize_t read_line (FILE *fp,
char **addr_of_buffer, size_t *length_of_buffer,
size_t *max_length);
/*-- b64enc.c and b64dec.c --*/
struct b64state
{
unsigned int flags;
int idx;
int quad_count;
FILE *fp;
estream_t stream;
char *title;
unsigned char radbuf[4];
u32 crc;
int stop_seen:1;
int invalid_encoding:1;
gpg_error_t lasterr;
};
gpg_error_t b64enc_start (struct b64state *state, FILE *fp, const char *title);
gpg_error_t b64enc_start_es (struct b64state *state, estream_t fp,
const char *title);
gpg_error_t b64enc_write (struct b64state *state,
const void *buffer, size_t nbytes);
gpg_error_t b64enc_finish (struct b64state *state);
gpg_error_t b64dec_start (struct b64state *state, const char *title);
gpg_error_t b64dec_proc (struct b64state *state, void *buffer, size_t length,
size_t *r_nbytes);
gpg_error_t b64dec_finish (struct b64state *state);
/*-- sexputil.c */
char *canon_sexp_to_string (const unsigned char *canon, size_t canonlen);
void log_printcanon (const char *text,
const unsigned char *sexp, size_t sexplen);
void log_printsexp (const char *text, gcry_sexp_t sexp);
gpg_error_t make_canon_sexp (gcry_sexp_t sexp,
unsigned char **r_buffer, size_t *r_buflen);
gpg_error_t make_canon_sexp_pad (gcry_sexp_t sexp, int secure,
unsigned char **r_buffer, size_t *r_buflen);
gpg_error_t keygrip_from_canon_sexp (const unsigned char *key, size_t keylen,
unsigned char *grip);
int cmp_simple_canon_sexp (const unsigned char *a, const unsigned char *b);
unsigned char *make_simple_sexp_from_hexstr (const char *line,
size_t *nscanned);
int hash_algo_from_sigval (const unsigned char *sigval);
unsigned char *make_canon_sexp_from_rsa_pk (const void *m, size_t mlen,
const void *e, size_t elen,
size_t *r_len);
gpg_error_t get_rsa_pk_from_canon_sexp (const unsigned char *keydata,
size_t keydatalen,
unsigned char const **r_n,
size_t *r_nlen,
unsigned char const **r_e,
size_t *r_elen);
int get_pk_algo_from_key (gcry_sexp_t key);
int get_pk_algo_from_canon_sexp (const unsigned char *keydata,
size_t keydatalen);
-char *pubkey_algo_string (gcry_sexp_t s_pkey);
+char *pubkey_algo_string (gcry_sexp_t s_pkey, enum gcry_pk_algos *r_algoid);
/*-- convert.c --*/
int hex2bin (const char *string, void *buffer, size_t length);
int hexcolon2bin (const char *string, void *buffer, size_t length);
char *bin2hex (const void *buffer, size_t length, char *stringbuf);
char *bin2hexcolon (const void *buffer, size_t length, char *stringbuf);
const char *hex2str (const char *hexstring,
char *buffer, size_t bufsize, size_t *buflen);
char *hex2str_alloc (const char *hexstring, size_t *r_count);
/*-- percent.c --*/
char *percent_plus_escape (const char *string);
char *percent_data_escape (int plus, const char *prefix,
const void *data, size_t datalen);
char *percent_plus_unescape (const char *string, int nulrepl);
char *percent_unescape (const char *string, int nulrepl);
size_t percent_plus_unescape_inplace (char *string, int nulrepl);
size_t percent_unescape_inplace (char *string, int nulrepl);
/*-- openpgp-oid.c --*/
gpg_error_t openpgp_oid_from_str (const char *string, gcry_mpi_t *r_mpi);
char *openpgp_oidbuf_to_str (const unsigned char *buf, size_t len);
char *openpgp_oid_to_str (gcry_mpi_t a);
int openpgp_oidbuf_is_ed25519 (const void *buf, size_t len);
int openpgp_oid_is_ed25519 (gcry_mpi_t a);
int openpgp_oidbuf_is_cv25519 (const void *buf, size_t len);
int openpgp_oid_is_cv25519 (gcry_mpi_t a);
const char *openpgp_curve_to_oid (const char *name, unsigned int *r_nbits);
const char *openpgp_oid_to_curve (const char *oid, int canon);
const char *openpgp_enum_curves (int *idxp);
const char *openpgp_is_curve_supported (const char *name,
int *r_algo, unsigned int *r_nbits);
/*-- homedir.c --*/
const char *standard_homedir (void);
const char *default_homedir (void);
void gnupg_set_homedir (const char *newdir);
const char *gnupg_homedir (void);
int gnupg_default_homedir_p (void);
const char *gnupg_daemon_rootdir (void);
const char *gnupg_socketdir (void);
const char *gnupg_sysconfdir (void);
const char *gnupg_bindir (void);
const char *gnupg_libexecdir (void);
const char *gnupg_libdir (void);
const char *gnupg_datadir (void);
const char *gnupg_localedir (void);
const char *gnupg_cachedir (void);
const char *dirmngr_socket_name (void);
char *_gnupg_socketdir_internal (int skip_checks, unsigned *r_info);
/* All module names. We also include gpg and gpgsm for the sake for
gpgconf. */
#define GNUPG_MODULE_NAME_AGENT 1
#define GNUPG_MODULE_NAME_PINENTRY 2
#define GNUPG_MODULE_NAME_SCDAEMON 3
#define GNUPG_MODULE_NAME_DIRMNGR 4
#define GNUPG_MODULE_NAME_PROTECT_TOOL 5
#define GNUPG_MODULE_NAME_CHECK_PATTERN 6
#define GNUPG_MODULE_NAME_GPGSM 7
#define GNUPG_MODULE_NAME_GPG 8
#define GNUPG_MODULE_NAME_CONNECT_AGENT 9
#define GNUPG_MODULE_NAME_GPGCONF 10
#define GNUPG_MODULE_NAME_DIRMNGR_LDAP 11
#define GNUPG_MODULE_NAME_GPGV 12
const char *gnupg_module_name (int which);
void gnupg_module_name_flush_some (void);
void gnupg_set_builddir (const char *newdir);
/* A list of constants to identify protocols. This is used by tools
* which need to distinguish between the different protocols
* implemented by GnuPG. May be used as bit flags. */
#define GNUPG_PROTOCOL_OPENPGP 1 /* The one and only (gpg). */
#define GNUPG_PROTOCOL_CMS 2 /* The core of S/MIME (gpgsm) */
#define GNUPG_PROTOCOL_SSH_AGENT 4 /* Out ssh-agent implementation */
/*-- gpgrlhelp.c --*/
void gnupg_rl_initialize (void);
/*-- helpfile.c --*/
char *gnupg_get_help_string (const char *key, int only_current_locale);
/*-- localename.c --*/
const char *gnupg_messages_locale_name (void);
/*-- miscellaneous.c --*/
/* This function is called at startup to tell libgcrypt to use our own
logging subsystem. */
void setup_libgcrypt_logging (void);
/* Print an out of core message and die. */
void xoutofcore (void);
/* Same as estream_asprintf but die on memory failure. */
char *xasprintf (const char *fmt, ...) GPGRT_ATTR_PRINTF(1,2);
/* This is now an alias to estream_asprintf. */
char *xtryasprintf (const char *fmt, ...) GPGRT_ATTR_PRINTF(1,2);
/* Replacement for gcry_cipher_algo_name. */
const char *gnupg_cipher_algo_name (int algo);
void obsolete_option (const char *configname, unsigned int configlineno,
const char *name);
const char *print_fname_stdout (const char *s);
const char *print_fname_stdin (const char *s);
void print_utf8_buffer3 (estream_t fp, const void *p, size_t n,
const char *delim);
void print_utf8_buffer2 (estream_t fp, const void *p, size_t n, int delim);
void print_utf8_buffer (estream_t fp, const void *p, size_t n);
void print_utf8_string (estream_t stream, const char *p);
void print_hexstring (FILE *fp, const void *buffer, size_t length,
int reserved);
char *try_make_printable_string (const void *p, size_t n, int delim);
char *make_printable_string (const void *p, size_t n, int delim);
char *decode_c_string (const char *src);
int is_file_compressed (const char *s, int *ret_rc);
int match_multistr (const char *multistr,const char *match);
int gnupg_compare_version (const char *a, const char *b);
struct debug_flags_s
{
unsigned int flag;
const char *name;
};
int parse_debug_flag (const char *string, unsigned int *debugvar,
const struct debug_flags_s *flags);
/*-- Simple replacement functions. */
/* We use the gnupg_ttyname macro to be safe not to run into conflicts
which an extisting but broken ttyname. */
#if !defined(HAVE_TTYNAME) || defined(HAVE_BROKEN_TTYNAME)
# define gnupg_ttyname(n) _gnupg_ttyname ((n))
/* Systems without ttyname (W32) will merely return NULL. */
static inline char *
_gnupg_ttyname (int fd)
{
(void)fd;
return NULL;
}
#else /*HAVE_TTYNAME*/
# define gnupg_ttyname(n) ttyname ((n))
#endif /*HAVE_TTYNAME */
#ifdef HAVE_W32CE_SYSTEM
#define getpid() GetCurrentProcessId ()
char *_gnupg_getenv (const char *name); /* See sysutils.c */
#define getenv(a) _gnupg_getenv ((a))
char *_gnupg_setenv (const char *name); /* See sysutils.c */
#define setenv(a,b,c) _gnupg_setenv ((a),(b),(c))
int _gnupg_isatty (int fd);
#define gnupg_isatty(a) _gnupg_isatty ((a))
#else
#define gnupg_isatty(a) isatty ((a))
#endif
/*-- Macros to replace ctype ones to avoid locale problems. --*/
#define spacep(p) (*(p) == ' ' || *(p) == '\t')
#define digitp(p) (*(p) >= '0' && *(p) <= '9')
#define alphap(p) ((*(p) >= 'A' && *(p) <= 'Z') \
|| (*(p) >= 'a' && *(p) <= 'z'))
#define alnump(p) (alphap (p) || digitp (p))
#define hexdigitp(a) (digitp (a) \
|| (*(a) >= 'A' && *(a) <= 'F') \
|| (*(a) >= 'a' && *(a) <= 'f'))
/* Note this isn't identical to a C locale isspace() without \f and
\v, but works for the purposes used here. */
#define ascii_isspace(a) ((a)==' ' || (a)=='\n' || (a)=='\r' || (a)=='\t')
/* The atoi macros assume that the buffer has only valid digits. */
#define atoi_1(p) (*(p) - '0' )
#define atoi_2(p) ((atoi_1(p) * 10) + atoi_1((p)+1))
#define atoi_4(p) ((atoi_2(p) * 100) + atoi_2((p)+2))
#define xtoi_1(p) (*(p) <= '9'? (*(p)- '0'): \
*(p) <= 'F'? (*(p)-'A'+10):(*(p)-'a'+10))
#define xtoi_2(p) ((xtoi_1(p) * 16) + xtoi_1((p)+1))
#define xtoi_4(p) ((xtoi_2(p) * 256) + xtoi_2((p)+2))
#endif /*GNUPG_COMMON_UTIL_H*/
diff --git a/dirmngr/certcache.c b/dirmngr/certcache.c
index adb005ec8..5486997b6 100644
--- a/dirmngr/certcache.c
+++ b/dirmngr/certcache.c
@@ -1,1824 +1,1858 @@
/* certcache.c - Certificate caching
* Copyright (C) 2004, 2005, 2007, 2008, 2017 g10 Code GmbH
*
* This file is part of DirMngr.
*
* DirMngr is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* DirMngr is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <assert.h>
#include <sys/types.h>
#include <dirent.h>
#include <npth.h>
#include "dirmngr.h"
#include "misc.h"
#include "../common/ksba-io-support.h"
#include "crlfetch.h"
#include "certcache.h"
#define MAX_NONPERM_CACHED_CERTS 1000
/* Constants used to classify search patterns. */
enum pattern_class
{
PATTERN_UNKNOWN = 0,
PATTERN_EMAIL,
PATTERN_EMAIL_SUBSTR,
PATTERN_FINGERPRINT16,
PATTERN_FINGERPRINT20,
PATTERN_SHORT_KEYID,
PATTERN_LONG_KEYID,
PATTERN_SUBJECT,
PATTERN_SERIALNO,
PATTERN_SERIALNO_ISSUER,
PATTERN_ISSUER,
PATTERN_SUBSTR
};
/* A certificate cache item. This consists of a the KSBA cert object
and some meta data for easier lookup. We use a hash table to keep
track of all items and use the (randomly distributed) first byte of
the fingerprint directly as the hash which makes it pretty easy. */
struct cert_item_s
{
struct cert_item_s *next; /* Next item with the same hash value. */
ksba_cert_t cert; /* The KSBA cert object or NULL is this is
not a valid item. */
unsigned char fpr[20]; /* The fingerprint of this object. */
char *issuer_dn; /* The malloced issuer DN. */
ksba_sexp_t sn; /* The malloced serial number */
char *subject_dn; /* The malloced subject DN - maybe NULL. */
/* If this field is set the certificate has been taken from some
* configuration and shall not be flushed from the cache. */
unsigned int permanent:1;
/* If this field is set the certificate is trusted. The actual
* value is a (possible) combination of CERTTRUST_CLASS values. */
unsigned int trustclasses:4;
};
typedef struct cert_item_s *cert_item_t;
/* The actual cert cache consisting of 256 slots for items indexed by
the first byte of the fingerprint. */
static cert_item_t cert_cache[256];
/* This is the global cache_lock variable. In general locking is not
needed but it would take extra efforts to make sure that no
indirect use of npth functions is done, so we simply lock it
always. Note: We can't use static initialization, as that is not
available through w32-pth. */
static npth_rwlock_t cert_cache_lock;
/* Flag to track whether the cache has been initialized. */
static int initialization_done;
/* Total number of non-permanent certificates. */
static unsigned int total_nonperm_certificates;
/* For each cert class the corresponding bit is set if at least one
* certificate of that class is loaded permanetly. */
static unsigned int any_cert_of_class;
#ifdef HAVE_W32_SYSTEM
/* We load some functions dynamically. Provide typedefs for tehse
* functions. */
typedef HCERTSTORE (WINAPI *CERTOPENSYSTEMSTORE)
(HCRYPTPROV hProv, LPCSTR szSubsystemProtocol);
typedef PCCERT_CONTEXT (WINAPI *CERTENUMCERTIFICATESINSTORE)
(HCERTSTORE hCertStore, PCCERT_CONTEXT pPrevCertContext);
typedef WINBOOL (WINAPI *CERTCLOSESTORE)
(HCERTSTORE hCertStore,DWORD dwFlags);
#endif /*HAVE_W32_SYSTEM*/
/* Helper to do the cache locking. */
static void
init_cache_lock (void)
{
int err;
err = npth_rwlock_init (&cert_cache_lock, NULL);
if (err)
log_fatal (_("can't initialize certificate cache lock: %s\n"),
strerror (err));
}
static void
acquire_cache_read_lock (void)
{
int err;
err = npth_rwlock_rdlock (&cert_cache_lock);
if (err)
log_fatal (_("can't acquire read lock on the certificate cache: %s\n"),
strerror (err));
}
static void
acquire_cache_write_lock (void)
{
int err;
err = npth_rwlock_wrlock (&cert_cache_lock);
if (err)
log_fatal (_("can't acquire write lock on the certificate cache: %s\n"),
strerror (err));
}
static void
release_cache_lock (void)
{
int err;
err = npth_rwlock_unlock (&cert_cache_lock);
if (err)
log_fatal (_("can't release lock on the certificate cache: %s\n"),
strerror (err));
}
/* Return false if both serial numbers match. Can't be used for
sorting. */
static int
compare_serialno (ksba_sexp_t serial1, ksba_sexp_t serial2 )
{
unsigned char *a = serial1;
unsigned char *b = serial2;
return cmp_simple_canon_sexp (a, b);
}
/* Return a malloced canonical S-Expression with the serial number
* converted from the hex string HEXSN. Return NULL on memory
* error. */
ksba_sexp_t
hexsn_to_sexp (const char *hexsn)
{
char *buffer, *p;
size_t len;
char numbuf[40];
len = unhexify (NULL, hexsn);
snprintf (numbuf, sizeof numbuf, "(%u:", (unsigned int)len);
buffer = xtrymalloc (strlen (numbuf) + len + 2 );
if (!buffer)
return NULL;
p = stpcpy (buffer, numbuf);
len = unhexify (p, hexsn);
p[len] = ')';
p[len+1] = 0;
return buffer;
}
/* Compute the fingerprint of the certificate CERT and put it into
the 20 bytes large buffer DIGEST. Return address of this buffer. */
unsigned char *
cert_compute_fpr (ksba_cert_t cert, unsigned char *digest)
{
gpg_error_t err;
gcry_md_hd_t md;
err = gcry_md_open (&md, GCRY_MD_SHA1, 0);
if (err)
log_fatal ("gcry_md_open failed: %s\n", gpg_strerror (err));
err = ksba_cert_hash (cert, 0, HASH_FNC, md);
if (err)
{
log_error ("oops: ksba_cert_hash failed: %s\n", gpg_strerror (err));
memset (digest, 0xff, 20); /* Use a dummy value. */
}
else
{
gcry_md_final (md);
memcpy (digest, gcry_md_read (md, GCRY_MD_SHA1), 20);
}
gcry_md_close (md);
return digest;
}
/* Cleanup one slot. This releases all resourses but keeps the actual
slot in the cache marked for reuse. */
static void
clean_cache_slot (cert_item_t ci)
{
ksba_cert_t cert;
if (!ci->cert)
return; /* Already cleaned. */
ksba_free (ci->sn);
ci->sn = NULL;
ksba_free (ci->issuer_dn);
ci->issuer_dn = NULL;
ksba_free (ci->subject_dn);
ci->subject_dn = NULL;
cert = ci->cert;
ci->cert = NULL;
ci->permanent = 0;
ci->trustclasses = 0;
ksba_cert_release (cert);
}
/* Put the certificate CERT into the cache. It is assumed that the
* cache is locked while this function is called.
*
* FROM_CONFIG indicates that CERT is a permanent certificate and
* should stay in the cache. IS_TRUSTED requests that the trusted
* flag is set for the certificate; a value of 1 indicates the
* cert is trusted due to GnuPG mechanisms, a value of 2 indicates
* that it is trusted because it has been taken from the system's
* store of trusted certificates. If FPR_BUFFER is not NULL the
* fingerprint of the certificate will be stored there. FPR_BUFFER
* needs to point to a buffer of at least 20 bytes. The fingerprint
* will be stored on success or when the function returns
* GPG_ERR_DUP_VALUE. */
static gpg_error_t
put_cert (ksba_cert_t cert, int permanent, unsigned int trustclass,
void *fpr_buffer)
{
unsigned char help_fpr_buffer[20], *fpr;
cert_item_t ci;
fpr = fpr_buffer? fpr_buffer : &help_fpr_buffer;
/* If we already reached the caching limit, drop a couple of certs
* from the cache. Our dropping strategy is simple: We keep a
* static index counter and use this to start looking for
* certificates, then we drop 5 percent of the oldest certificates
* starting at that index. For a large cache this is a fair way of
* removing items. An LRU strategy would be better of course.
* Because we append new entries to the head of the list and we want
* to remove old ones first, we need to do this from the tail. The
* implementation is not very efficient but compared to the long
* time it takes to retrieve a certificate from an external resource
* it seems to be reasonable. */
if (!permanent && total_nonperm_certificates >= MAX_NONPERM_CACHED_CERTS)
{
static int idx;
cert_item_t ci_mark;
int i;
unsigned int drop_count;
drop_count = MAX_NONPERM_CACHED_CERTS / 20;
if (drop_count < 2)
drop_count = 2;
log_info (_("dropping %u certificates from the cache\n"), drop_count);
assert (idx < 256);
for (i=idx; drop_count; i = ((i+1)%256))
{
ci_mark = NULL;
for (ci = cert_cache[i]; ci; ci = ci->next)
if (ci->cert && !ci->permanent)
ci_mark = ci;
if (ci_mark)
{
clean_cache_slot (ci_mark);
drop_count--;
total_nonperm_certificates--;
}
}
if (i==idx)
idx++;
else
idx = i;
idx %= 256;
}
cert_compute_fpr (cert, fpr);
for (ci=cert_cache[*fpr]; ci; ci = ci->next)
if (ci->cert && !memcmp (ci->fpr, fpr, 20))
return gpg_error (GPG_ERR_DUP_VALUE);
/* Try to reuse an existing entry. */
for (ci=cert_cache[*fpr]; ci; ci = ci->next)
if (!ci->cert)
break;
if (!ci)
{ /* No: Create a new entry. */
ci = xtrycalloc (1, sizeof *ci);
if (!ci)
return gpg_error_from_errno (errno);
ci->next = cert_cache[*fpr];
cert_cache[*fpr] = ci;
}
ksba_cert_ref (cert);
ci->cert = cert;
memcpy (ci->fpr, fpr, 20);
ci->sn = ksba_cert_get_serial (cert);
ci->issuer_dn = ksba_cert_get_issuer (cert, 0);
if (!ci->issuer_dn || !ci->sn)
{
clean_cache_slot (ci);
return gpg_error (GPG_ERR_INV_CERT_OBJ);
}
ci->subject_dn = ksba_cert_get_subject (cert, 0);
ci->permanent = !!permanent;
ci->trustclasses = trustclass;
if (permanent)
any_cert_of_class |= trustclass;
else
total_nonperm_certificates++;
return 0;
}
/* Load certificates from the directory DIRNAME. All certificates
matching the pattern "*.crt" or "*.der" are loaded. We assume that
certificates are DER encoded and not PEM encapsulated. The cache
should be in a locked state when calling this function. */
static gpg_error_t
load_certs_from_dir (const char *dirname, unsigned int trustclass)
{
gpg_error_t err;
DIR *dir;
struct dirent *ep;
char *p;
size_t n;
estream_t fp;
ksba_reader_t reader;
ksba_cert_t cert;
char *fname = NULL;
dir = opendir (dirname);
if (!dir)
{
return 0; /* We do not consider this a severe error. */
}
while ( (ep=readdir (dir)) )
{
p = ep->d_name;
if (*p == '.' || !*p)
continue; /* Skip any hidden files and invalid entries. */
n = strlen (p);
if ( n < 5 || (strcmp (p+n-4,".crt") && strcmp (p+n-4,".der")))
continue; /* Not the desired "*.crt" or "*.der" pattern. */
xfree (fname);
fname = make_filename (dirname, p, NULL);
fp = es_fopen (fname, "rb");
if (!fp)
{
log_error (_("can't open '%s': %s\n"),
fname, strerror (errno));
continue;
}
err = create_estream_ksba_reader (&reader, fp);
if (err)
{
es_fclose (fp);
continue;
}
err = ksba_cert_new (&cert);
if (!err)
err = ksba_cert_read_der (cert, reader);
ksba_reader_release (reader);
es_fclose (fp);
if (err)
{
log_error (_("can't parse certificate '%s': %s\n"),
fname, gpg_strerror (err));
ksba_cert_release (cert);
continue;
}
err = put_cert (cert, 1, trustclass, NULL);
if (gpg_err_code (err) == GPG_ERR_DUP_VALUE)
log_info (_("certificate '%s' already cached\n"), fname);
else if (!err)
{
if ((trustclass & CERTTRUST_CLASS_CONFIG))
http_register_cfg_ca (fname);
if (trustclass)
log_info (_("trusted certificate '%s' loaded\n"), fname);
else
log_info (_("certificate '%s' loaded\n"), fname);
if (opt.verbose)
{
p = get_fingerprint_hexstring_colon (cert);
log_info (_(" SHA1 fingerprint = %s\n"), p);
xfree (p);
cert_log_name (_(" issuer ="), cert);
cert_log_subject (_(" subject ="), cert);
}
}
else
log_error (_("error loading certificate '%s': %s\n"),
fname, gpg_strerror (err));
ksba_cert_release (cert);
}
xfree (fname);
closedir (dir);
return 0;
}
/* Load certificates from FILE. The certificates are expected to be
* PEM encoded so that it is possible to load several certificates.
* TRUSTCLASSES is used to mark the certificates as trusted. The
* cache should be in a locked state when calling this function.
* NO_ERROR repalces an error message when FNAME was not found by an
* information message. */
static gpg_error_t
load_certs_from_file (const char *fname, unsigned int trustclasses,
int no_error)
{
gpg_error_t err;
estream_t fp = NULL;
gnupg_ksba_io_t ioctx = NULL;
ksba_reader_t reader;
ksba_cert_t cert = NULL;
fp = es_fopen (fname, "rb");
if (!fp)
{
err = gpg_error_from_syserror ();
if (gpg_err_code (err) == GPG_ERR_ENONET && no_error)
log_info (_("can't open '%s': %s\n"), fname, gpg_strerror (err));
else
log_error (_("can't open '%s': %s\n"), fname, gpg_strerror (err));
goto leave;
}
err = gnupg_ksba_create_reader (&ioctx,
(GNUPG_KSBA_IO_AUTODETECT
| GNUPG_KSBA_IO_MULTIPEM),
fp, &reader);
if (err)
{
log_error ("can't create reader: %s\n", gpg_strerror (err));
goto leave;
}
/* Loop to read all certificates from the file. */
do
{
ksba_cert_release (cert);
cert = NULL;
err = ksba_cert_new (&cert);
if (!err)
err = ksba_cert_read_der (cert, reader);
if (err)
{
if (gpg_err_code (err) == GPG_ERR_EOF)
err = 0;
else
log_error (_("can't parse certificate '%s': %s\n"),
fname, gpg_strerror (err));
goto leave;
}
err = put_cert (cert, 1, trustclasses, NULL);
if (gpg_err_code (err) == GPG_ERR_DUP_VALUE)
log_info (_("certificate '%s' already cached\n"), fname);
else if (err)
log_error (_("error loading certificate '%s': %s\n"),
fname, gpg_strerror (err));
else if (opt.verbose > 1)
{
char *p;
log_info (_("trusted certificate '%s' loaded\n"), fname);
p = get_fingerprint_hexstring_colon (cert);
log_info (_(" SHA1 fingerprint = %s\n"), p);
xfree (p);
cert_log_name (_(" issuer ="), cert);
cert_log_subject (_(" subject ="), cert);
}
ksba_reader_clear (reader, NULL, NULL);
}
while (!gnupg_ksba_reader_eof_seen (ioctx));
leave:
ksba_cert_release (cert);
gnupg_ksba_destroy_reader (ioctx);
es_fclose (fp);
return err;
}
#ifdef HAVE_W32_SYSTEM
/* Load all certificates from the Windows store named STORENAME. All
* certificates are considered to be system provided trusted
* certificates. The cache should be in a locked state when calling
* this function. */
static void
load_certs_from_w32_store (const char *storename)
{
static int init_done;
static CERTOPENSYSTEMSTORE pCertOpenSystemStore;
static CERTENUMCERTIFICATESINSTORE pCertEnumCertificatesInStore;
static CERTCLOSESTORE pCertCloseStore;
gpg_error_t err;
HCERTSTORE w32store;
const CERT_CONTEXT *w32cert;
ksba_cert_t cert = NULL;
unsigned int count = 0;
/* Initialize on the first use. */
if (!init_done)
{
static HANDLE hCrypt32;
init_done = 1;
hCrypt32 = LoadLibrary ("Crypt32.dll");
if (!hCrypt32)
{
log_error ("can't load Crypt32.dll: %s\n", w32_strerror (-1));
return;
}
pCertOpenSystemStore = (CERTOPENSYSTEMSTORE)
GetProcAddress (hCrypt32, "CertOpenSystemStoreA");
pCertEnumCertificatesInStore = (CERTENUMCERTIFICATESINSTORE)
GetProcAddress (hCrypt32, "CertEnumCertificatesInStore");
pCertCloseStore = (CERTCLOSESTORE)
GetProcAddress (hCrypt32, "CertCloseStore");
if ( !pCertOpenSystemStore
|| !pCertEnumCertificatesInStore
|| !pCertCloseStore)
{
log_error ("can't load crypt32.dll: %s\n", "missing function");
pCertOpenSystemStore = NULL;
}
}
if (!pCertOpenSystemStore)
return; /* Not initialized. */
w32store = pCertOpenSystemStore (0, storename);
if (!w32store)
{
log_error ("can't open certificate store '%s': %s\n",
storename, w32_strerror (-1));
return;
}
w32cert = NULL;
while ((w32cert = pCertEnumCertificatesInStore (w32store, w32cert)))
{
if (w32cert->dwCertEncodingType == X509_ASN_ENCODING)
{
ksba_cert_release (cert);
cert = NULL;
err = ksba_cert_new (&cert);
if (!err)
err = ksba_cert_init_from_mem (cert,
w32cert->pbCertEncoded,
w32cert->cbCertEncoded);
if (err)
{
log_error (_("can't parse certificate '%s': %s\n"),
storename, gpg_strerror (err));
break;
}
err = put_cert (cert, 1, CERTTRUST_CLASS_SYSTEM, NULL);
if (!err)
count++;
if (gpg_err_code (err) == GPG_ERR_DUP_VALUE)
{
if (DBG_X509)
log_debug (_("certificate '%s' already cached\n"), storename);
}
else if (err)
log_error (_("error loading certificate '%s': %s\n"),
storename, gpg_strerror (err));
else if (opt.verbose > 1)
{
char *p;
log_info (_("trusted certificate '%s' loaded\n"), storename);
p = get_fingerprint_hexstring_colon (cert);
log_info (_(" SHA1 fingerprint = %s\n"), p);
xfree (p);
cert_log_name (_(" issuer ="), cert);
cert_log_subject (_(" subject ="), cert);
}
}
}
ksba_cert_release (cert);
pCertCloseStore (w32store, 0);
if (DBG_X509)
log_debug ("number of certs loaded from store '%s': %u\n",
storename, count);
}
#endif /*HAVE_W32_SYSTEM*/
/* Load the trusted certificates provided by the system. */
static gpg_error_t
load_certs_from_system (void)
{
#ifdef HAVE_W32_SYSTEM
load_certs_from_w32_store ("ROOT");
load_certs_from_w32_store ("CA");
return 0;
#else /*!HAVE_W32_SYSTEM*/
/* A list of certificate bundles to try. */
static struct {
const char *name;
} table[] = {
#ifdef DEFAULT_TRUST_STORE_FILE
{ DEFAULT_TRUST_STORE_FILE }
#else
{ "/etc/ssl/ca-bundle.pem" },
{ "/etc/ssl/certs/ca-certificates.crt" },
{ "/etc/pki/tls/cert.pem" },
{ "/usr/local/share/certs/ca-root-nss.crt" },
{ "/etc/ssl/cert.pem" }
#endif /*!DEFAULT_TRUST_STORE_FILE*/
};
int idx;
gpg_error_t err = 0;
for (idx=0; idx < DIM (table); idx++)
if (!access (table[idx].name, F_OK))
{
/* Take the first available bundle. */
err = load_certs_from_file (table[idx].name, CERTTRUST_CLASS_SYSTEM, 0);
break;
}
return err;
#endif /*!HAVE_W32_SYSTEM*/
}
/* Initialize the certificate cache if not yet done. */
void
cert_cache_init (strlist_t hkp_cacerts)
{
char *fname;
strlist_t sl;
if (initialization_done)
return;
init_cache_lock ();
acquire_cache_write_lock ();
load_certs_from_system ();
fname = make_filename_try (gnupg_sysconfdir (), "trusted-certs", NULL);
if (fname)
load_certs_from_dir (fname, CERTTRUST_CLASS_CONFIG);
xfree (fname);
fname = make_filename_try (gnupg_sysconfdir (), "extra-certs", NULL);
if (fname)
load_certs_from_dir (fname, 0);
xfree (fname);
fname = make_filename_try (gnupg_datadir (),
"sks-keyservers.netCA.pem", NULL);
if (fname)
load_certs_from_file (fname, CERTTRUST_CLASS_HKPSPOOL, 1);
xfree (fname);
for (sl = hkp_cacerts; sl; sl = sl->next)
load_certs_from_file (sl->d, CERTTRUST_CLASS_HKP, 0);
initialization_done = 1;
release_cache_lock ();
cert_cache_print_stats ();
}
/* Deinitialize the certificate cache. With FULL set to true even the
unused certificate slots are released. */
void
cert_cache_deinit (int full)
{
cert_item_t ci, ci2;
int i;
if (!initialization_done)
return;
acquire_cache_write_lock ();
for (i=0; i < 256; i++)
for (ci=cert_cache[i]; ci; ci = ci->next)
clean_cache_slot (ci);
if (full)
{
for (i=0; i < 256; i++)
{
for (ci=cert_cache[i]; ci; ci = ci2)
{
ci2 = ci->next;
xfree (ci);
}
cert_cache[i] = NULL;
}
}
http_register_cfg_ca (NULL);
total_nonperm_certificates = 0;
any_cert_of_class = 0;
initialization_done = 0;
release_cache_lock ();
}
/* Print some statistics to the log file. */
void
cert_cache_print_stats (void)
{
cert_item_t ci;
int idx;
unsigned int n_nonperm = 0;
unsigned int n_permanent = 0;
unsigned int n_trusted = 0;
unsigned int n_trustclass_system = 0;
unsigned int n_trustclass_config = 0;
unsigned int n_trustclass_hkp = 0;
unsigned int n_trustclass_hkpspool = 0;
acquire_cache_read_lock ();
for (idx = 0; idx < 256; idx++)
for (ci=cert_cache[idx]; ci; ci = ci->next)
if (ci->cert)
{
if (ci->permanent)
n_permanent++;
else
n_nonperm++;
if (ci->trustclasses)
{
n_trusted++;
if ((ci->trustclasses & CERTTRUST_CLASS_SYSTEM))
n_trustclass_system++;
if ((ci->trustclasses & CERTTRUST_CLASS_CONFIG))
n_trustclass_config++;
if ((ci->trustclasses & CERTTRUST_CLASS_HKP))
n_trustclass_hkp++;
if ((ci->trustclasses & CERTTRUST_CLASS_HKPSPOOL))
n_trustclass_hkpspool++;
}
}
release_cache_lock ();
log_info (_("permanently loaded certificates: %u\n"),
n_permanent);
log_info (_(" runtime cached certificates: %u\n"),
n_nonperm);
log_info (_(" trusted certificates: %u (%u,%u,%u,%u)\n"),
n_trusted,
n_trustclass_system,
n_trustclass_config,
n_trustclass_hkp,
n_trustclass_hkpspool);
}
/* Return true if any cert of a class in MASK is permanently
* loaded. */
int
cert_cache_any_in_class (unsigned int mask)
{
return !!(any_cert_of_class & mask);
}
/* Put CERT into the certificate cache. */
gpg_error_t
cache_cert (ksba_cert_t cert)
{
gpg_error_t err;
acquire_cache_write_lock ();
err = put_cert (cert, 0, 0, NULL);
release_cache_lock ();
if (gpg_err_code (err) == GPG_ERR_DUP_VALUE)
log_info (_("certificate already cached\n"));
else if (!err)
log_info (_("certificate cached\n"));
else
log_error (_("error caching certificate: %s\n"), gpg_strerror (err));
return err;
}
/* Put CERT into the certificate cache and store the fingerprint of
the certificate into FPR_BUFFER. If the certificate is already in
the cache do not print a warning; just store the
fingerprint. FPR_BUFFER needs to be at least 20 bytes. */
gpg_error_t
cache_cert_silent (ksba_cert_t cert, void *fpr_buffer)
{
gpg_error_t err;
acquire_cache_write_lock ();
err = put_cert (cert, 0, 0, fpr_buffer);
release_cache_lock ();
if (gpg_err_code (err) == GPG_ERR_DUP_VALUE)
err = 0;
if (err)
log_error (_("error caching certificate: %s\n"), gpg_strerror (err));
return err;
}
/* Return a certificate object for the given fingerprint. FPR is
expected to be a 20 byte binary SHA-1 fingerprint. If no matching
certificate is available in the cache NULL is returned. The caller
must release a returned certificate. Note that although we are
using reference counting the caller should not just compare the
pointers to check for identical certificates. */
ksba_cert_t
get_cert_byfpr (const unsigned char *fpr)
{
cert_item_t ci;
acquire_cache_read_lock ();
for (ci=cert_cache[*fpr]; ci; ci = ci->next)
if (ci->cert && !memcmp (ci->fpr, fpr, 20))
{
ksba_cert_ref (ci->cert);
release_cache_lock ();
return ci->cert;
}
release_cache_lock ();
return NULL;
}
/* Return a certificate object for the given fingerprint. STRING is
expected to be a SHA-1 fingerprint in standard hex notation with or
without colons. If no matching certificate is available in the
cache NULL is returned. The caller must release a returned
certificate. Note that although we are using reference counting
the caller should not just compare the pointers to check for
identical certificates. */
ksba_cert_t
get_cert_byhexfpr (const char *string)
{
unsigned char fpr[20];
const char *s;
int i;
if (strchr (string, ':'))
{
for (s=string,i=0; i < 20 && hexdigitp (s) && hexdigitp(s+1);)
{
if (s[2] && s[2] != ':')
break; /* Invalid string. */
fpr[i++] = xtoi_2 (s);
s += 2;
if (i!= 20 && *s == ':')
s++;
}
}
else
{
for (s=string,i=0; i < 20 && hexdigitp (s) && hexdigitp(s+1); s+=2 )
fpr[i++] = xtoi_2 (s);
}
if (i!=20 || *s)
{
log_error (_("invalid SHA1 fingerprint string '%s'\n"), string);
return NULL;
}
return get_cert_byfpr (fpr);
}
/* Return the certificate matching ISSUER_DN and SERIALNO. */
ksba_cert_t
get_cert_bysn (const char *issuer_dn, ksba_sexp_t serialno)
{
/* Simple and inefficient implementation. fixme! */
cert_item_t ci;
int i;
acquire_cache_read_lock ();
for (i=0; i < 256; i++)
{
for (ci=cert_cache[i]; ci; ci = ci->next)
if (ci->cert && !strcmp (ci->issuer_dn, issuer_dn)
&& !compare_serialno (ci->sn, serialno))
{
ksba_cert_ref (ci->cert);
release_cache_lock ();
return ci->cert;
}
}
release_cache_lock ();
return NULL;
}
/* Return the certificate matching ISSUER_DN. SEQ should initially be
set to 0 and bumped up to get the next issuer with that DN. */
ksba_cert_t
get_cert_byissuer (const char *issuer_dn, unsigned int seq)
{
/* Simple and very inefficient implementation and API. fixme! */
cert_item_t ci;
int i;
acquire_cache_read_lock ();
for (i=0; i < 256; i++)
{
for (ci=cert_cache[i]; ci; ci = ci->next)
if (ci->cert && !strcmp (ci->issuer_dn, issuer_dn))
if (!seq--)
{
ksba_cert_ref (ci->cert);
release_cache_lock ();
return ci->cert;
}
}
release_cache_lock ();
return NULL;
}
/* Return the certificate matching SUBJECT_DN. SEQ should initially be
set to 0 and bumped up to get the next subject with that DN. */
ksba_cert_t
get_cert_bysubject (const char *subject_dn, unsigned int seq)
{
/* Simple and very inefficient implementation and API. fixme! */
cert_item_t ci;
int i;
if (!subject_dn)
return NULL;
acquire_cache_read_lock ();
for (i=0; i < 256; i++)
{
for (ci=cert_cache[i]; ci; ci = ci->next)
if (ci->cert && ci->subject_dn
&& !strcmp (ci->subject_dn, subject_dn))
if (!seq--)
{
ksba_cert_ref (ci->cert);
release_cache_lock ();
return ci->cert;
}
}
release_cache_lock ();
return NULL;
}
/* Return a value describing the class of PATTERN. The offset of
the actual string to be used for the comparison is stored at
R_OFFSET. The offset of the serialnumer is stored at R_SN_OFFSET. */
static enum pattern_class
classify_pattern (const char *pattern, size_t *r_offset, size_t *r_sn_offset)
{
enum pattern_class result;
const char *s;
int hexprefix = 0;
int hexlength;
*r_offset = *r_sn_offset = 0;
/* Skip leading spaces. */
for(s = pattern; *s && spacep (s); s++ )
;
switch (*s)
{
case 0: /* Empty string is an error. */
result = PATTERN_UNKNOWN;
break;
case '.': /* An email address, compare from end. */
result = PATTERN_UNKNOWN; /* Not implemented. */
break;
case '<': /* An email address. */
result = PATTERN_EMAIL;
s++;
break;
case '@': /* Part of an email address. */
result = PATTERN_EMAIL_SUBSTR;
s++;
break;
case '=': /* Exact compare. */
result = PATTERN_UNKNOWN; /* Does not make sense for X.509. */
break;
case '*': /* Case insensitive substring search. */
result = PATTERN_SUBSTR;
s++;
break;
case '+': /* Compare individual words. */
result = PATTERN_UNKNOWN; /* Not implemented. */
break;
case '/': /* Subject's DN. */
s++;
if (!*s || spacep (s))
result = PATTERN_UNKNOWN; /* No DN or prefixed with a space. */
else
result = PATTERN_SUBJECT;
break;
case '#': /* Serial number or issuer DN. */
{
const char *si;
s++;
if ( *s == '/')
{
/* An issuer's DN is indicated by "#/" */
s++;
if (!*s || spacep (s))
result = PATTERN_UNKNOWN; /* No DN or prefixed with a space. */
else
result = PATTERN_ISSUER;
}
else
{ /* Serialnumber + optional issuer ID. */
for (si=s; *si && *si != '/'; si++)
if (!strchr("01234567890abcdefABCDEF", *si))
break;
if (*si && *si != '/')
result = PATTERN_UNKNOWN; /* Invalid digit in serial number. */
else
{
*r_sn_offset = s - pattern;
if (!*si)
result = PATTERN_SERIALNO;
else
{
s = si+1;
if (!*s || spacep (s))
result = PATTERN_UNKNOWN; /* No DN or prefixed
with a space. */
else
result = PATTERN_SERIALNO_ISSUER;
}
}
}
}
break;
case ':': /* Unified fingerprint. */
{
const char *se, *si;
int i;
se = strchr (++s, ':');
if (!se)
result = PATTERN_UNKNOWN;
else
{
for (i=0, si=s; si < se; si++, i++ )
if (!strchr("01234567890abcdefABCDEF", *si))
break;
if ( si < se )
result = PATTERN_UNKNOWN; /* Invalid digit. */
else if (i == 32)
result = PATTERN_FINGERPRINT16;
else if (i == 40)
result = PATTERN_FINGERPRINT20;
else
result = PATTERN_UNKNOWN; /* Invalid length for a fingerprint. */
}
}
break;
case '&': /* Keygrip. */
result = PATTERN_UNKNOWN; /* Not implemented. */
break;
default:
if (s[0] == '0' && s[1] == 'x')
{
hexprefix = 1;
s += 2;
}
hexlength = strspn(s, "0123456789abcdefABCDEF");
/* Check if a hexadecimal number is terminated by EOS or blank. */
if (hexlength && s[hexlength] && !spacep (s+hexlength))
{
/* If the "0x" prefix is used a correct termination is required. */
if (hexprefix)
{
result = PATTERN_UNKNOWN;
break; /* switch */
}
hexlength = 0; /* Not a hex number. */
}
if (hexlength == 8 || (!hexprefix && hexlength == 9 && *s == '0'))
{
if (hexlength == 9)
s++;
result = PATTERN_SHORT_KEYID;
}
else if (hexlength == 16 || (!hexprefix && hexlength == 17 && *s == '0'))
{
if (hexlength == 17)
s++;
result = PATTERN_LONG_KEYID;
}
else if (hexlength == 32 || (!hexprefix && hexlength == 33 && *s == '0'))
{
if (hexlength == 33)
s++;
result = PATTERN_FINGERPRINT16;
}
else if (hexlength == 40 || (!hexprefix && hexlength == 41 && *s == '0'))
{
if (hexlength == 41)
s++;
result = PATTERN_FINGERPRINT20;
}
else if (!hexprefix)
{
/* The fingerprints used with X.509 are often delimited by
colons, so we try to single this case out. */
result = PATTERN_UNKNOWN;
hexlength = strspn (s, ":0123456789abcdefABCDEF");
if (hexlength == 59 && (!s[hexlength] || spacep (s+hexlength)))
{
int i, c;
for (i=0; i < 20; i++, s += 3)
{
c = hextobyte(s);
if (c == -1 || (i < 19 && s[2] != ':'))
break;
}
if (i == 20)
result = PATTERN_FINGERPRINT20;
}
if (result == PATTERN_UNKNOWN) /* Default to substring match. */
{
result = PATTERN_SUBSTR;
}
}
else /* A hex number with a prefix but with a wrong length. */
result = PATTERN_UNKNOWN;
}
if (result != PATTERN_UNKNOWN)
*r_offset = s - pattern;
return result;
}
/* Given PATTERN, which is a string as used by GnuPG to specify a
certificate, return all matching certificates by calling the
supplied function RETFNC. */
gpg_error_t
get_certs_bypattern (const char *pattern,
gpg_error_t (*retfnc)(void*,ksba_cert_t),
void *retfnc_data)
{
gpg_error_t err = GPG_ERR_BUG;
enum pattern_class class;
size_t offset, sn_offset;
const char *hexserialno;
ksba_sexp_t serialno = NULL;
ksba_cert_t cert = NULL;
unsigned int seq;
if (!pattern || !retfnc)
return gpg_error (GPG_ERR_INV_ARG);
class = classify_pattern (pattern, &offset, &sn_offset);
hexserialno = pattern + sn_offset;
pattern += offset;
switch (class)
{
case PATTERN_UNKNOWN:
err = gpg_error (GPG_ERR_INV_NAME);
break;
case PATTERN_FINGERPRINT20:
cert = get_cert_byhexfpr (pattern);
err = cert? 0 : gpg_error (GPG_ERR_NOT_FOUND);
break;
case PATTERN_SERIALNO_ISSUER:
serialno = hexsn_to_sexp (hexserialno);
if (!serialno)
err = gpg_error_from_syserror ();
else
{
cert = get_cert_bysn (pattern, serialno);
err = cert? 0 : gpg_error (GPG_ERR_NOT_FOUND);
}
break;
case PATTERN_ISSUER:
for (seq=0,err=0; !err && (cert = get_cert_byissuer (pattern, seq)); seq++)
{
err = retfnc (retfnc_data, cert);
ksba_cert_release (cert);
cert = NULL;
}
if (!err && !seq)
err = gpg_error (GPG_ERR_NOT_FOUND);
break;
case PATTERN_SUBJECT:
for (seq=0,err=0; !err && (cert = get_cert_bysubject (pattern, seq));seq++)
{
err = retfnc (retfnc_data, cert);
ksba_cert_release (cert);
cert = NULL;
}
if (!err && !seq)
err = gpg_error (GPG_ERR_NOT_FOUND);
break;
case PATTERN_EMAIL:
case PATTERN_EMAIL_SUBSTR:
case PATTERN_FINGERPRINT16:
case PATTERN_SHORT_KEYID:
case PATTERN_LONG_KEYID:
case PATTERN_SUBSTR:
case PATTERN_SERIALNO:
/* Not supported. */
err = gpg_error (GPG_ERR_INV_NAME);
}
if (!err && cert)
err = retfnc (retfnc_data, cert);
ksba_cert_release (cert);
xfree (serialno);
return err;
}
/* Return the certificate matching ISSUER_DN and SERIALNO; if it is
* not already in the cache, try to find it from other resources. */
ksba_cert_t
find_cert_bysn (ctrl_t ctrl, const char *issuer_dn, ksba_sexp_t serialno)
{
gpg_error_t err;
ksba_cert_t cert;
cert_fetch_context_t context = NULL;
char *hexsn, *buf;
/* First check whether it has already been cached. */
cert = get_cert_bysn (issuer_dn, serialno);
if (cert)
return cert;
/* Ask back to the service requester to return the certificate.
* This is because we can assume that he already used the
* certificate while checking for the CRL. */
hexsn = serial_hex (serialno);
if (!hexsn)
{
log_error ("serial_hex() failed\n");
return NULL;
}
buf = strconcat ("#", hexsn, "/", issuer_dn, NULL);
if (!buf)
{
log_error ("can't allocate enough memory: %s\n", strerror (errno));
xfree (hexsn);
return NULL;
}
xfree (hexsn);
cert = get_cert_local (ctrl, buf);
xfree (buf);
if (cert)
{
cache_cert (cert);
return cert; /* Done. */
}
if (DBG_LOOKUP)
log_debug ("find_cert_bysn: certificate not returned by caller"
" - doing lookup\n");
/* Retrieve the certificate from external resources. */
while (!cert)
{
ksba_sexp_t sn;
char *issdn;
if (!context)
{
err = ca_cert_fetch (ctrl, &context, issuer_dn);
if (err)
{
log_error (_("error fetching certificate by S/N: %s\n"),
gpg_strerror (err));
break;
}
}
err = fetch_next_ksba_cert (context, &cert);
if (err)
{
log_error (_("error fetching certificate by S/N: %s\n"),
gpg_strerror (err) );
break;
}
issdn = ksba_cert_get_issuer (cert, 0);
if (strcmp (issuer_dn, issdn))
{
log_debug ("find_cert_bysn: Ooops: issuer DN does not match\n");
ksba_cert_release (cert);
cert = NULL;
ksba_free (issdn);
break;
}
sn = ksba_cert_get_serial (cert);
if (DBG_LOOKUP)
{
log_debug (" considering certificate (#");
dump_serial (sn);
log_printf ("/");
dump_string (issdn);
log_printf (")\n");
}
if (!compare_serialno (serialno, sn))
{
ksba_free (sn);
ksba_free (issdn);
cache_cert (cert);
if (DBG_LOOKUP)
log_debug (" found\n");
break; /* Ready. */
}
ksba_free (sn);
ksba_free (issdn);
ksba_cert_release (cert);
cert = NULL;
}
end_cert_fetch (context);
return cert;
}
/* Return the certificate matching SUBJECT_DN and (if not NULL)
* KEYID. If it is not already in the cache, try to find it from other
* resources. Note, that the external search does not work for user
* certificates because the LDAP lookup is on the caCertificate
* attribute. For our purposes this is just fine. */
ksba_cert_t
find_cert_bysubject (ctrl_t ctrl, const char *subject_dn, ksba_sexp_t keyid)
{
gpg_error_t err;
int seq;
ksba_cert_t cert = NULL;
cert_fetch_context_t context = NULL;
ksba_sexp_t subj;
/* If we have certificates from an OCSP request we first try to use
* them. This is because these certificates will really be the
* required ones and thus even in the case that they can't be
* uniquely located by the following code we can use them. This is
* for example required by Telesec certificates where a keyId is
* used but the issuer certificate comes without a subject keyId! */
if (ctrl->ocsp_certs && subject_dn)
{
cert_item_t ci;
cert_ref_t cr;
int i;
/* For efficiency reasons we won't use get_cert_bysubject here. */
acquire_cache_read_lock ();
for (i=0; i < 256; i++)
for (ci=cert_cache[i]; ci; ci = ci->next)
if (ci->cert && ci->subject_dn
&& !strcmp (ci->subject_dn, subject_dn))
for (cr=ctrl->ocsp_certs; cr; cr = cr->next)
if (!memcmp (ci->fpr, cr->fpr, 20))
{
ksba_cert_ref (ci->cert);
release_cache_lock ();
+ if (DBG_LOOKUP)
+ log_debug ("%s: certificate found in the cache"
+ " via ocsp_certs\n", __func__);
return ci->cert; /* We use this certificate. */
}
release_cache_lock ();
if (DBG_LOOKUP)
log_debug ("find_cert_bysubject: certificate not in ocsp_certs\n");
}
- /* No check whether the certificate is cached. */
+ /* Now check whether the certificate is cached. */
for (seq=0; (cert = get_cert_bysubject (subject_dn, seq)); seq++)
{
if (!keyid)
break; /* No keyid requested, so return the first one found. */
if (!ksba_cert_get_subj_key_id (cert, NULL, &subj)
&& !cmp_simple_canon_sexp (keyid, subj))
{
xfree (subj);
+ if (DBG_LOOKUP)
+ log_debug ("%s: certificate found in the cache"
+ " via subject DN\n", __func__);
break; /* Found matching cert. */
}
xfree (subj);
ksba_cert_release (cert);
}
if (cert)
return cert; /* Done. */
+ /* If we do not have a subject DN but have a keyid, try to locate it
+ * by keyid. */
+ if (!subject_dn && keyid)
+ {
+ int i;
+ cert_item_t ci;
+ ksba_sexp_t ski;
+
+ acquire_cache_read_lock ();
+ for (i=0; i < 256; i++)
+ for (ci=cert_cache[i]; ci; ci = ci->next)
+ if (ci->cert && !ksba_cert_get_subj_key_id (ci->cert, NULL, &ski))
+ {
+ if (!cmp_simple_canon_sexp (keyid, ski))
+ {
+ ksba_free (ski);
+ ksba_cert_ref (ci->cert);
+ release_cache_lock ();
+ if (DBG_LOOKUP)
+ log_debug ("%s: certificate found in the cache"
+ " via ski\n", __func__);
+ return ci->cert;
+ }
+ ksba_free (ski);
+ }
+ release_cache_lock ();
+ }
+
if (DBG_LOOKUP)
log_debug ("find_cert_bysubject: certificate not in cache\n");
/* Ask back to the service requester to return the certificate.
* This is because we can assume that he already used the
* certificate while checking for the CRL. */
if (keyid)
cert = get_cert_local_ski (ctrl, subject_dn, keyid);
else
{
/* In contrast to get_cert_local_ski, get_cert_local uses any
* passed pattern, so we need to make sure that an exact subject
* search is done. */
char *buf;
buf = strconcat ("/", subject_dn, NULL);
if (!buf)
{
log_error ("can't allocate enough memory: %s\n", strerror (errno));
return NULL;
}
cert = get_cert_local (ctrl, buf);
xfree (buf);
}
if (cert)
{
cache_cert (cert);
return cert; /* Done. */
}
if (DBG_LOOKUP)
log_debug ("find_cert_bysubject: certificate not returned by caller"
" - doing lookup\n");
/* Locate the certificate using external resources. */
while (!cert)
{
char *subjdn;
if (!context)
{
err = ca_cert_fetch (ctrl, &context, subject_dn);
if (err)
{
log_error (_("error fetching certificate by subject: %s\n"),
gpg_strerror (err));
break;
}
}
err = fetch_next_ksba_cert (context, &cert);
if (err)
{
log_error (_("error fetching certificate by subject: %s\n"),
gpg_strerror (err) );
break;
}
subjdn = ksba_cert_get_subject (cert, 0);
if (strcmp (subject_dn, subjdn))
{
log_info ("find_cert_bysubject: subject DN does not match\n");
ksba_cert_release (cert);
cert = NULL;
ksba_free (subjdn);
continue;
}
if (DBG_LOOKUP)
{
log_debug (" considering certificate (/");
dump_string (subjdn);
log_printf (")\n");
}
ksba_free (subjdn);
/* If no key ID has been provided, we return the first match. */
if (!keyid)
{
cache_cert (cert);
if (DBG_LOOKUP)
log_debug (" found\n");
break; /* Ready. */
}
/* With the key ID given we need to compare it. */
if (!ksba_cert_get_subj_key_id (cert, NULL, &subj))
{
if (!cmp_simple_canon_sexp (keyid, subj))
{
ksba_free (subj);
cache_cert (cert);
if (DBG_LOOKUP)
log_debug (" found\n");
break; /* Ready. */
}
}
ksba_free (subj);
ksba_cert_release (cert);
cert = NULL;
}
end_cert_fetch (context);
return cert;
}
/* Return 0 if the certificate is a trusted certificate. Returns
* GPG_ERR_NOT_TRUSTED if it is not trusted or other error codes in
* case of systems errors. TRUSTCLASSES are the bitwise ORed
* CERTTRUST_CLASS values to use for the check. */
gpg_error_t
is_trusted_cert (ksba_cert_t cert, unsigned int trustclasses)
{
unsigned char fpr[20];
cert_item_t ci;
cert_compute_fpr (cert, fpr);
acquire_cache_read_lock ();
for (ci=cert_cache[*fpr]; ci; ci = ci->next)
if (ci->cert && !memcmp (ci->fpr, fpr, 20))
{
if ((ci->trustclasses & trustclasses))
{
/* The certificate is trusted in one of the given
* TRUSTCLASSES. */
release_cache_lock ();
return 0; /* Yes, it is trusted. */
}
break;
}
release_cache_lock ();
return gpg_error (GPG_ERR_NOT_TRUSTED);
}
/* Given the certificate CERT locate the issuer for this certificate
* and return it at R_CERT. Returns 0 on success or
* GPG_ERR_NOT_FOUND. */
gpg_error_t
find_issuing_cert (ctrl_t ctrl, ksba_cert_t cert, ksba_cert_t *r_cert)
{
gpg_error_t err;
char *issuer_dn;
ksba_cert_t issuer_cert = NULL;
ksba_name_t authid;
ksba_sexp_t authidno;
ksba_sexp_t keyid;
*r_cert = NULL;
issuer_dn = ksba_cert_get_issuer (cert, 0);
if (!issuer_dn)
{
log_error (_("no issuer found in certificate\n"));
err = gpg_error (GPG_ERR_BAD_CERT);
goto leave;
}
/* First we need to check whether we can return that certificate
using the authorithyKeyIdentifier. */
err = ksba_cert_get_auth_key_id (cert, &keyid, &authid, &authidno);
if (err)
{
log_info (_("error getting authorityKeyIdentifier: %s\n"),
gpg_strerror (err));
}
else
{
const char *s = ksba_name_enum (authid, 0);
if (s && *authidno)
{
issuer_cert = find_cert_bysn (ctrl, s, authidno);
}
if (!issuer_cert && keyid)
{
/* Not found by issuer+s/n. Now that we have an AKI
* keyIdentifier look for a certificate with a matching
* SKI. */
issuer_cert = find_cert_bysubject (ctrl, issuer_dn, keyid);
}
/* Print a note so that the user does not feel too helpless when
* an issuer certificate was found and gpgsm prints BAD
* signature because it is not the correct one. */
if (!issuer_cert)
{
log_info ("issuer certificate ");
if (keyid)
{
log_printf ("{");
dump_serial (keyid);
log_printf ("} ");
}
if (authidno)
{
log_printf ("(#");
dump_serial (authidno);
log_printf ("/");
dump_string (s);
log_printf (") ");
}
log_printf ("not found using authorityKeyIdentifier\n");
}
ksba_name_release (authid);
xfree (authidno);
xfree (keyid);
}
/* If this did not work, try just with the issuer's name and assume
* that there is only one such certificate. We only look into our
* cache then. */
if (err || !issuer_cert)
{
issuer_cert = get_cert_bysubject (issuer_dn, 0);
if (issuer_cert)
err = 0;
}
leave:
if (!err && !issuer_cert)
err = gpg_error (GPG_ERR_NOT_FOUND);
xfree (issuer_dn);
if (err)
ksba_cert_release (issuer_cert);
else
*r_cert = issuer_cert;
return err;
}
/* Read a list of certificates in PEM format from stream FP and store
* them on success at R_CERTLIST. On error NULL is stored at R_CERT
* list and an error code returned. Note that even on success an
* empty list of certificates can be returned (i.e. NULL stored at
* R_CERTLIST) iff the input stream has no certificates. */
gpg_error_t
read_certlist_from_stream (certlist_t *r_certlist, estream_t fp)
{
gpg_error_t err;
gnupg_ksba_io_t ioctx = NULL;
ksba_reader_t reader;
ksba_cert_t cert = NULL;
certlist_t certlist = NULL;
certlist_t cl, *cltail;
*r_certlist = NULL;
err = gnupg_ksba_create_reader (&ioctx,
(GNUPG_KSBA_IO_PEM | GNUPG_KSBA_IO_MULTIPEM),
fp, &reader);
if (err)
goto leave;
/* Loop to read all certificates from the stream. */
cltail = &certlist;
do
{
ksba_cert_release (cert);
cert = NULL;
err = ksba_cert_new (&cert);
if (!err)
err = ksba_cert_read_der (cert, reader);
if (err)
{
if (gpg_err_code (err) == GPG_ERR_EOF)
err = 0;
goto leave;
}
/* Append the certificate to the list. We also store the
* fingerprint and check whether we have a cached certificate;
* in that case the cached certificate is put into the list to
* take advantage of a validation result which might be stored
* in the cached certificate. */
cl = xtrycalloc (1, sizeof *cl);
if (!cl)
{
err = gpg_error_from_syserror ();
goto leave;
}
cert_compute_fpr (cert, cl->fpr);
cl->cert = get_cert_byfpr (cl->fpr);
if (!cl->cert)
{
cl->cert = cert;
cert = NULL;
}
*cltail = cl;
cltail = &cl->next;
ksba_reader_clear (reader, NULL, NULL);
}
while (!gnupg_ksba_reader_eof_seen (ioctx));
leave:
ksba_cert_release (cert);
gnupg_ksba_destroy_reader (ioctx);
if (err)
release_certlist (certlist);
else
*r_certlist = certlist;
return err;
}
/* Release the certificate list CL. */
void
release_certlist (certlist_t cl)
{
while (cl)
{
certlist_t next = cl->next;
ksba_cert_release (cl->cert);
cl = next;
}
}
diff --git a/dirmngr/dns.c b/dirmngr/dns.c
index fa5e5283d..142e8d2c1 100644
--- a/dirmngr/dns.c
+++ b/dirmngr/dns.c
@@ -1,11564 +1,11564 @@
/* ==========================================================================
* dns.c - Recursive, Reentrant DNS Resolver.
* --------------------------------------------------------------------------
* Copyright (c) 2008, 2009, 2010, 2012-2016 William Ahern
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to permit
* persons to whom the Software is furnished to do so, subject to the
* following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
* NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
* USE OR OTHER DEALINGS IN THE SOFTWARE.
* ==========================================================================
*/
#if HAVE_CONFIG_H
#include "config.h"
#elif !defined _GNU_SOURCE
#define _GNU_SOURCE 1
#endif
#include <limits.h> /* INT_MAX */
#include <stdarg.h> /* va_list va_start va_end */
#include <stddef.h> /* offsetof() */
#ifdef _WIN32
/* JW: This breaks our mingw build: #define uint32_t unsigned int */
#else
#include <stdint.h> /* uint32_t */
#endif
#include <stdlib.h> /* malloc(3) realloc(3) free(3) rand(3) random(3) arc4random(3) */
#include <stdio.h> /* FILE fopen(3) fclose(3) getc(3) rewind(3) vsnprintf(3) */
#include <string.h> /* memcpy(3) strlen(3) memmove(3) memchr(3) memcmp(3) strchr(3) strsep(3) strcspn(3) */
#include <strings.h> /* strcasecmp(3) strncasecmp(3) */
#include <ctype.h> /* isspace(3) isdigit(3) */
#include <time.h> /* time_t time(2) difftime(3) */
#include <signal.h> /* SIGPIPE sigemptyset(3) sigaddset(3) sigpending(2) sigprocmask(2) pthread_sigmask(3) sigtimedwait(2) */
#include <errno.h> /* errno EINVAL ENOENT */
#undef NDEBUG
#include <assert.h> /* assert(3) */
#if _WIN32
#ifndef FD_SETSIZE
#define FD_SETSIZE 1024
#endif
#include <winsock2.h>
#include <ws2tcpip.h>
typedef SOCKET socket_fd_t;
#define STDCALL __stdcall
#ifdef TIME_WITH_SYS_TIME
#include <sys/time.h> /* gettimeofday(2) */
#endif
#else
typedef int socket_fd_t;
#define STDCALL
#include <sys/time.h> /* gettimeofday(2) */
#include <sys/types.h> /* FD_SETSIZE socklen_t */
#include <sys/select.h> /* FD_ZERO FD_SET fd_set select(2) */
#include <sys/socket.h> /* AF_INET AF_INET6 AF_UNIX struct sockaddr struct sockaddr_in struct sockaddr_in6 socket(2) */
#if defined(AF_UNIX)
#include <sys/un.h> /* struct sockaddr_un */
#endif
#include <fcntl.h> /* F_SETFD F_GETFL F_SETFL O_NONBLOCK fcntl(2) */
#include <unistd.h> /* _POSIX_THREADS gethostname(3) close(2) */
#include <poll.h> /* POLLIN POLLOUT */
#include <netinet/in.h> /* struct sockaddr_in struct sockaddr_in6 */
#include <arpa/inet.h> /* inet_pton(3) inet_ntop(3) htons(3) ntohs(3) */
#include <netdb.h> /* struct addrinfo */
#endif
#include "gpgrt.h" /* For GGPRT_GCC_VERSION */
#include "dns.h"
/*
* C O M P I L E R V E R S I O N & F E A T U R E D E T E C T I O N
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#define DNS_GNUC_2VER(M, m, p) (((M) * 10000) + ((m) * 100) + (p))
#define DNS_GNUC_PREREQ(M, m, p) (__GNUC__ > 0 && DNS_GNUC_2VER(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__) >= DNS_GNUC_2VER((M), (m), (p)))
#define DNS_MSC_2VER(M, m, p) ((((M) + 6) * 10000000) + ((m) * 1000000) + (p))
#define DNS_MSC_PREREQ(M, m, p) (_MSC_VER_FULL > 0 && _MSC_VER_FULL >= DNS_MSC_2VER((M), (m), (p)))
#define DNS_SUNPRO_PREREQ(M, m, p) (__SUNPRO_C > 0 && __SUNPRO_C >= 0x ## M ## m ## p)
#if defined __has_builtin
#define dns_has_builtin(x) __has_builtin(x)
#else
#define dns_has_builtin(x) 0
#endif
#if defined __has_extension
#define dns_has_extension(x) __has_extension(x)
#else
#define dns_has_extension(x) 0
#endif
#ifndef HAVE___ASSUME
#define HAVE___ASSUME DNS_MSC_PREREQ(8,0,0)
#endif
#ifndef HAVE___BUILTIN_TYPES_COMPATIBLE_P
#define HAVE___BUILTIN_TYPES_COMPATIBLE_P (DNS_GNUC_PREREQ(3,1,1) || __clang__)
#endif
#ifndef HAVE___BUILTIN_UNREACHABLE
#define HAVE___BUILTIN_UNREACHABLE (DNS_GNUC_PREREQ(4,5,0) || dns_has_builtin(__builtin_unreachable))
#endif
#ifndef HAVE_PRAGMA_MESSAGE
#define HAVE_PRAGMA_MESSAGE (DNS_GNUC_PREREQ(4,4,0) || __clang__ || _MSC_VER)
#endif
/*
* C O M P I L E R A N N O T A T I O N S
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#if __GNUC__
#define DNS_NOTUSED __attribute__((unused))
#define DNS_NORETURN __attribute__((noreturn))
#else
#define DNS_NOTUSED
#define DNS_NORETURN
#endif
#if __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-parameter"
#pragma clang diagnostic ignored "-Wmissing-field-initializers"
#elif DNS_GNUC_PREREQ(4,6,0)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-parameter"
#pragma GCC diagnostic ignored "-Wmissing-field-initializers"
#endif
/*
* S T A N D A R D M A C R O S
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#if HAVE___BUILTIN_TYPES_COMPATIBLE_P
#define dns_same_type(a, b, def) __builtin_types_compatible_p(__typeof__ (a), __typeof__ (b))
#else
#define dns_same_type(a, b, def) (def)
#endif
#define dns_isarray(a) (!dns_same_type((a), (&(a)[0]), 0))
/* NB: "_" field silences Sun Studio "zero-sized struct/union" error diagnostic */
#define dns_inline_assert(cond) ((void)(sizeof (struct { int:-!(cond); int _; })))
#if HAVE___ASSUME
#define dns_assume(cond) __assume(cond)
#elif HAVE___BUILTIN_UNREACHABLE
#define dns_assume(cond) do { if (!(cond)) __builtin_unreachable(); } while (0)
#else
#define dns_assume(cond) do { (void)(cond); } while (0)
#endif
#ifndef lengthof
#define lengthof(a) (dns_inline_assert(dns_isarray(a)), (sizeof (a) / sizeof (a)[0]))
#endif
#ifndef endof
#define endof(a) (dns_inline_assert(dns_isarray(a)), &(a)[lengthof((a))])
#endif
/*
* M I S C E L L A N E O U S C O M P A T
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#if _WIN32 || _WIN64
#define PRIuZ "Iu"
#else
#define PRIuZ "zu"
#endif
#ifndef DNS_THREAD_SAFE
#if (defined _REENTRANT || defined _THREAD_SAFE) && _POSIX_THREADS > 0
#define DNS_THREAD_SAFE 1
#else
#define DNS_THREAD_SAFE 0
#endif
#endif
#ifndef HAVE__STATIC_ASSERT
#define HAVE__STATIC_ASSERT \
(dns_has_extension(c_static_assert) || DNS_GNUC_PREREQ(4,6,0) || \
__C11FEATURES__ || __STDC_VERSION__ >= 201112L)
#endif
#ifndef HAVE_STATIC_ASSERT
#if DNS_GNUC_PREREQ(0,0,0) && !DNS_GNUC_PREREQ(4,6,0)
#define HAVE_STATIC_ASSERT 0 /* glibc doesn't check GCC version */
#elif defined(static_assert)
#define HAVE_STATIC_ASSERT 1
#else
#define HAVE_STATIC_ASSERT 0
#endif
#endif
#if HAVE_STATIC_ASSERT
#define dns_static_assert(cond, msg) static_assert(cond, msg)
#elif HAVE__STATIC_ASSERT
#define dns_static_assert(cond, msg) _Static_assert(cond, msg)
#else
#define dns_static_assert(cond, msg) extern char DNS_PP_XPASTE(dns_assert_, __LINE__)[sizeof (int[1 - 2*!(cond)])]
#endif
/*
* D E B U G M A C R O S
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
int *dns_debug_p(void) {
static int debug;
return &debug;
} /* dns_debug_p() */
#if DNS_DEBUG
#undef DNS_DEBUG
#define DNS_DEBUG dns_debug
#define DNS_SAY_(fmt, ...) \
do { if (DNS_DEBUG > 0) fprintf(stderr, fmt "%.1s", __func__, __LINE__, __VA_ARGS__); } while (0)
#define DNS_SAY(...) DNS_SAY_("@@ (%s:%d) " __VA_ARGS__, "\n")
#define DNS_HAI DNS_SAY("HAI")
#define DNS_SHOW_(P, fmt, ...) do { \
if (DNS_DEBUG > 1) { \
fprintf(stderr, "@@ BEGIN * * * * * * * * * * * *\n"); \
fprintf(stderr, "@@ " fmt "%.0s\n", __VA_ARGS__); \
dns_p_dump((P), stderr); \
fprintf(stderr, "@@ END * * * * * * * * * * * * *\n\n"); \
} \
} while (0)
#define DNS_SHOW(...) DNS_SHOW_(__VA_ARGS__, "")
#else /* !DNS_DEBUG */
#undef DNS_DEBUG
#define DNS_DEBUG 0
#define DNS_SAY(...)
#define DNS_HAI
#define DNS_SHOW(...)
#endif /* DNS_DEBUG */
#define DNS_CARP(...) DNS_SAY(__VA_ARGS__)
/*
* V E R S I O N R O U T I N E S
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
const char *dns_vendor(void) {
return DNS_VENDOR;
} /* dns_vendor() */
int dns_v_rel(void) {
return DNS_V_REL;
} /* dns_v_rel() */
int dns_v_abi(void) {
return DNS_V_ABI;
} /* dns_v_abi() */
int dns_v_api(void) {
return DNS_V_API;
} /* dns_v_api() */
/*
* E R R O R R O U T I N E S
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#ifndef EPROTO
# define EPROTO EPROTONOSUPPORT
#endif
#if _WIN32
#define DNS_EINTR WSAEINTR
#define DNS_EINPROGRESS WSAEINPROGRESS
#define DNS_EISCONN WSAEISCONN
#define DNS_EWOULDBLOCK WSAEWOULDBLOCK
#define DNS_EALREADY WSAEALREADY
#define DNS_EAGAIN EAGAIN
#define DNS_ETIMEDOUT WSAETIMEDOUT
#define dns_syerr() ((int)GetLastError())
#define dns_soerr() ((int)WSAGetLastError())
#else
#define DNS_EINTR EINTR
#define DNS_EINPROGRESS EINPROGRESS
#define DNS_EISCONN EISCONN
#define DNS_EWOULDBLOCK EWOULDBLOCK
#define DNS_EALREADY EALREADY
#define DNS_EAGAIN EAGAIN
#define DNS_ETIMEDOUT ETIMEDOUT
#define dns_syerr() errno
#define dns_soerr() errno
#endif
const char *dns_strerror(int error) {
switch (error) {
case DNS_ENOBUFS:
return "DNS packet buffer too small";
case DNS_EILLEGAL:
return "Illegal DNS RR name or data";
case DNS_EORDER:
return "Attempt to push RR out of section order";
case DNS_ESECTION:
return "Invalid section specified";
case DNS_EUNKNOWN:
return "Unknown DNS error";
case DNS_EADDRESS:
return "Invalid textual address form";
case DNS_ENOQUERY:
return "Bad execution state (missing query packet)";
case DNS_ENOANSWER:
return "Bad execution state (missing answer packet)";
case DNS_EFETCHED:
return "Answer already fetched";
case DNS_ESERVICE:
return "The service passed was not recognized for the specified socket type";
case DNS_ENONAME:
return "The name does not resolve for the supplied parameters";
case DNS_EFAIL:
return "A non-recoverable error occurred when attempting to resolve the name";
case DNS_ECONNFIN:
return "Connection closed";
case DNS_EVERIFY:
return "Reply failed verification";
default:
return strerror(error);
} /* switch() */
} /* dns_strerror() */
/*
* A T O M I C R O U T I N E S
*
* Use GCC's __atomic built-ins if possible. Unlike the __sync built-ins, we
* can use the preprocessor to detect API and, more importantly, ISA
* support. We want to avoid linking headaches where the API depends on an
* external library if the ISA (e.g. i386) doesn't support lockless
* operation.
*
* TODO: Support C11's atomic API. Although that may require some finesse
* with how we define some public types, such as dns_atomic_t and struct
* dns_resolv_conf.
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#ifndef HAVE___ATOMIC_FETCH_ADD
#ifdef __ATOMIC_RELAXED
#define HAVE___ATOMIC_FETCH_ADD 1
#else
#define HAVE___ATOMIC_FETCH_ADD 0
#endif
#endif
#ifndef HAVE___ATOMIC_FETCH_SUB
#define HAVE___ATOMIC_FETCH_SUB HAVE___ATOMIC_FETCH_ADD
#endif
#ifndef DNS_ATOMIC_FETCH_ADD
#if HAVE___ATOMIC_FETCH_ADD && __GCC_ATOMIC_LONG_LOCK_FREE == 2
#define DNS_ATOMIC_FETCH_ADD(i) __atomic_fetch_add((i), 1, __ATOMIC_RELAXED)
#else
#pragma message("no atomic_fetch_add available")
#define DNS_ATOMIC_FETCH_ADD(i) ((*(i))++)
#endif
#endif
#ifndef DNS_ATOMIC_FETCH_SUB
#if HAVE___ATOMIC_FETCH_SUB && __GCC_ATOMIC_LONG_LOCK_FREE == 2
#define DNS_ATOMIC_FETCH_SUB(i) __atomic_fetch_sub((i), 1, __ATOMIC_RELAXED)
#else
#pragma message("no atomic_fetch_sub available")
#define DNS_ATOMIC_FETCH_SUB(i) ((*(i))--)
#endif
#endif
static inline unsigned dns_atomic_fetch_add(dns_atomic_t *i) {
return DNS_ATOMIC_FETCH_ADD(i);
} /* dns_atomic_fetch_add() */
static inline unsigned dns_atomic_fetch_sub(dns_atomic_t *i) {
return DNS_ATOMIC_FETCH_SUB(i);
} /* dns_atomic_fetch_sub() */
/*
* C R Y P T O R O U T I N E S
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/*
* P R N G
*/
#ifndef DNS_RANDOM
#if defined(HAVE_ARC4RANDOM) \
|| defined(__OpenBSD__) \
|| defined(__FreeBSD__) \
|| defined(__NetBSD__) \
|| defined(__APPLE__)
#define DNS_RANDOM arc4random
#elif __linux
#define DNS_RANDOM random
#else
#define DNS_RANDOM rand
#endif
#endif
#define DNS_RANDOM_arc4random 1
#define DNS_RANDOM_random 2
#define DNS_RANDOM_rand 3
#define DNS_RANDOM_RAND_bytes 4
#define DNS_RANDOM_OPENSSL (DNS_RANDOM_RAND_bytes == DNS_PP_XPASTE(DNS_RANDOM_, DNS_RANDOM))
#if DNS_RANDOM_OPENSSL
#include <openssl/rand.h>
#endif
static unsigned dns_random_(void) {
#if DNS_RANDOM_OPENSSL
unsigned r;
_Bool ok;
ok = (1 == RAND_bytes((unsigned char *)&r, sizeof r));
assert(ok && "1 == RAND_bytes()");
return r;
#else
return DNS_RANDOM();
#endif
} /* dns_random_() */
dns_random_f **dns_random_p(void) {
static dns_random_f *random_f = &dns_random_;
return &random_f;
} /* dns_random_p() */
/*
* P E R M U T A T I O N G E N E R A T O R
*/
#define DNS_K_TEA_KEY_SIZE 16
#define DNS_K_TEA_BLOCK_SIZE 8
#define DNS_K_TEA_CYCLES 32
#define DNS_K_TEA_MAGIC 0x9E3779B9U
struct dns_k_tea {
uint32_t key[DNS_K_TEA_KEY_SIZE / sizeof (uint32_t)];
unsigned cycles;
}; /* struct dns_k_tea */
static void dns_k_tea_init(struct dns_k_tea *tea, uint32_t key[], unsigned cycles) {
memcpy(tea->key, key, sizeof tea->key);
tea->cycles = (cycles)? cycles : DNS_K_TEA_CYCLES;
} /* dns_k_tea_init() */
static void dns_k_tea_encrypt(struct dns_k_tea *tea, uint32_t v[], uint32_t *w) {
uint32_t y, z, sum, n;
y = v[0];
z = v[1];
sum = 0;
for (n = 0; n < tea->cycles; n++) {
sum += DNS_K_TEA_MAGIC;
y += ((z << 4) + tea->key[0]) ^ (z + sum) ^ ((z >> 5) + tea->key[1]);
z += ((y << 4) + tea->key[2]) ^ (y + sum) ^ ((y >> 5) + tea->key[3]);
}
w[0] = y;
w[1] = z;
return /* void */;
} /* dns_k_tea_encrypt() */
/*
* Permutation generator, based on a Luby-Rackoff Feistel construction.
*
* Specifically, this is a generic balanced Feistel block cipher using TEA
* (another block cipher) as the pseudo-random function, F. At best it's as
* strong as F (TEA), notwithstanding the seeding. F could be AES, SHA-1, or
* perhaps Bernstein's Salsa20 core; I am naively trying to keep things
* simple.
*
* The generator can create a permutation of any set of numbers, as long as
* the size of the set is an even power of 2. This limitation arises either
* out of an inherent property of balanced Feistel constructions, or by my
* own ignorance. I'll tackle an unbalanced construction after I wrap my
* head around Schneier and Kelsey's paper.
*
* CAVEAT EMPTOR. IANAC.
*/
#define DNS_K_PERMUTOR_ROUNDS 8
struct dns_k_permutor {
unsigned stepi, length, limit;
unsigned shift, mask, rounds;
struct dns_k_tea tea;
}; /* struct dns_k_permutor */
static inline unsigned dns_k_permutor_powof(unsigned n) {
unsigned m, i = 0;
for (m = 1; m < n; m <<= 1, i++)
;;
return i;
} /* dns_k_permutor_powof() */
static void dns_k_permutor_init(struct dns_k_permutor *p, unsigned low, unsigned high) {
uint32_t key[DNS_K_TEA_KEY_SIZE / sizeof (uint32_t)];
unsigned width, i;
p->stepi = 0;
p->length = (high - low) + 1;
p->limit = high;
width = dns_k_permutor_powof(p->length);
width += width % 2;
p->shift = width / 2;
p->mask = (1U << p->shift) - 1;
p->rounds = DNS_K_PERMUTOR_ROUNDS;
for (i = 0; i < lengthof(key); i++)
key[i] = dns_random();
dns_k_tea_init(&p->tea, key, 0);
return /* void */;
} /* dns_k_permutor_init() */
static unsigned dns_k_permutor_F(struct dns_k_permutor *p, unsigned k, unsigned x) {
uint32_t in[DNS_K_TEA_BLOCK_SIZE / sizeof (uint32_t)], out[DNS_K_TEA_BLOCK_SIZE / sizeof (uint32_t)];
memset(in, '\0', sizeof in);
in[0] = k;
in[1] = x;
dns_k_tea_encrypt(&p->tea, in, out);
return p->mask & out[0];
} /* dns_k_permutor_F() */
static unsigned dns_k_permutor_E(struct dns_k_permutor *p, unsigned n) {
unsigned l[2], r[2];
unsigned i;
i = 0;
l[i] = p->mask & (n >> p->shift);
r[i] = p->mask & (n >> 0);
do {
l[(i + 1) % 2] = r[i % 2];
r[(i + 1) % 2] = l[i % 2] ^ dns_k_permutor_F(p, i, r[i % 2]);
i++;
} while (i < p->rounds - 1);
return ((l[i % 2] & p->mask) << p->shift) | ((r[i % 2] & p->mask) << 0);
} /* dns_k_permutor_E() */
DNS_NOTUSED static unsigned dns_k_permutor_D(struct dns_k_permutor *p, unsigned n) {
unsigned l[2], r[2];
unsigned i;
i = p->rounds - 1;
l[i % 2] = p->mask & (n >> p->shift);
r[i % 2] = p->mask & (n >> 0);
do {
i--;
r[i % 2] = l[(i + 1) % 2];
l[i % 2] = r[(i + 1) % 2] ^ dns_k_permutor_F(p, i, l[(i + 1) % 2]);
} while (i > 0);
return ((l[i % 2] & p->mask) << p->shift) | ((r[i % 2] & p->mask) << 0);
} /* dns_k_permutor_D() */
static unsigned dns_k_permutor_step(struct dns_k_permutor *p) {
unsigned n;
do {
n = dns_k_permutor_E(p, p->stepi++);
} while (n >= p->length);
return n + (p->limit + 1 - p->length);
} /* dns_k_permutor_step() */
/*
* Simple permutation box. Useful for shuffling rrsets from an iterator.
* Uses AES s-box to provide good diffusion.
*
* Seems to pass muster under runs test.
*
* $ for i in 0 1 2 3 4 5 6 7 8 9; do ./dns shuffle-16 > /tmp/out; done
* $ R -q -f /dev/stdin 2>/dev/null <<-EOF | awk '/p-value/{ print $8 }'
* library(lawstat)
* runs.test(scan(file="/tmp/out"))
* EOF
*/
static unsigned short dns_k_shuffle16(unsigned short n, unsigned s) {
static const unsigned char sbox[256] =
{ 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5,
0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,
0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0,
0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,
0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc,
0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a,
0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75,
0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0,
0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84,
0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b,
0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,
0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85,
0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8,
0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5,
0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2,
0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17,
0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,
0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88,
0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb,
0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c,
0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,
0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9,
0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,
0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6,
0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a,
0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e,
0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e,
0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94,
0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68,
0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16 };
unsigned char a, b;
unsigned i;
a = 0xff & (n >> 0);
b = 0xff & (n >> 8);
for (i = 0; i < 4; i++) {
a ^= 0xff & s;
a = sbox[a] ^ b;
b = sbox[b] ^ a;
s >>= 8;
}
return ((0xff00 & (a << 8)) | (0x00ff & (b << 0)));
} /* dns_k_shuffle16() */
/*
* S T A T E M A C H I N E R O U T I N E S
*
* Application code should define DNS_SM_RESTORE and DNS_SM_SAVE, and the
* local variable pc.
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#define DNS_SM_ENTER \
do { \
static const int pc0 = __LINE__; \
DNS_SM_RESTORE; \
switch (pc0 + pc) { \
case __LINE__: (void)0
#define DNS_SM_SAVE_AND_DO(do_statement) \
do { \
pc = __LINE__ - pc0; \
DNS_SM_SAVE; \
do_statement; \
case __LINE__: (void)0; \
} while (0)
#define DNS_SM_YIELD(rv) \
DNS_SM_SAVE_AND_DO(return (rv))
#define DNS_SM_EXIT \
do { goto leave; } while (0)
#define DNS_SM_LEAVE \
leave: (void)0; \
DNS_SM_SAVE_AND_DO(break); \
} \
} while (0)
/*
* U T I L I T Y R O U T I N E S
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#define DNS_MAXINTERVAL 300
struct dns_clock {
time_t sample, elapsed;
}; /* struct dns_clock */
static void dns_begin(struct dns_clock *clk) {
clk->sample = time(0);
clk->elapsed = 0;
} /* dns_begin() */
static time_t dns_elapsed(struct dns_clock *clk) {
time_t curtime;
if ((time_t)-1 == time(&curtime))
return clk->elapsed;
if (curtime > clk->sample)
clk->elapsed += (time_t)DNS_PP_MIN(difftime(curtime, clk->sample), DNS_MAXINTERVAL);
clk->sample = curtime;
return clk->elapsed;
} /* dns_elapsed() */
DNS_NOTUSED static size_t dns_strnlen(const char *src, size_t m) {
size_t n = 0;
while (*src++ && n < m)
++n;
return n;
} /* dns_strnlen() */
DNS_NOTUSED static size_t dns_strnlcpy(char *dst, size_t lim, const char *src, size_t max) {
size_t len = dns_strnlen(src, max), n;
if (lim > 0) {
n = DNS_PP_MIN(lim - 1, len);
memcpy(dst, src, n);
dst[n] = '\0';
}
return len;
} /* dns_strnlcpy() */
#if (defined AF_UNIX && !defined _WIN32)
#define DNS_HAVE_SOCKADDR_UN 1
#else
#define DNS_HAVE_SOCKADDR_UN 0
#endif
static size_t dns_af_len(int af) {
static const size_t table[AF_MAX] = {
[AF_INET6] = sizeof (struct sockaddr_in6),
[AF_INET] = sizeof (struct sockaddr_in),
#if DNS_HAVE_SOCKADDR_UN
[AF_UNIX] = sizeof (struct sockaddr_un),
#endif
};
return table[af];
} /* dns_af_len() */
#define dns_sa_family(sa) (((struct sockaddr *)(sa))->sa_family)
#define dns_sa_len(sa) dns_af_len(dns_sa_family(sa))
#define DNS_SA_NOPORT &dns_sa_noport
static unsigned short dns_sa_noport;
static unsigned short *dns_sa_port(int af, void *sa) {
switch (af) {
case AF_INET6:
return &((struct sockaddr_in6 *)sa)->sin6_port;
case AF_INET:
return &((struct sockaddr_in *)sa)->sin_port;
default:
return DNS_SA_NOPORT;
}
} /* dns_sa_port() */
static void *dns_sa_addr(int af, const void *sa, socklen_t *size) {
switch (af) {
case AF_INET6: {
struct in6_addr *in6 = &((struct sockaddr_in6 *)sa)->sin6_addr;
if (size)
*size = sizeof *in6;
return in6;
}
case AF_INET: {
struct in_addr *in = &((struct sockaddr_in *)sa)->sin_addr;
if (size)
*size = sizeof *in;
return in;
}
default:
if (size)
*size = 0;
return 0;
}
} /* dns_sa_addr() */
#if DNS_HAVE_SOCKADDR_UN
#define DNS_SUNPATHMAX (sizeof ((struct sockaddr_un *)0)->sun_path)
#endif
DNS_NOTUSED static void *dns_sa_path(void *sa, socklen_t *size) {
switch (dns_sa_family(sa)) {
#if DNS_HAVE_SOCKADDR_UN
case AF_UNIX: {
char *path = ((struct sockaddr_un *)sa)->sun_path;
if (size)
*size = dns_strnlen(path, DNS_SUNPATHMAX);
return path;
}
#endif
default:
if (size)
*size = 0;
return NULL;
}
} /* dns_sa_path() */
static int dns_sa_cmp(void *a, void *b) {
int cmp, af;
if ((cmp = dns_sa_family(a) - dns_sa_family(b)))
return cmp;
switch ((af = dns_sa_family(a))) {
case AF_INET: {
struct in_addr *a4, *b4;
if ((cmp = htons(*dns_sa_port(af, a)) - htons(*dns_sa_port(af, b))))
return cmp;
a4 = dns_sa_addr(af, a, NULL);
b4 = dns_sa_addr(af, b, NULL);
if (ntohl(a4->s_addr) < ntohl(b4->s_addr))
return -1;
if (ntohl(a4->s_addr) > ntohl(b4->s_addr))
return 1;
return 0;
}
case AF_INET6: {
struct in6_addr *a6, *b6;
size_t i;
if ((cmp = htons(*dns_sa_port(af, a)) - htons(*dns_sa_port(af, b))))
return cmp;
a6 = dns_sa_addr(af, a, NULL);
b6 = dns_sa_addr(af, b, NULL);
/* XXX: do we need to use in6_clearscope()? */
for (i = 0; i < sizeof a6->s6_addr; i++) {
if ((cmp = a6->s6_addr[i] - b6->s6_addr[i]))
return cmp;
}
return 0;
}
#if DNS_HAVE_SOCKADDR_UN
case AF_UNIX: {
char a_path[DNS_SUNPATHMAX + 1], b_path[sizeof a_path];
dns_strnlcpy(a_path, sizeof a_path, dns_sa_path(a, NULL), DNS_SUNPATHMAX);
dns_strnlcpy(b_path, sizeof b_path, dns_sa_path(b, NULL), DNS_SUNPATHMAX);
return strcmp(a_path, b_path);
}
#endif
default:
return -1;
}
} /* dns_sa_cmp() */
#if _WIN32
static int dns_inet_pton(int af, const void *src, void *dst) {
union { struct sockaddr_in sin; struct sockaddr_in6 sin6; } u;
int size_of_u = (int)sizeof u;
u.sin.sin_family = af;
if (0 != WSAStringToAddressA((void *)src, af, (void *)0, (struct sockaddr *)&u, &size_of_u))
return -1;
switch (af) {
case AF_INET6:
*(struct in6_addr *)dst = u.sin6.sin6_addr;
return 1;
case AF_INET:
*(struct in_addr *)dst = u.sin.sin_addr;
return 1;
default:
return 0;
}
} /* dns_inet_pton() */
static const char *dns_inet_ntop(int af, const void *src, void *dst, unsigned long lim) {
union { struct sockaddr_in sin; struct sockaddr_in6 sin6; } u;
/* NOTE: WSAAddressToString will print .sin_port unless zeroed. */
memset(&u, 0, sizeof u);
u.sin.sin_family = af;
switch (af) {
case AF_INET6:
u.sin6.sin6_addr = *(struct in6_addr *)src;
break;
case AF_INET:
u.sin.sin_addr = *(struct in_addr *)src;
break;
default:
return 0;
}
if (0 != WSAAddressToStringA((struct sockaddr *)&u, dns_sa_len(&u), (void *)0, dst, &lim))
return 0;
return dst;
} /* dns_inet_ntop() */
#else
#define dns_inet_pton(...) inet_pton(__VA_ARGS__)
#define dns_inet_ntop(...) inet_ntop(__VA_ARGS__)
#endif
static dns_error_t dns_pton(int af, const void *src, void *dst) {
switch (dns_inet_pton(af, src, dst)) {
case 1:
return 0;
case -1:
return dns_soerr();
default:
return DNS_EADDRESS;
}
} /* dns_pton() */
static dns_error_t dns_ntop(int af, const void *src, void *dst, unsigned long lim) {
return (dns_inet_ntop(af, src, dst, lim))? 0 : dns_soerr();
} /* dns_ntop() */
size_t dns_strlcpy(char *dst, const char *src, size_t lim) {
char *d = dst;
char *e = &dst[lim];
const char *s = src;
if (d < e) {
do {
if ('\0' == (*d++ = *s++))
return s - src - 1;
} while (d < e);
d[-1] = '\0';
}
while (*s++ != '\0')
;;
return s - src - 1;
} /* dns_strlcpy() */
size_t dns_strlcat(char *dst, const char *src, size_t lim) {
char *d = memchr(dst, '\0', lim);
char *e = &dst[lim];
const char *s = src;
const char *p;
if (d && d < e) {
do {
if ('\0' == (*d++ = *s++))
return d - dst - 1;
} while (d < e);
d[-1] = '\0';
}
p = s;
while (*s++ != '\0')
;;
return lim + (s - p - 1);
} /* dns_strlcat() */
static void *dns_reallocarray(void *p, size_t nmemb, size_t size, dns_error_t *error) {
void *rp;
if (nmemb > 0 && SIZE_MAX / nmemb < size) {
*error = EOVERFLOW;
return NULL;
}
if (!(rp = realloc(p, nmemb * size)))
*error = (errno)? errno : EINVAL;
return rp;
} /* dns_reallocarray() */
#if _WIN32
static char *dns_strsep(char **sp, const char *delim) {
char *p;
if (!(p = *sp))
return 0;
*sp += strcspn(p, delim);
if (**sp != '\0') {
**sp = '\0';
++*sp;
} else
*sp = NULL;
return p;
} /* dns_strsep() */
#else
#define dns_strsep(...) strsep(__VA_ARGS__)
#endif
#if _WIN32
#define strcasecmp(...) _stricmp(__VA_ARGS__)
#define strncasecmp(...) _strnicmp(__VA_ARGS__)
#endif
static inline _Bool dns_isalpha(unsigned char c) {
return isalpha(c);
} /* dns_isalpha() */
static inline _Bool dns_isdigit(unsigned char c) {
return isdigit(c);
} /* dns_isdigit() */
static inline _Bool dns_isalnum(unsigned char c) {
return isalnum(c);
} /* dns_isalnum() */
static inline _Bool dns_isspace(unsigned char c) {
return isspace(c);
} /* dns_isspace() */
static inline _Bool dns_isgraph(unsigned char c) {
return isgraph(c);
} /* dns_isgraph() */
static int dns_poll(int fd, short events, int timeout) {
fd_set rset, wset;
struct timeval tv = { timeout, 0 };
if (!events)
return 0;
if (fd < 0 || (unsigned)fd >= FD_SETSIZE)
return EINVAL;
FD_ZERO(&rset);
FD_ZERO(&wset);
if (events & DNS_POLLIN)
FD_SET(fd, &rset);
if (events & DNS_POLLOUT)
FD_SET(fd, &wset);
select(fd + 1, &rset, &wset, 0, (timeout >= 0)? &tv : NULL);
return 0;
} /* dns_poll() */
#if !_WIN32
DNS_NOTUSED static int dns_sigmask(int how, const sigset_t *set, sigset_t *oset) {
#if DNS_THREAD_SAFE
return pthread_sigmask(how, set, oset);
#else
return (0 == sigprocmask(how, set, oset))? 0 : errno;
#endif
} /* dns_sigmask() */
#endif
static size_t dns_send(int fd, const void *src, size_t len, int flags, dns_error_t *error) {
long n = send(fd, src, len, flags);
if (n < 0) {
*error = dns_soerr();
return 0;
} else {
*error = 0;
return n;
}
} /* dns_send() */
static size_t dns_recv(int fd, void *dst, size_t lim, int flags, dns_error_t *error) {
long n = recv(fd, dst, lim, flags);
if (n < 0) {
*error = dns_soerr();
return 0;
} else if (n == 0) {
*error = (lim > 0)? DNS_ECONNFIN : EINVAL;
return 0;
} else {
*error = 0;
return n;
}
} /* dns_recv() */
static size_t dns_send_nopipe(int fd, const void *src, size_t len, int flags, dns_error_t *_error) {
#if _WIN32 || !defined SIGPIPE || defined SO_NOSIGPIPE
return dns_send(fd, src, len, flags, _error);
#elif defined MSG_NOSIGNAL
return dns_send(fd, src, len, (flags|MSG_NOSIGNAL), _error);
#elif _POSIX_REALTIME_SIGNALS > 0 /* require sigtimedwait */
/*
* SIGPIPE handling similar to the approach described in
* http://krokisplace.blogspot.com/2010/02/suppressing-sigpipe-in-library.html
*/
sigset_t pending, blocked, piped;
size_t count;
int error;
sigemptyset(&pending);
sigpending(&pending);
if (!sigismember(&pending, SIGPIPE)) {
sigemptyset(&piped);
sigaddset(&piped, SIGPIPE);
sigemptyset(&blocked);
if ((error = dns_sigmask(SIG_BLOCK, &piped, &blocked)))
goto error;
}
count = dns_send(fd, src, len, flags, &error);
if (!sigismember(&pending, SIGPIPE)) {
int saved = error;
const struct timespec ts = { 0, 0 };
if (!count && error == EPIPE) {
while (-1 == sigtimedwait(&piped, NULL, &ts) && errno == EINTR)
;;
}
if ((error = dns_sigmask(SIG_SETMASK, &blocked, NULL)))
goto error;
error = saved;
}
*_error = error;
return count;
error:
*_error = error;
return 0;
#else
#error "unable to suppress SIGPIPE"
return dns_send(fd, src, len, flags, _error);
#endif
} /* dns_send_nopipe() */
static dns_error_t dns_connect(int fd, const struct sockaddr *addr, socklen_t addrlen) {
if (0 != connect(fd, addr, addrlen))
return dns_soerr();
return 0;
} /* dns_connect() */
#define DNS_FOPEN_STDFLAGS "rwabt+"
static dns_error_t dns_fopen_addflag(char *dst, const char *src, size_t lim, int fc) {
char *p = dst, *pe = dst + lim;
/* copy standard flags */
while (*src && strchr(DNS_FOPEN_STDFLAGS, *src)) {
if (!(p < pe))
return ENOMEM;
*p++ = *src++;
}
/* append flag to standard flags */
if (!(p < pe))
return ENOMEM;
*p++ = fc;
/* copy remaining mode string, including '\0' */
do {
if (!(p < pe))
return ENOMEM;
} while ((*p++ = *src++));
return 0;
} /* dns_fopen_addflag() */
static FILE *dns_fopen(const char *path, const char *mode, dns_error_t *_error) {
FILE *fp;
char mode_cloexec[32];
int error;
assert(path && mode && *mode);
if (!*path) {
error = EINVAL;
goto error;
}
#if _WIN32 || _WIN64
if ((error = dns_fopen_addflag(mode_cloexec, mode, sizeof mode_cloexec, 'N')))
goto error;
if (!(fp = fopen(path, mode_cloexec)))
goto syerr;
#else
if ((error = dns_fopen_addflag(mode_cloexec, mode, sizeof mode_cloexec, 'e')))
goto error;
if (!(fp = fopen(path, mode_cloexec))) {
if (errno != EINVAL)
goto syerr;
if (!(fp = fopen(path, mode)))
goto syerr;
}
#endif
return fp;
syerr:
error = dns_syerr();
error:
*_error = error;
return NULL;
} /* dns_fopen() */
struct dns_hxd_lines_i {
int pc;
size_t p;
};
#define DNS_SM_RESTORE \
do { \
pc = state->pc; \
sp = src + state->p; \
se = src + len; \
} while (0)
#define DNS_SM_SAVE \
do { \
state->p = sp - src; \
state->pc = pc; \
} while (0)
static size_t dns_hxd_lines(void *dst, size_t lim, const unsigned char *src, size_t len, struct dns_hxd_lines_i *state) {
static const unsigned char hex[] = "0123456789abcdef";
static const unsigned char tmpl[] = " | |\n";
unsigned char ln[sizeof tmpl];
const unsigned char *sp, *se;
unsigned char *h, *g;
unsigned i, n;
int pc;
DNS_SM_ENTER;
while (sp < se) {
memcpy(ln, tmpl, sizeof ln);
h = &ln[2];
g = &ln[53];
for (n = 0; n < 2; n++) {
for (i = 0; i < 8 && se - sp > 0; i++, sp++) {
h[0] = hex[0x0f & (*sp >> 4)];
h[1] = hex[0x0f & (*sp >> 0)];
h += 3;
*g++ = (dns_isgraph(*sp))? *sp : '.';
}
h++;
}
n = dns_strlcpy(dst, (char *)ln, lim);
DNS_SM_YIELD(n);
}
DNS_SM_EXIT;
DNS_SM_LEAVE;
return 0;
}
#undef DNS_SM_SAVE
#undef DNS_SM_RESTORE
/*
* A R I T H M E T I C R O U T I N E S
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#define DNS_CHECK_OVERFLOW(error, r, f, ...) \
do { \
uintmax_t _r; \
*(error) = f(&_r, __VA_ARGS__); \
*(r) = _r; \
} while (0)
static dns_error_t dns_clamp_overflow(uintmax_t *r, uintmax_t n, uintmax_t clamp) {
if (n > clamp) {
*r = clamp;
return ERANGE;
} else {
*r = n;
return 0;
}
} /* dns_clamp_overflow() */
static dns_error_t dns_add_overflow(uintmax_t *r, uintmax_t a, uintmax_t b, uintmax_t clamp) {
if (~a < b) {
*r = DNS_PP_MIN(clamp, ~UINTMAX_C(0));
return ERANGE;
} else {
return dns_clamp_overflow(r, a + b, clamp);
}
} /* dns_add_overflow() */
static dns_error_t dns_mul_overflow(uintmax_t *r, uintmax_t a, uintmax_t b, uintmax_t clamp) {
if (a > 0 && UINTMAX_MAX / a < b) {
*r = DNS_PP_MIN(clamp, ~UINTMAX_C(0));
return ERANGE;
} else {
return dns_clamp_overflow(r, a * b, clamp);
}
} /* dns_mul_overflow() */
/*
* F I X E D - S I Z E D B U F F E R R O U T I N E S
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#define DNS_B_INIT(src, n) { \
(unsigned char *)(src), \
(unsigned char *)(src), \
(unsigned char *)(src) + (n), \
}
#define DNS_B_FROM(src, n) DNS_B_INIT((src), (n))
#define DNS_B_INTO(src, n) DNS_B_INIT((src), (n))
struct dns_buf {
const unsigned char *base;
unsigned char *p;
const unsigned char *pe;
dns_error_t error;
size_t overflow;
}; /* struct dns_buf */
static inline size_t
dns_b_tell(struct dns_buf *b)
{
return b->p - b->base;
}
static inline dns_error_t
dns_b_setoverflow(struct dns_buf *b, size_t n, dns_error_t error)
{
b->overflow += n;
return b->error = error;
}
DNS_NOTUSED static struct dns_buf *
dns_b_into(struct dns_buf *b, void *src, size_t n)
{
*b = (struct dns_buf)DNS_B_INTO(src, n);
return b;
}
static dns_error_t
dns_b_putc(struct dns_buf *b, unsigned char uc)
{
if (!(b->p < b->pe))
return dns_b_setoverflow(b, 1, DNS_ENOBUFS);
*b->p++ = uc;
return 0;
}
static dns_error_t
dns_b_pputc(struct dns_buf *b, unsigned char uc, size_t p)
{
size_t pe = b->pe - b->base;
if (pe <= p)
return dns_b_setoverflow(b, p - pe + 1, DNS_ENOBUFS);
*((unsigned char *)b->base + p) = uc;
return 0;
}
static inline dns_error_t
dns_b_put16(struct dns_buf *b, uint16_t u)
{
return dns_b_putc(b, u >> 8), dns_b_putc(b, u >> 0);
}
static inline dns_error_t
dns_b_pput16(struct dns_buf *b, uint16_t u, size_t p)
{
if (dns_b_pputc(b, u >> 8, p) || dns_b_pputc(b, u >> 0, p + 1))
return b->error;
return 0;
}
DNS_NOTUSED static inline dns_error_t
dns_b_put32(struct dns_buf *b, uint32_t u)
{
return dns_b_putc(b, u >> 24), dns_b_putc(b, u >> 16),
dns_b_putc(b, u >> 8), dns_b_putc(b, u >> 0);
}
static dns_error_t
dns_b_put(struct dns_buf *b, const void *src, size_t len)
{
size_t n = DNS_PP_MIN((size_t)(b->pe - b->p), len);
memcpy(b->p, src, n);
b->p += n;
if (n < len)
return dns_b_setoverflow(b, len - n, DNS_ENOBUFS);
return 0;
}
static dns_error_t
dns_b_puts(struct dns_buf *b, const void *src)
{
return dns_b_put(b, src, strlen(src));
}
DNS_NOTUSED static inline dns_error_t
dns_b_fmtju(struct dns_buf *b, const uintmax_t u, const unsigned width)
{
size_t digits, padding, overflow;
uintmax_t r;
unsigned char *tp, *te, tc;
digits = 0;
r = u;
do {
digits++;
r /= 10;
} while (r);
padding = width - DNS_PP_MIN(digits, width);
overflow = (digits + padding) - DNS_PP_MIN((size_t)(b->pe - b->p), (digits + padding));
while (padding--) {
dns_b_putc(b, '0');
}
digits = 0;
tp = b->p;
r = u;
do {
if (overflow < ++digits)
dns_b_putc(b, '0' + (r % 10));
r /= 10;
} while (r);
te = b->p;
while (tp < te) {
tc = *--te;
*te = *tp;
*tp++ = tc;
}
return b->error;
}
static void
dns_b_popc(struct dns_buf *b)
{
if (b->overflow && !--b->overflow)
b->error = 0;
if (b->p > b->base)
b->p--;
}
static inline const char *
dns_b_tolstring(struct dns_buf *b, size_t *n)
{
if (b->p < b->pe) {
*b->p = '\0';
*n = b->p - b->base;
return (const char *)b->base;
} else if (b->p > b->base) {
if (b->p[-1] != '\0') {
dns_b_setoverflow(b, 1, DNS_ENOBUFS);
b->p[-1] = '\0';
}
*n = &b->p[-1] - b->base;
return (const char *)b->base;
} else {
*n = 0;
return "";
}
}
static inline const char *
dns_b_tostring(struct dns_buf *b)
{
size_t n;
return dns_b_tolstring(b, &n);
}
static inline size_t
dns_b_strlen(struct dns_buf *b)
{
size_t n;
dns_b_tolstring(b, &n);
return n;
}
static inline size_t
dns_b_strllen(struct dns_buf *b)
{
size_t n = dns_b_strlen(b);
return n + b->overflow;
}
DNS_NOTUSED static const struct dns_buf *
dns_b_from(const struct dns_buf *b, const void *src, size_t n)
{
*(struct dns_buf *)b = (struct dns_buf)DNS_B_FROM(src, n);
return b;
}
static inline int
dns_b_getc(const struct dns_buf *_b, const int eof)
{
struct dns_buf *b = (struct dns_buf *)_b;
if (!(b->p < b->pe))
return dns_b_setoverflow(b, 1, DNS_EILLEGAL), eof;
return *b->p++;
}
static inline intmax_t
dns_b_get16(const struct dns_buf *b, const intmax_t eof)
{
intmax_t n;
n = (dns_b_getc(b, 0) << 8);
n |= (dns_b_getc(b, 0) << 0);
return (!b->overflow)? n : eof;
}
DNS_NOTUSED static inline intmax_t
dns_b_get32(const struct dns_buf *b, const intmax_t eof)
{
intmax_t n;
n = (dns_b_get16(b, 0) << 16);
n |= (dns_b_get16(b, 0) << 0);
return (!b->overflow)? n : eof;
}
static inline dns_error_t
dns_b_move(struct dns_buf *dst, const struct dns_buf *_src, size_t n)
{
struct dns_buf *src = (struct dns_buf *)_src;
size_t src_n = DNS_PP_MIN((size_t)(src->pe - src->p), n);
size_t src_r = n - src_n;
dns_b_put(dst, src->p, src_n);
src->p += src_n;
if (src_r)
return dns_b_setoverflow(src, src_r, DNS_EILLEGAL);
return dst->error;
}
/*
* T I M E R O U T I N E S
*
* Most functions still rely on the older time routines defined in the
* utility routines section, above.
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#define DNS_TIME_C(n) UINT64_C(n)
#define DNS_TIME_INF (~DNS_TIME_C(0))
typedef uint64_t dns_time_t;
typedef dns_time_t dns_microseconds_t;
static dns_error_t dns_time_add(dns_time_t *r, dns_time_t a, dns_time_t b) {
int error;
DNS_CHECK_OVERFLOW(&error, r, dns_add_overflow, a, b, DNS_TIME_INF);
return error;
}
static dns_error_t dns_time_mul(dns_time_t *r, dns_time_t a, dns_time_t b) {
int error;
DNS_CHECK_OVERFLOW(&error, r, dns_mul_overflow, a, b, DNS_TIME_INF);
return error;
}
static dns_error_t dns_time_diff(dns_time_t *r, dns_time_t a, dns_time_t b) {
if (a < b) {
*r = DNS_TIME_C(0);
return ERANGE;
} else {
*r = a - b;
return 0;
}
}
static dns_microseconds_t dns_ts2us(const struct timespec *ts, _Bool rup) {
if (ts) {
dns_time_t sec = DNS_PP_MAX(0, ts->tv_sec);
dns_time_t nsec = DNS_PP_MAX(0, ts->tv_nsec);
dns_time_t usec = nsec / 1000;
dns_microseconds_t r;
if (rup && nsec % 1000 > 0)
usec++;
dns_time_mul(&r, sec, DNS_TIME_C(1000000));
dns_time_add(&r, r, usec);
return r;
} else {
return DNS_TIME_INF;
}
} /* dns_ts2us() */
static struct timespec *dns_tv2ts(struct timespec *ts, const struct timeval *tv) {
if (tv) {
ts->tv_sec = tv->tv_sec;
ts->tv_nsec = tv->tv_usec * 1000;
return ts;
} else {
return NULL;
}
} /* dns_tv2ts() */
static size_t dns_utime_print(void *_dst, size_t lim, dns_microseconds_t us) {
struct dns_buf dst = DNS_B_INTO(_dst, lim);
dns_b_fmtju(&dst, us / 1000000, 1);
dns_b_putc(&dst, '.');
dns_b_fmtju(&dst, us % 1000000, 6);
return dns_b_strllen(&dst);
} /* dns_utime_print() */
/*
* P A C K E T R O U T I N E S
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
unsigned dns_p_count(struct dns_packet *P, enum dns_section section) {
unsigned count;
switch (section) {
case DNS_S_QD:
return ntohs(dns_header(P)->qdcount);
case DNS_S_AN:
return ntohs(dns_header(P)->ancount);
case DNS_S_NS:
return ntohs(dns_header(P)->nscount);
case DNS_S_AR:
return ntohs(dns_header(P)->arcount);
default:
count = 0;
if (section & DNS_S_QD)
count += ntohs(dns_header(P)->qdcount);
if (section & DNS_S_AN)
count += ntohs(dns_header(P)->ancount);
if (section & DNS_S_NS)
count += ntohs(dns_header(P)->nscount);
if (section & DNS_S_AR)
count += ntohs(dns_header(P)->arcount);
return count;
}
} /* dns_p_count() */
struct dns_packet *dns_p_init(struct dns_packet *P, size_t size) {
if (!P)
return 0;
assert(size >= offsetof(struct dns_packet, data) + 12);
memset(P, 0, sizeof *P);
P->size = size - offsetof(struct dns_packet, data);
P->end = 12;
memset(P->data, '\0', 12);
return P;
} /* dns_p_init() */
static struct dns_packet *dns_p_reset(struct dns_packet *P) {
return dns_p_init(P, offsetof(struct dns_packet, data) + P->size);
} /* dns_p_reset() */
static unsigned short dns_p_qend(struct dns_packet *P) {
unsigned short qend = 12;
unsigned i, count = dns_p_count(P, DNS_S_QD);
for (i = 0; i < count && qend < P->end; i++) {
if (P->end == (qend = dns_d_skip(qend, P)))
goto invalid;
if (P->end - qend < 4)
goto invalid;
qend += 4;
}
return DNS_PP_MIN(qend, P->end);
invalid:
return P->end;
} /* dns_p_qend() */
struct dns_packet *dns_p_make(size_t len, int *error) {
struct dns_packet *P;
size_t size = dns_p_calcsize(len);
if (!(P = dns_p_init(malloc(size), size)))
*error = dns_syerr();
return P;
} /* dns_p_make() */
static void dns_p_free(struct dns_packet *P) {
free(P);
} /* dns_p_free() */
/* convenience routine to free any existing packet before storing new packet */
static struct dns_packet *dns_p_setptr(struct dns_packet **dst, struct dns_packet *src) {
dns_p_free(*dst);
*dst = src;
return src;
} /* dns_p_setptr() */
static struct dns_packet *dns_p_movptr(struct dns_packet **dst, struct dns_packet **src) {
dns_p_setptr(dst, *src);
*src = NULL;
return *dst;
} /* dns_p_movptr() */
int dns_p_grow(struct dns_packet **P) {
struct dns_packet *tmp;
size_t size;
int error;
if (!*P) {
if (!(*P = dns_p_make(DNS_P_QBUFSIZ, &error)))
return error;
return 0;
}
size = dns_p_sizeof(*P);
size |= size >> 1;
size |= size >> 2;
size |= size >> 4;
size |= size >> 8;
size++;
if (size > 65536)
return DNS_ENOBUFS;
if (!(tmp = realloc(*P, dns_p_calcsize(size))))
return dns_syerr();
tmp->size = size;
*P = tmp;
return 0;
} /* dns_p_grow() */
struct dns_packet *dns_p_copy(struct dns_packet *P, const struct dns_packet *P0) {
if (!P)
return 0;
P->end = DNS_PP_MIN(P->size, P0->end);
memcpy(P->data, P0->data, P->end);
return P;
} /* dns_p_copy() */
struct dns_packet *dns_p_merge(struct dns_packet *A, enum dns_section Amask, struct dns_packet *B, enum dns_section Bmask, int *error_) {
size_t bufsiz = DNS_PP_MIN(65535, ((A)? A->end : 0) + ((B)? B->end : 0));
struct dns_packet *M;
enum dns_section section;
struct dns_rr rr, mr;
int error, copy;
if (!A && B) {
A = B;
Amask = Bmask;
B = 0;
}
merge:
if (!(M = dns_p_make(bufsiz, &error)))
goto error;
for (section = DNS_S_QD; (DNS_S_ALL & section); section <<= 1) {
if (A && (section & Amask)) {
dns_rr_foreach(&rr, A, .section = section) {
if ((error = dns_rr_copy(M, &rr, A)))
goto error;
}
}
if (B && (section & Bmask)) {
dns_rr_foreach(&rr, B, .section = section) {
copy = 1;
dns_rr_foreach(&mr, M, .type = rr.type, .section = DNS_S_ALL) {
if (!(copy = dns_rr_cmp(&rr, B, &mr, M)))
break;
}
if (copy && (error = dns_rr_copy(M, &rr, B)))
goto error;
}
}
}
return M;
error:
dns_p_setptr(&M, NULL);
if (error == DNS_ENOBUFS && bufsiz < 65535) {
bufsiz = DNS_PP_MIN(65535, bufsiz * 2);
goto merge;
}
*error_ = error;
return 0;
} /* dns_p_merge() */
static unsigned short dns_l_skip(unsigned short, const unsigned char *, size_t);
void dns_p_dictadd(struct dns_packet *P, unsigned short dn) {
unsigned short lp, lptr, i;
lp = dn;
while (lp < P->end) {
if (0xc0 == (0xc0 & P->data[lp]) && P->end - lp >= 2 && lp != dn) {
lptr = ((0x3f & P->data[lp + 0]) << 8)
| ((0xff & P->data[lp + 1]) << 0);
for (i = 0; i < lengthof(P->dict) && P->dict[i]; i++) {
if (P->dict[i] == lptr) {
P->dict[i] = dn;
return;
}
}
}
lp = dns_l_skip(lp, P->data, P->end);
}
for (i = 0; i < lengthof(P->dict); i++) {
if (!P->dict[i]) {
P->dict[i] = dn;
break;
}
}
} /* dns_p_dictadd() */
static inline uint16_t
plus1_ns (uint16_t count_net)
{
uint16_t count = ntohs (count_net);
count++;
return htons (count);
}
int dns_p_push(struct dns_packet *P, enum dns_section section, const void *dn, size_t dnlen, enum dns_type type, enum dns_class class, unsigned ttl, const void *any) {
size_t end = P->end;
int error;
if ((error = dns_d_push(P, dn, dnlen)))
goto error;
if (P->size - P->end < 4)
goto nobufs;
P->data[P->end++] = 0xff & (type >> 8);
P->data[P->end++] = 0xff & (type >> 0);
P->data[P->end++] = 0xff & (class >> 8);
P->data[P->end++] = 0xff & (class >> 0);
if (section == DNS_S_QD)
goto update;
if (P->size - P->end < 6)
goto nobufs;
if (type != DNS_T_OPT)
ttl = DNS_PP_MIN(ttl, 0x7fffffffU);
P->data[P->end++] = ttl >> 24;
P->data[P->end++] = ttl >> 16;
P->data[P->end++] = ttl >> 8;
P->data[P->end++] = ttl >> 0;
if ((error = dns_any_push(P, (union dns_any *)any, type)))
goto error;
update:
switch (section) {
case DNS_S_QD:
if (dns_p_count(P, DNS_S_AN|DNS_S_NS|DNS_S_AR))
goto order;
if (!P->memo.qd.base && (error = dns_p_study(P)))
goto error;
dns_header(P)->qdcount = plus1_ns (dns_header(P)->qdcount);
P->memo.qd.end = P->end;
P->memo.an.base = P->end;
P->memo.an.end = P->end;
P->memo.ns.base = P->end;
P->memo.ns.end = P->end;
P->memo.ar.base = P->end;
P->memo.ar.end = P->end;
break;
case DNS_S_AN:
if (dns_p_count(P, DNS_S_NS|DNS_S_AR))
goto order;
if (!P->memo.an.base && (error = dns_p_study(P)))
goto error;
dns_header(P)->ancount = plus1_ns (dns_header(P)->ancount);
P->memo.an.end = P->end;
P->memo.ns.base = P->end;
P->memo.ns.end = P->end;
P->memo.ar.base = P->end;
P->memo.ar.end = P->end;
break;
case DNS_S_NS:
if (dns_p_count(P, DNS_S_AR))
goto order;
if (!P->memo.ns.base && (error = dns_p_study(P)))
goto error;
dns_header(P)->nscount = plus1_ns (dns_header(P)->nscount);
P->memo.ns.end = P->end;
P->memo.ar.base = P->end;
P->memo.ar.end = P->end;
break;
case DNS_S_AR:
if (!P->memo.ar.base && (error = dns_p_study(P)))
goto error;
dns_header(P)->arcount = plus1_ns (dns_header(P)->arcount);
P->memo.ar.end = P->end;
if (type == DNS_T_OPT && !P->memo.opt.p) {
P->memo.opt.p = end;
P->memo.opt.maxudp = class;
P->memo.opt.ttl = ttl;
}
break;
default:
error = DNS_ESECTION;
goto error;
} /* switch() */
return 0;
nobufs:
error = DNS_ENOBUFS;
goto error;
order:
error = DNS_EORDER;
goto error;
error:
P->end = end;
return error;
} /* dns_p_push() */
#define DNS_SM_RESTORE do { pc = state->pc; error = state->error; } while (0)
#define DNS_SM_SAVE do { state->error = error; state->pc = pc; } while (0)
struct dns_p_lines_i {
int pc;
enum dns_section section;
struct dns_rr rr;
int error;
};
static size_t dns_p_lines_fmt(void *dst, size_t lim, dns_error_t *_error, const char *fmt, ...) {
va_list ap;
int error = 0, n;
va_start(ap, fmt);
if ((n = vsnprintf(dst, lim, fmt, ap)) < 0)
error = errno;
va_end(ap);
*_error = error;
return DNS_PP_MAX(n, 0);
} /* dns_p_lines_fmt() */
#define DNS_P_LINE(...) \
do { \
len = dns_p_lines_fmt(dst, lim, &error, __VA_ARGS__); \
if (len == 0 && error) \
goto error; \
DNS_SM_YIELD(len); \
} while (0)
static size_t dns_p_lines(void *dst, size_t lim, dns_error_t *_error, struct dns_packet *P, struct dns_rr_i *I, struct dns_p_lines_i *state) {
int error, pc;
size_t len;
*_error = 0;
DNS_SM_ENTER;
DNS_P_LINE(";; [HEADER]\n");
DNS_P_LINE(";; qid : %d\n", ntohs(dns_header(P)->qid));
DNS_P_LINE(";; qr : %s(%d)\n", (dns_header(P)->qr)? "RESPONSE" : "QUERY", dns_header(P)->qr);
DNS_P_LINE(";; opcode : %s(%d)\n", dns_stropcode(dns_header(P)->opcode), dns_header(P)->opcode);
DNS_P_LINE(";; aa : %s(%d)\n", (dns_header(P)->aa)? "AUTHORITATIVE" : "NON-AUTHORITATIVE", dns_header(P)->aa);
DNS_P_LINE(";; tc : %s(%d)\n", (dns_header(P)->tc)? "TRUNCATED" : "NOT-TRUNCATED", dns_header(P)->tc);
DNS_P_LINE(";; rd : %s(%d)\n", (dns_header(P)->rd)? "RECURSION-DESIRED" : "RECURSION-NOT-DESIRED", dns_header(P)->rd);
DNS_P_LINE(";; ra : %s(%d)\n", (dns_header(P)->ra)? "RECURSION-ALLOWED" : "RECURSION-NOT-ALLOWED", dns_header(P)->ra);
DNS_P_LINE(";; rcode : %s(%d)\n", dns_strrcode(dns_p_rcode(P)), dns_p_rcode(P));
while (dns_rr_grep(&state->rr, 1, I, P, &error)) {
if (state->section != state->rr.section) {
DNS_P_LINE("\n");
DNS_P_LINE(";; [%s:%d]\n", dns_strsection(state->rr.section), dns_p_count(P, state->rr.section));
}
if (!(len = dns_rr_print(dst, lim, &state->rr, P, &error)))
goto error;
dns_strlcat(dst, "\n", lim);
DNS_SM_YIELD(len + 1);
state->section = state->rr.section;
}
if (error)
goto error;
DNS_SM_EXIT;
error:
for (;;) {
*_error = error;
DNS_SM_YIELD(0);
}
DNS_SM_LEAVE;
*_error = 0;
return 0;
} /* dns_p_lines() */
#undef DNS_P_LINE
#undef DNS_SM_SAVE
#undef DNS_SM_RESTORE
static void dns_p_dump3(struct dns_packet *P, struct dns_rr_i *I, FILE *fp) {
struct dns_p_lines_i lines = { 0 };
char line[sizeof (union dns_any) * 2];
size_t len;
int error;
while ((len = dns_p_lines(line, sizeof line, &error, P, I, &lines))) {
if (len < sizeof line) {
fwrite(line, 1, len, fp);
} else {
fwrite(line, 1, sizeof line - 1, fp);
fputc('\n', fp);
}
}
} /* dns_p_dump3() */
void dns_p_dump(struct dns_packet *P, FILE *fp) {
- struct dns_rr_i _I = { 0 };
- dns_p_dump3(P, &_I, fp);
+ struct dns_rr_i I_instance = { 0 };
+ dns_p_dump3(P, &I_instance, fp);
} /* dns_p_dump() */
static void dns_s_unstudy(struct dns_s_memo *m)
{ m->base = 0; m->end = 0; }
static void dns_m_unstudy(struct dns_p_memo *m) {
dns_s_unstudy(&m->qd);
dns_s_unstudy(&m->an);
dns_s_unstudy(&m->ns);
dns_s_unstudy(&m->ar);
m->opt.p = 0;
m->opt.maxudp = 0;
m->opt.ttl = 0;
} /* dns_m_unstudy() */
static int dns_s_study(struct dns_s_memo *m, enum dns_section section, unsigned short base, struct dns_packet *P) {
unsigned short count, rp;
count = dns_p_count(P, section);
for (rp = base; count && rp < P->end; count--)
rp = dns_rr_skip(rp, P);
m->base = base;
m->end = rp;
return 0;
} /* dns_s_study() */
static int dns_m_study(struct dns_p_memo *m, struct dns_packet *P) {
struct dns_rr rr;
int error;
if ((error = dns_s_study(&m->qd, DNS_S_QD, 12, P)))
goto error;
if ((error = dns_s_study(&m->an, DNS_S_AN, m->qd.end, P)))
goto error;
if ((error = dns_s_study(&m->ns, DNS_S_NS, m->an.end, P)))
goto error;
if ((error = dns_s_study(&m->ar, DNS_S_AR, m->ns.end, P)))
goto error;
m->opt.p = 0;
m->opt.maxudp = 0;
m->opt.ttl = 0;
dns_rr_foreach(&rr, P, .type = DNS_T_OPT, .section = DNS_S_AR) {
m->opt.p = rr.dn.p;
m->opt.maxudp = rr.class;
m->opt.ttl = rr.ttl;
break;
}
return 0;
error:
dns_m_unstudy(m);
return error;
} /* dns_m_study() */
int dns_p_study(struct dns_packet *P) {
return dns_m_study(&P->memo, P);
} /* dns_p_study() */
enum dns_rcode dns_p_rcode(struct dns_packet *P) {
return 0xfff & ((P->memo.opt.ttl >> 20) | dns_header(P)->rcode);
} /* dns_p_rcode() */
/*
* Q U E R Y P A C K E T R O U T I N E S
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#define DNS_Q_RD 0x1 /* recursion desired */
#define DNS_Q_EDNS0 0x2 /* include OPT RR */
static dns_error_t
dns_q_make2(struct dns_packet **_Q, const char *qname, size_t qlen, enum dns_type qtype, enum dns_class qclass, int qflags)
{
struct dns_packet *Q = NULL;
int error;
if (dns_p_movptr(&Q, _Q)) {
dns_p_reset(Q);
} else if (!(Q = dns_p_make(DNS_P_QBUFSIZ, &error))) {
goto error;
}
if ((error = dns_p_push(Q, DNS_S_QD, qname, qlen, qtype, qclass, 0, 0)))
goto error;
dns_header(Q)->rd = !!(qflags & DNS_Q_RD);
if (qflags & DNS_Q_EDNS0) {
struct dns_opt opt = DNS_OPT_INIT(&opt);
opt.version = 0; /* RFC 6891 version */
opt.maxudp = 4096;
if ((error = dns_p_push(Q, DNS_S_AR, ".", 1, DNS_T_OPT, dns_opt_class(&opt), dns_opt_ttl(&opt), &opt)))
goto error;
}
*_Q = Q;
return 0;
error:
dns_p_free(Q);
return error;
}
static dns_error_t
dns_q_make(struct dns_packet **Q, const char *qname, enum dns_type qtype, enum dns_class qclass, int qflags)
{
return dns_q_make2(Q, qname, strlen(qname), qtype, qclass, qflags);
}
static dns_error_t
dns_q_remake(struct dns_packet **Q, int qflags)
{
char qname[DNS_D_MAXNAME + 1];
size_t qlen;
struct dns_rr rr;
int error;
assert(Q && *Q);
if ((error = dns_rr_parse(&rr, 12, *Q)))
return error;
if (!(qlen = dns_d_expand(qname, sizeof qname, rr.dn.p, *Q, &error)))
return error;
if (qlen >= sizeof qname)
return DNS_EILLEGAL;
return dns_q_make2(Q, qname, qlen, rr.type, rr.class, qflags);
}
/*
* D O M A I N N A M E R O U T I N E S
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#ifndef DNS_D_MAXPTRS
#define DNS_D_MAXPTRS 127 /* Arbitrary; possible, valid depth is something like packet size / 2 + fudge. */
#endif
static size_t dns_l_expand(unsigned char *dst, size_t lim, unsigned short src, unsigned short *nxt, const unsigned char *data, size_t end) {
unsigned short len;
unsigned nptrs = 0;
retry:
if (src >= end)
goto invalid;
switch (0x03 & (data[src] >> 6)) {
case 0x00:
len = (0x3f & (data[src++]));
if (end - src < len)
goto invalid;
if (lim > 0) {
memcpy(dst, &data[src], DNS_PP_MIN(lim, len));
dst[DNS_PP_MIN(lim - 1, len)] = '\0';
}
*nxt = src + len;
return len;
case 0x01:
goto invalid;
case 0x02:
goto invalid;
case 0x03:
if (++nptrs > DNS_D_MAXPTRS)
goto invalid;
if (end - src < 2)
goto invalid;
src = ((0x3f & data[src + 0]) << 8)
| ((0xff & data[src + 1]) << 0);
goto retry;
} /* switch() */
/* NOT REACHED */
invalid:
*nxt = end;
return 0;
} /* dns_l_expand() */
static unsigned short dns_l_skip(unsigned short src, const unsigned char *data, size_t end) {
unsigned short len;
if (src >= end)
goto invalid;
switch (0x03 & (data[src] >> 6)) {
case 0x00:
len = (0x3f & (data[src++]));
if (end - src < len)
goto invalid;
return (len)? src + len : end;
case 0x01:
goto invalid;
case 0x02:
goto invalid;
case 0x03:
return end;
} /* switch() */
/* NOT REACHED */
invalid:
return end;
} /* dns_l_skip() */
static _Bool dns_d_isanchored(const void *_src, size_t len) {
const unsigned char *src = _src;
return len > 0 && src[len - 1] == '.';
} /* dns_d_isanchored() */
static size_t dns_d_ndots(const void *_src, size_t len) {
const unsigned char *p = _src, *pe = p + len;
size_t ndots = 0;
while ((p = memchr(p, '.', pe - p))) {
ndots++;
p++;
}
return ndots;
} /* dns_d_ndots() */
static size_t dns_d_trim(void *dst_, size_t lim, const void *src_, size_t len, int flags) {
unsigned char *dst = dst_;
const unsigned char *src = src_;
size_t dp = 0, sp = 0;
int lc;
/* trim any leading dot(s) */
while (sp < len && src[sp] == '.')
sp++;
for (lc = 0; sp < len; lc = src[sp++]) {
/* trim extra dot(s) */
if (src[sp] == '.' && lc == '.')
continue;
if (dp < lim)
dst[dp] = src[sp];
dp++;
}
if ((flags & DNS_D_ANCHOR) && lc != '.') {
if (dp < lim)
dst[dp] = '.';
dp++;
}
if (lim > 0)
dst[DNS_PP_MIN(dp, lim - 1)] = '\0';
return dp;
} /* dns_d_trim() */
char *dns_d_init(void *dst, size_t lim, const void *src, size_t len, int flags) {
if (flags & DNS_D_TRIM) {
dns_d_trim(dst, lim, src, len, flags);
} if (flags & DNS_D_ANCHOR) {
dns_d_anchor(dst, lim, src, len);
} else {
memmove(dst, src, DNS_PP_MIN(lim, len));
if (lim > 0)
((char *)dst)[DNS_PP_MIN(len, lim - 1)] = '\0';
}
return dst;
} /* dns_d_init() */
size_t dns_d_anchor(void *dst, size_t lim, const void *src, size_t len) {
if (len == 0)
return 0;
memmove(dst, src, DNS_PP_MIN(lim, len));
if (((const char *)src)[len - 1] != '.') {
if (len < lim)
((char *)dst)[len] = '.';
len++;
}
if (lim > 0)
((char *)dst)[DNS_PP_MIN(lim - 1, len)] = '\0';
return len;
} /* dns_d_anchor() */
size_t dns_d_cleave(void *dst, size_t lim, const void *src, size_t len) {
const char *dot;
/* XXX: Skip any leading dot. Handles cleaving root ".". */
if (len == 0 || !(dot = memchr((const char *)src + 1, '.', len - 1)))
return 0;
len -= dot - (const char *)src;
/* XXX: Unless root, skip the label's trailing dot. */
if (len > 1) {
src = ++dot;
len--;
} else
src = dot;
memmove(dst, src, DNS_PP_MIN(lim, len));
if (lim > 0)
((char *)dst)[DNS_PP_MIN(lim - 1, len)] = '\0';
return len;
} /* dns_d_cleave() */
size_t dns_d_comp(void *dst_, size_t lim, const void *src_, size_t len, struct dns_packet *P, int *error) {
struct { unsigned char *b; size_t p, x; } dst, src;
unsigned char ch = '.';
dst.b = dst_;
dst.p = 0;
dst.x = 1;
src.b = (unsigned char *)src_;
src.p = 0;
src.x = 0;
while (src.x < len) {
ch = src.b[src.x];
if (ch == '.') {
if (dst.p < lim)
dst.b[dst.p] = (0x3f & (src.x - src.p));
dst.p = dst.x++;
src.p = ++src.x;
} else {
if (dst.x < lim)
dst.b[dst.x] = ch;
dst.x++;
src.x++;
}
} /* while() */
if (src.x > src.p) {
if (dst.p < lim)
dst.b[dst.p] = (0x3f & (src.x - src.p));
dst.p = dst.x;
}
if (dst.p > 1) {
if (dst.p < lim)
dst.b[dst.p] = 0x00;
dst.p++;
}
#if 1
if (dst.p < lim) {
struct { unsigned char label[DNS_D_MAXLABEL + 1]; size_t len; unsigned short p, x, y; } a, b;
unsigned i;
a.p = 0;
while ((a.len = dns_l_expand(a.label, sizeof a.label, a.p, &a.x, dst.b, lim))) {
for (i = 0; i < lengthof(P->dict) && P->dict[i]; i++) {
b.p = P->dict[i];
while ((b.len = dns_l_expand(b.label, sizeof b.label, b.p, &b.x, P->data, P->end))) {
a.y = a.x;
b.y = b.x;
while (a.len && b.len && 0 == strcasecmp((char *)a.label, (char *)b.label)) {
a.len = dns_l_expand(a.label, sizeof a.label, a.y, &a.y, dst.b, lim);
b.len = dns_l_expand(b.label, sizeof b.label, b.y, &b.y, P->data, P->end);
}
if (a.len == 0 && b.len == 0 && b.p <= 0x3fff) {
dst.b[a.p++] = 0xc0
| (0x3f & (b.p >> 8));
dst.b[a.p++] = (0xff & (b.p >> 0));
/* silence static analyzers */
dns_assume(a.p > 0);
return a.p;
}
b.p = b.x;
} /* while() */
} /* for() */
a.p = a.x;
} /* while() */
} /* if () */
#endif
if (!dst.p)
*error = DNS_EILLEGAL;
return dst.p;
} /* dns_d_comp() */
unsigned short dns_d_skip(unsigned short src, struct dns_packet *P) {
unsigned short len;
while (src < P->end) {
switch (0x03 & (P->data[src] >> 6)) {
case 0x00: /* FOLLOWS */
len = (0x3f & P->data[src++]);
if (0 == len) {
/* success ==> */ return src;
} else if (P->end - src > len) {
src += len;
break;
} else
goto invalid;
/* NOT REACHED */
case 0x01: /* RESERVED */
goto invalid;
case 0x02: /* RESERVED */
goto invalid;
case 0x03: /* POINTER */
if (P->end - src < 2)
goto invalid;
src += 2;
/* success ==> */ return src;
} /* switch() */
} /* while() */
invalid:
return P->end;
} /* dns_d_skip() */
#include <stdio.h>
size_t dns_d_expand(void *dst, size_t lim, unsigned short src, struct dns_packet *P, int *error) {
size_t dstp = 0;
unsigned nptrs = 0;
unsigned char len;
while (src < P->end) {
switch ((0x03 & (P->data[src] >> 6))) {
case 0x00: /* FOLLOWS */
len = (0x3f & P->data[src]);
if (0 == len) {
if (dstp == 0) {
if (dstp < lim)
((unsigned char *)dst)[dstp] = '.';
dstp++;
}
/* NUL terminate */
if (lim > 0)
((unsigned char *)dst)[DNS_PP_MIN(dstp, lim - 1)] = '\0';
/* success ==> */ return dstp;
}
src++;
if (P->end - src < len)
goto toolong;
if (dstp < lim)
memcpy(&((unsigned char *)dst)[dstp], &P->data[src], DNS_PP_MIN(len, lim - dstp));
src += len;
dstp += len;
if (dstp < lim)
((unsigned char *)dst)[dstp] = '.';
dstp++;
nptrs = 0;
continue;
case 0x01: /* RESERVED */
goto reserved;
case 0x02: /* RESERVED */
goto reserved;
case 0x03: /* POINTER */
if (++nptrs > DNS_D_MAXPTRS)
goto toolong;
if (P->end - src < 2)
goto toolong;
src = ((0x3f & P->data[src + 0]) << 8)
| ((0xff & P->data[src + 1]) << 0);
continue;
} /* switch() */
} /* while() */
toolong:
*error = DNS_EILLEGAL;
if (lim > 0)
((unsigned char *)dst)[DNS_PP_MIN(dstp, lim - 1)] = '\0';
return 0;
reserved:
*error = DNS_EILLEGAL;
if (lim > 0)
((unsigned char *)dst)[DNS_PP_MIN(dstp, lim - 1)] = '\0';
return 0;
} /* dns_d_expand() */
int dns_d_push(struct dns_packet *P, const void *dn, size_t len) {
size_t lim = P->size - P->end;
unsigned dp = P->end;
int error = DNS_EILLEGAL; /* silence compiler */
len = dns_d_comp(&P->data[dp], lim, dn, len, P, &error);
if (len == 0)
return error;
if (len > lim)
return DNS_ENOBUFS;
P->end += len;
dns_p_dictadd(P, dp);
return 0;
} /* dns_d_push() */
size_t dns_d_cname(void *dst, size_t lim, const void *dn, size_t len, struct dns_packet *P, int *error_) {
char host[DNS_D_MAXNAME + 1];
struct dns_rr_i i;
struct dns_rr rr;
unsigned depth;
int error;
if (sizeof host <= dns_d_anchor(host, sizeof host, dn, len))
{ error = ENAMETOOLONG; goto error; }
for (depth = 0; depth < 7; depth++) {
memset(&i, 0, sizeof i);
i.section = DNS_S_ALL & ~DNS_S_QD;
i.name = host;
i.type = DNS_T_CNAME;
if (!dns_rr_grep(&rr, 1, &i, P, &error))
break;
if ((error = dns_cname_parse((struct dns_cname *)host, &rr, P)))
goto error;
}
return dns_strlcpy(dst, host, lim);
error:
*error_ = error;
return 0;
} /* dns_d_cname() */
/*
* R E S O U R C E R E C O R D R O U T I N E S
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
int dns_rr_copy(struct dns_packet *P, struct dns_rr *rr, struct dns_packet *Q) {
unsigned char dn[DNS_D_MAXNAME + 1];
union dns_any any;
size_t len;
int error;
if (!(len = dns_d_expand(dn, sizeof dn, rr->dn.p, Q, &error)))
return error;
else if (len >= sizeof dn)
return DNS_EILLEGAL;
if (rr->section != DNS_S_QD && (error = dns_any_parse(dns_any_init(&any, sizeof any), rr, Q)))
return error;
return dns_p_push(P, rr->section, dn, len, rr->type, rr->class, rr->ttl, &any);
} /* dns_rr_copy() */
int dns_rr_parse(struct dns_rr *rr, unsigned short src, struct dns_packet *P) {
unsigned short p = src;
if (src >= P->end)
goto invalid;
rr->dn.p = p;
rr->dn.len = (p = dns_d_skip(p, P)) - rr->dn.p;
if (P->end - p < 4)
goto invalid;
rr->type = ((0xff & P->data[p + 0]) << 8)
| ((0xff & P->data[p + 1]) << 0);
rr->class = ((0xff & P->data[p + 2]) << 8)
| ((0xff & P->data[p + 3]) << 0);
p += 4;
if (src < dns_p_qend(P)) {
rr->section = DNS_S_QUESTION;
rr->ttl = 0;
rr->rd.p = 0;
rr->rd.len = 0;
return 0;
}
if (P->end - p < 4)
goto invalid;
rr->ttl = ((0xff & P->data[p + 0]) << 24)
| ((0xff & P->data[p + 1]) << 16)
| ((0xff & P->data[p + 2]) << 8)
| ((0xff & P->data[p + 3]) << 0);
if (rr->type != DNS_T_OPT)
rr->ttl = DNS_PP_MIN(rr->ttl, 0x7fffffffU);
p += 4;
if (P->end - p < 2)
goto invalid;
rr->rd.len = ((0xff & P->data[p + 0]) << 8)
| ((0xff & P->data[p + 1]) << 0);
rr->rd.p = p + 2;
p += 2;
if (P->end - p < rr->rd.len)
goto invalid;
return 0;
invalid:
return DNS_EILLEGAL;
} /* dns_rr_parse() */
static unsigned short dns_rr_len(const unsigned short src, struct dns_packet *P) {
unsigned short rp, rdlen;
rp = dns_d_skip(src, P);
if (P->end - rp < 4)
return P->end - src;
rp += 4; /* TYPE, CLASS */
if (rp <= dns_p_qend(P))
return rp - src;
if (P->end - rp < 6)
return P->end - src;
rp += 6; /* TTL, RDLEN */
rdlen = ((0xff & P->data[rp - 2]) << 8)
| ((0xff & P->data[rp - 1]) << 0);
if (P->end - rp < rdlen)
return P->end - src;
rp += rdlen;
return rp - src;
} /* dns_rr_len() */
unsigned short dns_rr_skip(unsigned short src, struct dns_packet *P) {
return src + dns_rr_len(src, P);
} /* dns_rr_skip() */
static enum dns_section dns_rr_section(unsigned short src, struct dns_packet *P) {
enum dns_section section;
unsigned count, index;
unsigned short rp;
if (src >= P->memo.qd.base && src < P->memo.qd.end)
return DNS_S_QD;
if (src >= P->memo.an.base && src < P->memo.an.end)
return DNS_S_AN;
if (src >= P->memo.ns.base && src < P->memo.ns.end)
return DNS_S_NS;
if (src >= P->memo.ar.base && src < P->memo.ar.end)
return DNS_S_AR;
/* NOTE: Possibly bad memoization. Try it the hard-way. */
for (rp = 12, index = 0; rp < src && rp < P->end; index++)
rp = dns_rr_skip(rp, P);
section = DNS_S_QD;
count = dns_p_count(P, section);
while (index >= count && section <= DNS_S_AR) {
section <<= 1;
count += dns_p_count(P, section);
}
return DNS_S_ALL & section;
} /* dns_rr_section() */
static enum dns_type dns_rr_type(unsigned short src, struct dns_packet *P) {
struct dns_rr rr;
int error;
if ((error = dns_rr_parse(&rr, src, P)))
return 0;
return rr.type;
} /* dns_rr_type() */
int dns_rr_cmp(struct dns_rr *r0, struct dns_packet *P0, struct dns_rr *r1, struct dns_packet *P1) {
char host0[DNS_D_MAXNAME + 1], host1[DNS_D_MAXNAME + 1];
union dns_any any0, any1;
int cmp, error;
size_t len;
if ((cmp = r0->type - r1->type))
return cmp;
if ((cmp = r0->class - r1->class))
return cmp;
/*
* FIXME: Do label-by-label comparison to handle illegally long names?
*/
if (!(len = dns_d_expand(host0, sizeof host0, r0->dn.p, P0, &error))
|| len >= sizeof host0)
return -1;
if (!(len = dns_d_expand(host1, sizeof host1, r1->dn.p, P1, &error))
|| len >= sizeof host1)
return 1;
if ((cmp = strcasecmp(host0, host1)))
return cmp;
if (DNS_S_QD & (r0->section | r1->section)) {
if (r0->section == r1->section)
return 0;
return (r0->section == DNS_S_QD)? -1 : 1;
}
if ((error = dns_any_parse(&any0, r0, P0)))
return -1;
if ((error = dns_any_parse(&any1, r1, P1)))
return 1;
return dns_any_cmp(&any0, r0->type, &any1, r1->type);
} /* dns_rr_cmp() */
static _Bool dns_rr_exists(struct dns_rr *rr0, struct dns_packet *P0, struct dns_packet *P1) {
struct dns_rr rr1;
dns_rr_foreach(&rr1, P1, .section = rr0->section, .type = rr0->type) {
if (0 == dns_rr_cmp(rr0, P0, &rr1, P1))
return 1;
}
return 0;
} /* dns_rr_exists() */
static unsigned short dns_rr_offset(struct dns_rr *rr) {
return rr->dn.p;
} /* dns_rr_offset() */
static _Bool dns_rr_i_match(struct dns_rr *rr, struct dns_rr_i *i, struct dns_packet *P) {
if (i->section && !(rr->section & i->section))
return 0;
if (i->type && rr->type != i->type && i->type != DNS_T_ALL)
return 0;
if (i->class && rr->class != i->class && i->class != DNS_C_ANY)
return 0;
if (i->name) {
char dn[DNS_D_MAXNAME + 1];
size_t len;
int error;
if (!(len = dns_d_expand(dn, sizeof dn, rr->dn.p, P, &error))
|| len >= sizeof dn)
return 0;
if (0 != strcasecmp(dn, i->name))
return 0;
}
if (i->data && i->type && rr->section > DNS_S_QD) {
union dns_any rd;
int error;
if ((error = dns_any_parse(&rd, rr, P)))
return 0;
if (0 != dns_any_cmp(&rd, rr->type, i->data, i->type))
return 0;
}
return 1;
} /* dns_rr_i_match() */
static unsigned short dns_rr_i_start(struct dns_rr_i *i, struct dns_packet *P) {
unsigned short rp;
struct dns_rr r0, rr;
int error;
if ((i->section & DNS_S_QD) && P->memo.qd.base)
rp = P->memo.qd.base;
else if ((i->section & DNS_S_AN) && P->memo.an.base)
rp = P->memo.an.base;
else if ((i->section & DNS_S_NS) && P->memo.ns.base)
rp = P->memo.ns.base;
else if ((i->section & DNS_S_AR) && P->memo.ar.base)
rp = P->memo.ar.base;
else
rp = 12;
for (; rp < P->end; rp = dns_rr_skip(rp, P)) {
if ((error = dns_rr_parse(&rr, rp, P)))
continue;
rr.section = dns_rr_section(rp, P);
if (!dns_rr_i_match(&rr, i, P))
continue;
r0 = rr;
goto lower;
}
return P->end;
lower:
if (i->sort == &dns_rr_i_packet)
return dns_rr_offset(&r0);
while ((rp = dns_rr_skip(rp, P)) < P->end) {
if ((error = dns_rr_parse(&rr, rp, P)))
continue;
rr.section = dns_rr_section(rp, P);
if (!dns_rr_i_match(&rr, i, P))
continue;
if (i->sort(&rr, &r0, i, P) < 0)
r0 = rr;
}
return dns_rr_offset(&r0);
} /* dns_rr_i_start() */
static unsigned short dns_rr_i_skip(unsigned short rp, struct dns_rr_i *i, struct dns_packet *P) {
struct dns_rr r0, r1, rr;
int error;
if ((error = dns_rr_parse(&r0, rp, P)))
return P->end;
r0.section = dns_rr_section(rp, P);
rp = (i->sort == &dns_rr_i_packet)? dns_rr_skip(rp, P) : 12;
for (; rp < P->end; rp = dns_rr_skip(rp, P)) {
if ((error = dns_rr_parse(&rr, rp, P)))
continue;
rr.section = dns_rr_section(rp, P);
if (!dns_rr_i_match(&rr, i, P))
continue;
if (i->sort(&rr, &r0, i, P) <= 0)
continue;
r1 = rr;
goto lower;
}
return P->end;
lower:
if (i->sort == &dns_rr_i_packet)
return dns_rr_offset(&r1);
while ((rp = dns_rr_skip(rp, P)) < P->end) {
if ((error = dns_rr_parse(&rr, rp, P)))
continue;
rr.section = dns_rr_section(rp, P);
if (!dns_rr_i_match(&rr, i, P))
continue;
if (i->sort(&rr, &r0, i, P) <= 0)
continue;
if (i->sort(&rr, &r1, i, P) >= 0)
continue;
r1 = rr;
}
return dns_rr_offset(&r1);
} /* dns_rr_i_skip() */
int dns_rr_i_packet(struct dns_rr *a, struct dns_rr *b, struct dns_rr_i *i, struct dns_packet *P) {
(void)i;
(void)P;
return (int)a->dn.p - (int)b->dn.p;
} /* dns_rr_i_packet() */
int dns_rr_i_order(struct dns_rr *a, struct dns_rr *b, struct dns_rr_i *i, struct dns_packet *P) {
int cmp;
(void)i;
if ((cmp = a->section - b->section))
return cmp;
if (a->type != b->type)
return (int)a->dn.p - (int)b->dn.p;
return dns_rr_cmp(a, P, b, P);
} /* dns_rr_i_order() */
int dns_rr_i_shuffle(struct dns_rr *a, struct dns_rr *b, struct dns_rr_i *i, struct dns_packet *P) {
int cmp;
(void)i;
(void)P;
while (!i->state.regs[0])
i->state.regs[0] = dns_random();
if ((cmp = a->section - b->section))
return cmp;
return dns_k_shuffle16(a->dn.p, i->state.regs[0]) - dns_k_shuffle16(b->dn.p, i->state.regs[0]);
} /* dns_rr_i_shuffle() */
void dns_rr_i_init(struct dns_rr_i *i) {
static const struct dns_rr_i i_initializer;
i->state = i_initializer.state;
i->saved = i->state;
} /* dns_rr_i_init() */
unsigned dns_rr_grep(struct dns_rr *rr, unsigned lim, struct dns_rr_i *i, struct dns_packet *P, int *error_) {
unsigned count = 0;
int error;
switch (i->state.exec) {
case 0:
if (!i->sort)
i->sort = &dns_rr_i_packet;
i->state.next = dns_rr_i_start(i, P);
i->state.exec++;
/* FALL THROUGH */
case 1:
while (count < lim && i->state.next < P->end) {
if ((error = dns_rr_parse(rr, i->state.next, P)))
goto error;
rr->section = dns_rr_section(i->state.next, P);
rr++;
count++;
i->state.count++;
i->state.next = dns_rr_i_skip(i->state.next, i, P);
} /* while() */
break;
} /* switch() */
return count;
error:
if (error_)
*error_ = error;
return count;
} /* dns_rr_grep() */
size_t dns_rr_print(void *_dst, size_t lim, struct dns_rr *rr, struct dns_packet *P, int *_error) {
struct dns_buf dst = DNS_B_INTO(_dst, lim);
union dns_any any;
size_t n;
int error;
if (rr->section == DNS_S_QD)
dns_b_putc(&dst, ';');
if (!(n = dns_d_expand(any.ns.host, sizeof any.ns.host, rr->dn.p, P, &error)))
goto error;
dns_b_put(&dst, any.ns.host, DNS_PP_MIN(n, sizeof any.ns.host - 1));
if (rr->section != DNS_S_QD) {
dns_b_putc(&dst, ' ');
dns_b_fmtju(&dst, rr->ttl, 0);
}
dns_b_putc(&dst, ' ');
dns_b_puts(&dst, dns_strclass(rr->class));
dns_b_putc(&dst, ' ');
dns_b_puts(&dst, dns_strtype(rr->type));
if (rr->section == DNS_S_QD)
goto epilog;
dns_b_putc(&dst, ' ');
if ((error = dns_any_parse(dns_any_init(&any, sizeof any), rr, P)))
goto error;
n = dns_any_print(dst.p, dst.pe - dst.p, &any, rr->type);
dst.p += DNS_PP_MIN(n, (size_t)(dst.pe - dst.p));
epilog:
return dns_b_strllen(&dst);
error:
*_error = error;
return 0;
} /* dns_rr_print() */
int dns_a_parse(struct dns_a *a, struct dns_rr *rr, struct dns_packet *P) {
unsigned long addr;
if (rr->rd.len != 4)
return DNS_EILLEGAL;
addr = ((0xffU & P->data[rr->rd.p + 0]) << 24)
| ((0xffU & P->data[rr->rd.p + 1]) << 16)
| ((0xffU & P->data[rr->rd.p + 2]) << 8)
| ((0xffU & P->data[rr->rd.p + 3]) << 0);
a->addr.s_addr = htonl(addr);
return 0;
} /* dns_a_parse() */
int dns_a_push(struct dns_packet *P, struct dns_a *a) {
unsigned long addr;
if (P->size - P->end < 6)
return DNS_ENOBUFS;
P->data[P->end++] = 0x00;
P->data[P->end++] = 0x04;
addr = ntohl(a->addr.s_addr);
P->data[P->end++] = 0xffU & (addr >> 24);
P->data[P->end++] = 0xffU & (addr >> 16);
P->data[P->end++] = 0xffU & (addr >> 8);
P->data[P->end++] = 0xffU & (addr >> 0);
return 0;
} /* dns_a_push() */
size_t dns_a_arpa(void *_dst, size_t lim, const struct dns_a *a) {
struct dns_buf dst = DNS_B_INTO(_dst, lim);
unsigned long octets = ntohl(a->addr.s_addr);
unsigned i;
for (i = 0; i < 4; i++) {
dns_b_fmtju(&dst, 0xff & octets, 0);
dns_b_putc(&dst, '.');
octets >>= 8;
}
dns_b_puts(&dst, "in-addr.arpa.");
return dns_b_strllen(&dst);
} /* dns_a_arpa() */
int dns_a_cmp(const struct dns_a *a, const struct dns_a *b) {
if (ntohl(a->addr.s_addr) < ntohl(b->addr.s_addr))
return -1;
if (ntohl(a->addr.s_addr) > ntohl(b->addr.s_addr))
return 1;
return 0;
} /* dns_a_cmp() */
size_t dns_a_print(void *dst, size_t lim, struct dns_a *a) {
char addr[INET_ADDRSTRLEN + 1] = "0.0.0.0";
dns_inet_ntop(AF_INET, &a->addr, addr, sizeof addr);
return dns_strlcpy(dst, addr, lim);
} /* dns_a_print() */
int dns_aaaa_parse(struct dns_aaaa *aaaa, struct dns_rr *rr, struct dns_packet *P) {
if (rr->rd.len != sizeof aaaa->addr.s6_addr)
return DNS_EILLEGAL;
memcpy(aaaa->addr.s6_addr, &P->data[rr->rd.p], sizeof aaaa->addr.s6_addr);
return 0;
} /* dns_aaaa_parse() */
int dns_aaaa_push(struct dns_packet *P, struct dns_aaaa *aaaa) {
if (P->size - P->end < 2 + sizeof aaaa->addr.s6_addr)
return DNS_ENOBUFS;
P->data[P->end++] = 0x00;
P->data[P->end++] = 0x10;
memcpy(&P->data[P->end], aaaa->addr.s6_addr, sizeof aaaa->addr.s6_addr);
P->end += sizeof aaaa->addr.s6_addr;
return 0;
} /* dns_aaaa_push() */
int dns_aaaa_cmp(const struct dns_aaaa *a, const struct dns_aaaa *b) {
unsigned i;
int cmp;
for (i = 0; i < lengthof(a->addr.s6_addr); i++) {
if ((cmp = (a->addr.s6_addr[i] - b->addr.s6_addr[i])))
return cmp;
}
return 0;
} /* dns_aaaa_cmp() */
size_t dns_aaaa_arpa(void *_dst, size_t lim, const struct dns_aaaa *aaaa) {
static const unsigned char hex[16] = "0123456789abcdef";
struct dns_buf dst = DNS_B_INTO(_dst, lim);
unsigned nyble;
int i, j;
for (i = sizeof aaaa->addr.s6_addr - 1; i >= 0; i--) {
nyble = aaaa->addr.s6_addr[i];
for (j = 0; j < 2; j++) {
dns_b_putc(&dst, hex[0x0f & nyble]);
dns_b_putc(&dst, '.');
nyble >>= 4;
}
}
dns_b_puts(&dst, "ip6.arpa.");
return dns_b_strllen(&dst);
} /* dns_aaaa_arpa() */
size_t dns_aaaa_print(void *dst, size_t lim, struct dns_aaaa *aaaa) {
char addr[INET6_ADDRSTRLEN + 1] = "::";
dns_inet_ntop(AF_INET6, &aaaa->addr, addr, sizeof addr);
return dns_strlcpy(dst, addr, lim);
} /* dns_aaaa_print() */
int dns_mx_parse(struct dns_mx *mx, struct dns_rr *rr, struct dns_packet *P) {
size_t len;
int error;
if (rr->rd.len < 3)
return DNS_EILLEGAL;
mx->preference = (0xff00 & (P->data[rr->rd.p + 0] << 8))
| (0x00ff & (P->data[rr->rd.p + 1] << 0));
if (!(len = dns_d_expand(mx->host, sizeof mx->host, rr->rd.p + 2, P, &error)))
return error;
else if (len >= sizeof mx->host)
return DNS_EILLEGAL;
return 0;
} /* dns_mx_parse() */
int dns_mx_push(struct dns_packet *P, struct dns_mx *mx) {
size_t end, len;
int error;
if (P->size - P->end < 5)
return DNS_ENOBUFS;
end = P->end;
P->end += 2;
P->data[P->end++] = 0xff & (mx->preference >> 8);
P->data[P->end++] = 0xff & (mx->preference >> 0);
if ((error = dns_d_push(P, mx->host, strlen(mx->host))))
goto error;
len = P->end - end - 2;
P->data[end + 0] = 0xff & (len >> 8);
P->data[end + 1] = 0xff & (len >> 0);
return 0;
error:
P->end = end;
return error;
} /* dns_mx_push() */
int dns_mx_cmp(const struct dns_mx *a, const struct dns_mx *b) {
int cmp;
if ((cmp = a->preference - b->preference))
return cmp;
return strcasecmp(a->host, b->host);
} /* dns_mx_cmp() */
size_t dns_mx_print(void *_dst, size_t lim, struct dns_mx *mx) {
struct dns_buf dst = DNS_B_INTO(_dst, lim);
dns_b_fmtju(&dst, mx->preference, 0);
dns_b_putc(&dst, ' ');
dns_b_puts(&dst, mx->host);
return dns_b_strllen(&dst);
} /* dns_mx_print() */
size_t dns_mx_cname(void *dst, size_t lim, struct dns_mx *mx) {
return dns_strlcpy(dst, mx->host, lim);
} /* dns_mx_cname() */
int dns_ns_parse(struct dns_ns *ns, struct dns_rr *rr, struct dns_packet *P) {
size_t len;
int error;
if (!(len = dns_d_expand(ns->host, sizeof ns->host, rr->rd.p, P, &error)))
return error;
else if (len >= sizeof ns->host)
return DNS_EILLEGAL;
return 0;
} /* dns_ns_parse() */
int dns_ns_push(struct dns_packet *P, struct dns_ns *ns) {
size_t end, len;
int error;
if (P->size - P->end < 3)
return DNS_ENOBUFS;
end = P->end;
P->end += 2;
if ((error = dns_d_push(P, ns->host, strlen(ns->host))))
goto error;
len = P->end - end - 2;
P->data[end + 0] = 0xff & (len >> 8);
P->data[end + 1] = 0xff & (len >> 0);
return 0;
error:
P->end = end;
return error;
} /* dns_ns_push() */
int dns_ns_cmp(const struct dns_ns *a, const struct dns_ns *b) {
return strcasecmp(a->host, b->host);
} /* dns_ns_cmp() */
size_t dns_ns_print(void *dst, size_t lim, struct dns_ns *ns) {
return dns_strlcpy(dst, ns->host, lim);
} /* dns_ns_print() */
size_t dns_ns_cname(void *dst, size_t lim, struct dns_ns *ns) {
return dns_strlcpy(dst, ns->host, lim);
} /* dns_ns_cname() */
int dns_cname_parse(struct dns_cname *cname, struct dns_rr *rr, struct dns_packet *P) {
return dns_ns_parse((struct dns_ns *)cname, rr, P);
} /* dns_cname_parse() */
int dns_cname_push(struct dns_packet *P, struct dns_cname *cname) {
return dns_ns_push(P, (struct dns_ns *)cname);
} /* dns_cname_push() */
int dns_cname_cmp(const struct dns_cname *a, const struct dns_cname *b) {
return strcasecmp(a->host, b->host);
} /* dns_cname_cmp() */
size_t dns_cname_print(void *dst, size_t lim, struct dns_cname *cname) {
return dns_ns_print(dst, lim, (struct dns_ns *)cname);
} /* dns_cname_print() */
size_t dns_cname_cname(void *dst, size_t lim, struct dns_cname *cname) {
return dns_strlcpy(dst, cname->host, lim);
} /* dns_cname_cname() */
int dns_soa_parse(struct dns_soa *soa, struct dns_rr *rr, struct dns_packet *P) {
struct { void *dst; size_t lim; } dn[] =
{ { soa->mname, sizeof soa->mname },
{ soa->rname, sizeof soa->rname } };
unsigned *ts[] =
{ &soa->serial, &soa->refresh, &soa->retry, &soa->expire, &soa->minimum };
unsigned short rp;
unsigned i, j, n;
int error;
/* MNAME / RNAME */
if ((rp = rr->rd.p) >= P->end)
return DNS_EILLEGAL;
for (i = 0; i < lengthof(dn); i++) {
if (!(n = dns_d_expand(dn[i].dst, dn[i].lim, rp, P, &error)))
return error;
else if (n >= dn[i].lim)
return DNS_EILLEGAL;
if ((rp = dns_d_skip(rp, P)) >= P->end)
return DNS_EILLEGAL;
}
/* SERIAL / REFRESH / RETRY / EXPIRE / MINIMUM */
for (i = 0; i < lengthof(ts); i++) {
for (j = 0; j < 4; j++, rp++) {
if (rp >= P->end)
return DNS_EILLEGAL;
*ts[i] <<= 8;
*ts[i] |= (0xff & P->data[rp]);
}
}
return 0;
} /* dns_soa_parse() */
int dns_soa_push(struct dns_packet *P, struct dns_soa *soa) {
void *dn[] = { soa->mname, soa->rname };
unsigned ts[] = { (0xffffffff & soa->serial),
(0x7fffffff & soa->refresh),
(0x7fffffff & soa->retry),
(0x7fffffff & soa->expire),
(0xffffffff & soa->minimum) };
unsigned i, j;
size_t end, len;
int error;
end = P->end;
if ((P->end += 2) >= P->size)
goto toolong;
/* MNAME / RNAME */
for (i = 0; i < lengthof(dn); i++) {
if ((error = dns_d_push(P, dn[i], strlen(dn[i]))))
goto error;
}
/* SERIAL / REFRESH / RETRY / EXPIRE / MINIMUM */
for (i = 0; i < lengthof(ts); i++) {
if ((P->end += 4) >= P->size)
goto toolong;
for (j = 1; j <= 4; j++) {
P->data[P->end - j] = (0xff & ts[i]);
ts[i] >>= 8;
}
}
len = P->end - end - 2;
P->data[end + 0] = (0xff & (len >> 8));
P->data[end + 1] = (0xff & (len >> 0));
return 0;
toolong:
error = DNS_ENOBUFS;
/* FALL THROUGH */
error:
P->end = end;
return error;
} /* dns_soa_push() */
int dns_soa_cmp(const struct dns_soa *a, const struct dns_soa *b) {
int cmp;
if ((cmp = strcasecmp(a->mname, b->mname)))
return cmp;
if ((cmp = strcasecmp(a->rname, b->rname)))
return cmp;
if (a->serial > b->serial)
return -1;
else if (a->serial < b->serial)
return 1;
if (a->refresh > b->refresh)
return -1;
else if (a->refresh < b->refresh)
return 1;
if (a->retry > b->retry)
return -1;
else if (a->retry < b->retry)
return 1;
if (a->expire > b->expire)
return -1;
else if (a->expire < b->expire)
return 1;
if (a->minimum > b->minimum)
return -1;
else if (a->minimum < b->minimum)
return 1;
return 0;
} /* dns_soa_cmp() */
size_t dns_soa_print(void *_dst, size_t lim, struct dns_soa *soa) {
struct dns_buf dst = DNS_B_INTO(_dst, lim);
dns_b_puts(&dst, soa->mname);
dns_b_putc(&dst, ' ');
dns_b_puts(&dst, soa->rname);
dns_b_putc(&dst, ' ');
dns_b_fmtju(&dst, soa->serial, 0);
dns_b_putc(&dst, ' ');
dns_b_fmtju(&dst, soa->refresh, 0);
dns_b_putc(&dst, ' ');
dns_b_fmtju(&dst, soa->retry, 0);
dns_b_putc(&dst, ' ');
dns_b_fmtju(&dst, soa->expire, 0);
dns_b_putc(&dst, ' ');
dns_b_fmtju(&dst, soa->minimum, 0);
return dns_b_strllen(&dst);
} /* dns_soa_print() */
int dns_srv_parse(struct dns_srv *srv, struct dns_rr *rr, struct dns_packet *P) {
unsigned short rp;
unsigned i;
size_t n;
int error;
memset(srv, '\0', sizeof *srv);
rp = rr->rd.p;
if (rr->rd.len < 7)
return DNS_EILLEGAL;
for (i = 0; i < 2; i++, rp++) {
srv->priority <<= 8;
srv->priority |= (0xff & P->data[rp]);
}
for (i = 0; i < 2; i++, rp++) {
srv->weight <<= 8;
srv->weight |= (0xff & P->data[rp]);
}
for (i = 0; i < 2; i++, rp++) {
srv->port <<= 8;
srv->port |= (0xff & P->data[rp]);
}
if (!(n = dns_d_expand(srv->target, sizeof srv->target, rp, P, &error)))
return error;
else if (n >= sizeof srv->target)
return DNS_EILLEGAL;
return 0;
} /* dns_srv_parse() */
int dns_srv_push(struct dns_packet *P, struct dns_srv *srv) {
size_t end, len;
int error;
end = P->end;
if (P->size - P->end < 2)
goto toolong;
P->end += 2;
if (P->size - P->end < 6)
goto toolong;
P->data[P->end++] = 0xff & (srv->priority >> 8);
P->data[P->end++] = 0xff & (srv->priority >> 0);
P->data[P->end++] = 0xff & (srv->weight >> 8);
P->data[P->end++] = 0xff & (srv->weight >> 0);
P->data[P->end++] = 0xff & (srv->port >> 8);
P->data[P->end++] = 0xff & (srv->port >> 0);
if (0 == (len = dns_d_comp(&P->data[P->end], P->size - P->end, srv->target, strlen(srv->target), P, &error)))
goto error;
else if (P->size - P->end < len)
goto toolong;
P->end += len;
if (P->end > 65535)
goto toolong;
len = P->end - end - 2;
P->data[end + 0] = 0xff & (len >> 8);
P->data[end + 1] = 0xff & (len >> 0);
return 0;
toolong:
error = DNS_ENOBUFS;
/* FALL THROUGH */
error:
P->end = end;
return error;
} /* dns_srv_push() */
int dns_srv_cmp(const struct dns_srv *a, const struct dns_srv *b) {
int cmp;
if ((cmp = a->priority - b->priority))
return cmp;
/*
* FIXME: We need some sort of random seed to implement the dynamic
* weighting required by RFC 2782.
*/
if ((cmp = a->weight - b->weight))
return cmp;
if ((cmp = a->port - b->port))
return cmp;
return strcasecmp(a->target, b->target);
} /* dns_srv_cmp() */
size_t dns_srv_print(void *_dst, size_t lim, struct dns_srv *srv) {
struct dns_buf dst = DNS_B_INTO(_dst, lim);
dns_b_fmtju(&dst, srv->priority, 0);
dns_b_putc(&dst, ' ');
dns_b_fmtju(&dst, srv->weight, 0);
dns_b_putc(&dst, ' ');
dns_b_fmtju(&dst, srv->port, 0);
dns_b_putc(&dst, ' ');
dns_b_puts(&dst, srv->target);
return dns_b_strllen(&dst);
} /* dns_srv_print() */
size_t dns_srv_cname(void *dst, size_t lim, struct dns_srv *srv) {
return dns_strlcpy(dst, srv->target, lim);
} /* dns_srv_cname() */
unsigned int dns_opt_ttl(const struct dns_opt *opt) {
unsigned int ttl = 0;
ttl |= (0xffU & opt->rcode) << 24;
ttl |= (0xffU & opt->version) << 16;
ttl |= (0xffffU & opt->flags) << 0;
return ttl;
} /* dns_opt_ttl() */
unsigned short dns_opt_class(const struct dns_opt *opt) {
return opt->maxudp;
} /* dns_opt_class() */
struct dns_opt *dns_opt_init(struct dns_opt *opt, size_t size) {
assert(size >= offsetof(struct dns_opt, data));
opt->size = size - offsetof(struct dns_opt, data);
opt->len = 0;
opt->rcode = 0;
opt->version = 0;
opt->maxudp = 0;
return opt;
} /* dns_opt_init() */
static union dns_any *dns_opt_initany(union dns_any *any, size_t size) {
return dns_opt_init(&any->opt, size), any;
} /* dns_opt_initany() */
int dns_opt_parse(struct dns_opt *opt, struct dns_rr *rr, struct dns_packet *P) {
const struct dns_buf src = DNS_B_FROM(&P->data[rr->rd.p], rr->rd.len);
struct dns_buf dst = DNS_B_INTO(opt->data, opt->size);
int error;
opt->rcode = 0xfff & ((rr->ttl >> 20) | dns_header(P)->rcode);
opt->version = 0xff & (rr->ttl >> 16);
opt->flags = 0xffff & rr->ttl;
opt->maxudp = 0xffff & rr->class;
while (src.p < src.pe) {
int code, len;
if (-1 == (code = dns_b_get16(&src, -1)))
return src.error;
if (-1 == (len = dns_b_get16(&src, -1)))
return src.error;
switch (code) {
default:
dns_b_put16(&dst, code);
dns_b_put16(&dst, len);
if ((error = dns_b_move(&dst, &src, len)))
return error;
break;
}
}
return 0;
} /* dns_opt_parse() */
int dns_opt_push(struct dns_packet *P, struct dns_opt *opt) {
const struct dns_buf src = DNS_B_FROM(opt->data, opt->len);
struct dns_buf dst = DNS_B_INTO(&P->data[P->end], (P->size - P->end));
int error;
/* rdata length (see below) */
if ((error = dns_b_put16(&dst, 0)))
goto error;
/* ... push known options here */
/* push opaque option data */
if ((error = dns_b_move(&dst, &src, (size_t)(src.pe - src.p))))
goto error;
/* rdata length */
if ((error = dns_b_pput16(&dst, dns_b_tell(&dst) - 2, 0)))
goto error;
#if !DNS_DEBUG_OPT_FORMERR
P->end += dns_b_tell(&dst);
#endif
return 0;
error:
return error;
} /* dns_opt_push() */
int dns_opt_cmp(const struct dns_opt *a, const struct dns_opt *b) {
(void)a;
(void)b;
return -1;
} /* dns_opt_cmp() */
size_t dns_opt_print(void *_dst, size_t lim, struct dns_opt *opt) {
struct dns_buf dst = DNS_B_INTO(_dst, lim);
size_t p;
dns_b_putc(&dst, '"');
for (p = 0; p < opt->len; p++) {
dns_b_putc(&dst, '\\');
dns_b_fmtju(&dst, opt->data[p], 3);
}
dns_b_putc(&dst, '"');
return dns_b_strllen(&dst);
} /* dns_opt_print() */
int dns_ptr_parse(struct dns_ptr *ptr, struct dns_rr *rr, struct dns_packet *P) {
return dns_ns_parse((struct dns_ns *)ptr, rr, P);
} /* dns_ptr_parse() */
int dns_ptr_push(struct dns_packet *P, struct dns_ptr *ptr) {
return dns_ns_push(P, (struct dns_ns *)ptr);
} /* dns_ptr_push() */
size_t dns_ptr_qname(void *dst, size_t lim, int af, void *addr) {
switch (af) {
case AF_INET6:
return dns_aaaa_arpa(dst, lim, addr);
case AF_INET:
return dns_a_arpa(dst, lim, addr);
default: {
struct dns_a a;
a.addr.s_addr = INADDR_NONE;
return dns_a_arpa(dst, lim, &a);
}
}
} /* dns_ptr_qname() */
int dns_ptr_cmp(const struct dns_ptr *a, const struct dns_ptr *b) {
return strcasecmp(a->host, b->host);
} /* dns_ptr_cmp() */
size_t dns_ptr_print(void *dst, size_t lim, struct dns_ptr *ptr) {
return dns_ns_print(dst, lim, (struct dns_ns *)ptr);
} /* dns_ptr_print() */
size_t dns_ptr_cname(void *dst, size_t lim, struct dns_ptr *ptr) {
return dns_strlcpy(dst, ptr->host, lim);
} /* dns_ptr_cname() */
int dns_sshfp_parse(struct dns_sshfp *fp, struct dns_rr *rr, struct dns_packet *P) {
unsigned p = rr->rd.p, pe = rr->rd.p + rr->rd.len;
if (pe - p < 2)
return DNS_EILLEGAL;
fp->algo = P->data[p++];
fp->type = P->data[p++];
switch (fp->type) {
case DNS_SSHFP_SHA1:
if (pe - p < sizeof fp->digest.sha1)
return DNS_EILLEGAL;
memcpy(fp->digest.sha1, &P->data[p], sizeof fp->digest.sha1);
break;
default:
break;
} /* switch() */
return 0;
} /* dns_sshfp_parse() */
int dns_sshfp_push(struct dns_packet *P, struct dns_sshfp *fp) {
unsigned p = P->end, pe = P->size, n;
if (pe - p < 4)
return DNS_ENOBUFS;
p += 2;
P->data[p++] = 0xff & fp->algo;
P->data[p++] = 0xff & fp->type;
switch (fp->type) {
case DNS_SSHFP_SHA1:
if (pe - p < sizeof fp->digest.sha1)
return DNS_ENOBUFS;
memcpy(&P->data[p], fp->digest.sha1, sizeof fp->digest.sha1);
p += sizeof fp->digest.sha1;
break;
default:
return DNS_EILLEGAL;
} /* switch() */
n = p - P->end - 2;
P->data[P->end++] = 0xff & (n >> 8);
P->data[P->end++] = 0xff & (n >> 0);
P->end = p;
return 0;
} /* dns_sshfp_push() */
int dns_sshfp_cmp(const struct dns_sshfp *a, const struct dns_sshfp *b) {
int cmp;
if ((cmp = a->algo - b->algo) || (cmp = a->type - b->type))
return cmp;
switch (a->type) {
case DNS_SSHFP_SHA1:
return memcmp(a->digest.sha1, b->digest.sha1, sizeof a->digest.sha1);
default:
return 0;
} /* switch() */
/* NOT REACHED */
} /* dns_sshfp_cmp() */
size_t dns_sshfp_print(void *_dst, size_t lim, struct dns_sshfp *fp) {
static const unsigned char hex[16] = "0123456789abcdef";
struct dns_buf dst = DNS_B_INTO(_dst, lim);
size_t i;
dns_b_fmtju(&dst, fp->algo, 0);
dns_b_putc(&dst, ' ');
dns_b_fmtju(&dst, fp->type, 0);
dns_b_putc(&dst, ' ');
switch (fp->type) {
case DNS_SSHFP_SHA1:
for (i = 0; i < sizeof fp->digest.sha1; i++) {
dns_b_putc(&dst, hex[0x0f & (fp->digest.sha1[i] >> 4)]);
dns_b_putc(&dst, hex[0x0f & (fp->digest.sha1[i] >> 0)]);
}
break;
default:
dns_b_putc(&dst, '0');
break;
} /* switch() */
return dns_b_strllen(&dst);
} /* dns_sshfp_print() */
struct dns_txt *dns_txt_init(struct dns_txt *txt, size_t size) {
assert(size > offsetof(struct dns_txt, data));
txt->size = size - offsetof(struct dns_txt, data);
txt->len = 0;
return txt;
} /* dns_txt_init() */
static union dns_any *dns_txt_initany(union dns_any *any, size_t size) {
/* NB: union dns_any is already initialized as struct dns_txt */
(void)size;
return any;
} /* dns_txt_initany() */
int dns_txt_parse(struct dns_txt *txt, struct dns_rr *rr, struct dns_packet *P) {
struct { unsigned char *b; size_t p, end; } dst, src;
unsigned n;
dst.b = txt->data;
dst.p = 0;
dst.end = txt->size;
src.b = P->data;
src.p = rr->rd.p;
src.end = src.p + rr->rd.len;
while (src.p < src.end) {
n = 0xff & P->data[src.p++];
if (src.end - src.p < n || dst.end - dst.p < n)
return DNS_EILLEGAL;
memcpy(&dst.b[dst.p], &src.b[src.p], n);
dst.p += n;
src.p += n;
}
txt->len = dst.p;
return 0;
} /* dns_txt_parse() */
int dns_txt_push(struct dns_packet *P, struct dns_txt *txt) {
struct { unsigned char *b; size_t p, end; } dst, src;
unsigned n;
dst.b = P->data;
dst.p = P->end;
dst.end = P->size;
src.b = txt->data;
src.p = 0;
src.end = txt->len;
if (dst.end - dst.p < 2)
return DNS_ENOBUFS;
n = txt->len + ((txt->len + 254) / 255);
dst.b[dst.p++] = 0xff & (n >> 8);
dst.b[dst.p++] = 0xff & (n >> 0);
while (src.p < src.end) {
n = DNS_PP_MIN(255, src.end - src.p);
if (dst.p >= dst.end)
return DNS_ENOBUFS;
dst.b[dst.p++] = n;
if (dst.end - dst.p < n)
return DNS_ENOBUFS;
memcpy(&dst.b[dst.p], &src.b[src.p], n);
dst.p += n;
src.p += n;
}
P->end = dst.p;
return 0;
} /* dns_txt_push() */
int dns_txt_cmp(const struct dns_txt *a, const struct dns_txt *b) {
(void)a;
(void)b;
return -1;
} /* dns_txt_cmp() */
size_t dns_txt_print(void *_dst, size_t lim, struct dns_txt *txt) {
struct dns_buf src = DNS_B_FROM(txt->data, txt->len);
struct dns_buf dst = DNS_B_INTO(_dst, lim);
unsigned i;
if (src.p < src.pe) {
do {
dns_b_putc(&dst, '"');
for (i = 0; i < 256 && src.p < src.pe; i++, src.p++) {
if (*src.p < 32 || *src.p > 126 || *src.p == '"' || *src.p == '\\') {
dns_b_putc(&dst, '\\');
dns_b_fmtju(&dst, *src.p, 3);
} else {
dns_b_putc(&dst, *src.p);
}
}
dns_b_putc(&dst, '"');
dns_b_putc(&dst, ' ');
} while (src.p < src.pe);
dns_b_popc(&dst);
} else {
dns_b_putc(&dst, '"');
dns_b_putc(&dst, '"');
}
return dns_b_strllen(&dst);
} /* dns_txt_print() */
/* Some of the function pointers of DNS_RRTYPES are initialized with
* slighlly different functions, thus we can't use prototypes. */
DNS_PRAGMA_PUSH
#if __clang__
#pragma clang diagnostic ignored "-Wstrict-prototypes"
#elif DNS_GNUC_PREREQ(4,6,0)
#pragma GCC diagnostic ignored "-Wstrict-prototypes"
#endif
static const struct dns_rrtype {
enum dns_type type;
const char *name;
union dns_any *(*init)(union dns_any *, size_t);
int (*parse)();
int (*push)();
int (*cmp)();
size_t (*print)();
size_t (*cname)();
} dns_rrtypes[] = {
{ DNS_T_A, "A", 0, &dns_a_parse, &dns_a_push, &dns_a_cmp, &dns_a_print, 0, },
{ DNS_T_AAAA, "AAAA", 0, &dns_aaaa_parse, &dns_aaaa_push, &dns_aaaa_cmp, &dns_aaaa_print, 0, },
{ DNS_T_MX, "MX", 0, &dns_mx_parse, &dns_mx_push, &dns_mx_cmp, &dns_mx_print, &dns_mx_cname, },
{ DNS_T_NS, "NS", 0, &dns_ns_parse, &dns_ns_push, &dns_ns_cmp, &dns_ns_print, &dns_ns_cname, },
{ DNS_T_CNAME, "CNAME", 0, &dns_cname_parse, &dns_cname_push, &dns_cname_cmp, &dns_cname_print, &dns_cname_cname, },
{ DNS_T_SOA, "SOA", 0, &dns_soa_parse, &dns_soa_push, &dns_soa_cmp, &dns_soa_print, 0, },
{ DNS_T_SRV, "SRV", 0, &dns_srv_parse, &dns_srv_push, &dns_srv_cmp, &dns_srv_print, &dns_srv_cname, },
{ DNS_T_OPT, "OPT", &dns_opt_initany, &dns_opt_parse, &dns_opt_push, &dns_opt_cmp, &dns_opt_print, 0, },
{ DNS_T_PTR, "PTR", 0, &dns_ptr_parse, &dns_ptr_push, &dns_ptr_cmp, &dns_ptr_print, &dns_ptr_cname, },
{ DNS_T_TXT, "TXT", &dns_txt_initany, &dns_txt_parse, &dns_txt_push, &dns_txt_cmp, &dns_txt_print, 0, },
{ DNS_T_SPF, "SPF", &dns_txt_initany, &dns_txt_parse, &dns_txt_push, &dns_txt_cmp, &dns_txt_print, 0, },
{ DNS_T_SSHFP, "SSHFP", 0, &dns_sshfp_parse, &dns_sshfp_push, &dns_sshfp_cmp, &dns_sshfp_print, 0, },
{ DNS_T_AXFR, "AXFR", 0, 0, 0, 0, 0, 0, },
}; /* dns_rrtypes[] */
DNS_PRAGMA_POP /*(-Wstrict-prototypes)*/
static const struct dns_rrtype *dns_rrtype(enum dns_type type) {
const struct dns_rrtype *t;
for (t = dns_rrtypes; t < endof(dns_rrtypes); t++) {
if (t->type == type && t->parse) {
return t;
}
}
return NULL;
} /* dns_rrtype() */
union dns_any *dns_any_init(union dns_any *any, size_t size) {
dns_static_assert(dns_same_type(any->txt, any->rdata, 1), "unexpected rdata type");
return (union dns_any *)dns_txt_init(&any->rdata, size);
} /* dns_any_init() */
static size_t dns_any_sizeof(union dns_any *any) {
dns_static_assert(dns_same_type(any->txt, any->rdata, 1), "unexpected rdata type");
return offsetof(struct dns_txt, data) + any->rdata.size;
} /* dns_any_sizeof() */
static union dns_any *dns_any_reinit(union dns_any *any, const struct dns_rrtype *t) {
return (t->init)? t->init(any, dns_any_sizeof(any)) : any;
} /* dns_any_reinit() */
int dns_any_parse(union dns_any *any, struct dns_rr *rr, struct dns_packet *P) {
const struct dns_rrtype *t;
if ((t = dns_rrtype(rr->type)))
return t->parse(dns_any_reinit(any, t), rr, P);
if (rr->rd.len > any->rdata.size)
return DNS_EILLEGAL;
memcpy(any->rdata.data, &P->data[rr->rd.p], rr->rd.len);
any->rdata.len = rr->rd.len;
return 0;
} /* dns_any_parse() */
int dns_any_push(struct dns_packet *P, union dns_any *any, enum dns_type type) {
const struct dns_rrtype *t;
if ((t = dns_rrtype(type)))
return t->push(P, any);
if (P->size - P->end < any->rdata.len + 2)
return DNS_ENOBUFS;
P->data[P->end++] = 0xff & (any->rdata.len >> 8);
P->data[P->end++] = 0xff & (any->rdata.len >> 0);
memcpy(&P->data[P->end], any->rdata.data, any->rdata.len);
P->end += any->rdata.len;
return 0;
} /* dns_any_push() */
int dns_any_cmp(const union dns_any *a, enum dns_type x, const union dns_any *b, enum dns_type y) {
const struct dns_rrtype *t;
int cmp;
if ((cmp = x - y))
return cmp;
if ((t = dns_rrtype(x)))
return t->cmp(a, b);
return -1;
} /* dns_any_cmp() */
size_t dns_any_print(void *_dst, size_t lim, union dns_any *any, enum dns_type type) {
const struct dns_rrtype *t;
struct dns_buf src, dst;
if ((t = dns_rrtype(type)))
return t->print(_dst, lim, any);
dns_b_from(&src, any->rdata.data, any->rdata.len);
dns_b_into(&dst, _dst, lim);
dns_b_putc(&dst, '"');
while (src.p < src.pe) {
dns_b_putc(&dst, '\\');
dns_b_fmtju(&dst, *src.p++, 3);
}
dns_b_putc(&dst, '"');
return dns_b_strllen(&dst);
} /* dns_any_print() */
size_t dns_any_cname(void *dst, size_t lim, union dns_any *any, enum dns_type type) {
const struct dns_rrtype *t;
if ((t = dns_rrtype(type)) && t->cname)
return t->cname(dst, lim, any);
return 0;
} /* dns_any_cname() */
/*
* E V E N T T R A C I N G R O U T I N E S
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include <float.h> /* DBL_MANT_DIG */
#include <inttypes.h> /* PRIu64 */
/* for default trace ID generation try to fit in lua_Number, usually double */
#define DNS_TRACE_ID_BITS DNS_PP_MIN(DBL_MANT_DIG, (sizeof (dns_trace_id_t) * CHAR_BIT)) /* assuming FLT_RADIX == 2 */
#define DNS_TRACE_ID_MASK (((DNS_TRACE_ID_C(1) << (DNS_TRACE_ID_BITS - 1)) - 1) | (DNS_TRACE_ID_C(1) << (DNS_TRACE_ID_BITS - 1)))
#define DNS_TRACE_ID_PRI PRIu64
static inline dns_trace_id_t dns_trace_mkid(void) {
dns_trace_id_t id = 0;
unsigned r; /* return type of dns_random() */
const size_t id_bit = sizeof id * CHAR_BIT;
const size_t r_bit = sizeof r * CHAR_BIT;
for (size_t n = 0; n < id_bit; n += r_bit) {
r = dns_random();
id <<= r_bit;
id |= r;
}
return DNS_TRACE_ID_MASK & id;
}
struct dns_trace {
dns_atomic_t refcount;
FILE *fp;
dns_trace_id_t id;
struct {
struct dns_trace_cname {
char host[DNS_D_MAXNAME + 1];
struct sockaddr_storage addr;
} base[4];
size_t p;
} cnames;
};
static void dns_te_initname(struct sockaddr_storage *ss, int fd, int (* STDCALL f)(socket_fd_t, struct sockaddr *, socklen_t *)) {
socklen_t n = sizeof *ss;
if (0 != f(fd, (struct sockaddr *)ss, &n))
goto unspec;
if (n > sizeof *ss)
goto unspec;
return;
unspec:
memset(ss, '\0', sizeof *ss);
ss->ss_family = AF_UNSPEC;
}
static void dns_te_initnames(struct sockaddr_storage *local, struct sockaddr_storage *remote, int fd) {
dns_te_initname(local, fd, &getsockname);
dns_te_initname(remote, fd, &getpeername);
}
static struct dns_trace_event *dns_te_init(struct dns_trace_event *te, int type) {
/* NB: silence valgrind */
memset(te, '\0', offsetof(struct dns_trace_event, data));
te->type = type;
return te;
}
int dns_trace_abi(void) {
return DNS_TRACE_ABI;
}
struct dns_trace *dns_trace_open(FILE *fp, dns_error_t *error) {
static const struct dns_trace trace_initializer = { .refcount = 1 };
struct dns_trace *trace;
if (!(trace = malloc(sizeof *trace)))
goto syerr;
*trace = trace_initializer;
if (fp) {
trace->fp = fp;
} else if (!(fp = tmpfile())) {
goto syerr;
}
trace->id = dns_trace_mkid();
return trace;
syerr:
*error = dns_syerr();
dns_trace_close(trace);
return NULL;
} /* dns_trace_open() */
void dns_trace_close(struct dns_trace *trace) {
if (!trace || 1 != dns_trace_release(trace))
return;
if (trace->fp)
fclose(trace->fp);
free(trace);
} /* dns_trace_close() */
dns_refcount_t dns_trace_acquire(struct dns_trace *trace) {
return dns_atomic_fetch_add(&trace->refcount);
} /* dns_trace_acquire() */
static struct dns_trace *dns_trace_acquire_p(struct dns_trace *trace) {
return (trace)? dns_trace_acquire(trace), trace : NULL;
} /* dns_trace_acquire_p() */
dns_refcount_t dns_trace_release(struct dns_trace *trace) {
return dns_atomic_fetch_sub(&trace->refcount);
} /* dns_trace_release() */
dns_trace_id_t dns_trace_id(struct dns_trace *trace) {
return trace->id;
} /* dns_trace_id() */
dns_trace_id_t dns_trace_setid(struct dns_trace *trace, dns_trace_id_t id) {
trace->id = (id)? id : dns_trace_mkid();
return trace->id;
} /* dns_trace_setid() */
struct dns_trace_event *dns_trace_get(struct dns_trace *trace, struct dns_trace_event **tp, dns_error_t *error) {
return dns_trace_fget(tp, trace->fp, error);
} /* dns_trace_get() */
dns_error_t dns_trace_put(struct dns_trace *trace, const struct dns_trace_event *te, const void *data, size_t datasize) {
return dns_trace_fput(te, data, datasize, trace->fp);
} /* dns_trace_put() */
struct dns_trace_event *dns_trace_tag(struct dns_trace *trace, struct dns_trace_event *te) {
struct timeval tv;
te->id = trace->id;
gettimeofday(&tv, NULL);
dns_tv2ts(&te->ts, &tv);
te->abi = DNS_TRACE_ABI;
return te;
} /* dns_trace_tag() */
static dns_error_t dns_trace_tag_and_put(struct dns_trace *trace, struct dns_trace_event *te, const void *data, size_t datasize) {
return dns_trace_put(trace, dns_trace_tag(trace, te), data, datasize);
} /* dns_trace_tag_and_put() */
struct dns_trace_event *dns_trace_fget(struct dns_trace_event **tp, FILE *fp, dns_error_t *error) {
const size_t headsize = offsetof(struct dns_trace_event, data);
struct dns_trace_event tmp, *te;
size_t n;
errno = 0;
if (!(n = fread(&tmp, 1, headsize, fp)))
goto none;
if (n < offsetof(struct dns_trace_event, data))
goto some;
if (!(te = realloc(*tp, DNS_PP_MAX(headsize, tmp.size)))) {
*error = errno;
return NULL;
}
*tp = te;
memcpy(te, &tmp, offsetof(struct dns_trace_event, data));
if (dns_te_datasize(te)) {
errno = 0;
if (!(n = fread(te->data, 1, dns_te_datasize(te), fp)))
goto none;
if (n < dns_te_datasize(te))
goto some;
}
return te;
none:
*error = (ferror(fp))? errno : 0;
return NULL;
some:
*error = 0;
return NULL;
}
dns_error_t dns_trace_fput(const struct dns_trace_event *te, const void *data, size_t datasize, FILE *fp) {
size_t headsize = offsetof(struct dns_trace_event, data);
struct dns_trace_event tmp;
memcpy(&tmp, te, headsize);
tmp.size = headsize + datasize;
/* NB: ignore seek error as fp might not point to a regular file */
(void)fseek(fp, 0, SEEK_END);
if (fwrite(&tmp, 1, headsize, fp) < headsize)
return errno;
if (data)
if (fwrite(data, 1, datasize, fp) < datasize)
return errno;
if (fflush(fp))
return errno;
return 0;
}
static void dns_trace_setcname(struct dns_trace *trace, const char *host, const struct sockaddr *addr) {
struct dns_trace_cname *cname;
if (!trace || !trace->fp)
return;
cname = &trace->cnames.base[trace->cnames.p];
dns_strlcpy(cname->host, host, sizeof cname->host);
memcpy(&cname->addr, addr, DNS_PP_MIN(dns_sa_len(addr), sizeof cname->addr));
trace->cnames.p = (trace->cnames.p + 1) % lengthof(trace->cnames.base);
}
static const char *dns_trace_cname(struct dns_trace *trace, const struct sockaddr *addr) {
if (!trace || !trace->fp)
return NULL;
/* NB: start search from the write cursor to */
for (const struct dns_trace_cname *cname = trace->cnames.base; cname < endof(trace->cnames.base); cname++) {
if (0 == dns_sa_cmp((struct sockaddr *)addr, (struct sockaddr *)&cname->addr))
return cname->host;
}
return NULL;
}
static void dns_trace_res_submit(struct dns_trace *trace, const char *qname, enum dns_type qtype, enum dns_class qclass, int error) {
struct dns_trace_event te;
if (!trace || !trace->fp)
return;
dns_te_init(&te, DNS_TE_RES_SUBMIT);
dns_strlcpy(te.res_submit.qname, qname, sizeof te.res_submit.qname);
te.res_submit.qtype = qtype;
te.res_submit.qclass = qclass;
te.res_submit.error = error;
dns_trace_tag_and_put(trace, &te, NULL, 0);
}
static void dns_trace_res_fetch(struct dns_trace *trace, const struct dns_packet *packet, int error) {
struct dns_trace_event te;
const void *data;
size_t datasize;
if (!trace || !trace->fp)
return;
dns_te_init(&te, DNS_TE_RES_FETCH);
data = (packet)? packet->data : NULL;
datasize = (packet)? packet->end : 0;
te.res_fetch.error = error;
dns_trace_tag_and_put(trace, &te, data, datasize);
}
static void dns_trace_so_submit(struct dns_trace *trace, const struct dns_packet *packet, const struct sockaddr *haddr, int error) {
struct dns_trace_event te;
const char *cname;
if (!trace || !trace->fp)
return;
dns_te_init(&te, DNS_TE_SO_SUBMIT);
memcpy(&te.so_submit.haddr, haddr, DNS_PP_MIN(dns_sa_len(haddr), sizeof te.so_submit.haddr));
if ((cname = dns_trace_cname(trace, haddr)))
dns_strlcpy(te.so_submit.hname, cname, sizeof te.so_submit.hname);
te.so_submit.error = error;
dns_trace_tag_and_put(trace, &te, packet->data, packet->end);
}
static void dns_trace_so_verify(struct dns_trace *trace, const struct dns_packet *packet, int error) {
struct dns_trace_event te;
if (!trace || !trace->fp)
return;
dns_te_init(&te, DNS_TE_SO_VERIFY);
te.so_verify.error = error;
dns_trace_tag_and_put(trace, &te, packet->data, packet->end);
}
static void dns_trace_so_fetch(struct dns_trace *trace, const struct dns_packet *packet, int error) {
struct dns_trace_event te;
const void *data;
size_t datasize;
if (!trace || !trace->fp)
return;
dns_te_init(&te, DNS_TE_SO_FETCH);
data = (packet)? packet->data : NULL;
datasize = (packet)? packet->end : 0;
te.so_fetch.error = error;
dns_trace_tag_and_put(trace, &te, data, datasize);
}
static void dns_trace_sys_connect(struct dns_trace *trace, int fd, int socktype, const struct sockaddr *dst, int error) {
struct dns_trace_event te;
if (!trace || !trace->fp)
return;
dns_te_init(&te, DNS_TE_SYS_CONNECT);
dns_te_initname(&te.sys_connect.src, fd, &getsockname);
memcpy(&te.sys_connect.dst, dst, DNS_PP_MIN(dns_sa_len(dst), sizeof te.sys_connect.dst));
te.sys_connect.socktype = socktype;
te.sys_connect.error = error;
dns_trace_tag_and_put(trace, &te, NULL, 0);
}
static void dns_trace_sys_send(struct dns_trace *trace, int fd, int socktype, const void *data, size_t datasize, int error) {
struct dns_trace_event te;
if (!trace || !trace->fp)
return;
dns_te_init(&te, DNS_TE_SYS_SEND);
dns_te_initnames(&te.sys_send.src, &te.sys_send.dst, fd);
te.sys_send.socktype = socktype;
te.sys_send.error = error;
dns_trace_tag_and_put(trace, &te, data, datasize);
}
static void dns_trace_sys_recv(struct dns_trace *trace, int fd, int socktype, const void *data, size_t datasize, int error) {
struct dns_trace_event te;
if (!trace || !trace->fp)
return;
dns_te_init(&te, DNS_TE_SYS_RECV);
dns_te_initnames(&te.sys_recv.dst, &te.sys_recv.src, fd);
te.sys_recv.socktype = socktype;
te.sys_recv.error = error;
dns_trace_tag_and_put(trace, &te, data, datasize);
}
static dns_error_t dns_trace_dump_packet(struct dns_trace *trace, const char *prefix, const unsigned char *data, size_t datasize, FILE *fp) {
struct dns_packet *packet = NULL;
char *line = NULL, *p;
size_t size = 1, skip = 0;
struct dns_rr_i records;
struct dns_p_lines_i lines;
size_t len, count;
int error;
if (!(packet = dns_p_make(datasize, &error)))
goto error;
memcpy(packet->data, data, datasize);
packet->end = datasize;
(void)dns_p_study(packet);
resize:
if (!(p = dns_reallocarray(line, size, 2, &error)))
goto error;
line = p;
size *= 2;
memset(&records, 0, sizeof records);
memset(&lines, 0, sizeof lines);
count = 0;
while ((len = dns_p_lines(line, size, &error, packet, &records, &lines))) {
if (!(len < size)) {
skip = count;
goto resize;
} else if (skip <= count) {
fputs(prefix, fp);
fwrite(line, 1, len, fp);
}
count++;
}
if (error)
goto error;
error = 0;
error:
free(line);
dns_p_free(packet);
return error;
}
static dns_error_t dns_trace_dump_data(struct dns_trace *trace, const char *prefix, const unsigned char *data, size_t datasize, FILE *fp) {
struct dns_hxd_lines_i lines = { 0 };
char line[128];
size_t len;
while ((len = dns_hxd_lines(line, sizeof line, data, datasize, &lines))) {
if (len >= sizeof line)
return EOVERFLOW; /* shouldn't be possible */
fputs(prefix, fp);
fwrite(line, 1, len, fp);
}
return 0;
}
static dns_error_t dns_trace_dump_addr(struct dns_trace *trace, const char *prefix, const struct sockaddr_storage *ss, FILE *fp) {
const void *addr;
const char *path;
socklen_t len;
int error;
if ((addr = dns_sa_addr(ss->ss_family, (struct sockaddr *)ss, NULL))) {
char ip[INET6_ADDRSTRLEN + 1];
if ((error = dns_ntop(ss->ss_family, addr, ip, sizeof ip)))
return error;
fprintf(fp, "%s%s\n", prefix, ip);
} else if ((path = dns_sa_path((struct sockaddr *)ss, &len))) {
fprintf(fp, "%sunix:%.*s", prefix, (int)len, path);
} else {
return EINVAL;
}
return 0;
}
static dns_error_t dns_trace_dump_meta(struct dns_trace *trace, const char *prefix, const struct dns_trace_event *te, dns_microseconds_t elapsed, FILE *fp) {
char time_s[48], elapsed_s[48];
dns_utime_print(time_s, sizeof time_s, dns_ts2us(&te->ts, 0));
dns_utime_print(elapsed_s, sizeof elapsed_s, elapsed);
fprintf(fp, "%sid: %"DNS_TRACE_ID_PRI"\n", prefix, te->id);
fprintf(fp, "%sts: %s (%s)\n", prefix, time_s, elapsed_s);
fprintf(fp, "%sabi: 0x%x (0x%x)\n", prefix, te->abi, DNS_TRACE_ABI);
return 0;
}
static dns_error_t dns_trace_dump_error(struct dns_trace *trace, const char *prefix, int error, FILE *fp) {
fprintf(fp, "%s%d (%s)\n", prefix, error, (error)? dns_strerror(error) : "none");
return 0;
}
dns_error_t dns_trace_dump(struct dns_trace *trace, FILE *fp) {
struct dns_trace_event *te = NULL;
struct {
dns_trace_id_t id;
dns_microseconds_t begin, elapsed;
} state = { 0 };
int error;
if (!trace || !trace->fp)
return EINVAL;
if (0 != fseek(trace->fp, 0, SEEK_SET))
goto syerr;
while (dns_trace_fget(&te, trace->fp, &error)) {
size_t datasize = dns_te_datasize(te);
const unsigned char *data = (datasize)? te->data : NULL;
if (state.id != te->id) {
state.id = te->id;
state.begin = dns_ts2us(&te->ts, 0);
}
dns_time_diff(&state.elapsed, dns_ts2us(&te->ts, 0), state.begin);
switch(te->type) {
case DNS_TE_RES_SUBMIT:
fprintf(fp, "dns_res_submit:\n");
dns_trace_dump_meta(trace, " ", te, state.elapsed, fp);
fprintf(fp, " qname: %s\n", te->res_submit.qname);
fprintf(fp, " qtype: %s\n", dns_strtype(te->res_submit.qtype));
fprintf(fp, " qclass: %s\n", dns_strclass(te->res_submit.qclass));
dns_trace_dump_error(trace, " error: ", te->res_submit.error, fp);
break;
case DNS_TE_RES_FETCH:
fprintf(fp, "dns_res_fetch:\n");
dns_trace_dump_meta(trace, " ", te, state.elapsed, fp);
dns_trace_dump_error(trace, " error: ", te->res_fetch.error, fp);
if (data) {
fprintf(fp, " packet: |\n");
if ((error = dns_trace_dump_packet(trace, " ", data, datasize, fp)))
goto error;
fprintf(fp, " data: |\n");
if ((error = dns_trace_dump_data(trace, " ", data, datasize, fp)))
goto error;
}
break;
case DNS_TE_SO_SUBMIT:
fprintf(fp, "dns_so_submit:\n");
dns_trace_dump_meta(trace, " ", te, state.elapsed, fp);
fprintf(fp, " hname: %s\n", te->so_submit.hname);
dns_trace_dump_addr(trace, " haddr: ", &te->so_submit.haddr, fp);
dns_trace_dump_error(trace, " error: ", te->so_submit.error, fp);
if (data) {
fprintf(fp, " packet: |\n");
if ((error = dns_trace_dump_packet(trace, " ", data, datasize, fp)))
goto error;
fprintf(fp, " data: |\n");
if ((error = dns_trace_dump_data(trace, " ", data, datasize, fp)))
goto error;
}
break;
case DNS_TE_SO_VERIFY:
fprintf(fp, "dns_so_verify:\n");
dns_trace_dump_meta(trace, " ", te, state.elapsed, fp);
dns_trace_dump_error(trace, " error: ", te->so_verify.error, fp);
if (data) {
fprintf(fp, " packet: |\n");
if ((error = dns_trace_dump_packet(trace, " ", data, datasize, fp)))
goto error;
fprintf(fp, " data: |\n");
if ((error = dns_trace_dump_data(trace, " ", data, datasize, fp)))
goto error;
}
break;
case DNS_TE_SO_FETCH:
fprintf(fp, "dns_so_fetch:\n");
dns_trace_dump_meta(trace, " ", te, state.elapsed, fp);
dns_trace_dump_error(trace, " error: ", te->so_fetch.error, fp);
if (data) {
fprintf(fp, " packet: |\n");
if ((error = dns_trace_dump_packet(trace, " ", data, datasize, fp)))
goto error;
fprintf(fp, " data: |\n");
if ((error = dns_trace_dump_data(trace, " ", data, datasize, fp)))
goto error;
}
break;
case DNS_TE_SYS_CONNECT: {
int socktype = te->sys_connect.socktype;
fprintf(fp, "dns_sys_connect:\n");
dns_trace_dump_meta(trace, " ", te, state.elapsed, fp);
dns_trace_dump_addr(trace, " src: ", &te->sys_connect.src, fp);
dns_trace_dump_addr(trace, " dst: ", &te->sys_connect.dst, fp);
fprintf(fp, " socktype: %d (%s)\n", socktype, ((socktype == SOCK_STREAM)? "SOCK_STREAM" : (socktype == SOCK_DGRAM)? "SOCK_DGRAM" : "?"));
dns_trace_dump_error(trace, " error: ", te->sys_connect.error, fp);
break;
}
case DNS_TE_SYS_SEND: {
int socktype = te->sys_send.socktype;
fprintf(fp, "dns_sys_send:\n");
dns_trace_dump_meta(trace, " ", te, state.elapsed, fp);
dns_trace_dump_addr(trace, " src: ", &te->sys_send.src, fp);
dns_trace_dump_addr(trace, " dst: ", &te->sys_send.dst, fp);
fprintf(fp, " socktype: %d (%s)\n", socktype, ((socktype == SOCK_STREAM)? "SOCK_STREAM" : (socktype == SOCK_DGRAM)? "SOCK_DGRAM" : "?"));
dns_trace_dump_error(trace, " error: ", te->sys_send.error, fp);
if (data) {
fprintf(fp, " data: |\n");
if ((error = dns_trace_dump_data(trace, " ", data, datasize, fp)))
goto error;
}
break;
}
case DNS_TE_SYS_RECV: {
int socktype = te->sys_recv.socktype;
fprintf(fp, "dns_sys_recv:\n");
dns_trace_dump_meta(trace, " ", te, state.elapsed, fp);
dns_trace_dump_addr(trace, " src: ", &te->sys_recv.src, fp);
dns_trace_dump_addr(trace, " dst: ", &te->sys_recv.dst, fp);
fprintf(fp, " socktype: %d (%s)\n", socktype, ((socktype == SOCK_STREAM)? "SOCK_STREAM" : (socktype == SOCK_DGRAM)? "SOCK_DGRAM" : "?"));
dns_trace_dump_error(trace, " error: ", te->sys_recv.error, fp);
if (data) {
fprintf(fp, " data: |\n");
if ((error = dns_trace_dump_data(trace, " ", data, datasize, fp)))
goto error;
}
break;
}
default:
fprintf(fp, "unknown(0x%.2x):\n", te->type);
dns_trace_dump_meta(trace, " ", te, state.elapsed, fp);
if (data) {
fprintf(fp, " data: |\n");
if ((error = dns_trace_dump_data(trace, " ", data, datasize, fp)))
goto error;
}
break;
}
}
goto epilog;
syerr:
error = errno;
error:
(void)0;
epilog:
free(te);
return error;
}
/*
* H O S T S R O U T I N E S
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
struct dns_hosts {
struct dns_hosts_entry {
char host[DNS_D_MAXNAME + 1];
char arpa[73 + 1];
int af;
union {
struct in_addr a4;
struct in6_addr a6;
} addr;
_Bool alias;
struct dns_hosts_entry *next;
} *head, **tail;
dns_atomic_t refcount;
}; /* struct dns_hosts */
struct dns_hosts *dns_hosts_open(int *error) {
static const struct dns_hosts hosts_initializer = { .refcount = 1 };
struct dns_hosts *hosts;
if (!(hosts = malloc(sizeof *hosts)))
goto syerr;
*hosts = hosts_initializer;
hosts->tail = &hosts->head;
return hosts;
syerr:
*error = dns_syerr();
free(hosts);
return 0;
} /* dns_hosts_open() */
void dns_hosts_close(struct dns_hosts *hosts) {
struct dns_hosts_entry *ent, *xnt;
if (!hosts || 1 != dns_hosts_release(hosts))
return;
for (ent = hosts->head; ent; ent = xnt) {
xnt = ent->next;
free(ent);
}
free(hosts);
return;
} /* dns_hosts_close() */
dns_refcount_t dns_hosts_acquire(struct dns_hosts *hosts) {
return dns_atomic_fetch_add(&hosts->refcount);
} /* dns_hosts_acquire() */
dns_refcount_t dns_hosts_release(struct dns_hosts *hosts) {
return dns_atomic_fetch_sub(&hosts->refcount);
} /* dns_hosts_release() */
struct dns_hosts *dns_hosts_mortal(struct dns_hosts *hosts) {
if (hosts)
dns_hosts_release(hosts);
return hosts;
} /* dns_hosts_mortal() */
struct dns_hosts *dns_hosts_local(int *error_) {
struct dns_hosts *hosts;
int error;
if (!(hosts = dns_hosts_open(&error)))
goto error;
if ((error = dns_hosts_loadpath(hosts, "/etc/hosts")))
goto error;
return hosts;
error:
*error_ = error;
dns_hosts_close(hosts);
return 0;
} /* dns_hosts_local() */
#define dns_hosts_issep(ch) (dns_isspace(ch))
#define dns_hosts_iscom(ch) ((ch) == '#' || (ch) == ';')
int dns_hosts_loadfile(struct dns_hosts *hosts, FILE *fp) {
struct dns_hosts_entry ent;
char word[DNS_PP_MAX(INET6_ADDRSTRLEN, DNS_D_MAXNAME) + 1];
unsigned wp, wc, skip;
int ch, error;
rewind(fp);
do {
memset(&ent, '\0', sizeof ent);
wc = 0;
skip = 0;
do {
memset(word, '\0', sizeof word);
wp = 0;
while (EOF != (ch = fgetc(fp)) && ch != '\n') {
skip |= !!dns_hosts_iscom(ch);
if (skip)
continue;
if (dns_hosts_issep(ch))
break;
if (wp < sizeof word - 1)
word[wp] = ch;
wp++;
}
if (!wp)
continue;
wc++;
switch (wc) {
case 0:
break;
case 1:
ent.af = (strchr(word, ':'))? AF_INET6 : AF_INET;
skip = (1 != dns_inet_pton(ent.af, word, &ent.addr));
break;
default:
if (!wp)
break;
dns_d_anchor(ent.host, sizeof ent.host, word, wp);
if ((error = dns_hosts_insert(hosts, ent.af, &ent.addr, ent.host, (wc > 2))))
return error;
break;
} /* switch() */
} while (ch != EOF && ch != '\n');
} while (ch != EOF);
return 0;
} /* dns_hosts_loadfile() */
int dns_hosts_loadpath(struct dns_hosts *hosts, const char *path) {
FILE *fp;
int error;
if (!(fp = dns_fopen(path, "rt", &error)))
return error;
error = dns_hosts_loadfile(hosts, fp);
fclose(fp);
return error;
} /* dns_hosts_loadpath() */
int dns_hosts_dump(struct dns_hosts *hosts, FILE *fp) {
struct dns_hosts_entry *ent, *xnt;
char addr[INET6_ADDRSTRLEN + 1];
unsigned i;
for (ent = hosts->head; ent; ent = xnt) {
xnt = ent->next;
dns_inet_ntop(ent->af, &ent->addr, addr, sizeof addr);
fputs(addr, fp);
for (i = strlen(addr); i < INET_ADDRSTRLEN; i++)
fputc(' ', fp);
fputc(' ', fp);
fputs(ent->host, fp);
fputc('\n', fp);
}
return 0;
} /* dns_hosts_dump() */
int dns_hosts_insert(struct dns_hosts *hosts, int af, const void *addr, const void *host, _Bool alias) {
struct dns_hosts_entry *ent;
int error;
if (!(ent = malloc(sizeof *ent)))
goto syerr;
dns_d_anchor(ent->host, sizeof ent->host, host, strlen(host));
switch ((ent->af = af)) {
case AF_INET6:
memcpy(&ent->addr.a6, addr, sizeof ent->addr.a6);
dns_aaaa_arpa(ent->arpa, sizeof ent->arpa, addr);
break;
case AF_INET:
memcpy(&ent->addr.a4, addr, sizeof ent->addr.a4);
dns_a_arpa(ent->arpa, sizeof ent->arpa, addr);
break;
default:
error = EINVAL;
goto error;
} /* switch() */
ent->alias = alias;
ent->next = 0;
*hosts->tail = ent;
hosts->tail = &ent->next;
return 0;
syerr:
error = dns_syerr();
error:
free(ent);
return error;
} /* dns_hosts_insert() */
struct dns_packet *dns_hosts_query(struct dns_hosts *hosts, struct dns_packet *Q, int *error_) {
- union { unsigned char b[dns_p_calcsize((512))]; struct dns_packet p; } _P = { 0 };
- struct dns_packet *P = dns_p_init(&_P.p, 512);
+ union { unsigned char b[dns_p_calcsize((512))]; struct dns_packet p; } P_instance = { 0 };
+ struct dns_packet *P = dns_p_init(&P_instance.p, 512);
struct dns_packet *A = 0;
struct dns_rr rr;
struct dns_hosts_entry *ent;
int error, af;
char qname[DNS_D_MAXNAME + 1];
size_t qlen;
if ((error = dns_rr_parse(&rr, 12, Q)))
goto error;
if (!(qlen = dns_d_expand(qname, sizeof qname, rr.dn.p, Q, &error)))
goto error;
else if (qlen >= sizeof qname)
goto toolong;
if ((error = dns_p_push(P, DNS_S_QD, qname, qlen, rr.type, rr.class, 0, 0)))
goto error;
switch (rr.type) {
case DNS_T_PTR:
for (ent = hosts->head; ent; ent = ent->next) {
if (ent->alias || 0 != strcasecmp(qname, ent->arpa))
continue;
if ((error = dns_p_push(P, DNS_S_AN, qname, qlen, rr.type, rr.class, 0, ent->host)))
goto error;
}
break;
case DNS_T_AAAA:
af = AF_INET6;
goto loop;
case DNS_T_A:
af = AF_INET;
loop: for (ent = hosts->head; ent; ent = ent->next) {
if (ent->af != af || 0 != strcasecmp(qname, ent->host))
continue;
if ((error = dns_p_push(P, DNS_S_AN, qname, qlen, rr.type, rr.class, 0, &ent->addr)))
goto error;
}
break;
default:
break;
} /* switch() */
if (!(A = dns_p_copy(dns_p_make(P->end, &error), P)))
goto error;
return A;
toolong:
error = DNS_EILLEGAL;
error:
*error_ = error;
dns_p_free(A);
return 0;
} /* dns_hosts_query() */
/*
* R E S O L V . C O N F R O U T I N E S
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
struct dns_resolv_conf *dns_resconf_open(int *error) {
static const struct dns_resolv_conf resconf_initializer = {
.lookup = "bf",
.family = { AF_INET, AF_INET6 },
.options = { .ndots = 1, .timeout = 5, .attempts = 2, .tcp = DNS_RESCONF_TCP_ENABLE, },
.iface = { .ss_family = AF_INET },
};
struct dns_resolv_conf *resconf;
struct sockaddr_in *sin;
if (!(resconf = malloc(sizeof *resconf)))
goto syerr;
*resconf = resconf_initializer;
sin = (struct sockaddr_in *)&resconf->nameserver[0];
sin->sin_family = AF_INET;
sin->sin_addr.s_addr = INADDR_ANY;
sin->sin_port = htons(53);
#if defined(SA_LEN)
sin->sin_len = sizeof *sin;
#endif
if (0 != gethostname(resconf->search[0], sizeof resconf->search[0]))
goto syerr;
/*
* If gethostname() returned a string without any label
* separator, then search[0][0] should be NUL.
*/
if (strchr (resconf->search[0], '.')) {
dns_d_anchor(resconf->search[0], sizeof resconf->search[0], resconf->search[0], strlen(resconf->search[0]));
dns_d_cleave(resconf->search[0], sizeof resconf->search[0], resconf->search[0], strlen(resconf->search[0]));
} else {
memset (resconf->search[0], 0, sizeof resconf->search[0]);
}
dns_resconf_acquire(resconf);
return resconf;
syerr:
*error = dns_syerr();
free(resconf);
return 0;
} /* dns_resconf_open() */
void dns_resconf_close(struct dns_resolv_conf *resconf) {
if (!resconf || 1 != dns_resconf_release(resconf))
return /* void */;
free(resconf);
} /* dns_resconf_close() */
dns_refcount_t dns_resconf_acquire(struct dns_resolv_conf *resconf) {
return dns_atomic_fetch_add(&resconf->_.refcount);
} /* dns_resconf_acquire() */
dns_refcount_t dns_resconf_release(struct dns_resolv_conf *resconf) {
return dns_atomic_fetch_sub(&resconf->_.refcount);
} /* dns_resconf_release() */
struct dns_resolv_conf *dns_resconf_mortal(struct dns_resolv_conf *resconf) {
if (resconf)
dns_resconf_release(resconf);
return resconf;
} /* dns_resconf_mortal() */
struct dns_resolv_conf *dns_resconf_local(int *error_) {
struct dns_resolv_conf *resconf;
int error;
if (!(resconf = dns_resconf_open(&error)))
goto error;
if ((error = dns_resconf_loadpath(resconf, "/etc/resolv.conf"))) {
/*
* NOTE: Both the glibc and BIND9 resolvers ignore a missing
* /etc/resolv.conf, defaulting to a nameserver of
* 127.0.0.1. See also dns_hints_insert_resconf, and the
* default initialization of nameserver[0] in
* dns_resconf_open.
*/
if (error != ENOENT)
goto error;
}
if ((error = dns_nssconf_loadpath(resconf, "/etc/nsswitch.conf"))) {
if (error != ENOENT)
goto error;
}
return resconf;
error:
*error_ = error;
dns_resconf_close(resconf);
return 0;
} /* dns_resconf_local() */
struct dns_resolv_conf *dns_resconf_root(int *error) {
struct dns_resolv_conf *resconf;
if ((resconf = dns_resconf_local(error)))
resconf->options.recurse = 1;
return resconf;
} /* dns_resconf_root() */
static time_t dns_resconf_timeout(const struct dns_resolv_conf *resconf) {
return (time_t)DNS_PP_MIN(INT_MAX, resconf->options.timeout);
} /* dns_resconf_timeout() */
enum dns_resconf_keyword {
DNS_RESCONF_NAMESERVER,
DNS_RESCONF_DOMAIN,
DNS_RESCONF_SEARCH,
DNS_RESCONF_LOOKUP,
DNS_RESCONF_FILE,
DNS_RESCONF_BIND,
DNS_RESCONF_CACHE,
DNS_RESCONF_FAMILY,
DNS_RESCONF_INET4,
DNS_RESCONF_INET6,
DNS_RESCONF_OPTIONS,
DNS_RESCONF_EDNS0,
DNS_RESCONF_NDOTS,
DNS_RESCONF_TIMEOUT,
DNS_RESCONF_ATTEMPTS,
DNS_RESCONF_ROTATE,
DNS_RESCONF_RECURSE,
DNS_RESCONF_SMART,
DNS_RESCONF_TCP,
DNS_RESCONF_TCPx,
DNS_RESCONF_INTERFACE,
DNS_RESCONF_ZERO,
DNS_RESCONF_ONE,
DNS_RESCONF_ENABLE,
DNS_RESCONF_ONLY,
DNS_RESCONF_DISABLE,
}; /* enum dns_resconf_keyword */
static enum dns_resconf_keyword dns_resconf_keyword(const char *word) {
static const char *words[] = {
[DNS_RESCONF_NAMESERVER] = "nameserver",
[DNS_RESCONF_DOMAIN] = "domain",
[DNS_RESCONF_SEARCH] = "search",
[DNS_RESCONF_LOOKUP] = "lookup",
[DNS_RESCONF_FILE] = "file",
[DNS_RESCONF_BIND] = "bind",
[DNS_RESCONF_CACHE] = "cache",
[DNS_RESCONF_FAMILY] = "family",
[DNS_RESCONF_INET4] = "inet4",
[DNS_RESCONF_INET6] = "inet6",
[DNS_RESCONF_OPTIONS] = "options",
[DNS_RESCONF_EDNS0] = "edns0",
[DNS_RESCONF_ROTATE] = "rotate",
[DNS_RESCONF_RECURSE] = "recurse",
[DNS_RESCONF_SMART] = "smart",
[DNS_RESCONF_TCP] = "tcp",
[DNS_RESCONF_INTERFACE] = "interface",
[DNS_RESCONF_ZERO] = "0",
[DNS_RESCONF_ONE] = "1",
[DNS_RESCONF_ENABLE] = "enable",
[DNS_RESCONF_ONLY] = "only",
[DNS_RESCONF_DISABLE] = "disable",
};
unsigned i;
for (i = 0; i < lengthof(words); i++) {
if (words[i] && 0 == strcasecmp(words[i], word))
return i;
}
if (0 == strncasecmp(word, "ndots:", sizeof "ndots:" - 1))
return DNS_RESCONF_NDOTS;
if (0 == strncasecmp(word, "timeout:", sizeof "timeout:" - 1))
return DNS_RESCONF_TIMEOUT;
if (0 == strncasecmp(word, "attempts:", sizeof "attempts:" - 1))
return DNS_RESCONF_ATTEMPTS;
if (0 == strncasecmp(word, "tcp:", sizeof "tcp:" - 1))
return DNS_RESCONF_TCPx;
return -1;
} /* dns_resconf_keyword() */
/** OpenBSD-style "[1.2.3.4]:53" nameserver syntax */
int dns_resconf_pton(struct sockaddr_storage *ss, const char *src) {
struct { char buf[128], *p; } addr = { "", addr.buf };
unsigned short port = 0;
int ch, af = AF_INET, error;
memset(ss, 0, sizeof *ss);
while ((ch = *src++)) {
switch (ch) {
case ' ':
/* FALL THROUGH */
case '\t':
break;
case '[':
break;
case ']':
while ((ch = *src++)) {
if (dns_isdigit(ch)) {
port *= 10;
port += ch - '0';
}
}
goto inet;
case ':':
af = AF_INET6;
/* FALL THROUGH */
default:
if (addr.p < endof(addr.buf) - 1)
*addr.p++ = ch;
break;
} /* switch() */
} /* while() */
inet:
if ((error = dns_pton(af, addr.buf, dns_sa_addr(af, ss, NULL))))
return error;
port = (!port)? 53 : port;
*dns_sa_port(af, ss) = htons(port);
dns_sa_family(ss) = af;
return 0;
} /* dns_resconf_pton() */
#define dns_resconf_issep(ch) (dns_isspace(ch) || (ch) == ',')
#define dns_resconf_iscom(ch) ((ch) == '#' || (ch) == ';')
int dns_resconf_loadfile(struct dns_resolv_conf *resconf, FILE *fp) {
unsigned sa_count = 0;
char words[6][DNS_D_MAXNAME + 1];
unsigned wp, wc, i, j, n;
int ch, error;
rewind(fp);
do {
memset(words, '\0', sizeof words);
wp = 0;
wc = 0;
while (EOF != (ch = getc(fp)) && ch != '\n') {
if (dns_resconf_issep(ch)) {
if (wp > 0) {
wp = 0;
if (++wc >= lengthof(words))
goto skip;
}
} else if (dns_resconf_iscom(ch)) {
skip:
do {
ch = getc(fp);
} while (ch != EOF && ch != '\n');
break;
} else if (wp < sizeof words[wc] - 1) {
words[wc][wp++] = ch;
} else {
wp = 0; /* drop word */
goto skip;
}
}
if (wp > 0)
wc++;
if (wc < 2)
continue;
switch (dns_resconf_keyword(words[0])) {
case DNS_RESCONF_NAMESERVER:
if (sa_count >= lengthof(resconf->nameserver))
continue;
if ((error = dns_resconf_pton(&resconf->nameserver[sa_count], words[1])))
continue;
sa_count++;
break;
case DNS_RESCONF_DOMAIN:
case DNS_RESCONF_SEARCH:
memset(resconf->search, '\0', sizeof resconf->search);
for (i = 1, j = 0; i < wc && j < lengthof(resconf->search); i++, j++)
dns_d_anchor(resconf->search[j], sizeof resconf->search[j], words[i], strlen(words[i]));
break;
case DNS_RESCONF_LOOKUP:
for (i = 1, j = 0; i < wc && j < lengthof(resconf->lookup); i++) {
switch (dns_resconf_keyword(words[i])) {
case DNS_RESCONF_FILE:
resconf->lookup[j++] = 'f';
break;
case DNS_RESCONF_BIND:
resconf->lookup[j++] = 'b';
break;
case DNS_RESCONF_CACHE:
resconf->lookup[j++] = 'c';
break;
default:
break;
} /* switch() */
} /* for() */
break;
case DNS_RESCONF_FAMILY:
for (i = 1, j = 0; i < wc && j < lengthof(resconf->family); i++) {
switch (dns_resconf_keyword(words[i])) {
case DNS_RESCONF_INET4:
resconf->family[j++] = AF_INET;
break;
case DNS_RESCONF_INET6:
resconf->family[j++] = AF_INET6;
break;
default:
break;
}
}
break;
case DNS_RESCONF_OPTIONS:
for (i = 1; i < wc; i++) {
switch (dns_resconf_keyword(words[i])) {
case DNS_RESCONF_EDNS0:
resconf->options.edns0 = 1;
break;
case DNS_RESCONF_NDOTS:
for (j = sizeof "ndots:" - 1, n = 0; dns_isdigit(words[i][j]); j++) {
n *= 10;
n += words[i][j] - '0';
} /* for() */
resconf->options.ndots = n;
break;
case DNS_RESCONF_TIMEOUT:
for (j = sizeof "timeout:" - 1, n = 0; dns_isdigit(words[i][j]); j++) {
n *= 10;
n += words[i][j] - '0';
} /* for() */
resconf->options.timeout = n;
break;
case DNS_RESCONF_ATTEMPTS:
for (j = sizeof "attempts:" - 1, n = 0; dns_isdigit(words[i][j]); j++) {
n *= 10;
n += words[i][j] - '0';
} /* for() */
resconf->options.attempts = n;
break;
case DNS_RESCONF_ROTATE:
resconf->options.rotate = 1;
break;
case DNS_RESCONF_RECURSE:
resconf->options.recurse = 1;
break;
case DNS_RESCONF_SMART:
resconf->options.smart = 1;
break;
case DNS_RESCONF_TCP:
resconf->options.tcp = DNS_RESCONF_TCP_ONLY;
break;
case DNS_RESCONF_TCPx:
switch (dns_resconf_keyword(&words[i][sizeof "tcp:" - 1])) {
case DNS_RESCONF_ENABLE:
resconf->options.tcp = DNS_RESCONF_TCP_ENABLE;
break;
case DNS_RESCONF_ONE:
case DNS_RESCONF_ONLY:
resconf->options.tcp = DNS_RESCONF_TCP_ONLY;
break;
case DNS_RESCONF_ZERO:
case DNS_RESCONF_DISABLE:
resconf->options.tcp = DNS_RESCONF_TCP_DISABLE;
break;
default:
break;
} /* switch() */
break;
default:
break;
} /* switch() */
} /* for() */
break;
case DNS_RESCONF_INTERFACE:
for (i = 0, n = 0; dns_isdigit(words[2][i]); i++) {
n *= 10;
n += words[2][i] - '0';
}
dns_resconf_setiface(resconf, words[1], n);
break;
default:
break;
} /* switch() */
} while (ch != EOF);
return 0;
} /* dns_resconf_loadfile() */
int dns_resconf_loadpath(struct dns_resolv_conf *resconf, const char *path) {
FILE *fp;
int error;
if (!(fp = dns_fopen(path, "rt", &error)))
return error;
error = dns_resconf_loadfile(resconf, fp);
fclose(fp);
return error;
} /* dns_resconf_loadpath() */
struct dns_anyconf {
char *token[16];
unsigned count;
char buffer[1024], *tp, *cp;
}; /* struct dns_anyconf */
static void dns_anyconf_reset(struct dns_anyconf *cf) {
cf->count = 0;
cf->tp = cf->cp = cf->buffer;
} /* dns_anyconf_reset() */
static int dns_anyconf_push(struct dns_anyconf *cf) {
if (!(cf->cp < endof(cf->buffer) && cf->count < lengthof(cf->token)))
return ENOMEM;
*cf->cp++ = '\0';
cf->token[cf->count++] = cf->tp;
cf->tp = cf->cp;
return 0;
} /* dns_anyconf_push() */
static void dns_anyconf_pop(struct dns_anyconf *cf) {
if (cf->count > 0) {
--cf->count;
cf->tp = cf->cp = cf->token[cf->count];
cf->token[cf->count] = 0;
}
} /* dns_anyconf_pop() */
static int dns_anyconf_addc(struct dns_anyconf *cf, int ch) {
if (!(cf->cp < endof(cf->buffer)))
return ENOMEM;
*cf->cp++ = ch;
return 0;
} /* dns_anyconf_addc() */
static _Bool dns_anyconf_match(const char *pat, int mc) {
_Bool match;
int pc;
if (*pat == '^') {
match = 0;
++pat;
} else {
match = 1;
}
while ((pc = *(const unsigned char *)pat++)) {
switch (pc) {
case '%':
if (!(pc = *(const unsigned char *)pat++))
return !match;
switch (pc) {
case 'a':
if (dns_isalpha(mc))
return match;
break;
case 'd':
if (dns_isdigit(mc))
return match;
break;
case 'w':
if (dns_isalnum(mc))
return match;
break;
case 's':
if (dns_isspace(mc))
return match;
break;
default:
if (mc == pc)
return match;
break;
} /* switch() */
break;
default:
if (mc == pc)
return match;
break;
} /* switch() */
} /* while() */
return !match;
} /* dns_anyconf_match() */
static int dns_anyconf_peek(FILE *fp) {
int ch;
ch = getc(fp);
ungetc(ch, fp);
return ch;
} /* dns_anyconf_peek() */
static size_t dns_anyconf_skip(const char *pat, FILE *fp) {
size_t count = 0;
int ch;
while (EOF != (ch = getc(fp))) {
if (dns_anyconf_match(pat, ch)) {
count++;
continue;
}
ungetc(ch, fp);
break;
}
return count;
} /* dns_anyconf_skip() */
static size_t dns_anyconf_scan(struct dns_anyconf *cf, const char *pat, FILE *fp, int *error) {
size_t len;
int ch;
while (EOF != (ch = getc(fp))) {
if (dns_anyconf_match(pat, ch)) {
if ((*error = dns_anyconf_addc(cf, ch)))
return 0;
continue;
} else {
ungetc(ch, fp);
break;
}
}
if ((len = cf->cp - cf->tp)) {
if ((*error = dns_anyconf_push(cf)))
return 0;
return len;
} else {
*error = 0;
return 0;
}
} /* dns_anyconf_scan() */
DNS_NOTUSED static void dns_anyconf_dump(struct dns_anyconf *cf, FILE *fp) {
unsigned i;
fprintf(fp, "tokens:");
for (i = 0; i < cf->count; i++) {
fprintf(fp, " %s", cf->token[i]);
}
fputc('\n', fp);
} /* dns_anyconf_dump() */
enum dns_nssconf_keyword {
DNS_NSSCONF_INVALID = 0,
DNS_NSSCONF_HOSTS = 1,
DNS_NSSCONF_SUCCESS,
DNS_NSSCONF_NOTFOUND,
DNS_NSSCONF_UNAVAIL,
DNS_NSSCONF_TRYAGAIN,
DNS_NSSCONF_CONTINUE,
DNS_NSSCONF_RETURN,
DNS_NSSCONF_FILES,
DNS_NSSCONF_DNS,
DNS_NSSCONF_MDNS,
DNS_NSSCONF_LAST,
}; /* enum dns_nssconf_keyword */
static enum dns_nssconf_keyword dns_nssconf_keyword(const char *word) {
static const char *list[] = {
[DNS_NSSCONF_HOSTS] = "hosts",
[DNS_NSSCONF_SUCCESS] = "success",
[DNS_NSSCONF_NOTFOUND] = "notfound",
[DNS_NSSCONF_UNAVAIL] = "unavail",
[DNS_NSSCONF_TRYAGAIN] = "tryagain",
[DNS_NSSCONF_CONTINUE] = "continue",
[DNS_NSSCONF_RETURN] = "return",
[DNS_NSSCONF_FILES] = "files",
[DNS_NSSCONF_DNS] = "dns",
[DNS_NSSCONF_MDNS] = "mdns",
};
unsigned i;
for (i = 1; i < lengthof(list); i++) {
if (list[i] && 0 == strcasecmp(list[i], word))
return i;
}
return DNS_NSSCONF_INVALID;
} /* dns_nssconf_keyword() */
static enum dns_nssconf_keyword dns_nssconf_c2k(int ch) {
static const char map[] = {
['S'] = DNS_NSSCONF_SUCCESS,
['N'] = DNS_NSSCONF_NOTFOUND,
['U'] = DNS_NSSCONF_UNAVAIL,
['T'] = DNS_NSSCONF_TRYAGAIN,
['C'] = DNS_NSSCONF_CONTINUE,
['R'] = DNS_NSSCONF_RETURN,
['f'] = DNS_NSSCONF_FILES,
['F'] = DNS_NSSCONF_FILES,
['d'] = DNS_NSSCONF_DNS,
['D'] = DNS_NSSCONF_DNS,
['b'] = DNS_NSSCONF_DNS,
['B'] = DNS_NSSCONF_DNS,
['m'] = DNS_NSSCONF_MDNS,
['M'] = DNS_NSSCONF_MDNS,
};
return (ch >= 0 && ch < (int)lengthof(map))? map[ch] : DNS_NSSCONF_INVALID;
} /* dns_nssconf_c2k() */
DNS_PRAGMA_PUSH
DNS_PRAGMA_QUIET
static int dns_nssconf_k2c(int k) {
static const char map[DNS_NSSCONF_LAST] = {
[DNS_NSSCONF_SUCCESS] = 'S',
[DNS_NSSCONF_NOTFOUND] = 'N',
[DNS_NSSCONF_UNAVAIL] = 'U',
[DNS_NSSCONF_TRYAGAIN] = 'T',
[DNS_NSSCONF_CONTINUE] = 'C',
[DNS_NSSCONF_RETURN] = 'R',
[DNS_NSSCONF_FILES] = 'f',
[DNS_NSSCONF_DNS] = 'b',
[DNS_NSSCONF_MDNS] = 'm',
};
return (k >= 0 && k < (int)lengthof(map))? (map[k]? map[k] : '?') : '?';
} /* dns_nssconf_k2c() */
static const char *dns_nssconf_k2s(int k) {
static const char *const map[DNS_NSSCONF_LAST] = {
[DNS_NSSCONF_SUCCESS] = "SUCCESS",
[DNS_NSSCONF_NOTFOUND] = "NOTFOUND",
[DNS_NSSCONF_UNAVAIL] = "UNAVAIL",
[DNS_NSSCONF_TRYAGAIN] = "TRYAGAIN",
[DNS_NSSCONF_CONTINUE] = "continue",
[DNS_NSSCONF_RETURN] = "return",
[DNS_NSSCONF_FILES] = "files",
[DNS_NSSCONF_DNS] = "dns",
[DNS_NSSCONF_MDNS] = "mdns",
};
return (k >= 0 && k < (int)lengthof(map))? (map[k]? map[k] : "") : "";
} /* dns_nssconf_k2s() */
DNS_PRAGMA_POP
int dns_nssconf_loadfile(struct dns_resolv_conf *resconf, FILE *fp) {
enum dns_nssconf_keyword source, status, action;
char lookup[sizeof resconf->lookup] = "", *lp;
struct dns_anyconf cf;
size_t i;
int error;
while (!feof(fp) && !ferror(fp)) {
dns_anyconf_reset(&cf);
dns_anyconf_skip("%s", fp);
if (!dns_anyconf_scan(&cf, "%w_", fp, &error))
goto nextent;
if (DNS_NSSCONF_HOSTS != dns_nssconf_keyword(cf.token[0]))
goto nextent;
dns_anyconf_pop(&cf);
if (!dns_anyconf_skip(": \t", fp))
goto nextent;
*(lp = lookup) = '\0';
while (dns_anyconf_scan(&cf, "%w_", fp, &error)) {
dns_anyconf_skip(" \t", fp);
if ('[' == dns_anyconf_peek(fp)) {
dns_anyconf_skip("[! \t", fp);
while (dns_anyconf_scan(&cf, "%w_", fp, &error)) {
dns_anyconf_skip("= \t", fp);
if (!dns_anyconf_scan(&cf, "%w_", fp, &error)) {
dns_anyconf_pop(&cf); /* discard status */
dns_anyconf_skip("^#;]\n", fp); /* skip to end of criteria */
break;
}
dns_anyconf_skip(" \t", fp);
}
dns_anyconf_skip("] \t", fp);
}
if ((size_t)(endof(lookup) - lp) < cf.count + 1) /* +1 for '\0' */
goto nextsrc;
source = dns_nssconf_keyword(cf.token[0]);
switch (source) {
case DNS_NSSCONF_DNS:
case DNS_NSSCONF_MDNS:
case DNS_NSSCONF_FILES:
*lp++ = dns_nssconf_k2c(source);
break;
default:
goto nextsrc;
}
for (i = 1; i + 1 < cf.count; i += 2) {
status = dns_nssconf_keyword(cf.token[i]);
action = dns_nssconf_keyword(cf.token[i + 1]);
switch (status) {
case DNS_NSSCONF_SUCCESS:
case DNS_NSSCONF_NOTFOUND:
case DNS_NSSCONF_UNAVAIL:
case DNS_NSSCONF_TRYAGAIN:
*lp++ = dns_nssconf_k2c(status);
break;
default:
continue;
}
switch (action) {
case DNS_NSSCONF_CONTINUE:
case DNS_NSSCONF_RETURN:
break;
default:
action = (status == DNS_NSSCONF_SUCCESS)
? DNS_NSSCONF_RETURN
: DNS_NSSCONF_CONTINUE;
break;
}
*lp++ = dns_nssconf_k2c(action);
}
nextsrc:
*lp = '\0';
dns_anyconf_reset(&cf);
}
nextent:
dns_anyconf_skip("^\n", fp);
}
if (*lookup)
strncpy(resconf->lookup, lookup, sizeof resconf->lookup);
return 0;
} /* dns_nssconf_loadfile() */
int dns_nssconf_loadpath(struct dns_resolv_conf *resconf, const char *path) {
FILE *fp;
int error;
if (!(fp = dns_fopen(path, "rt", &error)))
return error;
error = dns_nssconf_loadfile(resconf, fp);
fclose(fp);
return error;
} /* dns_nssconf_loadpath() */
struct dns_nssconf_source {
enum dns_nssconf_keyword source, success, notfound, unavail, tryagain;
}; /* struct dns_nssconf_source */
typedef unsigned dns_nssconf_i;
static inline int dns_nssconf_peek(const struct dns_resolv_conf *resconf, dns_nssconf_i state) {
return (state < lengthof(resconf->lookup) && resconf->lookup[state])? resconf->lookup[state] : 0;
} /* dns_nssconf_peek() */
static _Bool dns_nssconf_next(struct dns_nssconf_source *src, const struct dns_resolv_conf *resconf, dns_nssconf_i *state) {
int source, status, action;
src->source = DNS_NSSCONF_INVALID;
src->success = DNS_NSSCONF_RETURN;
src->notfound = DNS_NSSCONF_CONTINUE;
src->unavail = DNS_NSSCONF_CONTINUE;
src->tryagain = DNS_NSSCONF_CONTINUE;
while ((source = dns_nssconf_peek(resconf, *state))) {
source = dns_nssconf_c2k(source);
++*state;
switch (source) {
case DNS_NSSCONF_FILES:
case DNS_NSSCONF_DNS:
case DNS_NSSCONF_MDNS:
src->source = source;
break;
default:
continue;
}
while ((status = dns_nssconf_peek(resconf, *state)) && (action = dns_nssconf_peek(resconf, *state + 1))) {
status = dns_nssconf_c2k(status);
action = dns_nssconf_c2k(action);
switch (action) {
case DNS_NSSCONF_RETURN:
case DNS_NSSCONF_CONTINUE:
break;
default:
goto done;
}
switch (status) {
case DNS_NSSCONF_SUCCESS:
src->success = action;
break;
case DNS_NSSCONF_NOTFOUND:
src->notfound = action;
break;
case DNS_NSSCONF_UNAVAIL:
src->unavail = action;
break;
case DNS_NSSCONF_TRYAGAIN:
src->tryagain = action;
break;
default:
goto done;
}
*state += 2;
}
break;
}
done:
return src->source != DNS_NSSCONF_INVALID;
} /* dns_nssconf_next() */
static int dns_nssconf_dump_status(int status, int action, unsigned *count, FILE *fp) {
switch (status) {
case DNS_NSSCONF_SUCCESS:
if (action == DNS_NSSCONF_RETURN)
return 0;
break;
default:
if (action == DNS_NSSCONF_CONTINUE)
return 0;
break;
}
fputc(' ', fp);
if (!*count)
fputc('[', fp);
fprintf(fp, "%s=%s", dns_nssconf_k2s(status), dns_nssconf_k2s(action));
++*count;
return 0;
} /* dns_nssconf_dump_status() */
int dns_nssconf_dump(struct dns_resolv_conf *resconf, FILE *fp) {
struct dns_nssconf_source src;
dns_nssconf_i i = 0;
fputs("hosts:", fp);
while (dns_nssconf_next(&src, resconf, &i)) {
unsigned n = 0;
fprintf(fp, " %s", dns_nssconf_k2s(src.source));
dns_nssconf_dump_status(DNS_NSSCONF_SUCCESS, src.success, &n, fp);
dns_nssconf_dump_status(DNS_NSSCONF_NOTFOUND, src.notfound, &n, fp);
dns_nssconf_dump_status(DNS_NSSCONF_UNAVAIL, src.unavail, &n, fp);
dns_nssconf_dump_status(DNS_NSSCONF_TRYAGAIN, src.tryagain, &n, fp);
if (n)
fputc(']', fp);
}
fputc('\n', fp);
return 0;
} /* dns_nssconf_dump() */
int dns_resconf_setiface(struct dns_resolv_conf *resconf, const char *addr, unsigned short port) {
int af = (strchr(addr, ':'))? AF_INET6 : AF_INET;
int error;
memset(&resconf->iface, 0, sizeof (struct sockaddr_storage));
if ((error = dns_pton(af, addr, dns_sa_addr(af, &resconf->iface, NULL))))
return error;
*dns_sa_port(af, &resconf->iface) = htons(port);
resconf->iface.ss_family = af;
return 0;
} /* dns_resconf_setiface() */
#define DNS_SM_RESTORE \
do { \
pc = 0xff & (*state >> 0); \
srchi = 0xff & (*state >> 8); \
ndots = 0xff & (*state >> 16); \
} while (0)
#define DNS_SM_SAVE \
do { \
*state = ((0xff & pc) << 0) \
| ((0xff & srchi) << 8) \
| ((0xff & ndots) << 16); \
} while (0)
size_t dns_resconf_search(void *dst, size_t lim, const void *qname, size_t qlen, struct dns_resolv_conf *resconf, dns_resconf_i_t *state) {
unsigned pc, srchi, ndots, len;
DNS_SM_ENTER;
/* if FQDN then return as-is and finish */
if (dns_d_isanchored(qname, qlen)) {
len = dns_d_anchor(dst, lim, qname, qlen);
DNS_SM_YIELD(len);
DNS_SM_EXIT;
}
ndots = dns_d_ndots(qname, qlen);
if (ndots >= resconf->options.ndots) {
len = dns_d_anchor(dst, lim, qname, qlen);
DNS_SM_YIELD(len);
}
while (srchi < lengthof(resconf->search) && resconf->search[srchi][0]) {
struct dns_buf buf = DNS_B_INTO(dst, lim);
const char *dn = resconf->search[srchi++];
dns_b_put(&buf, qname, qlen);
dns_b_putc(&buf, '.');
dns_b_puts(&buf, dn);
if (!dns_d_isanchored(dn, strlen(dn)))
dns_b_putc(&buf, '.');
len = dns_b_strllen(&buf);
DNS_SM_YIELD(len);
}
if (ndots < resconf->options.ndots) {
len = dns_d_anchor(dst, lim, qname, qlen);
DNS_SM_YIELD(len);
}
DNS_SM_LEAVE;
return dns_strlcpy(dst, "", lim);
} /* dns_resconf_search() */
#undef DNS_SM_SAVE
#undef DNS_SM_RESTORE
int dns_resconf_dump(struct dns_resolv_conf *resconf, FILE *fp) {
unsigned i;
int af;
for (i = 0; i < lengthof(resconf->nameserver) && (af = resconf->nameserver[i].ss_family) != AF_UNSPEC; i++) {
char addr[INET6_ADDRSTRLEN + 1] = "[INVALID]";
unsigned short port;
dns_inet_ntop(af, dns_sa_addr(af, &resconf->nameserver[i], NULL), addr, sizeof addr);
port = ntohs(*dns_sa_port(af, &resconf->nameserver[i]));
if (port == 53)
fprintf(fp, "nameserver %s\n", addr);
else
fprintf(fp, "nameserver [%s]:%hu\n", addr, port);
}
fprintf(fp, "search");
for (i = 0; i < lengthof(resconf->search) && resconf->search[i][0]; i++)
fprintf(fp, " %s", resconf->search[i]);
fputc('\n', fp);
fputs("; ", fp);
dns_nssconf_dump(resconf, fp);
fprintf(fp, "lookup");
for (i = 0; i < lengthof(resconf->lookup) && resconf->lookup[i]; i++) {
switch (resconf->lookup[i]) {
case 'b':
fprintf(fp, " bind"); break;
case 'f':
fprintf(fp, " file"); break;
case 'c':
fprintf(fp, " cache"); break;
}
}
fputc('\n', fp);
fprintf(fp, "options ndots:%u timeout:%u attempts:%u", resconf->options.ndots, resconf->options.timeout, resconf->options.attempts);
if (resconf->options.edns0)
fprintf(fp, " edns0");
if (resconf->options.rotate)
fprintf(fp, " rotate");
if (resconf->options.recurse)
fprintf(fp, " recurse");
if (resconf->options.smart)
fprintf(fp, " smart");
switch (resconf->options.tcp) {
case DNS_RESCONF_TCP_ENABLE:
break;
case DNS_RESCONF_TCP_ONLY:
fprintf(fp, " tcp");
break;
case DNS_RESCONF_TCP_SOCKS:
fprintf(fp, " tcp:socks");
break;
case DNS_RESCONF_TCP_DISABLE:
fprintf(fp, " tcp:disable");
break;
}
fputc('\n', fp);
if ((af = resconf->iface.ss_family) != AF_UNSPEC) {
char addr[INET6_ADDRSTRLEN + 1] = "[INVALID]";
dns_inet_ntop(af, dns_sa_addr(af, &resconf->iface, NULL), addr, sizeof addr);
fprintf(fp, "interface %s %hu\n", addr, ntohs(*dns_sa_port(af, &resconf->iface)));
}
return 0;
} /* dns_resconf_dump() */
/*
* H I N T S E R V E R R O U T I N E S
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
struct dns_hints_soa {
unsigned char zone[DNS_D_MAXNAME + 1];
struct {
struct sockaddr_storage ss;
unsigned priority;
} addrs[16];
unsigned count;
struct dns_hints_soa *next;
}; /* struct dns_hints_soa */
struct dns_hints {
dns_atomic_t refcount;
struct dns_hints_soa *head;
}; /* struct dns_hints */
struct dns_hints *dns_hints_open(struct dns_resolv_conf *resconf, int *error) {
static const struct dns_hints H_initializer;
struct dns_hints *H;
(void)resconf;
if (!(H = malloc(sizeof *H)))
goto syerr;
*H = H_initializer;
dns_hints_acquire(H);
return H;
syerr:
*error = dns_syerr();
free(H);
return 0;
} /* dns_hints_open() */
void dns_hints_close(struct dns_hints *H) {
struct dns_hints_soa *soa, *nxt;
if (!H || 1 != dns_hints_release(H))
return /* void */;
for (soa = H->head; soa; soa = nxt) {
nxt = soa->next;
free(soa);
}
free(H);
return /* void */;
} /* dns_hints_close() */
dns_refcount_t dns_hints_acquire(struct dns_hints *H) {
return dns_atomic_fetch_add(&H->refcount);
} /* dns_hints_acquire() */
dns_refcount_t dns_hints_release(struct dns_hints *H) {
return dns_atomic_fetch_sub(&H->refcount);
} /* dns_hints_release() */
struct dns_hints *dns_hints_mortal(struct dns_hints *hints) {
if (hints)
dns_hints_release(hints);
return hints;
} /* dns_hints_mortal() */
struct dns_hints *dns_hints_local(struct dns_resolv_conf *resconf, int *error_) {
struct dns_hints *hints = 0;
int error;
if (resconf)
dns_resconf_acquire(resconf);
else if (!(resconf = dns_resconf_local(&error)))
goto error;
if (!(hints = dns_hints_open(resconf, &error)))
goto error;
error = 0;
if (0 == dns_hints_insert_resconf(hints, ".", resconf, &error) && error)
goto error;
dns_resconf_close(resconf);
return hints;
error:
*error_ = error;
dns_resconf_close(resconf);
dns_hints_close(hints);
return 0;
} /* dns_hints_local() */
struct dns_hints *dns_hints_root(struct dns_resolv_conf *resconf, int *error_) {
static const struct {
int af;
char addr[INET6_ADDRSTRLEN];
} root_hints[] = {
{ AF_INET, "198.41.0.4" }, /* A.ROOT-SERVERS.NET. */
{ AF_INET6, "2001:503:ba3e::2:30" }, /* A.ROOT-SERVERS.NET. */
{ AF_INET, "192.228.79.201" }, /* B.ROOT-SERVERS.NET. */
{ AF_INET6, "2001:500:84::b" }, /* B.ROOT-SERVERS.NET. */
{ AF_INET, "192.33.4.12" }, /* C.ROOT-SERVERS.NET. */
{ AF_INET6, "2001:500:2::c" }, /* C.ROOT-SERVERS.NET. */
{ AF_INET, "199.7.91.13" }, /* D.ROOT-SERVERS.NET. */
{ AF_INET6, "2001:500:2d::d" }, /* D.ROOT-SERVERS.NET. */
{ AF_INET, "192.203.230.10" }, /* E.ROOT-SERVERS.NET. */
{ AF_INET, "192.5.5.241" }, /* F.ROOT-SERVERS.NET. */
{ AF_INET6, "2001:500:2f::f" }, /* F.ROOT-SERVERS.NET. */
{ AF_INET, "192.112.36.4" }, /* G.ROOT-SERVERS.NET. */
{ AF_INET, "128.63.2.53" }, /* H.ROOT-SERVERS.NET. */
{ AF_INET6, "2001:500:1::803f:235" }, /* H.ROOT-SERVERS.NET. */
{ AF_INET, "192.36.148.17" }, /* I.ROOT-SERVERS.NET. */
{ AF_INET6, "2001:7FE::53" }, /* I.ROOT-SERVERS.NET. */
{ AF_INET, "192.58.128.30" }, /* J.ROOT-SERVERS.NET. */
{ AF_INET6, "2001:503:c27::2:30" }, /* J.ROOT-SERVERS.NET. */
{ AF_INET, "193.0.14.129" }, /* K.ROOT-SERVERS.NET. */
{ AF_INET6, "2001:7FD::1" }, /* K.ROOT-SERVERS.NET. */
{ AF_INET, "199.7.83.42" }, /* L.ROOT-SERVERS.NET. */
{ AF_INET6, "2001:500:3::42" }, /* L.ROOT-SERVERS.NET. */
{ AF_INET, "202.12.27.33" }, /* M.ROOT-SERVERS.NET. */
{ AF_INET6, "2001:DC3::35" }, /* M.ROOT-SERVERS.NET. */
};
struct dns_hints *hints = 0;
struct sockaddr_storage ss;
unsigned i;
int error, af;
if (!(hints = dns_hints_open(resconf, &error)))
goto error;
for (i = 0; i < lengthof(root_hints); i++) {
af = root_hints[i].af;
memset(&ss, 0, sizeof ss);
if ((error = dns_pton(af, root_hints[i].addr, dns_sa_addr(af, &ss, NULL))))
goto error;
*dns_sa_port(af, &ss) = htons(53);
ss.ss_family = af;
if ((error = dns_hints_insert(hints, ".", (struct sockaddr *)&ss, 1)))
goto error;
}
return hints;
error:
*error_ = error;
dns_hints_close(hints);
return 0;
} /* dns_hints_root() */
static struct dns_hints_soa *dns_hints_fetch(struct dns_hints *H, const char *zone) {
struct dns_hints_soa *soa;
for (soa = H->head; soa; soa = soa->next) {
if (0 == strcasecmp(zone, (char *)soa->zone))
return soa;
}
return 0;
} /* dns_hints_fetch() */
int dns_hints_insert(struct dns_hints *H, const char *zone, const struct sockaddr *sa, unsigned priority) {
static const struct dns_hints_soa soa_initializer;
struct dns_hints_soa *soa;
unsigned i;
if (!(soa = dns_hints_fetch(H, zone))) {
if (!(soa = malloc(sizeof *soa)))
return dns_syerr();
*soa = soa_initializer;
dns_strlcpy((char *)soa->zone, zone, sizeof soa->zone);
soa->next = H->head;
H->head = soa;
}
i = soa->count % lengthof(soa->addrs);
memcpy(&soa->addrs[i].ss, sa, dns_sa_len(sa));
soa->addrs[i].priority = DNS_PP_MAX(1, priority);
if (soa->count < lengthof(soa->addrs))
soa->count++;
return 0;
} /* dns_hints_insert() */
static _Bool dns_hints_isinaddr_any(const void *sa) {
struct in_addr *addr;
if (dns_sa_family(sa) != AF_INET)
return 0;
addr = dns_sa_addr(AF_INET, sa, NULL);
return addr->s_addr == htonl(INADDR_ANY);
}
unsigned dns_hints_insert_resconf(struct dns_hints *H, const char *zone, const struct dns_resolv_conf *resconf, int *error_) {
unsigned i, n, p;
int error;
for (i = 0, n = 0, p = 1; i < lengthof(resconf->nameserver) && resconf->nameserver[i].ss_family != AF_UNSPEC; i++, n++) {
union { struct sockaddr_in sin; } tmp;
struct sockaddr *ns;
/*
* dns_resconf_open initializes nameserver[0] to INADDR_ANY.
*
* Traditionally the semantics of 0.0.0.0 meant the default
* interface, which evolved to mean the loopback interface.
* See comment block preceding resolv/res_init.c:res_init in
* glibc 2.23. As of 2.23, glibc no longer translates
* 0.0.0.0 despite the code comment, but it does default to
* 127.0.0.1 when no nameservers are present.
*
* BIND9 as of 9.10.3 still translates 0.0.0.0 to 127.0.0.1.
* See lib/lwres/lwconfig.c:lwres_create_addr and the
* convert_zero flag. 127.0.0.1 is also the default when no
* nameservers are present.
*/
if (dns_hints_isinaddr_any(&resconf->nameserver[i])) {
memcpy(&tmp.sin, &resconf->nameserver[i], sizeof tmp.sin);
tmp.sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
ns = (struct sockaddr *)&tmp.sin;
} else {
ns = (struct sockaddr *)&resconf->nameserver[i];
}
if ((error = dns_hints_insert(H, zone, ns, p)))
goto error;
p += !resconf->options.rotate;
}
return n;
error:
*error_ = error;
return n;
} /* dns_hints_insert_resconf() */
static int dns_hints_i_cmp(unsigned a, unsigned b, struct dns_hints_i *i, struct dns_hints_soa *soa) {
int cmp;
if ((cmp = soa->addrs[a].priority - soa->addrs[b].priority))
return cmp;
return dns_k_shuffle16(a, i->state.seed) - dns_k_shuffle16(b, i->state.seed);
} /* dns_hints_i_cmp() */
static unsigned dns_hints_i_start(struct dns_hints_i *i, struct dns_hints_soa *soa) {
unsigned p0, p;
p0 = 0;
for (p = 1; p < soa->count; p++) {
if (dns_hints_i_cmp(p, p0, i, soa) < 0)
p0 = p;
}
return p0;
} /* dns_hints_i_start() */
static unsigned dns_hints_i_skip(unsigned p0, struct dns_hints_i *i, struct dns_hints_soa *soa) {
unsigned pZ, p;
for (pZ = 0; pZ < soa->count; pZ++) {
if (dns_hints_i_cmp(pZ, p0, i, soa) > 0)
goto cont;
}
return soa->count;
cont:
for (p = pZ + 1; p < soa->count; p++) {
if (dns_hints_i_cmp(p, p0, i, soa) <= 0)
continue;
if (dns_hints_i_cmp(p, pZ, i, soa) >= 0)
continue;
pZ = p;
}
return pZ;
} /* dns_hints_i_skip() */
static struct dns_hints_i *dns_hints_i_init(struct dns_hints_i *i, struct dns_hints *hints) {
static const struct dns_hints_i i_initializer;
struct dns_hints_soa *soa;
i->state = i_initializer.state;
do {
i->state.seed = dns_random();
} while (0 == i->state.seed);
if ((soa = dns_hints_fetch(hints, i->zone))) {
i->state.next = dns_hints_i_start(i, soa);
}
return i;
} /* dns_hints_i_init() */
unsigned dns_hints_grep(struct sockaddr **sa, socklen_t *sa_len, unsigned lim, struct dns_hints_i *i, struct dns_hints *H) {
struct dns_hints_soa *soa;
unsigned n;
if (!(soa = dns_hints_fetch(H, i->zone)))
return 0;
n = 0;
while (i->state.next < soa->count && n < lim) {
*sa = (struct sockaddr *)&soa->addrs[i->state.next].ss;
*sa_len = dns_sa_len(*sa);
sa++;
sa_len++;
n++;
i->state.next = dns_hints_i_skip(i->state.next, i, soa);
}
return n;
} /* dns_hints_grep() */
struct dns_packet *dns_hints_query(struct dns_hints *hints, struct dns_packet *Q, int *error_) {
- union { unsigned char b[dns_p_calcsize((512))]; struct dns_packet p; } _P = { 0 };
+ union { unsigned char b[dns_p_calcsize((512))]; struct dns_packet p; } P_instance = { 0 };
struct dns_packet *A, *P;
struct dns_rr rr;
char zone[DNS_D_MAXNAME + 1];
size_t zlen;
struct dns_hints_i i;
struct sockaddr *sa;
socklen_t slen;
int error;
- struct dns_rr_i _I = { 0 };
+ struct dns_rr_i I_instance = { 0 };
- _I.section = DNS_S_QUESTION;
+ I_instance.section = DNS_S_QUESTION;
- if (!dns_rr_grep(&rr, 1, &_I, Q, &error))
+ if (!dns_rr_grep(&rr, 1, &I_instance, Q, &error))
goto error;
if (!(zlen = dns_d_expand(zone, sizeof zone, rr.dn.p, Q, &error)))
goto error;
else if (zlen >= sizeof zone)
goto toolong;
- P = dns_p_init(&_P.p, 512);
+ P = dns_p_init(&P_instance.p, 512);
dns_header(P)->qr = 1;
if ((error = dns_rr_copy(P, &rr, Q)))
goto error;
if ((error = dns_p_push(P, DNS_S_AUTHORITY, ".", strlen("."), DNS_T_NS, DNS_C_IN, 0, "hints.local.")))
goto error;
do {
i.zone = zone;
dns_hints_i_init(&i, hints);
while (dns_hints_grep(&sa, &slen, 1, &i, hints)) {
int af = sa->sa_family;
int rtype = (af == AF_INET6)? DNS_T_AAAA : DNS_T_A;
if ((error = dns_p_push(P, DNS_S_ADDITIONAL, "hints.local.", strlen("hints.local."), rtype, DNS_C_IN, 0, dns_sa_addr(af, sa, NULL))))
goto error;
}
} while ((zlen = dns_d_cleave(zone, sizeof zone, zone, zlen)));
if (!(A = dns_p_copy(dns_p_make(P->end, &error), P)))
goto error;
return A;
toolong:
error = DNS_EILLEGAL;
error:
*error_ = error;
return 0;
} /* dns_hints_query() */
/** ugly hack to support specifying ports other than 53 in resolv.conf. */
static unsigned short dns_hints_port(struct dns_hints *hints, int af, void *addr) {
struct dns_hints_soa *soa;
void *addrsoa;
socklen_t addrlen;
unsigned short port;
unsigned i;
for (soa = hints->head; soa; soa = soa->next) {
for (i = 0; i < soa->count; i++) {
if (af != soa->addrs[i].ss.ss_family)
continue;
if (!(addrsoa = dns_sa_addr(af, &soa->addrs[i].ss, &addrlen)))
continue;
if (memcmp(addr, addrsoa, addrlen))
continue;
port = *dns_sa_port(af, &soa->addrs[i].ss);
return (port)? port : htons(53);
}
}
return htons(53);
} /* dns_hints_port() */
int dns_hints_dump(struct dns_hints *hints, FILE *fp) {
struct dns_hints_soa *soa;
char addr[INET6_ADDRSTRLEN];
unsigned i;
int af, error;
for (soa = hints->head; soa; soa = soa->next) {
fprintf(fp, "ZONE \"%s\"\n", soa->zone);
for (i = 0; i < soa->count; i++) {
af = soa->addrs[i].ss.ss_family;
if ((error = dns_ntop(af, dns_sa_addr(af, &soa->addrs[i].ss, NULL), addr, sizeof addr)))
return error;
fprintf(fp, "\t(%d) [%s]:%hu\n", (int)soa->addrs[i].priority, addr, ntohs(*dns_sa_port(af, &soa->addrs[i].ss)));
}
}
return 0;
} /* dns_hints_dump() */
/*
* C A C H E R O U T I N E S
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
static dns_refcount_t dns_cache_acquire(struct dns_cache *cache) {
return dns_atomic_fetch_add(&cache->_.refcount);
} /* dns_cache_acquire() */
static dns_refcount_t dns_cache_release(struct dns_cache *cache) {
return dns_atomic_fetch_sub(&cache->_.refcount);
} /* dns_cache_release() */
static struct dns_packet *dns_cache_query(struct dns_packet *query, struct dns_cache *cache, int *error) {
(void)query;
(void)cache;
(void)error;
return NULL;
} /* dns_cache_query() */
static int dns_cache_submit(struct dns_packet *query, struct dns_cache *cache) {
(void)query;
(void)cache;
return 0;
} /* dns_cache_submit() */
static int dns_cache_check(struct dns_cache *cache) {
(void)cache;
return 0;
} /* dns_cache_check() */
static struct dns_packet *dns_cache_fetch(struct dns_cache *cache, int *error) {
(void)cache;
(void)error;
return NULL;
} /* dns_cache_fetch() */
static int dns_cache_pollfd(struct dns_cache *cache) {
(void)cache;
return -1;
} /* dns_cache_pollfd() */
static short dns_cache_events(struct dns_cache *cache) {
(void)cache;
return 0;
} /* dns_cache_events() */
static void dns_cache_clear(struct dns_cache *cache) {
(void)cache;
return;
} /* dns_cache_clear() */
struct dns_cache *dns_cache_init(struct dns_cache *cache) {
static const struct dns_cache c_init = {
.acquire = &dns_cache_acquire,
.release = &dns_cache_release,
.query = &dns_cache_query,
.submit = &dns_cache_submit,
.check = &dns_cache_check,
.fetch = &dns_cache_fetch,
.pollfd = &dns_cache_pollfd,
.events = &dns_cache_events,
.clear = &dns_cache_clear,
._ = { .refcount = 1, },
};
*cache = c_init;
return cache;
} /* dns_cache_init() */
void dns_cache_close(struct dns_cache *cache) {
if (cache)
cache->release(cache);
} /* dns_cache_close() */
/*
* S O C K E T R O U T I N E S
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
static void dns_socketclose(int *fd, const struct dns_options *opts) {
if (opts && opts->closefd.cb)
opts->closefd.cb(fd, opts->closefd.arg);
if (*fd != -1) {
#if _WIN32
closesocket(*fd);
#else
close(*fd);
#endif
*fd = -1;
}
} /* dns_socketclose() */
#ifndef HAVE_IOCTLSOCKET
#define HAVE_IOCTLSOCKET (_WIN32 || _WIN64)
#endif
#ifndef HAVE_SOCK_CLOEXEC
#ifdef SOCK_CLOEXEC
#define HAVE_SOCK_CLOEXEC 1
#else
#define HAVE_SOCK_CLOEXEC 0
#endif
#endif
#ifndef HAVE_SOCK_NONBLOCK
#ifdef SOCK_NONBLOCK
#define HAVE_SOCK_NONBLOCK 1
#else
#define HAVE_SOCK_NONBLOCK 0
#endif
#endif
#define DNS_SO_MAXTRY 7
static int dns_socket(struct sockaddr *local, int type, int *error_) {
int fd = -1, flags, error;
#if defined FIONBIO
unsigned long opt;
#endif
flags = 0;
#if HAVE_SOCK_CLOEXEC
flags |= SOCK_CLOEXEC;
#endif
#if HAVE_SOCK_NONBLOCK
flags |= SOCK_NONBLOCK;
#endif
if (-1 == (fd = socket(local->sa_family, type|flags, 0)))
goto soerr;
#if defined F_SETFD && !HAVE_SOCK_CLOEXEC
if (-1 == fcntl(fd, F_SETFD, 1))
goto syerr;
#endif
#if defined O_NONBLOCK && !HAVE_SOCK_NONBLOCK
if (-1 == (flags = fcntl(fd, F_GETFL)))
goto syerr;
if (-1 == fcntl(fd, F_SETFL, flags | O_NONBLOCK))
goto syerr;
#elif defined FIONBIO && HAVE_IOCTLSOCKET
opt = 1;
if (0 != ioctlsocket(fd, FIONBIO, &opt))
goto soerr;
#endif
#if defined SO_NOSIGPIPE
if (type != SOCK_DGRAM) {
const int v = 1;
if (0 != setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &v, sizeof (int)))
goto soerr;
}
#endif
if (local->sa_family != AF_INET && local->sa_family != AF_INET6)
return fd;
if (type != SOCK_DGRAM)
return fd;
#define LEAVE_SELECTION_OF_PORT_TO_KERNEL
#if !defined(LEAVE_SELECTION_OF_PORT_TO_KERNEL)
/*
* FreeBSD, Linux, OpenBSD, OS X, and Solaris use random ports by
* default. Though the ephemeral range is quite small on OS X
* (49152-65535 on 10.10) and Linux (32768-60999 on 4.4.0, Ubuntu
* Xenial). See also RFC 6056.
*
* TODO: Optionally rely on the kernel to select a random port.
*/
if (*dns_sa_port(local->sa_family, local) == 0) {
struct sockaddr_storage tmp;
unsigned i, port;
memcpy(&tmp, local, dns_sa_len(local));
for (i = 0; i < DNS_SO_MAXTRY; i++) {
port = 1025 + (dns_random() % 64510);
*dns_sa_port(tmp.ss_family, &tmp) = htons(port);
if (0 == bind(fd, (struct sockaddr *)&tmp, dns_sa_len(&tmp)))
return fd;
}
/* NB: continue to next bind statement */
}
#endif
if (0 == bind(fd, local, dns_sa_len(local)))
return fd;
/* FALL THROUGH */
soerr:
error = dns_soerr();
goto error;
#if (defined F_SETFD && !HAVE_SOCK_CLOEXEC) || (defined O_NONBLOCK && !HAVE_SOCK_NONBLOCK)
syerr:
error = dns_syerr();
goto error;
#endif
error:
*error_ = error;
dns_socketclose(&fd, NULL);
return -1;
} /* dns_socket() */
enum {
DNS_SO_UDP_INIT = 1,
DNS_SO_UDP_CONN,
DNS_SO_UDP_SEND,
DNS_SO_UDP_RECV,
DNS_SO_UDP_DONE,
DNS_SO_TCP_INIT,
DNS_SO_TCP_CONN,
DNS_SO_TCP_SEND,
DNS_SO_TCP_RECV,
DNS_SO_TCP_DONE,
DNS_SO_SOCKS_INIT,
DNS_SO_SOCKS_CONN,
DNS_SO_SOCKS_HELLO_SEND,
DNS_SO_SOCKS_HELLO_RECV,
DNS_SO_SOCKS_AUTH_SEND,
DNS_SO_SOCKS_AUTH_RECV,
DNS_SO_SOCKS_REQUEST_PREPARE,
DNS_SO_SOCKS_REQUEST_SEND,
DNS_SO_SOCKS_REQUEST_RECV,
DNS_SO_SOCKS_REQUEST_RECV_V6,
DNS_SO_SOCKS_HANDSHAKE_DONE,
};
struct dns_socket {
struct dns_options opts;
int udp;
int tcp;
int *old;
unsigned onum, olim;
int type;
struct sockaddr_storage local, remote;
struct dns_k_permutor qids;
struct dns_stat stat;
struct dns_trace *trace;
/*
* NOTE: dns_so_reset() zeroes everything from here down.
*/
int state;
unsigned short qid;
char qname[DNS_D_MAXNAME + 1];
size_t qlen;
enum dns_type qtype;
enum dns_class qclass;
struct dns_packet *query;
size_t qout;
/* During a SOCKS handshake the query is temporarily stored
* here. */
struct dns_packet *query_backup;
struct dns_clock elapsed;
struct dns_packet *answer;
size_t alen, apos;
}; /* struct dns_socket */
/*
* NOTE: Actual closure delayed so that kqueue(2) and epoll(2) callers have
* a chance to recognize a state change after installing a persistent event
* and where sequential descriptors with the same integer value returned
* from _pollfd() would be ambiguous. See dns_so_closefds().
*/
static int dns_so_closefd(struct dns_socket *so, int *fd) {
int error;
if (*fd == -1)
return 0;
if (so->opts.closefd.cb) {
if ((error = so->opts.closefd.cb(fd, so->opts.closefd.arg))) {
return error;
} else if (*fd == -1)
return 0;
}
if (!(so->onum < so->olim)) {
unsigned olim = DNS_PP_MAX(4, so->olim * 2);
void *old;
if (!(old = realloc(so->old, sizeof so->old[0] * olim)))
return dns_syerr();
so->old = old;
so->olim = olim;
}
so->old[so->onum++] = *fd;
*fd = -1;
return 0;
} /* dns_so_closefd() */
#define DNS_SO_CLOSE_UDP 0x01
#define DNS_SO_CLOSE_TCP 0x02
#define DNS_SO_CLOSE_OLD 0x04
#define DNS_SO_CLOSE_ALL (DNS_SO_CLOSE_UDP|DNS_SO_CLOSE_TCP|DNS_SO_CLOSE_OLD)
static void dns_so_closefds(struct dns_socket *so, int which) {
if (DNS_SO_CLOSE_UDP & which)
dns_socketclose(&so->udp, &so->opts);
if (DNS_SO_CLOSE_TCP & which)
dns_socketclose(&so->tcp, &so->opts);
if (DNS_SO_CLOSE_OLD & which) {
unsigned i;
for (i = 0; i < so->onum; i++)
dns_socketclose(&so->old[i], &so->opts);
so->onum = 0;
free(so->old);
so->old = 0;
so->olim = 0;
}
} /* dns_so_closefds() */
static void dns_so_destroy(struct dns_socket *);
static struct dns_socket *dns_so_init(struct dns_socket *so, const struct sockaddr *local, int type, const struct dns_options *opts, int *error) {
static const struct dns_socket so_initializer = { .opts = DNS_OPTS_INITIALIZER, .udp = -1, .tcp = -1, };
*so = so_initializer;
so->type = type;
if (opts)
so->opts = *opts;
if (local)
memcpy(&so->local, local, dns_sa_len(local));
if (-1 == (so->udp = dns_socket((struct sockaddr *)&so->local, SOCK_DGRAM, error)))
goto error;
dns_k_permutor_init(&so->qids, 1, 65535);
return so;
error:
dns_so_destroy(so);
return 0;
} /* dns_so_init() */
struct dns_socket *dns_so_open(const struct sockaddr *local, int type, const struct dns_options *opts, int *error) {
struct dns_socket *so;
if (!(so = malloc(sizeof *so)))
goto syerr;
if (!dns_so_init(so, local, type, opts, error))
goto error;
return so;
syerr:
*error = dns_syerr();
error:
dns_so_close(so);
return 0;
} /* dns_so_open() */
static void dns_so_destroy(struct dns_socket *so) {
dns_so_reset(so);
dns_so_closefds(so, DNS_SO_CLOSE_ALL);
dns_trace_close(so->trace);
} /* dns_so_destroy() */
void dns_so_close(struct dns_socket *so) {
if (!so)
return;
dns_so_destroy(so);
free(so);
} /* dns_so_close() */
void dns_so_reset(struct dns_socket *so) {
dns_p_setptr(&so->answer, NULL);
memset(&so->state, '\0', sizeof *so - offsetof(struct dns_socket, state));
} /* dns_so_reset() */
unsigned short dns_so_mkqid(struct dns_socket *so) {
return dns_k_permutor_step(&so->qids);
} /* dns_so_mkqid() */
#define DNS_SO_MINBUF 768
static int dns_so_newanswer(struct dns_socket *so, size_t len) {
size_t size = offsetof(struct dns_packet, data) + DNS_PP_MAX(len, DNS_SO_MINBUF);
void *p;
if (!(p = realloc(so->answer, size)))
return dns_syerr();
so->answer = dns_p_init(p, size);
return 0;
} /* dns_so_newanswer() */
int dns_so_submit(struct dns_socket *so, struct dns_packet *Q, struct sockaddr *host) {
struct dns_rr rr;
int error = DNS_EUNKNOWN;
dns_so_reset(so);
if ((error = dns_rr_parse(&rr, 12, Q)))
goto error;
if (!(so->qlen = dns_d_expand(so->qname, sizeof so->qname, rr.dn.p, Q, &error)))
goto error;
/*
* NOTE: Don't bail if expansion is too long; caller may be
* intentionally sending long names. However, we won't be able to
* verify it on return.
*/
so->qtype = rr.type;
so->qclass = rr.class;
if ((error = dns_so_newanswer(so, (Q->memo.opt.maxudp)? Q->memo.opt.maxudp : DNS_SO_MINBUF)))
goto syerr;
memcpy(&so->remote, host, dns_sa_len(host));
so->query = Q;
so->qout = 0;
dns_begin(&so->elapsed);
if (dns_header(so->query)->qid == 0)
dns_header(so->query)->qid = dns_so_mkqid(so);
so->qid = dns_header(so->query)->qid;
so->state = (so->opts.socks_host && so->opts.socks_host->ss_family) ? DNS_SO_SOCKS_INIT :
(so->type == SOCK_STREAM)? DNS_SO_TCP_INIT : DNS_SO_UDP_INIT;
so->stat.queries++;
dns_trace_so_submit(so->trace, Q, host, 0);
return 0;
syerr:
error = dns_syerr();
error:
dns_so_reset(so);
dns_trace_so_submit(so->trace, Q, host, error);
return error;
} /* dns_so_submit() */
static int dns_so_verify(struct dns_socket *so, struct dns_packet *P) {
char qname[DNS_D_MAXNAME + 1];
size_t qlen;
struct dns_rr rr;
int error = -1;
if (P->end < 12)
goto reject;
if (so->qid != dns_header(P)->qid)
goto reject;
if (!dns_p_count(P, DNS_S_QD))
goto reject;
if (0 != dns_rr_parse(&rr, 12, P))
goto reject;
if (rr.type != so->qtype || rr.class != so->qclass)
goto reject;
if (!(qlen = dns_d_expand(qname, sizeof qname, rr.dn.p, P, &error)))
goto error;
else if (qlen >= sizeof qname || qlen != so->qlen)
goto reject;
if (0 != strcasecmp(so->qname, qname))
goto reject;
dns_trace_so_verify(so->trace, P, 0);
return 0;
reject:
error = DNS_EVERIFY;
error:
DNS_SHOW(P, "rejecting packet (%s)", dns_strerror(error));
dns_trace_so_verify(so->trace, P, error);
return error;
} /* dns_so_verify() */
static _Bool dns_so_tcp_keep(struct dns_socket *so) {
struct sockaddr_storage remote;
socklen_t l = sizeof remote;
if (so->tcp == -1)
return 0;
if (0 != getpeername(so->tcp, (struct sockaddr *)&remote, &l))
return 0;
return 0 == dns_sa_cmp(&remote, &so->remote);
} /* dns_so_tcp_keep() */
/* Convenience functions for sending non-DNS data. */
/* Set up everything for sending LENGTH octets. Returns the buffer
for the data. */
static unsigned char *dns_so_tcp_send_buffer(struct dns_socket *so, size_t length) {
/* Skip the length octets, we are not doing DNS. */
so->qout = 2;
so->query->end = length;
return so->query->data;
}
/* Set up everything for receiving LENGTH octets. */
static void dns_so_tcp_recv_expect(struct dns_socket *so, size_t length) {
/* Skip the length octets, we are not doing DNS. */
so->apos = 2;
so->alen = length;
}
/* Returns the buffer containing the received data. */
static unsigned char *dns_so_tcp_recv_buffer(struct dns_socket *so) {
return so->answer->data;
}
#if GPGRT_GCC_VERSION >= 80000
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Warray-bounds"
#elif defined __clang__
# pragma clang diagnostic push
# pragma clang diagnostic ignored "-Warray-bounds"
#endif
static int dns_so_tcp_send(struct dns_socket *so) {
unsigned char *qsrc;
size_t qend;
int error;
size_t n;
so->query->data[-2] = 0xff & (so->query->end >> 8);
so->query->data[-1] = 0xff & (so->query->end >> 0);
qend = so->query->end + 2;
while (so->qout < qend) {
qsrc = &so->query->data[-2] + so->qout;
n = dns_send_nopipe(so->tcp, (void *)qsrc, qend - so->qout, 0, &error);
dns_trace_sys_send(so->trace, so->tcp, SOCK_STREAM, qsrc, n, error);
if (error)
return error;
so->qout += n;
so->stat.tcp.sent.bytes += n;
}
so->stat.tcp.sent.count++;
return 0;
} /* dns_so_tcp_send() */
static int dns_so_tcp_recv(struct dns_socket *so) {
unsigned char *asrc;
size_t aend, alen, n;
int error;
aend = so->alen + 2;
while (so->apos < aend) {
asrc = &so->answer->data[-2];
n = dns_recv(so->tcp, (void *)&asrc[so->apos], aend - so->apos, 0, &error);
dns_trace_sys_recv(so->trace, so->tcp, SOCK_STREAM, &asrc[so->apos], n, error);
if (error)
return error;
so->apos += n;
so->stat.tcp.rcvd.bytes += n;
if (so->alen == 0 && so->apos >= 2) {
alen = ((0xff & so->answer->data[-2]) << 8)
| ((0xff & so->answer->data[-1]) << 0);
if ((error = dns_so_newanswer(so, alen)))
return error;
so->alen = alen;
aend = alen + 2;
}
}
so->answer->end = so->alen;
so->stat.tcp.rcvd.count++;
return 0;
} /* dns_so_tcp_recv() */
#if GPGRT_GCC_VERSION >= 80000
# pragma GCC diagnostic pop
#elif __clang__
# pragma clang diagnostic pop
#endif
int dns_so_check(struct dns_socket *so) {
int error;
size_t n;
unsigned char *buffer;
retry:
switch (so->state) {
case DNS_SO_UDP_INIT:
if (so->remote.ss_family != so->local.ss_family) {
/* Family mismatch. Reinitialize. */
if ((error = dns_so_closefd(so, &so->udp)))
goto error;
if ((error = dns_so_closefd(so, &so->tcp)))
goto error;
/* If the user supplied an interface
statement, that is gone now. Sorry. */
memset(&so->local, 0, sizeof so->local);
so->local.ss_family = so->remote.ss_family;
if (-1 == (so->udp = dns_socket((struct sockaddr *)&so->local, SOCK_DGRAM, &error)))
goto error;
}
so->state++; /* FALL THROUGH */
case DNS_SO_UDP_CONN:
udp_connect_retry:
error = dns_connect(so->udp, (struct sockaddr *)&so->remote, dns_sa_len(&so->remote));
dns_trace_sys_connect(so->trace, so->udp, SOCK_DGRAM, (struct sockaddr *)&so->remote, error);
/* Linux returns EINVAL when address was bound to
localhost and it's external IP address now. */
if (error == EINVAL) {
struct sockaddr unspec_addr;
memset (&unspec_addr, 0, sizeof unspec_addr);
unspec_addr.sa_family = AF_UNSPEC;
connect(so->udp, &unspec_addr, sizeof unspec_addr);
goto udp_connect_retry;
} else if (error == ECONNREFUSED)
/* Error for previous socket operation may
be reserverd(?) asynchronously. */
goto udp_connect_retry;
if (error)
goto error;
so->state++; /* FALL THROUGH */
case DNS_SO_UDP_SEND:
n = dns_send(so->udp, (void *)so->query->data, so->query->end, 0, &error);
dns_trace_sys_send(so->trace, so->udp, SOCK_DGRAM, so->query->data, n, error);
if (error)
goto error;
so->stat.udp.sent.bytes += n;
so->stat.udp.sent.count++;
so->state++; /* FALL THROUGH */
case DNS_SO_UDP_RECV:
n = dns_recv(so->udp, (void *)so->answer->data, so->answer->size, 0, &error);
dns_trace_sys_recv(so->trace, so->udp, SOCK_DGRAM, so->answer->data, n, error);
if (error)
goto error;
so->answer->end = n;
so->stat.udp.rcvd.bytes += n;
so->stat.udp.rcvd.count++;
if ((error = dns_so_verify(so, so->answer)))
goto trash;
so->state++; /* FALL THROUGH */
case DNS_SO_UDP_DONE:
if (!dns_header(so->answer)->tc || so->type == SOCK_DGRAM)
return 0;
so->state++; /* FALL THROUGH */
case DNS_SO_TCP_INIT:
if (so->remote.ss_family != so->local.ss_family) {
/* Family mismatch. Reinitialize. */
if ((error = dns_so_closefd(so, &so->udp)))
goto error;
if ((error = dns_so_closefd(so, &so->tcp)))
goto error;
/* If the user supplied an interface
statement, that is gone now. Sorry. */
memset(&so->local, 0, sizeof so->local);
so->local.ss_family = so->remote.ss_family;
}
if (dns_so_tcp_keep(so)) {
so->state = DNS_SO_TCP_SEND;
goto retry;
}
if ((error = dns_so_closefd(so, &so->tcp)))
goto error;
if (-1 == (so->tcp = dns_socket((struct sockaddr *)&so->local, SOCK_STREAM, &error)))
goto error;
so->state++; /* FALL THROUGH */
case DNS_SO_TCP_CONN:
error = dns_connect(so->tcp, (struct sockaddr *)&so->remote, dns_sa_len(&so->remote));
dns_trace_sys_connect(so->trace, so->tcp, SOCK_STREAM, (struct sockaddr *)&so->remote, error);
if (error && error != DNS_EISCONN)
goto error;
so->state++; /* FALL THROUGH */
case DNS_SO_TCP_SEND:
if ((error = dns_so_tcp_send(so)))
goto error;
so->state++; /* FALL THROUGH */
case DNS_SO_TCP_RECV:
if ((error = dns_so_tcp_recv(so)))
goto error;
so->state++; /* FALL THROUGH */
case DNS_SO_TCP_DONE:
/* close unless DNS_RESCONF_TCP_ONLY (see dns_res_tcp2type) */
if (so->type != SOCK_STREAM) {
if ((error = dns_so_closefd(so, &so->tcp)))
goto error;
}
if ((error = dns_so_verify(so, so->answer)))
goto error;
return 0;
case DNS_SO_SOCKS_INIT:
if ((error = dns_so_closefd(so, &so->tcp)))
goto error;
if (-1 == (so->tcp = dns_socket((struct sockaddr *)&so->local, SOCK_STREAM, &error)))
goto error;
so->state++; /* FALL THROUGH */
case DNS_SO_SOCKS_CONN: {
unsigned char method;
error = dns_connect(so->tcp, (struct sockaddr *)so->opts.socks_host, dns_sa_len(so->opts.socks_host));
dns_trace_sys_connect(so->trace, so->tcp, SOCK_STREAM, (struct sockaddr *)so->opts.socks_host, error);
if (error && error != DNS_EISCONN)
goto error;
/* We need to do a handshake with the SOCKS server,
* but the query is already in the buffer. Move it
* out of the way. */
dns_p_movptr(&so->query_backup, &so->query);
/* Create a new buffer for the handshake. */
dns_p_grow(&so->query);
/* Negotiate method. */
buffer = dns_so_tcp_send_buffer(so, 3);
buffer[0] = 5; /* RFC-1928 VER field. */
buffer[1] = 1; /* NMETHODS */
if (so->opts.socks_user)
method = 2; /* Method: username/password authentication. */
else
method = 0; /* Method: No authentication required. */
buffer[2] = method;
so->state++;
} /* FALL THROUGH */
case DNS_SO_SOCKS_HELLO_SEND:
if ((error = dns_so_tcp_send(so)))
goto error;
dns_so_tcp_recv_expect(so, 2);
so->state++; /* FALL THROUGH */
case DNS_SO_SOCKS_HELLO_RECV: {
unsigned char method;
if ((error = dns_so_tcp_recv(so)))
goto error;
buffer = dns_so_tcp_recv_buffer(so);
method = so->opts.socks_user ? 2 : 0;
if (buffer[0] != 5 || buffer[1] != method) {
/* Socks server returned wrong version or does
not support our requested method. */
error = ENOTSUP; /* Fixme: Is there a better errno? */
goto error;
}
if (method == 0) {
/* No authentication, go ahead and send the
request. */
so->state = DNS_SO_SOCKS_REQUEST_PREPARE;
goto retry;
}
/* Prepare username/password sub-negotiation. */
if (! so->opts.socks_password) {
error = EINVAL; /* No password given. */
goto error;
} else {
size_t buflen, ulen, plen;
ulen = strlen(so->opts.socks_user);
plen = strlen(so->opts.socks_password);
if (!ulen || ulen > 255 || !plen || plen > 255) {
error = EINVAL; /* Credentials too long or too short. */
goto error;
}
buffer = dns_so_tcp_send_buffer(so, 3 + ulen + plen);
buffer[0] = 1; /* VER of the sub-negotiation. */
buffer[1] = (unsigned char) ulen;
buflen = 2;
memcpy (buffer+buflen, so->opts.socks_user, ulen);
buflen += ulen;
buffer[buflen++] = (unsigned char) plen;
memcpy (buffer+buflen, so->opts.socks_password, plen);
}
so->state++;
} /* FALL THROUGH */
case DNS_SO_SOCKS_AUTH_SEND:
if ((error = dns_so_tcp_send(so)))
goto error;
/* Skip the two length octets, and receive two octets. */
dns_so_tcp_recv_expect(so, 2);
so->state++; /* FALL THROUGH */
case DNS_SO_SOCKS_AUTH_RECV:
if ((error = dns_so_tcp_recv(so)))
goto error;
buffer = dns_so_tcp_recv_buffer(so);
if (buffer[0] != 1) {
/* SOCKS server returned wrong version. */
error = EPROTO;
goto error;
}
if (buffer[1]) {
/* SOCKS server denied access. */
error = EACCES;
goto error;
}
so->state++; /* FALL THROUGH */
case DNS_SO_SOCKS_REQUEST_PREPARE:
/* Send request details (rfc-1928, 4). */
buffer = dns_so_tcp_send_buffer(so, so->remote.ss_family == AF_INET6 ? 22 : 10);
buffer[0] = 5; /* VER */
buffer[1] = 1; /* CMD = CONNECT */
buffer[2] = 0; /* RSV */
if (so->remote.ss_family == AF_INET6) {
struct sockaddr_in6 *addr_in6 = (struct sockaddr_in6 *)&so->remote;
buffer[3] = 4; /* ATYP = IPv6 */
memcpy (buffer+ 4, &addr_in6->sin6_addr.s6_addr, 16); /* DST.ADDR */
memcpy (buffer+20, &addr_in6->sin6_port, 2); /* DST.PORT */
} else {
struct sockaddr_in *addr_in = (struct sockaddr_in *)&so->remote;
buffer[3] = 1; /* ATYP = IPv4 */
memcpy (buffer+4, &addr_in->sin_addr.s_addr, 4); /* DST.ADDR */
memcpy (buffer+8, &addr_in->sin_port, 2); /* DST.PORT */
}
so->state++; /* FALL THROUGH */
case DNS_SO_SOCKS_REQUEST_SEND:
if ((error = dns_so_tcp_send(so)))
goto error;
/* Expect ten octets. This is the length of the
* response assuming a IPv4 address is used. */
dns_so_tcp_recv_expect(so, 10);
so->state++; /* FALL THROUGH */
case DNS_SO_SOCKS_REQUEST_RECV:
if ((error = dns_so_tcp_recv(so)))
goto error;
buffer = dns_so_tcp_recv_buffer(so);
if (buffer[0] != 5 || buffer[2] != 0) {
/* Socks server returned wrong version or the
reserved field is not zero. */
error = EPROTO;
goto error;
}
if (buffer[1]) {
switch (buffer[1]) {
case 0x01: /* general SOCKS server failure. */
error = ENETDOWN;
break;
case 0x02: /* connection not allowed by ruleset. */
error = EACCES;
break;
case 0x03: /* Network unreachable */
error = ENETUNREACH;
break;
case 0x04: /* Host unreachable */
error = EHOSTUNREACH;
break;
case 0x05: /* Connection refused */
error = ECONNREFUSED;
break;
case 0x06: /* TTL expired */
error = ETIMEDOUT;
break;
case 0x08: /* Address type not supported */
error = EPROTONOSUPPORT;
break;
case 0x07: /* Command not supported */
default:
error = ENOTSUP; /* Fixme: Is there a better error? */
break;
}
goto error;
}
if (buffer[3] == 1) {
/* This was indeed an IPv4 address. */
so->state = DNS_SO_SOCKS_HANDSHAKE_DONE;
goto retry;
}
if (buffer[3] != 4) {
error = ENOTSUP;
goto error;
}
/* Expect receive twelve octets. This accounts for
* the remaining bytes assuming an IPv6 address is
* used. */
dns_so_tcp_recv_expect(so, 12);
so->state++; /* FALL THROUGH */
case DNS_SO_SOCKS_REQUEST_RECV_V6:
if ((error = dns_so_tcp_recv(so)))
goto error;
so->state++; /* FALL THROUGH */
case DNS_SO_SOCKS_HANDSHAKE_DONE:
/* We have not way to store the actual address used by
* the server. Then again, we don't really care. */
/* Restore the query. */
dns_p_movptr(&so->query, &so->query_backup);
/* Reset cursors. */
so->qout = 0;
so->apos = 0;
so->alen = 0;
/* SOCKS handshake is done. Proceed with the
* lookup. */
so->state = DNS_SO_TCP_SEND;
goto retry;
default:
error = DNS_EUNKNOWN;
goto error;
} /* switch() */
trash:
DNS_CARP("discarding packet");
goto retry;
error:
switch (error) {
case DNS_EINTR:
goto retry;
case DNS_EINPROGRESS:
/* FALL THROUGH */
case DNS_EALREADY:
/* FALL THROUGH */
#if DNS_EWOULDBLOCK != DNS_EAGAIN
case DNS_EWOULDBLOCK:
/* FALL THROUGH */
#endif
error = DNS_EAGAIN;
break;
} /* switch() */
return error;
} /* dns_so_check() */
struct dns_packet *dns_so_fetch(struct dns_socket *so, int *error) {
struct dns_packet *answer;
switch (so->state) {
case DNS_SO_UDP_DONE:
case DNS_SO_TCP_DONE:
answer = so->answer;
so->answer = 0;
dns_trace_so_fetch(so->trace, answer, 0);
return answer;
default:
*error = DNS_EUNKNOWN;
dns_trace_so_fetch(so->trace, NULL, *error);
return 0;
}
} /* dns_so_fetch() */
struct dns_packet *dns_so_query(struct dns_socket *so, struct dns_packet *Q, struct sockaddr *host, int *error_) {
struct dns_packet *A;
int error;
if (!so->state) {
if ((error = dns_so_submit(so, Q, host)))
goto error;
}
if ((error = dns_so_check(so)))
goto error;
if (!(A = dns_so_fetch(so, &error)))
goto error;
dns_so_reset(so);
return A;
error:
*error_ = error;
return 0;
} /* dns_so_query() */
time_t dns_so_elapsed(struct dns_socket *so) {
return dns_elapsed(&so->elapsed);
} /* dns_so_elapsed() */
void dns_so_clear(struct dns_socket *so) {
dns_so_closefds(so, DNS_SO_CLOSE_OLD);
} /* dns_so_clear() */
static int dns_so_events2(struct dns_socket *so, enum dns_events type) {
int events = 0;
switch (so->state) {
case DNS_SO_UDP_CONN:
case DNS_SO_UDP_SEND:
events |= DNS_POLLOUT;
break;
case DNS_SO_UDP_RECV:
events |= DNS_POLLIN;
break;
case DNS_SO_TCP_CONN:
case DNS_SO_TCP_SEND:
events |= DNS_POLLOUT;
break;
case DNS_SO_TCP_RECV:
events |= DNS_POLLIN;
break;
} /* switch() */
switch (type) {
case DNS_LIBEVENT:
return DNS_POLL2EV(events);
default:
return events;
} /* switch() */
} /* dns_so_events2() */
int dns_so_events(struct dns_socket *so) {
return dns_so_events2(so, so->opts.events);
} /* dns_so_events() */
int dns_so_pollfd(struct dns_socket *so) {
switch (so->state) {
case DNS_SO_UDP_CONN:
case DNS_SO_UDP_SEND:
case DNS_SO_UDP_RECV:
return so->udp;
case DNS_SO_TCP_CONN:
case DNS_SO_TCP_SEND:
case DNS_SO_TCP_RECV:
return so->tcp;
} /* switch() */
return -1;
} /* dns_so_pollfd() */
int dns_so_poll(struct dns_socket *so, int timeout) {
return dns_poll(dns_so_pollfd(so), dns_so_events2(so, DNS_SYSPOLL), timeout);
} /* dns_so_poll() */
const struct dns_stat *dns_so_stat(struct dns_socket *so) {
return &so->stat;
} /* dns_so_stat() */
struct dns_trace *dns_so_trace(struct dns_socket *so) {
return so->trace;
} /* dns_so_trace() */
void dns_so_settrace(struct dns_socket *so, struct dns_trace *trace) {
struct dns_trace *otrace = so->trace;
so->trace = dns_trace_acquire_p(trace);
dns_trace_close(otrace);
} /* dns_so_settrace() */
/*
* R E S O L V E R R O U T I N E S
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
enum dns_res_state {
DNS_R_INIT,
DNS_R_GLUE,
DNS_R_SWITCH, /* (B)IND, (F)ILE, (C)ACHE */
DNS_R_FILE, /* Lookup in local hosts database */
DNS_R_CACHE, /* Lookup in application cache */
DNS_R_SUBMIT,
DNS_R_CHECK,
DNS_R_FETCH,
DNS_R_BIND, /* Lookup in the network */
DNS_R_SEARCH,
DNS_R_HINTS,
DNS_R_ITERATE,
DNS_R_FOREACH_NS,
DNS_R_RESOLV0_NS, /* Prologue: Setup next frame and recurse */
DNS_R_RESOLV1_NS, /* Epilog: Inspect answer */
DNS_R_FOREACH_A,
DNS_R_QUERY_A,
DNS_R_FOREACH_AAAA,
DNS_R_QUERY_AAAA,
DNS_R_CNAME0_A,
DNS_R_CNAME1_A,
DNS_R_FINISH,
DNS_R_SMART0_A,
DNS_R_SMART1_A,
DNS_R_DONE,
DNS_R_SERVFAIL,
}; /* enum dns_res_state */
#define DNS_R_MAXDEPTH 8
#define DNS_R_ENDFRAME (DNS_R_MAXDEPTH - 1)
struct dns_resolver {
struct dns_socket so;
struct dns_resolv_conf *resconf;
struct dns_hosts *hosts;
struct dns_hints *hints;
struct dns_cache *cache;
struct dns_trace *trace;
dns_atomic_t refcount;
/* Reset zeroes everything below here. */
char qname[DNS_D_MAXNAME + 1];
size_t qlen;
enum dns_type qtype;
enum dns_class qclass;
struct dns_clock elapsed;
dns_resconf_i_t search;
struct dns_rr_i smart;
struct dns_packet *nodata; /* answer if nothing better */
unsigned sp;
struct dns_res_frame {
enum dns_res_state state;
int error;
int which; /* (B)IND, (F)ILE; index into resconf->lookup */
int qflags;
unsigned attempts;
struct dns_packet *query, *answer, *hints;
struct dns_rr_i hints_i, hints_j;
struct dns_rr hints_ns, ans_cname;
} stack[DNS_R_MAXDEPTH];
}; /* struct dns_resolver */
static int dns_res_tcp2type(int tcp) {
switch (tcp) {
case DNS_RESCONF_TCP_ONLY:
case DNS_RESCONF_TCP_SOCKS:
return SOCK_STREAM;
case DNS_RESCONF_TCP_DISABLE:
return SOCK_DGRAM;
default:
return 0;
}
} /* dns_res_tcp2type() */
struct dns_resolver *dns_res_open(struct dns_resolv_conf *resconf, struct dns_hosts *hosts, struct dns_hints *hints, struct dns_cache *cache, const struct dns_options *opts, int *_error) {
static const struct dns_resolver R_initializer
= { .refcount = 1, };
struct dns_resolver *R = 0;
int type, error;
/*
* Grab ref count early because the caller may have passed us a mortal
* reference, and we want to do the right thing if we return early
* from an error.
*/
if (resconf)
dns_resconf_acquire(resconf);
if (hosts)
dns_hosts_acquire(hosts);
if (hints)
dns_hints_acquire(hints);
if (cache)
dns_cache_acquire(cache);
/*
* Don't try to load it ourselves because a NULL object might be an
* error from, say, dns_resconf_root(), and loading
* dns_resconf_local() by default would create undesirable surpises.
*/
if (!resconf || !hosts || !hints) {
if (!*_error)
*_error = EINVAL;
goto _error;
}
if (!(R = malloc(sizeof *R)))
goto syerr;
*R = R_initializer;
type = dns_res_tcp2type(resconf->options.tcp);
if (!dns_so_init(&R->so, (struct sockaddr *)&resconf->iface, type, opts, &error))
goto error;
R->resconf = resconf;
R->hosts = hosts;
R->hints = hints;
R->cache = cache;
return R;
syerr:
error = dns_syerr();
error:
*_error = error;
_error:
dns_res_close(R);
dns_resconf_close(resconf);
dns_hosts_close(hosts);
dns_hints_close(hints);
dns_cache_close(cache);
return 0;
} /* dns_res_open() */
struct dns_resolver *dns_res_stub(const struct dns_options *opts, int *error) {
struct dns_resolv_conf *resconf = 0;
struct dns_hosts *hosts = 0;
struct dns_hints *hints = 0;
struct dns_resolver *res = 0;
if (!(resconf = dns_resconf_local(error)))
goto epilog;
if (!(hosts = dns_hosts_local(error)))
goto epilog;
if (!(hints = dns_hints_local(resconf, error)))
goto epilog;
if (!(res = dns_res_open(resconf, hosts, hints, NULL, opts, error)))
goto epilog;
epilog:
dns_resconf_close(resconf);
dns_hosts_close(hosts);
dns_hints_close(hints);
return res;
} /* dns_res_stub() */
static void dns_res_frame_destroy(struct dns_resolver *R, struct dns_res_frame *frame) {
(void)R;
dns_p_setptr(&frame->query, NULL);
dns_p_setptr(&frame->answer, NULL);
dns_p_setptr(&frame->hints, NULL);
} /* dns_res_frame_destroy() */
static void dns_res_frame_init(struct dns_resolver *R, struct dns_res_frame *frame) {
memset(frame, '\0', sizeof *frame);
/*
* NB: Can be invoked from dns_res_open, before R->resconf has been
* initialized.
*/
if (R->resconf) {
if (!R->resconf->options.recurse)
frame->qflags |= DNS_Q_RD;
if (R->resconf->options.edns0)
frame->qflags |= DNS_Q_EDNS0;
}
} /* dns_res_frame_init() */
static void dns_res_frame_reset(struct dns_resolver *R, struct dns_res_frame *frame) {
dns_res_frame_destroy(R, frame);
dns_res_frame_init(R, frame);
} /* dns_res_frame_reset() */
static dns_error_t dns_res_frame_prepare(struct dns_resolver *R, struct dns_res_frame *F, const char *qname, enum dns_type qtype, enum dns_class qclass) {
struct dns_packet *P = NULL;
if (!(F < endof(R->stack)))
return DNS_EUNKNOWN;
dns_p_movptr(&P, &F->query);
dns_res_frame_reset(R, F);
dns_p_movptr(&F->query, &P);
return dns_q_make(&F->query, qname, qtype, qclass, F->qflags);
} /* dns_res_frame_prepare() */
void dns_res_reset(struct dns_resolver *R) {
unsigned i;
dns_so_reset(&R->so);
dns_p_setptr(&R->nodata, NULL);
for (i = 0; i < lengthof(R->stack); i++)
dns_res_frame_destroy(R, &R->stack[i]);
memset(&R->qname, '\0', sizeof *R - offsetof(struct dns_resolver, qname));
for (i = 0; i < lengthof(R->stack); i++)
dns_res_frame_init(R, &R->stack[i]);
} /* dns_res_reset() */
void dns_res_close(struct dns_resolver *R) {
if (!R || 1 < dns_res_release(R))
return;
dns_res_reset(R);
dns_so_destroy(&R->so);
dns_hints_close(R->hints);
dns_hosts_close(R->hosts);
dns_resconf_close(R->resconf);
dns_cache_close(R->cache);
dns_trace_close(R->trace);
free(R);
} /* dns_res_close() */
dns_refcount_t dns_res_acquire(struct dns_resolver *R) {
return dns_atomic_fetch_add(&R->refcount);
} /* dns_res_acquire() */
dns_refcount_t dns_res_release(struct dns_resolver *R) {
return dns_atomic_fetch_sub(&R->refcount);
} /* dns_res_release() */
struct dns_resolver *dns_res_mortal(struct dns_resolver *res) {
if (res)
dns_res_release(res);
return res;
} /* dns_res_mortal() */
static struct dns_packet *dns_res_merge(struct dns_packet *P0, struct dns_packet *P1, int *error_) {
size_t bufsiz = P0->end + P1->end;
struct dns_packet *P[3] = { P0, P1, 0 };
struct dns_rr rr[3];
int error, copy, i;
enum dns_section section;
retry:
if (!(P[2] = dns_p_make(bufsiz, &error)))
goto error;
dns_rr_foreach(&rr[0], P[0], .section = DNS_S_QD) {
if ((error = dns_rr_copy(P[2], &rr[0], P[0])))
goto error;
}
for (section = DNS_S_AN; (DNS_S_ALL & section); section <<= 1) {
for (i = 0; i < 2; i++) {
dns_rr_foreach(&rr[i], P[i], .section = section) {
copy = 1;
dns_rr_foreach(&rr[2], P[2], .type = rr[i].type, .section = (DNS_S_ALL & ~DNS_S_QD)) {
if (0 == dns_rr_cmp(&rr[i], P[i], &rr[2], P[2])) {
copy = 0;
break;
}
}
if (copy && (error = dns_rr_copy(P[2], &rr[i], P[i]))) {
if (error == DNS_ENOBUFS && bufsiz < 65535) {
dns_p_setptr(&P[2], NULL);
bufsiz = DNS_PP_MAX(65535, bufsiz * 2);
goto retry;
}
goto error;
}
} /* foreach(rr) */
} /* foreach(packet) */
} /* foreach(section) */
return P[2];
error:
*error_ = error;
dns_p_free(P[2]);
return 0;
} /* dns_res_merge() */
static struct dns_packet *dns_res_glue(struct dns_resolver *R, struct dns_packet *Q) {
- union { unsigned char b[dns_p_calcsize((512))]; struct dns_packet p; } _P = { 0 };
- struct dns_packet *P = dns_p_init(&_P.p, 512);
+ union { unsigned char b[dns_p_calcsize((512))]; struct dns_packet p; } P_instance = { 0 };
+ struct dns_packet *P = dns_p_init(&P_instance.p, 512);
char qname[DNS_D_MAXNAME + 1];
size_t qlen;
enum dns_type qtype;
struct dns_rr rr;
unsigned sp;
int error;
if (!(qlen = dns_d_expand(qname, sizeof qname, 12, Q, &error))
|| qlen >= sizeof qname)
return 0;
if (!(qtype = dns_rr_type(12, Q)))
return 0;
if ((error = dns_p_push(P, DNS_S_QD, qname, strlen(qname), qtype, DNS_C_IN, 0, 0)))
return 0;
for (sp = 0; sp <= R->sp; sp++) {
if (!R->stack[sp].answer)
continue;
dns_rr_foreach(&rr, R->stack[sp].answer, .name = qname, .type = qtype, .section = (DNS_S_ALL & ~DNS_S_QD)) {
rr.section = DNS_S_AN;
if ((error = dns_rr_copy(P, &rr, R->stack[sp].answer)))
return 0;
}
}
if (dns_p_count(P, DNS_S_AN) > 0)
goto copy;
/* Otherwise, look for a CNAME */
for (sp = 0; sp <= R->sp; sp++) {
if (!R->stack[sp].answer)
continue;
dns_rr_foreach(&rr, R->stack[sp].answer, .name = qname, .type = DNS_T_CNAME, .section = (DNS_S_ALL & ~DNS_S_QD)) {
rr.section = DNS_S_AN;
if ((error = dns_rr_copy(P, &rr, R->stack[sp].answer)))
return 0;
}
}
if (!dns_p_count(P, DNS_S_AN))
return 0;
copy:
return dns_p_copy(dns_p_make(P->end, &error), P);
} /* dns_res_glue() */
/*
* Sort NS records by three criteria:
*
* 1) Whether glue is present.
* 2) Whether glue record is original or of recursive lookup.
* 3) Randomly shuffle records which share the above criteria.
*
* NOTE: Assumes only NS records passed, AND ASSUMES no new NS records will
* be added during an iteration.
*
* FIXME: Only groks A glue, not AAAA glue.
*/
static int dns_res_nameserv_cmp(struct dns_rr *a, struct dns_rr *b, struct dns_rr_i *i, struct dns_packet *P) {
_Bool glued[2] = { 0 };
struct dns_rr x = { 0 }, y = { 0 };
struct dns_ns ns;
int cmp, error;
if (!(error = dns_ns_parse(&ns, a, P))) {
- struct dns_rr_i _I = { 0 };
+ struct dns_rr_i I_instance = { 0 };
- _I.section = (DNS_S_ALL & ~DNS_S_QD);
- _I.name = ns.host;
- _I.type = DNS_T_A;
- glued[0] = !!dns_rr_grep(&x, 1, &_I, P, &error);
+ I_instance.section = (DNS_S_ALL & ~DNS_S_QD);
+ I_instance.name = ns.host;
+ I_instance.type = DNS_T_A;
+ glued[0] = !!dns_rr_grep(&x, 1, &I_instance, P, &error);
}
if (!(error = dns_ns_parse(&ns, b, P))) {
- struct dns_rr_i _I = { 0 };
+ struct dns_rr_i I_instance = { 0 };
- _I.section = (DNS_S_ALL & ~DNS_S_QD);
- _I.name = ns.host;
- _I.type = DNS_T_A;
- glued[1] = !!dns_rr_grep(&y, 1, &_I, P, &error);
+ I_instance.section = (DNS_S_ALL & ~DNS_S_QD);
+ I_instance.name = ns.host;
+ I_instance.type = DNS_T_A;
+ glued[1] = !!dns_rr_grep(&y, 1, &I_instance, P, &error);
}
if ((cmp = glued[1] - glued[0])) {
return cmp;
} else if ((cmp = (dns_rr_offset(&y) < i->args[0]) - (dns_rr_offset(&x) < i->args[0]))) {
return cmp;
} else {
return dns_rr_i_shuffle(a, b, i, P);
}
} /* dns_res_nameserv_cmp() */
#define dgoto(sp, i) \
do { R->stack[(sp)].state = (i); goto exec; } while (0)
static int dns_res_exec(struct dns_resolver *R) {
struct dns_res_frame *F;
struct dns_packet *P;
union {
char host[DNS_D_MAXNAME + 1];
char name[DNS_D_MAXNAME + 1];
struct dns_ns ns;
struct dns_cname cname;
} u;
size_t len;
struct dns_rr rr;
int error;
exec:
F = &R->stack[R->sp];
switch (F->state) {
case DNS_R_INIT:
F->state++; /* FALL THROUGH */
case DNS_R_GLUE:
if (R->sp == 0)
dgoto(R->sp, DNS_R_SWITCH);
if (!F->query)
goto noquery;
if (!(F->answer = dns_res_glue(R, F->query)))
dgoto(R->sp, DNS_R_SWITCH);
if (!(len = dns_d_expand(u.name, sizeof u.name, 12, F->query, &error)))
goto error;
else if (len >= sizeof u.name)
goto toolong;
dns_rr_foreach(&rr, F->answer, .name = u.name, .type = dns_rr_type(12, F->query), .section = DNS_S_AN) {
dgoto(R->sp, DNS_R_FINISH);
}
dns_rr_foreach(&rr, F->answer, .name = u.name, .type = DNS_T_CNAME, .section = DNS_S_AN) {
F->ans_cname = rr;
dgoto(R->sp, DNS_R_CNAME0_A);
}
F->state++;
case DNS_R_SWITCH:
while (F->which < (int)sizeof R->resconf->lookup && R->resconf->lookup[F->which]) {
switch (R->resconf->lookup[F->which++]) {
case 'b': case 'B':
dgoto(R->sp, DNS_R_BIND);
case 'f': case 'F':
dgoto(R->sp, DNS_R_FILE);
case 'c': case 'C':
if (R->cache)
dgoto(R->sp, DNS_R_CACHE);
break;
default:
break;
}
}
/*
* FIXME: Examine more closely whether our logic is correct
* and DNS_R_SERVFAIL is the correct default response.
*
* Case 1: We got here because we never got an answer on the
* wire. All queries timed-out and we reached maximum
* attempts count. See DNS_R_FOREACH_NS. In that case
* DNS_R_SERVFAIL is the correct state, unless we want to
* return DNS_ETIMEDOUT.
*
* Case 2: We were a stub resolver and got an unsatisfactory
* answer (empty ANSWER section) which caused us to jump
* back to DNS_R_SEARCH and ultimately to DNS_R_SWITCH. We
* return the answer returned from the wire, which we
* stashed in R->nodata.
*
* Case 3: We reached maximum attempts count as in case #1,
* but never got an authoritative response which caused us
* to short-circuit. See end of DNS_R_QUERY_A case. We
* should probably prepare R->nodata as in case #2.
*/
if (R->sp == 0 && R->nodata) { /* XXX: can we just return nodata regardless? */
dns_p_movptr(&F->answer, &R->nodata);
dgoto(R->sp, DNS_R_FINISH);
}
dgoto(R->sp, DNS_R_SERVFAIL);
case DNS_R_FILE:
if (R->sp > 0) {
if (!dns_p_setptr(&F->answer, dns_hosts_query(R->hosts, F->query, &error)))
goto error;
if (dns_p_count(F->answer, DNS_S_AN) > 0)
dgoto(R->sp, DNS_R_FINISH);
dns_p_setptr(&F->answer, NULL);
} else {
R->search = 0;
while ((len = dns_resconf_search(u.name, sizeof u.name, R->qname, R->qlen, R->resconf, &R->search))) {
if ((error = dns_q_make2(&F->query, u.name, len, R->qtype, R->qclass, F->qflags)))
goto error;
if (!dns_p_setptr(&F->answer, dns_hosts_query(R->hosts, F->query, &error)))
goto error;
if (dns_p_count(F->answer, DNS_S_AN) > 0)
dgoto(R->sp, DNS_R_FINISH);
dns_p_setptr(&F->answer, NULL);
}
}
dgoto(R->sp, DNS_R_SWITCH);
case DNS_R_CACHE:
error = 0;
if (!F->query && (error = dns_q_make(&F->query, R->qname, R->qtype, R->qclass, F->qflags)))
goto error;
if (dns_p_setptr(&F->answer, R->cache->query(F->query, R->cache, &error))) {
if (dns_p_count(F->answer, DNS_S_AN) > 0)
dgoto(R->sp, DNS_R_FINISH);
dns_p_setptr(&F->answer, NULL);
dgoto(R->sp, DNS_R_SWITCH);
} else if (error)
goto error;
F->state++; /* FALL THROUGH */
case DNS_R_SUBMIT:
if ((error = R->cache->submit(F->query, R->cache)))
goto error;
F->state++; /* FALL THROUGH */
case DNS_R_CHECK:
if ((error = R->cache->check(R->cache)))
goto error;
F->state++; /* FALL THROUGH */
case DNS_R_FETCH:
error = 0;
if (dns_p_setptr(&F->answer, R->cache->fetch(R->cache, &error))) {
if (dns_p_count(F->answer, DNS_S_AN) > 0)
dgoto(R->sp, DNS_R_FINISH);
dns_p_setptr(&F->answer, NULL);
dgoto(R->sp, DNS_R_SWITCH);
} else if (error)
goto error;
dgoto(R->sp, DNS_R_SWITCH);
case DNS_R_BIND:
if (R->sp > 0) {
if (!F->query)
goto noquery;
dgoto(R->sp, DNS_R_HINTS);
}
R->search = 0;
F->state++; /* FALL THROUGH */
case DNS_R_SEARCH:
/*
* XXX: We probably should only apply the domain search
* algorithm if R->sp == 0.
*/
if (!(len = dns_resconf_search(u.name, sizeof u.name, R->qname, R->qlen, R->resconf, &R->search)))
dgoto(R->sp, DNS_R_SWITCH);
if ((error = dns_q_make2(&F->query, u.name, len, R->qtype, R->qclass, F->qflags)))
goto error;
F->state++; /* FALL THROUGH */
case DNS_R_HINTS:
if (!dns_p_setptr(&F->hints, dns_hints_query(R->hints, F->query, &error)))
goto error;
F->state++; /* FALL THROUGH */
case DNS_R_ITERATE:
dns_rr_i_init(&F->hints_i);
F->hints_i.section = DNS_S_AUTHORITY;
F->hints_i.type = DNS_T_NS;
F->hints_i.sort = &dns_res_nameserv_cmp;
F->hints_i.args[0] = F->hints->end;
F->state++; /* FALL THROUGH */
case DNS_R_FOREACH_NS:
dns_rr_i_save(&F->hints_i);
/* Load our next nameserver host. */
if (!dns_rr_grep(&F->hints_ns, 1, &F->hints_i, F->hints, &error)) {
if (++F->attempts < R->resconf->options.attempts)
dgoto(R->sp, DNS_R_ITERATE);
dgoto(R->sp, DNS_R_SWITCH);
}
dns_rr_i_init(&F->hints_j);
/* Assume there are glue records */
dgoto(R->sp, DNS_R_FOREACH_A);
case DNS_R_RESOLV0_NS:
/* Have we reached our max depth? */
if (&F[1] >= endof(R->stack))
dgoto(R->sp, DNS_R_FOREACH_NS);
if ((error = dns_ns_parse(&u.ns, &F->hints_ns, F->hints)))
goto error;
if ((error = dns_res_frame_prepare(R, &F[1], u.ns.host, DNS_T_A, DNS_C_IN)))
goto error;
F->state++;
dgoto(++R->sp, DNS_R_INIT);
case DNS_R_RESOLV1_NS:
if (!(len = dns_d_expand(u.host, sizeof u.host, 12, F[1].query, &error)))
goto error;
else if (len >= sizeof u.host)
goto toolong;
dns_rr_foreach(&rr, F[1].answer, .name = u.host, .type = DNS_T_A, .section = (DNS_S_ALL & ~DNS_S_QD)) {
rr.section = DNS_S_AR;
if ((error = dns_rr_copy(F->hints, &rr, F[1].answer)))
goto error;
dns_rr_i_rewind(&F->hints_i); /* Now there's glue. */
}
dgoto(R->sp, DNS_R_FOREACH_NS);
case DNS_R_FOREACH_A: {
struct dns_a a;
struct sockaddr_in sin;
/*
* NOTE: Iterator initialized in DNS_R_FOREACH_NS because
* this state is re-entrant, but we need to reset
* .name to a valid pointer each time.
*/
if ((error = dns_ns_parse(&u.ns, &F->hints_ns, F->hints)))
goto error;
F->hints_j.name = u.ns.host;
F->hints_j.type = DNS_T_A;
F->hints_j.section = DNS_S_ALL & ~DNS_S_QD;
if (!dns_rr_grep(&rr, 1, &F->hints_j, F->hints, &error)) {
if (!dns_rr_i_count(&F->hints_j)) {
/* Check if we have in fact servers
with an IPv6 address. */
dns_rr_i_init(&F->hints_j);
F->hints_j.name = u.ns.host;
F->hints_j.type = DNS_T_AAAA;
F->hints_j.section = DNS_S_ALL & ~DNS_S_QD;
if (dns_rr_grep(&rr, 1, &F->hints_j, F->hints, &error)) {
/* We do. Reinitialize
iterator and handle it. */
dns_rr_i_init(&F->hints_j);
dgoto(R->sp, DNS_R_FOREACH_AAAA);
}
dgoto(R->sp, DNS_R_RESOLV0_NS);
}
dgoto(R->sp, DNS_R_FOREACH_NS);
}
if ((error = dns_a_parse(&a, &rr, F->hints)))
goto error;
memset(&sin, '\0', sizeof sin); /* NB: silence valgrind */
sin.sin_family = AF_INET;
sin.sin_addr = a.addr;
if (R->sp == 0)
sin.sin_port = dns_hints_port(R->hints, AF_INET, &sin.sin_addr);
else
sin.sin_port = htons(53);
if (DNS_DEBUG) {
char addr[INET_ADDRSTRLEN + 1];
dns_a_print(addr, sizeof addr, &a);
dns_header(F->query)->qid = dns_so_mkqid(&R->so);
DNS_SHOW(F->query, "ASKING: %s/%s @ DEPTH: %u)", u.ns.host, addr, R->sp);
}
dns_trace_setcname(R->trace, u.ns.host, (struct sockaddr *)&sin);
if ((error = dns_so_submit(&R->so, F->query, (struct sockaddr *)&sin)))
goto error;
F->state++;
} /* FALL THROUGH */
case DNS_R_QUERY_A:
if (dns_so_elapsed(&R->so) >= dns_resconf_timeout(R->resconf))
dgoto(R->sp, DNS_R_FOREACH_A);
error = dns_so_check(&R->so);
if (R->so.state != DNS_SO_SOCKS_CONN && error == ECONNREFUSED)
dgoto(R->sp, DNS_R_FOREACH_A);
else if (error)
goto error;
if (!dns_p_setptr(&F->answer, dns_so_fetch(&R->so, &error)))
goto error;
if (DNS_DEBUG) {
DNS_SHOW(F->answer, "ANSWER @ DEPTH: %u)", R->sp);
}
if (dns_p_rcode(F->answer) == DNS_RC_FORMERR ||
dns_p_rcode(F->answer) == DNS_RC_NOTIMP ||
dns_p_rcode(F->answer) == DNS_RC_BADVERS) {
/* Temporarily disable EDNS0 and try again. */
if (F->qflags & DNS_Q_EDNS0) {
F->qflags &= ~DNS_Q_EDNS0;
if ((error = dns_q_remake(&F->query, F->qflags)))
goto error;
dgoto(R->sp, DNS_R_FOREACH_A);
}
}
if ((error = dns_rr_parse(&rr, 12, F->query)))
goto error;
if (!(len = dns_d_expand(u.name, sizeof u.name, rr.dn.p, F->query, &error)))
goto error;
else if (len >= sizeof u.name)
goto toolong;
dns_rr_foreach(&rr, F->answer, .section = DNS_S_AN, .name = u.name, .type = rr.type) {
dgoto(R->sp, DNS_R_FINISH); /* Found */
}
dns_rr_foreach(&rr, F->answer, .section = DNS_S_AN, .name = u.name, .type = DNS_T_CNAME) {
F->ans_cname = rr;
dgoto(R->sp, DNS_R_CNAME0_A);
}
/*
* XXX: The condition here should probably check whether
* R->sp == 0, because DNS_R_SEARCH runs regardless of
* options.recurse. See DNS_R_BIND.
*/
if (!R->resconf->options.recurse) {
/* Make first answer our tentative answer */
if (!R->nodata)
dns_p_movptr(&R->nodata, &F->answer);
dgoto(R->sp, DNS_R_SEARCH);
}
dns_rr_foreach(&rr, F->answer, .section = DNS_S_NS, .type = DNS_T_NS) {
dns_p_movptr(&F->hints, &F->answer);
dgoto(R->sp, DNS_R_ITERATE);
}
/* XXX: Should this go further up? */
if (dns_header(F->answer)->aa)
dgoto(R->sp, DNS_R_FINISH);
/* XXX: Should we copy F->answer to R->nodata? */
dgoto(R->sp, DNS_R_FOREACH_A);
case DNS_R_FOREACH_AAAA: {
struct dns_aaaa aaaa;
struct sockaddr_in6 sin6;
/*
* NOTE: Iterator initialized in DNS_R_FOREACH_NS because
* this state is re-entrant, but we need to reset
* .name to a valid pointer each time.
*/
if ((error = dns_ns_parse(&u.ns, &F->hints_ns, F->hints)))
goto error;
F->hints_j.name = u.ns.host;
F->hints_j.type = DNS_T_AAAA;
F->hints_j.section = DNS_S_ALL & ~DNS_S_QD;
if (!dns_rr_grep(&rr, 1, &F->hints_j, F->hints, &error)) {
if (!dns_rr_i_count(&F->hints_j)) {
/* Check if we have in fact servers
with an IPv4 address. */
dns_rr_i_init(&F->hints_j);
F->hints_j.name = u.ns.host;
F->hints_j.type = DNS_T_A;
F->hints_j.section = DNS_S_ALL & ~DNS_S_QD;
if (dns_rr_grep(&rr, 1, &F->hints_j, F->hints, &error)) {
/* We do. Reinitialize
iterator and handle it. */
dns_rr_i_init(&F->hints_j);
dgoto(R->sp, DNS_R_FOREACH_A);
}
dgoto(R->sp, DNS_R_RESOLV0_NS);
}
dgoto(R->sp, DNS_R_FOREACH_NS);
}
if ((error = dns_aaaa_parse(&aaaa, &rr, F->hints)))
goto error;
memset(&sin6, '\0', sizeof sin6); /* NB: silence valgrind */
sin6.sin6_family = AF_INET6;
sin6.sin6_addr = aaaa.addr;
if (R->sp == 0)
sin6.sin6_port = dns_hints_port(R->hints, AF_INET, &sin6.sin6_addr);
else
sin6.sin6_port = htons(53);
if (DNS_DEBUG) {
char addr[INET6_ADDRSTRLEN + 1];
dns_aaaa_print(addr, sizeof addr, &aaaa);
dns_header(F->query)->qid = dns_so_mkqid(&R->so);
DNS_SHOW(F->query, "ASKING: %s/%s @ DEPTH: %u)", u.ns.host, addr, R->sp);
}
dns_trace_setcname(R->trace, u.ns.host, (struct sockaddr *)&sin6);
if ((error = dns_so_submit(&R->so, F->query, (struct sockaddr *)&sin6)))
goto error;
F->state++;
} /* FALL THROUGH */
case DNS_R_QUERY_AAAA:
if (dns_so_elapsed(&R->so) >= dns_resconf_timeout(R->resconf))
dgoto(R->sp, DNS_R_FOREACH_AAAA);
error = dns_so_check(&R->so);
if (error == ECONNREFUSED)
dgoto(R->sp, DNS_R_FOREACH_AAAA);
else if (error)
goto error;
if (!dns_p_setptr(&F->answer, dns_so_fetch(&R->so, &error)))
goto error;
if (DNS_DEBUG) {
DNS_SHOW(F->answer, "ANSWER @ DEPTH: %u)", R->sp);
}
if (dns_p_rcode(F->answer) == DNS_RC_FORMERR ||
dns_p_rcode(F->answer) == DNS_RC_NOTIMP ||
dns_p_rcode(F->answer) == DNS_RC_BADVERS) {
/* Temporarily disable EDNS0 and try again. */
if (F->qflags & DNS_Q_EDNS0) {
F->qflags &= ~DNS_Q_EDNS0;
if ((error = dns_q_remake(&F->query, F->qflags)))
goto error;
dgoto(R->sp, DNS_R_FOREACH_AAAA);
}
}
if ((error = dns_rr_parse(&rr, 12, F->query)))
goto error;
if (!(len = dns_d_expand(u.name, sizeof u.name, rr.dn.p, F->query, &error)))
goto error;
else if (len >= sizeof u.name)
goto toolong;
dns_rr_foreach(&rr, F->answer, .section = DNS_S_AN, .name = u.name, .type = rr.type) {
dgoto(R->sp, DNS_R_FINISH); /* Found */
}
dns_rr_foreach(&rr, F->answer, .section = DNS_S_AN, .name = u.name, .type = DNS_T_CNAME) {
F->ans_cname = rr;
dgoto(R->sp, DNS_R_CNAME0_A);
}
/*
* XXX: The condition here should probably check whether
* R->sp == 0, because DNS_R_SEARCH runs regardless of
* options.recurse. See DNS_R_BIND.
*/
if (!R->resconf->options.recurse) {
/* Make first answer our tentative answer */
if (!R->nodata)
dns_p_movptr(&R->nodata, &F->answer);
dgoto(R->sp, DNS_R_SEARCH);
}
dns_rr_foreach(&rr, F->answer, .section = DNS_S_NS, .type = DNS_T_NS) {
dns_p_movptr(&F->hints, &F->answer);
dgoto(R->sp, DNS_R_ITERATE);
}
/* XXX: Should this go further up? */
if (dns_header(F->answer)->aa)
dgoto(R->sp, DNS_R_FINISH);
/* XXX: Should we copy F->answer to R->nodata? */
dgoto(R->sp, DNS_R_FOREACH_AAAA);
case DNS_R_CNAME0_A:
if (&F[1] >= endof(R->stack))
dgoto(R->sp, DNS_R_FINISH);
if ((error = dns_cname_parse(&u.cname, &F->ans_cname, F->answer)))
goto error;
if ((error = dns_res_frame_prepare(R, &F[1], u.cname.host, dns_rr_type(12, F->query), DNS_C_IN)))
goto error;
F->state++;
dgoto(++R->sp, DNS_R_INIT);
case DNS_R_CNAME1_A:
if (!(P = dns_res_merge(F->answer, F[1].answer, &error)))
goto error;
dns_p_setptr(&F->answer, P);
dgoto(R->sp, DNS_R_FINISH);
case DNS_R_FINISH:
if (!F->answer)
goto noanswer;
if (!R->resconf->options.smart || R->sp > 0)
dgoto(R->sp, DNS_R_DONE);
R->smart.section = DNS_S_AN;
R->smart.type = R->qtype;
dns_rr_i_init(&R->smart);
F->state++; /* FALL THROUGH */
case DNS_R_SMART0_A:
if (&F[1] >= endof(R->stack))
dgoto(R->sp, DNS_R_DONE);
while (dns_rr_grep(&rr, 1, &R->smart, F->answer, &error)) {
union {
struct dns_ns ns;
struct dns_mx mx;
struct dns_srv srv;
} rd;
const char *qname;
enum dns_type qtype;
enum dns_class qclass;
switch (rr.type) {
case DNS_T_NS:
if ((error = dns_ns_parse(&rd.ns, &rr, F->answer)))
goto error;
qname = rd.ns.host;
qtype = DNS_T_A;
qclass = DNS_C_IN;
break;
case DNS_T_MX:
if ((error = dns_mx_parse(&rd.mx, &rr, F->answer)))
goto error;
qname = rd.mx.host;
qtype = DNS_T_A;
qclass = DNS_C_IN;
break;
case DNS_T_SRV:
if ((error = dns_srv_parse(&rd.srv, &rr, F->answer)))
goto error;
qname = rd.srv.target;
qtype = DNS_T_A;
qclass = DNS_C_IN;
break;
default:
continue;
} /* switch() */
if ((error = dns_res_frame_prepare(R, &F[1], qname, qtype, qclass)))
goto error;
F->state++;
dgoto(++R->sp, DNS_R_INIT);
} /* while() */
/*
* NOTE: SMTP specification says to fallback to A record.
*
* XXX: Should we add a mock MX answer?
*/
if (R->qtype == DNS_T_MX && R->smart.state.count == 0) {
if ((error = dns_res_frame_prepare(R, &F[1], R->qname, DNS_T_A, DNS_C_IN)))
goto error;
R->smart.state.count++;
F->state++;
dgoto(++R->sp, DNS_R_INIT);
}
dgoto(R->sp, DNS_R_DONE);
case DNS_R_SMART1_A:
if (!F[1].answer)
goto noanswer;
/*
* FIXME: For CNAME chains (which are typically illegal in
* this context), we should rewrite the record host name
* to the original smart qname. All the user cares about
* is locating that A/AAAA record.
*/
dns_rr_foreach(&rr, F[1].answer, .section = DNS_S_AN, .type = DNS_T_A) {
rr.section = DNS_S_AR;
if (dns_rr_exists(&rr, F[1].answer, F->answer))
continue;
while ((error = dns_rr_copy(F->answer, &rr, F[1].answer))) {
if (error != DNS_ENOBUFS)
goto error;
if ((error = dns_p_grow(&F->answer)))
goto error;
}
}
dgoto(R->sp, DNS_R_SMART0_A);
case DNS_R_DONE:
if (!F->answer)
goto noanswer;
if (R->sp > 0)
dgoto(--R->sp, F[-1].state);
break;
case DNS_R_SERVFAIL:
if (!dns_p_setptr(&F->answer, dns_p_make(DNS_P_QBUFSIZ, &error)))
goto error;
dns_header(F->answer)->qr = 1;
dns_header(F->answer)->rcode = DNS_RC_SERVFAIL;
if ((error = dns_p_push(F->answer, DNS_S_QD, R->qname, strlen(R->qname), R->qtype, R->qclass, 0, 0)))
goto error;
dgoto(R->sp, DNS_R_DONE);
default:
error = EINVAL;
goto error;
} /* switch () */
return 0;
noquery:
error = DNS_ENOQUERY;
goto error;
noanswer:
error = DNS_ENOANSWER;
goto error;
toolong:
error = DNS_EILLEGAL;
/* FALL THROUGH */
error:
return error;
} /* dns_res_exec() */
#undef goto
void dns_res_clear(struct dns_resolver *R) {
switch (R->stack[R->sp].state) {
case DNS_R_CHECK:
R->cache->clear(R->cache);
break;
default:
dns_so_clear(&R->so);
break;
}
} /* dns_res_clear() */
static int dns_res_events2(struct dns_resolver *R, enum dns_events type) {
int events;
switch (R->stack[R->sp].state) {
case DNS_R_CHECK:
events = R->cache->events(R->cache);
return (type == DNS_LIBEVENT)? DNS_POLL2EV(events) : events;
default:
return dns_so_events2(&R->so, type);
}
} /* dns_res_events2() */
int dns_res_events(struct dns_resolver *R) {
return dns_res_events2(R, R->so.opts.events);
} /* dns_res_events() */
int dns_res_pollfd(struct dns_resolver *R) {
switch (R->stack[R->sp].state) {
case DNS_R_CHECK:
return R->cache->pollfd(R->cache);
default:
return dns_so_pollfd(&R->so);
}
} /* dns_res_pollfd() */
time_t dns_res_timeout(struct dns_resolver *R) {
time_t elapsed;
switch (R->stack[R->sp].state) {
#if 0
case DNS_R_QUERY_AAAA:
#endif
case DNS_R_QUERY_A:
elapsed = dns_so_elapsed(&R->so);
if (elapsed <= dns_resconf_timeout(R->resconf))
return R->resconf->options.timeout - elapsed;
break;
default:
break;
} /* switch() */
/*
* NOTE: We're not in a pollable state, or the user code hasn't
* called dns_res_check properly. The calling code is probably
* broken. Put them into a slow-burn pattern.
*/
return 1;
} /* dns_res_timeout() */
time_t dns_res_elapsed(struct dns_resolver *R) {
return dns_elapsed(&R->elapsed);
} /* dns_res_elapsed() */
int dns_res_poll(struct dns_resolver *R, int timeout) {
return dns_poll(dns_res_pollfd(R), dns_res_events2(R, DNS_SYSPOLL), timeout);
} /* dns_res_poll() */
int dns_res_submit2(struct dns_resolver *R, const char *qname, size_t qlen, enum dns_type qtype, enum dns_class qclass) {
dns_res_reset(R);
/* Don't anchor; that can conflict with searchlist generation. */
dns_d_init(R->qname, sizeof R->qname, qname, (R->qlen = qlen), 0);
R->qtype = qtype;
R->qclass = qclass;
dns_begin(&R->elapsed);
dns_trace_res_submit(R->trace, R->qname, R->qtype, R->qclass, 0);
return 0;
} /* dns_res_submit2() */
int dns_res_submit(struct dns_resolver *R, const char *qname, enum dns_type qtype, enum dns_class qclass) {
return dns_res_submit2(R, qname, strlen(qname), qtype, qclass);
} /* dns_res_submit() */
int dns_res_check(struct dns_resolver *R) {
int error;
if (R->stack[0].state != DNS_R_DONE) {
if ((error = dns_res_exec(R)))
return error;
}
return 0;
} /* dns_res_check() */
struct dns_packet *dns_res_fetch(struct dns_resolver *R, int *_error) {
struct dns_packet *P = NULL;
int error;
if (R->stack[0].state != DNS_R_DONE) {
error = DNS_EUNKNOWN;
goto error;
}
if (!dns_p_movptr(&P, &R->stack[0].answer)) {
error = DNS_EFETCHED;
goto error;
}
dns_trace_res_fetch(R->trace, P, 0);
return P;
error:
*_error = error;
dns_trace_res_fetch(R->trace, NULL, error);
return NULL;
} /* dns_res_fetch() */
static struct dns_packet *dns_res_fetch_and_study(struct dns_resolver *R, int *_error) {
struct dns_packet *P = NULL;
int error;
if (!(P = dns_res_fetch(R, &error)))
goto error;
if ((error = dns_p_study(P)))
goto error;
return P;
error:
*_error = error;
dns_p_free(P);
return NULL;
} /* dns_res_fetch_and_study() */
struct dns_packet *dns_res_query(struct dns_resolver *res, const char *qname, enum dns_type qtype, enum dns_class qclass, int timeout, int *error_) {
int error;
if ((error = dns_res_submit(res, qname, qtype, qclass)))
goto error;
while ((error = dns_res_check(res))) {
if (dns_res_elapsed(res) > timeout)
error = DNS_ETIMEDOUT;
if (error != DNS_EAGAIN)
goto error;
if ((error = dns_res_poll(res, 1)))
goto error;
}
return dns_res_fetch(res, error_);
error:
*error_ = error;
return 0;
} /* dns_res_query() */
const struct dns_stat *dns_res_stat(struct dns_resolver *res) {
return dns_so_stat(&res->so);
} /* dns_res_stat() */
void dns_res_sethints(struct dns_resolver *res, struct dns_hints *hints) {
dns_hints_acquire(hints); /* acquire first in case same hints object */
dns_hints_close(res->hints);
res->hints = hints;
} /* dns_res_sethints() */
struct dns_trace *dns_res_trace(struct dns_resolver *res) {
return res->trace;
} /* dns_res_trace() */
void dns_res_settrace(struct dns_resolver *res, struct dns_trace *trace) {
struct dns_trace *otrace = res->trace;
res->trace = dns_trace_acquire_p(trace);
dns_trace_close(otrace);
dns_so_settrace(&res->so, trace);
} /* dns_res_settrace() */
/*
* A D D R I N F O R O U T I N E S
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
struct dns_addrinfo {
struct addrinfo hints;
struct dns_resolver *res;
struct dns_trace *trace;
char qname[DNS_D_MAXNAME + 1];
enum dns_type qtype;
unsigned short qport, port;
struct {
unsigned long todo;
int state;
int atype;
enum dns_type qtype;
} af;
struct dns_packet *answer;
struct dns_packet *glue;
struct dns_rr_i i, g;
struct dns_rr rr;
char cname[DNS_D_MAXNAME + 1];
char i_cname[DNS_D_MAXNAME + 1], g_cname[DNS_D_MAXNAME + 1];
int g_depth;
int state;
int found;
struct dns_stat st;
}; /* struct dns_addrinfo */
#define DNS_AI_AFMAX 32
#define DNS_AI_AF2INDEX(af) (1UL << ((af) - 1))
static inline unsigned long dns_ai_af2index(int af) {
dns_static_assert(dns_same_type(unsigned long, DNS_AI_AF2INDEX(1), 1), "internal type mismatch");
dns_static_assert(dns_same_type(unsigned long, ((struct dns_addrinfo *)0)->af.todo, 1), "internal type mismatch");
return (af > 0 && af <= DNS_AI_AFMAX)? DNS_AI_AF2INDEX(af) : 0;
}
static int dns_ai_setaf(struct dns_addrinfo *ai, int af, int qtype) {
ai->af.atype = af;
ai->af.qtype = qtype;
ai->af.todo &= ~dns_ai_af2index(af);
return af;
} /* dns_ai_setaf() */
#define DNS_SM_RESTORE \
do { pc = 0xff & (ai->af.state >> 0); i = 0xff & (ai->af.state >> 8); } while (0)
#define DNS_SM_SAVE \
do { ai->af.state = ((0xff & pc) << 0) | ((0xff & i) << 8); } while (0)
static int dns_ai_nextaf(struct dns_addrinfo *ai) {
int i, pc;
dns_static_assert(AF_UNSPEC == 0, "AF_UNSPEC constant not 0");
dns_static_assert(AF_INET <= DNS_AI_AFMAX, "AF_INET constant too large");
dns_static_assert(AF_INET6 <= DNS_AI_AFMAX, "AF_INET6 constant too large");
DNS_SM_ENTER;
if (ai->res) {
/*
* NB: On OpenBSD, at least, the types of entries resolved
* is the intersection of the /etc/resolv.conf families and
* the families permitted by the .ai_type hint. So if
* /etc/resolv.conf has "family inet4" and .ai_type
* is AF_INET6, then the address ::1 will return 0 entries
* even if AI_NUMERICHOST is specified in .ai_flags.
*/
while (i < (int)lengthof(ai->res->resconf->family)) {
int af = ai->res->resconf->family[i++];
if (af == AF_UNSPEC) {
DNS_SM_EXIT;
} else if (af < 0 || af > DNS_AI_AFMAX) {
continue;
} else if (!(DNS_AI_AF2INDEX(af) & ai->af.todo)) {
continue;
} else if (af == AF_INET) {
DNS_SM_YIELD(dns_ai_setaf(ai, AF_INET, DNS_T_A));
} else if (af == AF_INET6) {
DNS_SM_YIELD(dns_ai_setaf(ai, AF_INET6, DNS_T_AAAA));
}
}
} else {
/*
* NB: If we get here than AI_NUMERICFLAGS should be set and
* order shouldn't matter.
*/
if (DNS_AI_AF2INDEX(AF_INET) & ai->af.todo)
DNS_SM_YIELD(dns_ai_setaf(ai, AF_INET, DNS_T_A));
if (DNS_AI_AF2INDEX(AF_INET6) & ai->af.todo)
DNS_SM_YIELD(dns_ai_setaf(ai, AF_INET6, DNS_T_AAAA));
}
DNS_SM_LEAVE;
return dns_ai_setaf(ai, AF_UNSPEC, 0);
} /* dns_ai_nextaf() */
#undef DNS_SM_RESTORE
#undef DNS_SM_SAVE
static enum dns_type dns_ai_qtype(struct dns_addrinfo *ai) {
return (ai->qtype)? ai->qtype : ai->af.qtype;
} /* dns_ai_qtype() */
/* JW: This is not defined on mingw. */
#ifndef AI_NUMERICSERV
#define AI_NUMERICSERV 0
#endif
static dns_error_t dns_ai_parseport(unsigned short *port, const char *serv, const struct addrinfo *hints) {
const char *cp = serv;
unsigned long n = 0;
while (*cp >= '0' && *cp <= '9' && n < 65536) {
n *= 10;
n += *cp++ - '0';
}
if (*cp == '\0') {
if (cp == serv || n >= 65536)
return DNS_ESERVICE;
*port = n;
return 0;
}
if (hints->ai_flags & AI_NUMERICSERV)
return DNS_ESERVICE;
/* TODO: try getaddrinfo(NULL, serv, { .ai_flags = AI_NUMERICSERV }) */
return DNS_ESERVICE;
} /* dns_ai_parseport() */
struct dns_addrinfo *dns_ai_open(const char *host, const char *serv, enum dns_type qtype, const struct addrinfo *hints, struct dns_resolver *res, int *_error) {
static const struct dns_addrinfo ai_initializer;
struct dns_addrinfo *ai;
int error;
if (res) {
dns_res_acquire(res);
} else if (!(hints->ai_flags & AI_NUMERICHOST)) {
/*
* NOTE: it's assumed that *_error is set from a previous
* API function call, such as dns_res_stub(). Should change
* this semantic, but it's applied elsewhere, too.
*/
if (!*_error)
*_error = EINVAL;
return NULL;
}
if (!(ai = malloc(sizeof *ai)))
goto syerr;
*ai = ai_initializer;
ai->hints = *hints;
ai->res = res;
res = NULL;
if (sizeof ai->qname <= dns_strlcpy(ai->qname, host, sizeof ai->qname))
{ error = ENAMETOOLONG; goto error; }
ai->qtype = qtype;
ai->qport = 0;
if (serv && (error = dns_ai_parseport(&ai->qport, serv, hints)))
goto error;
ai->port = ai->qport;
/*
* FIXME: If an explicit A or AAAA record type conflicts with
* .ai_family or with resconf.family (i.e. AAAA specified but
* AF_INET6 not in interection of .ai_family and resconf.family),
* then what?
*/
switch (ai->qtype) {
case DNS_T_A:
ai->af.todo = DNS_AI_AF2INDEX(AF_INET);
break;
case DNS_T_AAAA:
ai->af.todo = DNS_AI_AF2INDEX(AF_INET6);
break;
default: /* 0, MX, SRV, etc */
switch (ai->hints.ai_family) {
case AF_UNSPEC:
ai->af.todo = DNS_AI_AF2INDEX(AF_INET) | DNS_AI_AF2INDEX(AF_INET6);
break;
case AF_INET:
ai->af.todo = DNS_AI_AF2INDEX(AF_INET);
break;
case AF_INET6:
ai->af.todo = DNS_AI_AF2INDEX(AF_INET6);
break;
default:
break;
}
}
return ai;
syerr:
error = dns_syerr();
error:
*_error = error;
dns_ai_close(ai);
dns_res_close(res);
return NULL;
} /* dns_ai_open() */
void dns_ai_close(struct dns_addrinfo *ai) {
if (!ai)
return;
dns_res_close(ai->res);
dns_trace_close(ai->trace);
if (ai->answer != ai->glue)
dns_p_free(ai->glue);
dns_p_free(ai->answer);
free(ai);
} /* dns_ai_close() */
static int dns_ai_setent(struct addrinfo **ent, union dns_any *any, enum dns_type type, struct dns_addrinfo *ai) {
union u {
struct sockaddr_in sin;
struct sockaddr_in6 sin6;
struct sockaddr_storage ss;
} addr;
const char *cname;
size_t clen;
switch (type) {
case DNS_T_A:
memset(&addr.sin, '\0', sizeof addr.sin);
addr.sin.sin_family = AF_INET;
addr.sin.sin_port = htons(ai->port);
memcpy(&addr.sin.sin_addr, any, sizeof addr.sin.sin_addr);
break;
case DNS_T_AAAA:
memset(&addr.sin6, '\0', sizeof addr.sin6);
addr.sin6.sin6_family = AF_INET6;
addr.sin6.sin6_port = htons(ai->port);
memcpy(&addr.sin6.sin6_addr, any, sizeof addr.sin6.sin6_addr);
break;
default:
return EINVAL;
} /* switch() */
if (ai->hints.ai_flags & AI_CANONNAME) {
cname = (*ai->cname)? ai->cname : ai->qname;
clen = strlen(cname);
} else {
cname = NULL;
clen = 0;
}
if (!(*ent = malloc(sizeof **ent + dns_sa_len(&addr) + ((ai->hints.ai_flags & AI_CANONNAME)? clen + 1 : 0))))
return dns_syerr();
memset(*ent, '\0', sizeof **ent);
(*ent)->ai_family = addr.ss.ss_family;
(*ent)->ai_socktype = ai->hints.ai_socktype;
(*ent)->ai_protocol = ai->hints.ai_protocol;
(*ent)->ai_addr = memcpy((unsigned char *)*ent + sizeof **ent, &addr, dns_sa_len(&addr));
(*ent)->ai_addrlen = dns_sa_len(&addr);
if (ai->hints.ai_flags & AI_CANONNAME)
(*ent)->ai_canonname = memcpy((unsigned char *)*ent + sizeof **ent + dns_sa_len(&addr), cname, clen + 1);
ai->found++;
return 0;
} /* dns_ai_setent() */
enum dns_ai_state {
DNS_AI_S_INIT,
DNS_AI_S_NEXTAF,
DNS_AI_S_NUMERIC,
DNS_AI_S_SUBMIT,
DNS_AI_S_CHECK,
DNS_AI_S_FETCH,
DNS_AI_S_FOREACH_I,
DNS_AI_S_INIT_G,
DNS_AI_S_ITERATE_G,
DNS_AI_S_FOREACH_G,
DNS_AI_S_SUBMIT_G,
DNS_AI_S_CHECK_G,
DNS_AI_S_FETCH_G,
DNS_AI_S_DONE,
}; /* enum dns_ai_state */
#define dns_ai_goto(which) do { ai->state = (which); goto exec; } while (0)
int dns_ai_nextent(struct addrinfo **ent, struct dns_addrinfo *ai) {
struct dns_packet *ans, *glue;
struct dns_rr rr;
char qname[DNS_D_MAXNAME + 1];
union dns_any any;
size_t qlen, clen;
int error;
*ent = 0;
exec:
switch (ai->state) {
case DNS_AI_S_INIT:
ai->state++; /* FALL THROUGH */
case DNS_AI_S_NEXTAF:
if (!dns_ai_nextaf(ai))
dns_ai_goto(DNS_AI_S_DONE);
ai->state++; /* FALL THROUGH */
case DNS_AI_S_NUMERIC:
if (1 == dns_inet_pton(AF_INET, ai->qname, &any.a)) {
if (ai->af.atype == AF_INET) {
ai->state = DNS_AI_S_NEXTAF;
return dns_ai_setent(ent, &any, DNS_T_A, ai);
} else {
dns_ai_goto(DNS_AI_S_NEXTAF);
}
}
if (1 == dns_inet_pton(AF_INET6, ai->qname, &any.aaaa)) {
if (ai->af.atype == AF_INET6) {
ai->state = DNS_AI_S_NEXTAF;
return dns_ai_setent(ent, &any, DNS_T_AAAA, ai);
} else {
dns_ai_goto(DNS_AI_S_NEXTAF);
}
}
if (ai->hints.ai_flags & AI_NUMERICHOST)
dns_ai_goto(DNS_AI_S_NEXTAF);
ai->state++; /* FALL THROUGH */
case DNS_AI_S_SUBMIT:
assert(ai->res);
if ((error = dns_res_submit(ai->res, ai->qname, dns_ai_qtype(ai), DNS_C_IN)))
return error;
ai->state++; /* FALL THROUGH */
case DNS_AI_S_CHECK:
if ((error = dns_res_check(ai->res)))
return error;
ai->state++; /* FALL THROUGH */
case DNS_AI_S_FETCH:
if (!(ans = dns_res_fetch_and_study(ai->res, &error)))
return error;
if (ai->glue != ai->answer)
dns_p_free(ai->glue);
ai->glue = dns_p_movptr(&ai->answer, &ans);
/* Search generator may have changed the qname. */
if (!(qlen = dns_d_expand(qname, sizeof qname, 12, ai->answer, &error)))
return error;
else if (qlen >= sizeof qname)
return DNS_EILLEGAL;
if (!dns_d_cname(ai->cname, sizeof ai->cname, qname, qlen, ai->answer, &error))
return error;
dns_strlcpy(ai->i_cname, ai->cname, sizeof ai->i_cname);
dns_rr_i_init(&ai->i);
ai->i.section = DNS_S_AN;
ai->i.name = ai->i_cname;
ai->i.type = dns_ai_qtype(ai);
ai->i.sort = &dns_rr_i_order;
ai->state++; /* FALL THROUGH */
case DNS_AI_S_FOREACH_I:
if (!dns_rr_grep(&rr, 1, &ai->i, ai->answer, &error))
dns_ai_goto(DNS_AI_S_NEXTAF);
if ((error = dns_any_parse(&any, &rr, ai->answer)))
return error;
ai->port = ai->qport;
switch (rr.type) {
case DNS_T_A:
case DNS_T_AAAA:
return dns_ai_setent(ent, &any, rr.type, ai);
default:
if (!(clen = dns_any_cname(ai->cname, sizeof ai->cname, &any, rr.type)))
dns_ai_goto(DNS_AI_S_FOREACH_I);
/*
* Find the "real" canonical name. Some authorities
* publish aliases where an RFC defines a canonical
* name. We trust that the resolver followed any
* CNAME chains on it's own, regardless of whether
* the "smart" option is enabled.
*/
if (!dns_d_cname(ai->cname, sizeof ai->cname, ai->cname, clen, ai->answer, &error))
return error;
if (rr.type == DNS_T_SRV)
ai->port = any.srv.port;
break;
} /* switch() */
ai->state++; /* FALL THROUGH */
case DNS_AI_S_INIT_G:
ai->g_depth = 0;
ai->state++; /* FALL THROUGH */
case DNS_AI_S_ITERATE_G:
dns_strlcpy(ai->g_cname, ai->cname, sizeof ai->g_cname);
dns_rr_i_init(&ai->g);
ai->g.section = DNS_S_ALL & ~DNS_S_QD;
ai->g.name = ai->g_cname;
ai->g.type = ai->af.qtype;
ai->state++; /* FALL THROUGH */
case DNS_AI_S_FOREACH_G:
if (!dns_rr_grep(&rr, 1, &ai->g, ai->glue, &error)) {
if (dns_rr_i_count(&ai->g) > 0)
dns_ai_goto(DNS_AI_S_FOREACH_I);
else
dns_ai_goto(DNS_AI_S_SUBMIT_G);
}
if ((error = dns_any_parse(&any, &rr, ai->glue)))
return error;
return dns_ai_setent(ent, &any, rr.type, ai);
case DNS_AI_S_SUBMIT_G:
{
- struct dns_rr_i _I = { 0 };
+ struct dns_rr_i I_instance = { 0 };
- _I.section = DNS_S_QD;
- _I.name = ai->g.name;
- _I.type = ai->g.type;
+ I_instance.section = DNS_S_QD;
+ I_instance.name = ai->g.name;
+ I_instance.type = ai->g.type;
/* skip if already queried */
- if (dns_rr_grep(&rr, 1, &_I, ai->glue, &error))
+ if (dns_rr_grep(&rr, 1, &I_instance, ai->glue, &error))
dns_ai_goto(DNS_AI_S_FOREACH_I);
/* skip if we recursed (CNAME chains should have been handled in the resolver) */
if (++ai->g_depth > 1)
dns_ai_goto(DNS_AI_S_FOREACH_I);
if ((error = dns_res_submit(ai->res, ai->g.name, ai->g.type, DNS_C_IN)))
return error;
ai->state++;
} /* FALL THROUGH */
case DNS_AI_S_CHECK_G:
if ((error = dns_res_check(ai->res)))
return error;
ai->state++; /* FALL THROUGH */
case DNS_AI_S_FETCH_G:
if (!(ans = dns_res_fetch_and_study(ai->res, &error)))
return error;
glue = dns_p_merge(ai->glue, DNS_S_ALL, ans, DNS_S_ALL, &error);
dns_p_setptr(&ans, NULL);
if (!glue)
return error;
if (ai->glue != ai->answer)
dns_p_free(ai->glue);
ai->glue = glue;
if (!dns_d_cname(ai->cname, sizeof ai->cname, ai->g.name, strlen(ai->g.name), ai->glue, &error))
dns_ai_goto(DNS_AI_S_FOREACH_I);
dns_ai_goto(DNS_AI_S_ITERATE_G);
case DNS_AI_S_DONE:
if (ai->found) {
return ENOENT; /* TODO: Just return 0 */
} else if (ai->answer) {
switch (dns_p_rcode(ai->answer)) {
case DNS_RC_NOERROR:
/* FALL THROUGH */
case DNS_RC_NXDOMAIN:
return DNS_ENONAME;
default:
return DNS_EFAIL;
}
} else {
return DNS_EFAIL;
}
default:
return EINVAL;
} /* switch() */
} /* dns_ai_nextent() */
time_t dns_ai_elapsed(struct dns_addrinfo *ai) {
return (ai->res)? dns_res_elapsed(ai->res) : 0;
} /* dns_ai_elapsed() */
void dns_ai_clear(struct dns_addrinfo *ai) {
if (ai->res)
dns_res_clear(ai->res);
} /* dns_ai_clear() */
int dns_ai_events(struct dns_addrinfo *ai) {
return (ai->res)? dns_res_events(ai->res) : 0;
} /* dns_ai_events() */
int dns_ai_pollfd(struct dns_addrinfo *ai) {
return (ai->res)? dns_res_pollfd(ai->res) : -1;
} /* dns_ai_pollfd() */
time_t dns_ai_timeout(struct dns_addrinfo *ai) {
return (ai->res)? dns_res_timeout(ai->res) : 0;
} /* dns_ai_timeout() */
int dns_ai_poll(struct dns_addrinfo *ai, int timeout) {
return (ai->res)? dns_res_poll(ai->res, timeout) : 0;
} /* dns_ai_poll() */
size_t dns_ai_print(void *_dst, size_t lim, struct addrinfo *ent, struct dns_addrinfo *ai) {
struct dns_buf dst = DNS_B_INTO(_dst, lim);
char addr[DNS_PP_MAX(INET_ADDRSTRLEN, INET6_ADDRSTRLEN) + 1];
dns_b_puts(&dst, "[ ");
dns_b_puts(&dst, ai->qname);
dns_b_puts(&dst, " IN ");
if (ai->qtype) {
dns_b_puts(&dst, dns_strtype(ai->qtype));
} else if (ent->ai_family == AF_INET) {
dns_b_puts(&dst, dns_strtype(DNS_T_A));
} else if (ent->ai_family == AF_INET6) {
dns_b_puts(&dst, dns_strtype(DNS_T_AAAA));
} else {
dns_b_puts(&dst, "0");
}
dns_b_puts(&dst, " ]\n");
dns_b_puts(&dst, ".ai_family = ");
switch (ent->ai_family) {
case AF_INET:
dns_b_puts(&dst, "AF_INET");
break;
case AF_INET6:
dns_b_puts(&dst, "AF_INET6");
break;
default:
dns_b_fmtju(&dst, ent->ai_family, 0);
break;
}
dns_b_putc(&dst, '\n');
dns_b_puts(&dst, ".ai_socktype = ");
switch (ent->ai_socktype) {
case SOCK_STREAM:
dns_b_puts(&dst, "SOCK_STREAM");
break;
case SOCK_DGRAM:
dns_b_puts(&dst, "SOCK_DGRAM");
break;
default:
dns_b_fmtju(&dst, ent->ai_socktype, 0);
break;
}
dns_b_putc(&dst, '\n');
dns_inet_ntop(dns_sa_family(ent->ai_addr), dns_sa_addr(dns_sa_family(ent->ai_addr), ent->ai_addr, NULL), addr, sizeof addr);
dns_b_puts(&dst, ".ai_addr = [");
dns_b_puts(&dst, addr);
dns_b_puts(&dst, "]:");
dns_b_fmtju(&dst, ntohs(*dns_sa_port(dns_sa_family(ent->ai_addr), ent->ai_addr)), 0);
dns_b_putc(&dst, '\n');
dns_b_puts(&dst, ".ai_canonname = ");
dns_b_puts(&dst, (ent->ai_canonname)? ent->ai_canonname : "[NULL]");
dns_b_putc(&dst, '\n');
return dns_b_strllen(&dst);
} /* dns_ai_print() */
const struct dns_stat *dns_ai_stat(struct dns_addrinfo *ai) {
return (ai->res)? dns_res_stat(ai->res) : &ai->st;
} /* dns_ai_stat() */
struct dns_trace *dns_ai_trace(struct dns_addrinfo *ai) {
return ai->trace;
} /* dns_ai_trace() */
void dns_ai_settrace(struct dns_addrinfo *ai, struct dns_trace *trace) {
struct dns_trace *otrace = ai->trace;
ai->trace = dns_trace_acquire_p(trace);
dns_trace_close(otrace);
if (ai->res)
dns_res_settrace(ai->res, trace);
} /* dns_ai_settrace() */
/*
* M I S C E L L A N E O U S R O U T I N E S
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
static const struct {
char name[16];
enum dns_section type;
} dns_sections[] = {
{ "QUESTION", DNS_S_QUESTION },
{ "QD", DNS_S_QUESTION },
{ "ANSWER", DNS_S_ANSWER },
{ "AN", DNS_S_ANSWER },
{ "AUTHORITY", DNS_S_AUTHORITY },
{ "NS", DNS_S_AUTHORITY },
{ "ADDITIONAL", DNS_S_ADDITIONAL },
{ "AR", DNS_S_ADDITIONAL },
};
const char *(dns_strsection)(enum dns_section section) {
char _dst[DNS_STRMAXLEN + 1] = { 0 };
struct dns_buf dst = DNS_B_INTO(_dst, sizeof _dst);
unsigned i;
for (i = 0; i < lengthof(dns_sections); i++) {
if (dns_sections[i].type & section) {
dns_b_puts(&dst, dns_sections[i].name);
section &= ~dns_sections[i].type;
if (section)
dns_b_putc(&dst, '|');
}
}
if (section || dst.p == dst.base)
dns_b_fmtju(&dst, (0xffff & section), 0);
return dns_b_tostring(&dst);
} /* dns_strsection() */
enum dns_section dns_isection(const char *src) {
enum dns_section section = 0;
char sbuf[128];
char *name, *next;
unsigned i;
dns_strlcpy(sbuf, src, sizeof sbuf);
next = sbuf;
while ((name = dns_strsep(&next, "|+, \t"))) {
for (i = 0; i < lengthof(dns_sections); i++) {
if (!strcasecmp(dns_sections[i].name, name)) {
section |= dns_sections[i].type;
break;
}
}
}
return section;
} /* dns_isection() */
static const struct {
char name[8];
enum dns_class type;
} dns_classes[] = {
{ "IN", DNS_C_IN },
};
const char *(dns_strclass)(enum dns_class type) {
char _dst[DNS_STRMAXLEN + 1] = { 0 };
struct dns_buf dst = DNS_B_INTO(_dst, sizeof _dst);
unsigned i;
for (i = 0; i < lengthof(dns_classes); i++) {
if (dns_classes[i].type == type) {
dns_b_puts(&dst, dns_classes[i].name);
break;
}
}
if (dst.p == dst.base)
dns_b_fmtju(&dst, (0xffff & type), 0);
return dns_b_tostring(&dst);
} /* dns_strclass() */
enum dns_class dns_iclass(const char *name) {
unsigned i, class;
for (i = 0; i < lengthof(dns_classes); i++) {
if (!strcasecmp(dns_classes[i].name, name))
return dns_classes[i].type;
}
class = 0;
while (dns_isdigit(*name)) {
class *= 10;
class += *name++ - '0';
}
return DNS_PP_MIN(class, 0xffff);
} /* dns_iclass() */
const char *(dns_strtype)(enum dns_type type) {
char _dst[DNS_STRMAXLEN + 1] = { 0 };
struct dns_buf dst = DNS_B_INTO(_dst, sizeof _dst);
unsigned i;
for (i = 0; i < lengthof(dns_rrtypes); i++) {
if (dns_rrtypes[i].type == type) {
dns_b_puts(&dst, dns_rrtypes[i].name);
break;
}
}
if (dst.p == dst.base)
dns_b_fmtju(&dst, (0xffff & type), 0);
return dns_b_tostring(&dst);
} /* dns_strtype() */
enum dns_type dns_itype(const char *name) {
unsigned i, type;
for (i = 0; i < lengthof(dns_rrtypes); i++) {
if (!strcasecmp(dns_rrtypes[i].name, name))
return dns_rrtypes[i].type;
}
type = 0;
while (dns_isdigit(*name)) {
type *= 10;
type += *name++ - '0';
}
return DNS_PP_MIN(type, 0xffff);
} /* dns_itype() */
static char dns_opcodes[16][16] = {
[DNS_OP_QUERY] = "QUERY",
[DNS_OP_IQUERY] = "IQUERY",
[DNS_OP_STATUS] = "STATUS",
[DNS_OP_NOTIFY] = "NOTIFY",
[DNS_OP_UPDATE] = "UPDATE",
};
static const char *dns__strcode(int code, volatile char *dst, size_t lim) {
char _tmp[48] = "";
struct dns_buf tmp;
size_t p;
assert(lim > 0);
dns_b_fmtju(dns_b_into(&tmp, _tmp, DNS_PP_MIN(sizeof _tmp, lim - 1)), code, 0);
/* copy downwards so first byte is copied last (see below) */
p = (size_t)(tmp.p - tmp.base);
dst[p] = '\0';
while (p--)
dst[p] = _tmp[p];
return (const char *)dst;
}
const char *dns_stropcode(enum dns_opcode opcode) {
opcode = (unsigned)opcode % lengthof(dns_opcodes);
if ('\0' == dns_opcodes[opcode][0])
return dns__strcode(opcode, dns_opcodes[opcode], sizeof dns_opcodes[opcode]);
return dns_opcodes[opcode];
} /* dns_stropcode() */
enum dns_opcode dns_iopcode(const char *name) {
unsigned opcode;
for (opcode = 0; opcode < lengthof(dns_opcodes); opcode++) {
if (!strcasecmp(name, dns_opcodes[opcode]))
return opcode;
}
opcode = 0;
while (dns_isdigit(*name)) {
opcode *= 10;
opcode += *name++ - '0';
}
return DNS_PP_MIN(opcode, 0x0f);
} /* dns_iopcode() */
static char dns_rcodes[32][16] = {
[DNS_RC_NOERROR] = "NOERROR",
[DNS_RC_FORMERR] = "FORMERR",
[DNS_RC_SERVFAIL] = "SERVFAIL",
[DNS_RC_NXDOMAIN] = "NXDOMAIN",
[DNS_RC_NOTIMP] = "NOTIMP",
[DNS_RC_REFUSED] = "REFUSED",
[DNS_RC_YXDOMAIN] = "YXDOMAIN",
[DNS_RC_YXRRSET] = "YXRRSET",
[DNS_RC_NXRRSET] = "NXRRSET",
[DNS_RC_NOTAUTH] = "NOTAUTH",
[DNS_RC_NOTZONE] = "NOTZONE",
/* EDNS(0) extended RCODEs ... */
[DNS_RC_BADVERS] = "BADVERS",
};
const char *dns_strrcode(enum dns_rcode rcode) {
rcode = (unsigned)rcode % lengthof(dns_rcodes);
if ('\0' == dns_rcodes[rcode][0])
return dns__strcode(rcode, dns_rcodes[rcode], sizeof dns_rcodes[rcode]);
return dns_rcodes[rcode];
} /* dns_strrcode() */
enum dns_rcode dns_ircode(const char *name) {
unsigned rcode;
for (rcode = 0; rcode < lengthof(dns_rcodes); rcode++) {
if (!strcasecmp(name, dns_rcodes[rcode]))
return rcode;
}
rcode = 0;
while (dns_isdigit(*name)) {
rcode *= 10;
rcode += *name++ - '0';
}
return DNS_PP_MIN(rcode, 0xfff);
} /* dns_ircode() */
/*
* C O M M A N D - L I N E / R E G R E S S I O N R O U T I N E S
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#if DNS_MAIN
#include <stdarg.h>
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#if _WIN32
#include <getopt.h>
#endif
#if !_WIN32
#include <err.h>
#endif
struct {
struct {
const char *path[8];
unsigned count;
} resconf, nssconf, hosts, cache;
const char *qname;
enum dns_type qtype;
int (*sort)();
const char *trace;
int verbose;
struct {
struct dns_resolv_conf *resconf;
struct dns_hosts *hosts;
struct dns_trace *trace;
} memo;
struct sockaddr_storage socks_host;
const char *socks_user;
const char *socks_password;
} MAIN = {
.sort = &dns_rr_i_packet,
};
static void hexdump(const unsigned char *src, size_t len, FILE *fp) {
struct dns_hxd_lines_i lines = { 0 };
char line[128];
while (dns_hxd_lines(line, sizeof line, src, len, &lines)) {
fputs(line, fp);
}
} /* hexdump() */
DNS_NORETURN static void panic(const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
#if _WIN32
vfprintf(stderr, fmt, ap);
exit(EXIT_FAILURE);
#else
verrx(EXIT_FAILURE, fmt, ap);
#endif
} /* panic() */
#define panic_(fn, ln, fmt, ...) \
panic(fmt "%0s", (fn), (ln), __VA_ARGS__)
#define panic(...) \
panic_(__func__, __LINE__, "(%s:%d) " __VA_ARGS__, "")
static void *grow(unsigned char *p, size_t size) {
void *tmp;
if (!(tmp = realloc(p, size)))
panic("realloc(%"PRIuZ"): %s", size, dns_strerror(errno));
return tmp;
} /* grow() */
static size_t add(size_t a, size_t b) {
if (~a < b)
panic("%"PRIuZ" + %"PRIuZ": integer overflow", a, b);
return a + b;
} /* add() */
static size_t append(unsigned char **dst, size_t osize, const void *src, size_t len) {
size_t size = add(osize, len);
*dst = grow(*dst, size);
memcpy(*dst + osize, src, len);
return size;
} /* append() */
static size_t slurp(unsigned char **dst, size_t osize, FILE *fp, const char *path) {
size_t size = osize;
unsigned char buf[1024];
size_t count;
while ((count = fread(buf, 1, sizeof buf, fp)))
size = append(dst, size, buf, count);
if (ferror(fp))
panic("%s: %s", path, dns_strerror(errno));
return size;
} /* slurp() */
static struct dns_resolv_conf *resconf(void) {
struct dns_resolv_conf **resconf = &MAIN.memo.resconf;
const char *path;
unsigned i;
int error;
if (*resconf)
return *resconf;
if (!(*resconf = dns_resconf_open(&error)))
panic("dns_resconf_open: %s", dns_strerror(error));
if (!MAIN.resconf.count)
MAIN.resconf.path[MAIN.resconf.count++] = "/etc/resolv.conf";
for (i = 0; i < MAIN.resconf.count; i++) {
path = MAIN.resconf.path[i];
if (0 == strcmp(path, "-"))
error = dns_resconf_loadfile(*resconf, stdin);
else
error = dns_resconf_loadpath(*resconf, path);
if (error)
panic("%s: %s", path, dns_strerror(error));
}
for (i = 0; i < MAIN.nssconf.count; i++) {
path = MAIN.nssconf.path[i];
if (0 == strcmp(path, "-"))
error = dns_nssconf_loadfile(*resconf, stdin);
else
error = dns_nssconf_loadpath(*resconf, path);
if (error)
panic("%s: %s", path, dns_strerror(error));
}
if (!MAIN.nssconf.count) {
path = "/etc/nsswitch.conf";
if (!(error = dns_nssconf_loadpath(*resconf, path)))
MAIN.nssconf.path[MAIN.nssconf.count++] = path;
else if (error != ENOENT)
panic("%s: %s", path, dns_strerror(error));
}
return *resconf;
} /* resconf() */
static struct dns_hosts *hosts(void) {
struct dns_hosts **hosts = &MAIN.memo.hosts;
const char *path;
unsigned i;
int error;
if (*hosts)
return *hosts;
if (!MAIN.hosts.count) {
MAIN.hosts.path[MAIN.hosts.count++] = "/etc/hosts";
/* Explicitly test dns_hosts_local() */
if (!(*hosts = dns_hosts_local(&error)))
panic("%s: %s", "/etc/hosts", dns_strerror(error));
return *hosts;
}
if (!(*hosts = dns_hosts_open(&error)))
panic("dns_hosts_open: %s", dns_strerror(error));
for (i = 0; i < MAIN.hosts.count; i++) {
path = MAIN.hosts.path[i];
if (0 == strcmp(path, "-"))
error = dns_hosts_loadfile(*hosts, stdin);
else
error = dns_hosts_loadpath(*hosts, path);
if (error)
panic("%s: %s", path, dns_strerror(error));
}
return *hosts;
} /* hosts() */
#if DNS_CACHE
#include "cache.h"
static struct dns_cache *cache(void) {
static struct cache *cache;
const char *path;
unsigned i;
int error;
if (cache)
return cache_resi(cache);
if (!MAIN.cache.count)
return NULL;
if (!(cache = cache_open(&error)))
panic("%s: %s", MAIN.cache.path[0], dns_strerror(error));
for (i = 0; i < MAIN.cache.count; i++) {
path = MAIN.cache.path[i];
if (!strcmp(path, "-")) {
if ((error = cache_loadfile(cache, stdin, NULL, 0)))
panic("%s: %s", path, dns_strerror(error));
} else if ((error = cache_loadpath(cache, path, NULL, 0)))
panic("%s: %s", path, dns_strerror(error));
}
return cache_resi(cache);
} /* cache() */
#else
static struct dns_cache *cache(void) { return NULL; }
#endif
static struct dns_trace *trace(const char *mode) {
static char omode[64] = "";
struct dns_trace **trace = &MAIN.memo.trace;
FILE *fp;
int error;
if (*trace && 0 == strcmp(omode, mode))
return *trace;
if (!MAIN.trace)
return NULL;
if (!(fp = fopen(MAIN.trace, mode)))
panic("%s: %s", MAIN.trace, strerror(errno));
dns_trace_close(*trace);
if (!(*trace = dns_trace_open(fp, &error)))
panic("%s: %s", MAIN.trace, dns_strerror(error));
dns_strlcpy(omode, mode, sizeof omode);
return *trace;
}
static void print_packet(struct dns_packet *P, FILE *fp) {
- struct dns_rr_i _I = { 0 };
+ struct dns_rr_i I_instance = { 0 };
I.sort = MAIN.sort;
dns_p_dump3(P, &I, fp);
if (MAIN.verbose > 2)
hexdump(P->data, P->end, fp);
} /* print_packet() */
static int parse_packet(int argc DNS_NOTUSED, char *argv[] DNS_NOTUSED) {
- union { unsigned char b[dns_p_calcsize((512))]; struct dns_packet p; } _P = { 0 };
- union { unsigned char b[dns_p_calcsize((512))]; struct dns_packet p; } _Q = { 0 };
- struct dns_packet *P = dns_p_init(&_P.p, 512);
- struct dns_packet *Q = dns_p_init(&_Q.p, 512);
+ union { unsigned char b[dns_p_calcsize((512))]; struct dns_packet p; } P_instance = { 0 };
+ union { unsigned char b[dns_p_calcsize((512))]; struct dns_packet p; } Q_instance = { 0 };
+ struct dns_packet *P = dns_p_init(&P_instance.p, 512);
+ struct dns_packet *Q = dns_p_init(&Q_instance.p, 512);
enum dns_section section;
struct dns_rr rr;
int error;
union dns_any any;
char pretty[sizeof any * 2];
size_t len;
P->end = fread(P->data, 1, P->size, stdin);
fputs(";; [HEADER]\n", stdout);
fprintf(stdout, ";; qr : %s(%d)\n", (dns_header(P)->qr)? "RESPONSE" : "QUERY", dns_header(P)->qr);
fprintf(stdout, ";; opcode : %s(%d)\n", dns_stropcode(dns_header(P)->opcode), dns_header(P)->opcode);
fprintf(stdout, ";; aa : %s(%d)\n", (dns_header(P)->aa)? "AUTHORITATIVE" : "NON-AUTHORITATIVE", dns_header(P)->aa);
fprintf(stdout, ";; tc : %s(%d)\n", (dns_header(P)->tc)? "TRUNCATED" : "NOT-TRUNCATED", dns_header(P)->tc);
fprintf(stdout, ";; rd : %s(%d)\n", (dns_header(P)->rd)? "RECURSION-DESIRED" : "RECURSION-NOT-DESIRED", dns_header(P)->rd);
fprintf(stdout, ";; ra : %s(%d)\n", (dns_header(P)->ra)? "RECURSION-ALLOWED" : "RECURSION-NOT-ALLOWED", dns_header(P)->ra);
fprintf(stdout, ";; rcode : %s(%d)\n", dns_strrcode(dns_p_rcode(P)), dns_p_rcode(P));
section = 0;
dns_rr_foreach(&rr, P, .sort = MAIN.sort) {
if (section != rr.section)
fprintf(stdout, "\n;; [%s:%d]\n", dns_strsection(rr.section), dns_p_count(P, rr.section));
if ((len = dns_rr_print(pretty, sizeof pretty, &rr, P, &error)))
fprintf(stdout, "%s\n", pretty);
dns_rr_copy(Q, &rr, P);
section = rr.section;
}
fputs("; ; ; ; ; ; ; ;\n\n", stdout);
section = 0;
#if 0
dns_rr_foreach(&rr, Q, .name = "ns8.yahoo.com.") {
#else
char _p[DNS_D_MAXNAME + 1] = { 0 };
const char *dn = "ns8.yahoo.com";
char *_name = dns_d_init(_p, sizeof _p, dn, strlen (dn), DNS_D_ANCHOR);
struct dns_rr rrset[32];
- struct dns_rr_i _I = { 0 };
+ struct dns_rr_i I_instance = { 0 };
struct dns_rr_i *rri = &I;
unsigned rrcount = dns_rr_grep(rrset, lengthof(rrset), rri, Q, &error);
I.name = _name;
I.sort = MAIN.sort;
for (unsigned i = 0; i < rrcount; i++) {
rr = rrset[i];
#endif
if (section != rr.section)
fprintf(stdout, "\n;; [%s:%d]\n", dns_strsection(rr.section), dns_p_count(Q, rr.section));
if ((len = dns_rr_print(pretty, sizeof pretty, &rr, Q, &error)))
fprintf(stdout, "%s\n", pretty);
section = rr.section;
}
if (MAIN.verbose > 1) {
fprintf(stderr, "orig:%"PRIuZ"\n", P->end);
hexdump(P->data, P->end, stdout);
fprintf(stderr, "copy:%"PRIuZ"\n", Q->end);
hexdump(Q->data, Q->end, stdout);
}
return 0;
} /* parse_packet() */
static int parse_domain(int argc, char *argv[]) {
char _p[DNS_D_MAXNAME + 1] = { 0 };
char *dn;
dn = (argc > 1)? argv[1] : "f.l.google.com";
printf("[%s]\n", dn);
dn = dns_d_init(_p, sizeof _p, dn, strlen (dn), DNS_D_ANCHOR);
do {
puts(dn);
} while (dns_d_cleave(dn, strlen(dn) + 1, dn, strlen(dn)));
return 0;
} /* parse_domain() */
static int trim_domain(int argc, char **argv) {
for (argc--, argv++; argc > 0; argc--, argv++) {
char name[DNS_D_MAXNAME + 1];
dns_d_trim(name, sizeof name, *argv, strlen(*argv), DNS_D_ANCHOR);
puts(name);
}
return 0;
} /* trim_domain() */
static int expand_domain(int argc, char *argv[]) {
unsigned short rp = 0;
unsigned char *src = NULL;
unsigned char *dst;
struct dns_packet *pkt;
size_t lim = 0, len;
int error;
if (argc > 1)
rp = atoi(argv[1]);
len = slurp(&src, 0, stdin, "-");
if (!(pkt = dns_p_make(len, &error)))
panic("malloc(%"PRIuZ"): %s", len, dns_strerror(error));
memcpy(pkt->data, src, len);
pkt->end = len;
lim = 1;
dst = grow(NULL, lim);
while (lim <= (len = dns_d_expand(dst, lim, rp, pkt, &error))) {
lim = add(len, 1);
dst = grow(dst, lim);
}
if (!len)
panic("expand: %s", dns_strerror(error));
fwrite(dst, 1, len, stdout);
fflush(stdout);
free(src);
free(dst);
free(pkt);
return 0;
} /* expand_domain() */
static int show_resconf(int argc DNS_NOTUSED, char *argv[] DNS_NOTUSED) {
unsigned i;
resconf(); /* load it */
fputs("; SOURCES\n", stdout);
for (i = 0; i < MAIN.resconf.count; i++)
fprintf(stdout, "; %s\n", MAIN.resconf.path[i]);
for (i = 0; i < MAIN.nssconf.count; i++)
fprintf(stdout, "; %s\n", MAIN.nssconf.path[i]);
fputs(";\n", stdout);
dns_resconf_dump(resconf(), stdout);
return 0;
} /* show_resconf() */
static int show_nssconf(int argc DNS_NOTUSED, char *argv[] DNS_NOTUSED) {
unsigned i;
resconf();
fputs("# SOURCES\n", stdout);
for (i = 0; i < MAIN.resconf.count; i++)
fprintf(stdout, "# %s\n", MAIN.resconf.path[i]);
for (i = 0; i < MAIN.nssconf.count; i++)
fprintf(stdout, "# %s\n", MAIN.nssconf.path[i]);
fputs("#\n", stdout);
dns_nssconf_dump(resconf(), stdout);
return 0;
} /* show_nssconf() */
static int show_hosts(int argc DNS_NOTUSED, char *argv[] DNS_NOTUSED) {
unsigned i;
hosts();
fputs("# SOURCES\n", stdout);
for (i = 0; i < MAIN.hosts.count; i++)
fprintf(stdout, "# %s\n", MAIN.hosts.path[i]);
fputs("#\n", stdout);
dns_hosts_dump(hosts(), stdout);
return 0;
} /* show_hosts() */
static int query_hosts(int argc, char *argv[]) {
- union { unsigned char b[dns_p_calcsize((512))]; struct dns_packet p; } _Q = { 0 };
- struct dns_packet *Q = dns_p_init(&_Q.p, 512);
+ union { unsigned char b[dns_p_calcsize((512))]; struct dns_packet p; } Q_instance = { 0 };
+ struct dns_packet *Q = dns_p_init(&Q_instance.p, 512);
struct dns_packet *A;
char qname[DNS_D_MAXNAME + 1];
size_t qlen;
int error;
if (!MAIN.qname)
MAIN.qname = (argc > 1)? argv[1] : "localhost";
if (!MAIN.qtype)
MAIN.qtype = DNS_T_A;
hosts();
if (MAIN.qtype == DNS_T_PTR && !strstr(MAIN.qname, "arpa")) {
union { struct in_addr a; struct in6_addr a6; } addr;
int af = (strchr(MAIN.qname, ':'))? AF_INET6 : AF_INET;
if ((error = dns_pton(af, MAIN.qname, &addr)))
panic("%s: %s", MAIN.qname, dns_strerror(error));
qlen = dns_ptr_qname(qname, sizeof qname, af, &addr);
} else
qlen = dns_strlcpy(qname, MAIN.qname, sizeof qname);
if ((error = dns_p_push(Q, DNS_S_QD, qname, qlen, MAIN.qtype, DNS_C_IN, 0, 0)))
panic("%s: %s", qname, dns_strerror(error));
if (!(A = dns_hosts_query(hosts(), Q, &error)))
panic("%s: %s", qname, dns_strerror(error));
print_packet(A, stdout);
free(A);
return 0;
} /* query_hosts() */
static int search_list(int argc, char *argv[]) {
const char *qname = (argc > 1)? argv[1] : "f.l.google.com";
unsigned long i = 0;
char name[DNS_D_MAXNAME + 1];
printf("[%s]\n", qname);
while (dns_resconf_search(name, sizeof name, qname, strlen(qname), resconf(), &i))
puts(name);
return 0;
} /* search_list() */
static int permute_set(int argc, char *argv[]) {
unsigned lo, hi, i;
struct dns_k_permutor p;
hi = (--argc > 0)? atoi(argv[argc]) : 8;
lo = (--argc > 0)? atoi(argv[argc]) : 0;
fprintf(stderr, "[%u .. %u]\n", lo, hi);
dns_k_permutor_init(&p, lo, hi);
for (i = lo; i <= hi; i++)
fprintf(stdout, "%u\n", dns_k_permutor_step(&p));
// printf("%u -> %u -> %u\n", i, dns_k_permutor_E(&p, i), dns_k_permutor_D(&p, dns_k_permutor_E(&p, i)));
return 0;
} /* permute_set() */
static int shuffle_16(int argc, char *argv[]) {
unsigned n, r;
if (--argc > 0) {
n = 0xffff & atoi(argv[argc]);
r = (--argc > 0)? (unsigned)atoi(argv[argc]) : dns_random();
fprintf(stdout, "%hu\n", dns_k_shuffle16(n, r));
} else {
r = dns_random();
for (n = 0; n < 65536; n++)
fprintf(stdout, "%hu\n", dns_k_shuffle16(n, r));
}
return 0;
} /* shuffle_16() */
static int dump_random(int argc, char *argv[]) {
unsigned char b[32];
unsigned i, j, n, r;
n = (argc > 1)? atoi(argv[1]) : 32;
while (n) {
i = 0;
do {
r = dns_random();
for (j = 0; j < sizeof r && i < n && i < sizeof b; i++, j++) {
b[i] = 0xff & r;
r >>= 8;
}
} while (i < n && i < sizeof b);
hexdump(b, i, stdout);
n -= i;
}
return 0;
} /* dump_random() */
static int send_query(int argc, char *argv[]) {
- union { unsigned char b[dns_p_calcsize((512))]; struct dns_packet p; } _Q = { 0 };
- struct dns_packet *A, *Q = dns_p_init(&_Q.p, 512);
+ union { unsigned char b[dns_p_calcsize((512))]; struct dns_packet p; } Q_instance = { 0 };
+ struct dns_packet *A, *Q = dns_p_init(&Q_instance.p, 512);
char host[INET6_ADDRSTRLEN + 1];
struct sockaddr_storage ss;
struct dns_socket *so;
int error, type;
struct dns_options opts = { 0 };
memset(&ss, 0, sizeof ss);
if (argc > 1) {
ss.ss_family = (strchr(argv[1], ':'))? AF_INET6 : AF_INET;
if ((error = dns_pton(ss.ss_family, argv[1], dns_sa_addr(ss.ss_family, &ss, NULL))))
panic("%s: %s", argv[1], dns_strerror(error));
*dns_sa_port(ss.ss_family, &ss) = htons(53);
} else
memcpy(&ss, &resconf()->nameserver[0], dns_sa_len(&resconf()->nameserver[0]));
if (!dns_inet_ntop(ss.ss_family, dns_sa_addr(ss.ss_family, &ss, NULL), host, sizeof host))
panic("bad host address, or none provided");
if (!MAIN.qname)
MAIN.qname = "ipv6.google.com";
if (!MAIN.qtype)
MAIN.qtype = DNS_T_AAAA;
if ((error = dns_p_push(Q, DNS_S_QD, MAIN.qname, strlen(MAIN.qname), MAIN.qtype, DNS_C_IN, 0, 0)))
panic("dns_p_push: %s", dns_strerror(error));
dns_header(Q)->rd = 1;
if (strstr(argv[0], "udp"))
type = SOCK_DGRAM;
else if (strstr(argv[0], "tcp"))
type = SOCK_STREAM;
else
type = dns_res_tcp2type(resconf()->options.tcp);
fprintf(stderr, "querying %s for %s IN %s\n", host, MAIN.qname, dns_strtype(MAIN.qtype));
if (!(so = dns_so_open((struct sockaddr *)&resconf()->iface, type, &opts, &error)))
panic("dns_so_open: %s", dns_strerror(error));
while (!(A = dns_so_query(so, Q, (struct sockaddr *)&ss, &error))) {
if (error != DNS_EAGAIN)
panic("dns_so_query: %s (%d)", dns_strerror(error), error);
if (dns_so_elapsed(so) > 10)
panic("query timed-out");
dns_so_poll(so, 1);
}
print_packet(A, stdout);
dns_so_close(so);
return 0;
} /* send_query() */
static int print_arpa(int argc, char *argv[]) {
const char *ip = (argc > 1)? argv[1] : "::1";
int af = (strchr(ip, ':'))? AF_INET6 : AF_INET;
union { struct in_addr a4; struct in6_addr a6; } addr;
char host[DNS_D_MAXNAME + 1];
if (1 != dns_inet_pton(af, ip, &addr) || 0 == dns_ptr_qname(host, sizeof host, af, &addr))
panic("%s: invalid address", ip);
fprintf(stdout, "%s\n", host);
return 0;
} /* print_arpa() */
static int show_hints(int argc, char *argv[]) {
struct dns_hints *(*load)(struct dns_resolv_conf *, int *);
const char *which, *how, *who;
struct dns_hints *hints;
int error;
which = (argc > 1)? argv[1] : "local";
how = (argc > 2)? argv[2] : "plain";
who = (argc > 3)? argv[3] : "google.com";
load = (0 == strcmp(which, "local"))
? &dns_hints_local
: &dns_hints_root;
if (!(hints = load(resconf(), &error)))
panic("%s: %s", argv[0], dns_strerror(error));
if (0 == strcmp(how, "plain")) {
dns_hints_dump(hints, stdout);
} else {
- union { unsigned char b[dns_p_calcsize((512))]; struct dns_packet p; } _P = { 0 };
+ union { unsigned char b[dns_p_calcsize((512))]; struct dns_packet p; } P_instance = { 0 };
struct dns_packet *query, *answer;
- query = dns_p_init(&_P.p, 512);
+ query = dns_p_init(&P_instance.p, 512);
if ((error = dns_p_push(query, DNS_S_QUESTION, who, strlen(who), DNS_T_A, DNS_C_IN, 0, 0)))
panic("%s: %s", who, dns_strerror(error));
if (!(answer = dns_hints_query(hints, query, &error)))
panic("%s: %s", who, dns_strerror(error));
print_packet(answer, stdout);
free(answer);
}
dns_hints_close(hints);
return 0;
} /* show_hints() */
static int resolve_query(int argc DNS_NOTUSED, char *argv[]) {
_Bool recurse = !!strstr(argv[0], "recurse");
struct dns_hints *(*hints)() = (recurse)? &dns_hints_root : &dns_hints_local;
struct dns_resolver *R;
struct dns_packet *ans;
const struct dns_stat *st;
int error;
struct dns_options opts = { 0 };
opts.socks_host = &MAIN.socks_host;
opts.socks_user = MAIN.socks_user;
opts.socks_password = MAIN.socks_password;
if (!MAIN.qname)
MAIN.qname = "www.google.com";
if (!MAIN.qtype)
MAIN.qtype = DNS_T_A;
resconf()->options.recurse = recurse;
if (!(R = dns_res_open(resconf(), hosts(), dns_hints_mortal(hints(resconf(), &error)), cache(),
&opts, &error)))
panic("%s: %s", MAIN.qname, dns_strerror(error));
dns_res_settrace(R, trace("w+b"));
if ((error = dns_res_submit(R, MAIN.qname, MAIN.qtype, DNS_C_IN)))
panic("%s: %s", MAIN.qname, dns_strerror(error));
while ((error = dns_res_check(R))) {
if (error != DNS_EAGAIN)
panic("dns_res_check: %s (%d)", dns_strerror(error), error);
if (dns_res_elapsed(R) > 30)
panic("query timed-out");
dns_res_poll(R, 1);
}
ans = dns_res_fetch(R, &error);
print_packet(ans, stdout);
free(ans);
st = dns_res_stat(R);
putchar('\n');
printf(";; queries: %"PRIuZ"\n", st->queries);
printf(";; udp sent: %"PRIuZ" in %"PRIuZ" bytes\n", st->udp.sent.count, st->udp.sent.bytes);
printf(";; udp rcvd: %"PRIuZ" in %"PRIuZ" bytes\n", st->udp.rcvd.count, st->udp.rcvd.bytes);
printf(";; tcp sent: %"PRIuZ" in %"PRIuZ" bytes\n", st->tcp.sent.count, st->tcp.sent.bytes);
printf(";; tcp rcvd: %"PRIuZ" in %"PRIuZ" bytes\n", st->tcp.rcvd.count, st->tcp.rcvd.bytes);
dns_res_close(R);
return 0;
} /* resolve_query() */
static int resolve_addrinfo(int argc DNS_NOTUSED, char *argv[]) {
_Bool recurse = !!strstr(argv[0], "recurse");
struct dns_hints *(*hints)() = (recurse)? &dns_hints_root : &dns_hints_local;
struct dns_resolver *res = NULL;
struct dns_addrinfo *ai = NULL;
struct addrinfo ai_hints = { .ai_family = PF_UNSPEC, .ai_socktype = SOCK_STREAM, .ai_flags = AI_CANONNAME };
struct addrinfo *ent;
char pretty[512];
int error;
struct dns_options opts = { 0 };
if (!MAIN.qname)
MAIN.qname = "www.google.com";
/* NB: MAIN.qtype of 0 means obey hints.ai_family */
resconf()->options.recurse = recurse;
if (!(res = dns_res_open(resconf(), hosts(), dns_hints_mortal(hints(resconf(), &error)), cache(), &opts, &error)))
panic("%s: %s", MAIN.qname, dns_strerror(error));
if (!(ai = dns_ai_open(MAIN.qname, "80", MAIN.qtype, &ai_hints, res, &error)))
panic("%s: %s", MAIN.qname, dns_strerror(error));
dns_ai_settrace(ai, trace("w+b"));
do {
switch (error = dns_ai_nextent(&ent, ai)) {
case 0:
dns_ai_print(pretty, sizeof pretty, ent, ai);
fputs(pretty, stdout);
free(ent);
break;
case ENOENT:
break;
case DNS_EAGAIN:
if (dns_ai_elapsed(ai) > 30)
panic("query timed-out");
dns_ai_poll(ai, 1);
break;
default:
panic("dns_ai_nextent: %s (%d)", dns_strerror(error), error);
}
} while (error != ENOENT);
dns_res_close(res);
dns_ai_close(ai);
return 0;
} /* resolve_addrinfo() */
static int dump_trace(int argc DNS_NOTUSED, char *argv[]) {
int error;
if (!MAIN.trace)
panic("no trace file specified");
if ((error = dns_trace_dump(trace("r"), stdout)))
panic("dump_trace: %s", dns_strerror(error));
return 0;
} /* dump_trace() */
static int echo_port(int argc DNS_NOTUSED, char *argv[] DNS_NOTUSED) {
union {
struct sockaddr sa;
struct sockaddr_in sin;
} port;
int fd;
memset(&port, 0, sizeof port);
port.sin.sin_family = AF_INET;
port.sin.sin_port = htons(5354);
port.sin.sin_addr.s_addr = inet_addr("127.0.0.1");
if (-1 == (fd = socket(PF_INET, SOCK_DGRAM, 0)))
panic("socket: %s", strerror(errno));
if (0 != bind(fd, &port.sa, sizeof port.sa))
panic("127.0.0.1:5353: %s", dns_strerror(errno));
for (;;) {
- union { unsigned char b[dns_p_calcsize((512))]; struct dns_packet p; } _P = { 0 };
- struct dns_packet *pkt = dns_p_init(&_P.p, 512);
+ union { unsigned char b[dns_p_calcsize((512))]; struct dns_packet p; } P_instance = { 0 };
+ struct dns_packet *pkt = dns_p_init(&P_instance.p, 512);
struct sockaddr_storage ss;
socklen_t slen = sizeof ss;
ssize_t count;
#if defined(MSG_WAITALL) /* MinGW issue */
int rflags = MSG_WAITALL;
#else
int rflags = 0;
#endif
count = recvfrom(fd, (char *)pkt->data, pkt->size, rflags, (struct sockaddr *)&ss, &slen);
if (!count || count < 0)
panic("recvfrom: %s", strerror(errno));
pkt->end = count;
dns_p_dump(pkt, stdout);
(void)sendto(fd, (char *)pkt->data, pkt->end, 0, (struct sockaddr *)&ss, slen);
}
return 0;
} /* echo_port() */
static int isection(int argc, char *argv[]) {
const char *name = (argc > 1)? argv[1] : "";
int type;
type = dns_isection(name);
name = dns_strsection(type);
printf("%s (%d)\n", name, type);
return 0;
} /* isection() */
static int iclass(int argc, char *argv[]) {
const char *name = (argc > 1)? argv[1] : "";
int type;
type = dns_iclass(name);
name = dns_strclass(type);
printf("%s (%d)\n", name, type);
return 0;
} /* iclass() */
static int itype(int argc, char *argv[]) {
const char *name = (argc > 1)? argv[1] : "";
int type;
type = dns_itype(name);
name = dns_strtype(type);
printf("%s (%d)\n", name, type);
return 0;
} /* itype() */
static int iopcode(int argc, char *argv[]) {
const char *name = (argc > 1)? argv[1] : "";
int type;
type = dns_iopcode(name);
name = dns_stropcode(type);
printf("%s (%d)\n", name, type);
return 0;
} /* iopcode() */
static int ircode(int argc, char *argv[]) {
const char *name = (argc > 1)? argv[1] : "";
int type;
type = dns_ircode(name);
name = dns_strrcode(type);
printf("%s (%d)\n", name, type);
return 0;
} /* ircode() */
#define SIZE1(x) { DNS_PP_STRINGIFY(x), sizeof (x) }
#define SIZE2(x, ...) SIZE1(x), SIZE1(__VA_ARGS__)
#define SIZE3(x, ...) SIZE1(x), SIZE2(__VA_ARGS__)
#define SIZE4(x, ...) SIZE1(x), SIZE3(__VA_ARGS__)
#define SIZE(...) DNS_PP_CALL(DNS_PP_XPASTE(SIZE, DNS_PP_NARG(__VA_ARGS__)), __VA_ARGS__)
static int sizes(int argc DNS_NOTUSED, char *argv[] DNS_NOTUSED) {
static const struct { const char *name; size_t size; } type[] = {
SIZE(struct dns_header, struct dns_packet, struct dns_rr, struct dns_rr_i),
SIZE(struct dns_a, struct dns_aaaa, struct dns_mx, struct dns_ns),
SIZE(struct dns_cname, struct dns_soa, struct dns_ptr, struct dns_srv),
SIZE(struct dns_sshfp, struct dns_txt, union dns_any),
SIZE(struct dns_resolv_conf, struct dns_hosts, struct dns_hints, struct dns_hints_i),
SIZE(struct dns_options, struct dns_socket, struct dns_resolver, struct dns_addrinfo),
SIZE(struct dns_cache), SIZE(size_t), SIZE(void *), SIZE(long)
};
unsigned i, max;
for (i = 0, max = 0; i < lengthof(type); i++)
max = DNS_PP_MAX(max, strlen(type[i].name));
for (i = 0; i < lengthof(type); i++)
printf("%*s : %"PRIuZ"\n", max, type[i].name, type[i].size);
return 0;
} /* sizes() */
static const struct { const char *cmd; int (*run)(); const char *help; } cmds[] = {
{ "parse-packet", &parse_packet, "parse binary packet from stdin" },
{ "parse-domain", &parse_domain, "anchor and iteratively cleave domain" },
{ "trim-domain", &trim_domain, "trim and anchor domain name" },
{ "expand-domain", &expand_domain, "expand domain at offset NN in packet from stdin" },
{ "show-resconf", &show_resconf, "show resolv.conf data" },
{ "show-hosts", &show_hosts, "show hosts data" },
{ "show-nssconf", &show_nssconf, "show nsswitch.conf data" },
{ "query-hosts", &query_hosts, "query A, AAAA or PTR in hosts data" },
{ "search-list", &search_list, "generate query search list from domain" },
{ "permute-set", &permute_set, "generate random permutation -> (0 .. N or N .. M)" },
{ "shuffle-16", &shuffle_16, "simple 16-bit permutation" },
{ "dump-random", &dump_random, "generate random bytes" },
{ "send-query", &send_query, "send query to host" },
{ "send-query-udp", &send_query, "send udp query to host" },
{ "send-query-tcp", &send_query, "send tcp query to host" },
{ "print-arpa", &print_arpa, "print arpa. zone name of address" },
{ "show-hints", &show_hints, "print hints: show-hints [local|root] [plain|packet]" },
{ "resolve-stub", &resolve_query, "resolve as stub resolver" },
{ "resolve-recurse", &resolve_query, "resolve as recursive resolver" },
{ "addrinfo-stub", &resolve_addrinfo, "resolve through getaddrinfo clone" },
{ "addrinfo-recurse", &resolve_addrinfo, "resolve through getaddrinfo clone" },
/* { "resolve-nameinfo", &resolve_query, "resolve as recursive resolver" }, */
{ "dump-trace", &dump_trace, "dump the contents of a trace file" },
{ "echo", &echo_port, "server echo mode, for nmap fuzzing" },
{ "isection", &isection, "parse section string" },
{ "iclass", &iclass, "parse class string" },
{ "itype", &itype, "parse type string" },
{ "iopcode", &iopcode, "parse opcode string" },
{ "ircode", &ircode, "parse rcode string" },
{ "sizes", &sizes, "print data structure sizes" },
};
static void print_usage(const char *progname, FILE *fp) {
static const char *usage =
" [OPTIONS] COMMAND [ARGS]\n"
" -c PATH Path to resolv.conf\n"
" -n PATH Path to nsswitch.conf\n"
" -l PATH Path to local hosts\n"
" -z PATH Path to zone cache\n"
" -q QNAME Query name\n"
" -t QTYPE Query type\n"
" -s HOW Sort records\n"
" -S ADDR Address of SOCKS server to use\n"
" -P PORT Port of SOCKS server to use\n"
" -A USER:PASSWORD Credentials for the SOCKS server\n"
" -f PATH Path to trace file\n"
" -v Be more verbose (-vv show packets; -vvv hexdump packets)\n"
" -V Print version info\n"
" -h Print this usage message\n"
"\n";
unsigned i, n, m;
fputs(progname, fp);
fputs(usage, fp);
for (i = 0, m = 0; i < lengthof(cmds); i++) {
if (strlen(cmds[i].cmd) > m)
m = strlen(cmds[i].cmd);
}
for (i = 0; i < lengthof(cmds); i++) {
fprintf(fp, " %s ", cmds[i].cmd);
for (n = strlen(cmds[i].cmd); n < m; n++)
putc(' ', fp);
fputs(cmds[i].help, fp);
putc('\n', fp);
}
fputs("\nReport bugs to William Ahern <william@25thandClement.com>\n", fp);
} /* print_usage() */
static void print_version(const char *progname, FILE *fp) {
fprintf(fp, "%s (dns.c) %.8X\n", progname, dns_v_rel());
fprintf(fp, "vendor %s\n", dns_vendor());
fprintf(fp, "release %.8X\n", dns_v_rel());
fprintf(fp, "abi %.8X\n", dns_v_abi());
fprintf(fp, "api %.8X\n", dns_v_api());
} /* print_version() */
static void main_exit(void) {
dns_trace_close(MAIN.memo.trace);
MAIN.memo.trace = NULL;
dns_hosts_close(MAIN.memo.hosts);
MAIN.memo.hosts = NULL;
dns_resconf_close(MAIN.memo.resconf);
MAIN.memo.resconf = NULL;
} /* main_exit() */
int main(int argc, char **argv) {
extern int optind;
extern char *optarg;
const char *progname = argv[0];
unsigned i;
int ch;
atexit(&main_exit);
while (-1 != (ch = getopt(argc, argv, "q:t:c:n:l:z:s:S:P:A:f:vVh"))) {
switch (ch) {
case 'c':
assert(MAIN.resconf.count < lengthof(MAIN.resconf.path));
MAIN.resconf.path[MAIN.resconf.count++] = optarg;
break;
case 'n':
assert(MAIN.nssconf.count < lengthof(MAIN.nssconf.path));
MAIN.nssconf.path[MAIN.nssconf.count++] = optarg;
break;
case 'l':
assert(MAIN.hosts.count < lengthof(MAIN.hosts.path));
MAIN.hosts.path[MAIN.hosts.count++] = optarg;
break;
case 'z':
assert(MAIN.cache.count < lengthof(MAIN.cache.path));
MAIN.cache.path[MAIN.cache.count++] = optarg;
break;
case 'q':
MAIN.qname = optarg;
break;
case 't':
for (i = 0; i < lengthof(dns_rrtypes); i++) {
if (0 == strcasecmp(dns_rrtypes[i].name, optarg))
{ MAIN.qtype = dns_rrtypes[i].type; break; }
}
if (MAIN.qtype)
break;
for (i = 0; dns_isdigit(optarg[i]); i++) {
MAIN.qtype *= 10;
MAIN.qtype += optarg[i] - '0';
}
if (!MAIN.qtype)
panic("%s: invalid query type", optarg);
break;
case 's':
if (0 == strcasecmp(optarg, "packet"))
MAIN.sort = &dns_rr_i_packet;
else if (0 == strcasecmp(optarg, "shuffle"))
MAIN.sort = &dns_rr_i_shuffle;
else if (0 == strcasecmp(optarg, "order"))
MAIN.sort = &dns_rr_i_order;
else
panic("%s: invalid sort method", optarg);
break;
case 'S': {
dns_error_t error;
struct dns_resolv_conf *conf = resconf();
conf->options.tcp = DNS_RESCONF_TCP_SOCKS;
MAIN.socks_host.ss_family = (strchr(optarg, ':')) ? AF_INET6 : AF_INET;
if ((error = dns_pton(MAIN.socks_host.ss_family, optarg,
dns_sa_addr(MAIN.socks_host.ss_family,
&MAIN.socks_host, NULL))))
panic("%s: %s", optarg, dns_strerror(error));
*dns_sa_port(MAIN.socks_host.ss_family, &MAIN.socks_host) = htons(1080);
break;
}
case 'P':
if (! MAIN.socks_host.ss_family)
panic("-P without prior -S");
*dns_sa_port(MAIN.socks_host.ss_family, &MAIN.socks_host) = htons(atoi(optarg));
break;
case 'A': {
char *password;
if (! MAIN.socks_host.ss_family)
panic("-A without prior -S");
if (! (password = strchr(optarg, ':')))
panic("Usage: -A USER:PASSWORD");
*password = 0;
password += 1;
MAIN.socks_user = optarg;
MAIN.socks_password = password;
break;
}
case 'f':
MAIN.trace = optarg;
break;
case 'v':
dns_debug = ++MAIN.verbose;
break;
case 'V':
print_version(progname, stdout);
return 0;
case 'h':
print_usage(progname, stdout);
return 0;
default:
print_usage(progname, stderr);
return EXIT_FAILURE;
} /* switch() */
} /* while() */
argc -= optind;
argv += optind;
for (i = 0; i < lengthof(cmds) && argv[0]; i++) {
if (0 == strcmp(cmds[i].cmd, argv[0]))
return cmds[i].run(argc, argv);
}
print_usage(progname, stderr);
return EXIT_FAILURE;
} /* main() */
#endif /* DNS_MAIN */
/*
* pop file-scoped compiler annotations
*/
#if __clang__
#pragma clang diagnostic pop
#elif DNS_GNUC_PREREQ(4,6,0)
#pragma GCC diagnostic pop
#endif
diff --git a/dirmngr/domaininfo.c b/dirmngr/domaininfo.c
index f6263b06d..b41aef366 100644
--- a/dirmngr/domaininfo.c
+++ b/dirmngr/domaininfo.c
@@ -1,291 +1,378 @@
/* domaininfo.c - Gather statistics about accessed domains
* Copyright (C) 2017 Werner Koch
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0+
*/
#include <config.h>
#include <stdlib.h>
#include <string.h>
#include "dirmngr.h"
/* Number of bucket for the hash array and limit for the length of a
* bucket chain. For debugging values of 13 and 10 are more suitable
* and a command like
* for j in a b c d e f g h i j k l m n o p q r s t u v w z y z; do \
* for i in a b c d e f g h i j k l m n o p q r s t u v w z y z; do \
* gpg-connect-agent --dirmngr "wkd_get foo@$i.$j.gnupg.net" /bye \
* >/dev/null ; done; done
* will quickly add a couple of domains.
*/
#define NO_OF_DOMAINBUCKETS 103
#define MAX_DOMAINBUCKET_LEN 20
/* Object to keep track of a domain name. */
struct domaininfo_s
{
struct domaininfo_s *next;
unsigned int no_name:1; /* Domain name not found. */
unsigned int wkd_not_found:1; /* A WKD query failed. */
unsigned int wkd_supported:1; /* One WKD entry was found. */
unsigned int wkd_not_supported:1; /* Definitely does not support WKD. */
+ unsigned int keepmark:1; /* Private to insert_or_update(). */
char name[1];
};
typedef struct domaininfo_s *domaininfo_t;
/* And the hashed array. */
static domaininfo_t domainbuckets[NO_OF_DOMAINBUCKETS];
/* The hash function we use. Must not call a system function. */
static inline u32
hash_domain (const char *domain)
{
const unsigned char *s = (const unsigned char*)domain;
u32 hashval = 0;
u32 carry;
for (; *s; s++)
{
if (*s == '.')
continue;
hashval = (hashval << 4) + *s;
if ((carry = (hashval & 0xf0000000)))
{
hashval ^= (carry >> 24);
hashval ^= carry;
}
}
return hashval % NO_OF_DOMAINBUCKETS;
}
void
domaininfo_print_stats (void)
{
int bidx;
domaininfo_t di;
int count, no_name, wkd_not_found, wkd_supported, wkd_not_supported;
int len, minlen, maxlen;
count = no_name = wkd_not_found = wkd_supported = wkd_not_supported = 0;
maxlen = 0;
minlen = -1;
for (bidx = 0; bidx < NO_OF_DOMAINBUCKETS; bidx++)
{
len = 0;
for (di = domainbuckets[bidx]; di; di = di->next)
{
count++;
len++;
if (di->no_name)
no_name++;
if (di->wkd_not_found)
wkd_not_found++;
if (di->wkd_supported)
wkd_supported++;
if (di->wkd_not_supported)
wkd_not_supported++;
}
if (len > maxlen)
maxlen = len;
if (minlen == -1 || len < minlen)
minlen = len;
}
log_info ("domaininfo: items=%d chainlen=%d..%d nn=%d nf=%d ns=%d s=%d\n",
count,
minlen > 0? minlen : 0,
maxlen,
no_name, wkd_not_found, wkd_not_supported, wkd_supported);
}
/* Return true if DOMAIN definitely does not support WKD. Note that
* DOMAIN is expected to be lowercase. */
int
domaininfo_is_wkd_not_supported (const char *domain)
{
domaininfo_t di;
for (di = domainbuckets[hash_domain (domain)]; di; di = di->next)
if (!strcmp (di->name, domain))
return !!di->wkd_not_supported;
return 0; /* We don't know. */
}
/* Core update function. DOMAIN is expected to be lowercase.
* CALLBACK is called to update the existing or the newly inserted
* item. */
static void
insert_or_update (const char *domain,
void (*callback)(domaininfo_t di, int insert_mode))
{
domaininfo_t di;
domaininfo_t di_new;
- domaininfo_t di_cut;
+ domaininfo_t drop = NULL;
+ domaininfo_t drop_extra = NULL;
+ int nkept = 0;
+ int ndropped = 0;
u32 hash;
int count;
hash = hash_domain (domain);
for (di = domainbuckets[hash]; di; di = di->next)
if (!strcmp (di->name, domain))
{
callback (di, 0); /* Update */
return;
}
di_new = xtrycalloc (1, sizeof *di + strlen (domain));
if (!di_new)
return; /* Out of core - we ignore this. */
strcpy (di_new->name, domain);
/* Need to do another lookup because the malloc is a system call and
* thus the hash array may have been changed by another thread. */
- di_cut = NULL;
for (count=0, di = domainbuckets[hash]; di; di = di->next, count++)
if (!strcmp (di->name, domain))
{
callback (di, 0); /* Update */
xfree (di_new);
return;
}
/* Before we insert we need to check whether the chain gets too long. */
- di_cut = NULL;
if (count >= MAX_DOMAINBUCKET_LEN)
{
- for (count=0, di = domainbuckets[hash]; di; di = di->next, count++)
- if (count >= MAX_DOMAINBUCKET_LEN/2)
- {
- di_cut = di->next;
- di->next = NULL;
- break;
- }
+ domaininfo_t bucket;
+ domaininfo_t *array;
+ int narray, idx;
+ domaininfo_t keep = NULL;
+
+ /* Unlink from the global list before doing a syscall. */
+ bucket = domainbuckets[hash];
+ domainbuckets[hash] = NULL;
+
+ array = xtrycalloc (count, sizeof *array);
+ if (!array)
+ {
+ /* That's bad; give up the entire bucket. */
+ log_error ("domaininfo: error allocating helper array: %s\n",
+ gpg_strerror (gpg_err_code_from_syserror ()));
+ drop_extra = bucket;
+ goto leave;
+ }
+ narray = 0;
+
+ /* Move all items into an array for easier processing. */
+ for (di = bucket; di; di = di->next)
+ array[narray++] = di;
+ log_assert (narray == count);
+
+ /* Mark all item in the array which are flagged to support wkd
+ * but not more than half of the maximum. This way we will at
+ * the end drop half of the items. */
+ count = 0;
+ for (idx=0; idx < narray; idx++)
+ {
+ di = array[idx];
+ di->keepmark = 0; /* Clear flag here on the first pass. */
+ if (di->wkd_supported && count < MAX_DOMAINBUCKET_LEN/2)
+ {
+ di->keepmark = 1;
+ count++;
+ }
+ }
+ /* Now mark those which are marked as not found. */
+ /* FIXME: we should use an LRU algorithm here. */
+ for (idx=0; idx < narray; idx++)
+ {
+ di = array[idx];
+ if (!di->keepmark
+ && di->wkd_not_supported && count < MAX_DOMAINBUCKET_LEN/2)
+ {
+ di->keepmark = 1;
+ count++;
+ }
+ }
+
+ /* Build a bucket list and a second list for later freeing the
+ * items (we can't do it directly because a free is a system
+ * call and we want to avoid locks in this module. Note that
+ * the kept items will be reversed order which does not matter. */
+ for (idx=0; idx < narray; idx++)
+ {
+ di = array[idx];
+ if (di->keepmark)
+ {
+ di->next = keep;
+ keep = di;
+ nkept++;
+ }
+ else
+ {
+ di->next = drop;
+ drop = di;
+ ndropped++;
+ }
+ }
+
+ /* In case another thread added new stuff to the domain list we
+ * simply drop them instead all. It would also be possible to
+ * append them to our list but then we can't guarantee that a
+ * bucket list is almost all of the time limited to
+ * MAX_DOMAINBUCKET_LEN. Not sure whether this is really a
+ * sensible strategy. */
+ drop_extra = domainbuckets[hash];
+ domainbuckets[hash] = keep;
}
/* Insert */
callback (di_new, 1);
di = di_new;
di->next = domainbuckets[hash];
domainbuckets[hash] = di;
- /* Remove the rest of the cutted chain. */
- while (di_cut)
+ if (opt.verbose && (nkept || ndropped))
+ log_info ("domaininfo: bucket=%lu kept=%d purged=%d\n",
+ (unsigned long)hash, nkept, ndropped);
+
+ leave:
+ /* Remove the dropped items. */
+ while (drop)
+ {
+ di = drop->next;
+ xfree (drop);
+ drop = di;
+ }
+ while (drop_extra)
{
- di = di_cut->next;
- xfree (di_cut);
- di_cut = di;
+ di = drop_extra->next;
+ xfree (drop_extra);
+ drop_extra = di;
}
}
-/* Helper for domaininfo_set_no_name. */
+/* Helper for domaininfo_set_no_name. May not do any syscalls. */
static void
set_no_name_cb (domaininfo_t di, int insert_mode)
{
(void)insert_mode;
di->no_name = 1;
/* Obviously the domain is in this case also not supported. */
di->wkd_not_supported = 1;
/* The next should already be 0 but we clear it anyway in the case
* of a temporary DNS failure. */
di->wkd_supported = 0;
}
/* Mark DOMAIN as not existent. */
void
domaininfo_set_no_name (const char *domain)
{
insert_or_update (domain, set_no_name_cb);
}
-/* Helper for domaininfo_set_wkd_supported. */
+/* Helper for domaininfo_set_wkd_supported. May not do any syscalls. */
static void
set_wkd_supported_cb (domaininfo_t di, int insert_mode)
{
(void)insert_mode;
di->wkd_supported = 1;
/* The next will already be set unless the domain enabled WKD in the
* meantime. Thus we need to clear it. */
di->wkd_not_supported = 0;
}
/* Mark DOMAIN as supporting WKD. */
void
domaininfo_set_wkd_supported (const char *domain)
{
insert_or_update (domain, set_wkd_supported_cb);
}
-/* Helper for domaininfo_set_wkd_not_supported. */
+/* Helper for domaininfo_set_wkd_not_supported. May not do any syscalls. */
static void
set_wkd_not_supported_cb (domaininfo_t di, int insert_mode)
{
(void)insert_mode;
di->wkd_not_supported = 1;
di->wkd_supported = 0;
}
/* Mark DOMAIN as not supporting WKD queries (e.g. no policy file). */
void
domaininfo_set_wkd_not_supported (const char *domain)
{
insert_or_update (domain, set_wkd_not_supported_cb);
}
-/* Helper for domaininfo_set_wkd_not_found. */
+/* Helper for domaininfo_set_wkd_not_found. May not do any syscalls. */
static void
set_wkd_not_found_cb (domaininfo_t di, int insert_mode)
{
/* Set the not found flag but there is no need to do this if we
* already know that the domain either does not support WKD or we
* know that it supports WKD. */
if (insert_mode)
di->wkd_not_found = 1;
else if (!di->wkd_not_supported && !di->wkd_supported)
di->wkd_not_found = 1;
/* Better clear this flag in case we had a DNS failure in the
* past. */
di->no_name = 0;
}
/* Update a counter for DOMAIN to keep track of failed WKD queries. */
void
domaininfo_set_wkd_not_found (const char *domain)
{
insert_or_update (domain, set_wkd_not_found_cb);
}
diff --git a/dirmngr/http.c b/dirmngr/http.c
index d6856fe05..81b7ba897 100644
--- a/dirmngr/http.c
+++ b/dirmngr/http.c
@@ -1,3724 +1,3745 @@
/* http.c - HTTP protocol handler
* Copyright (C) 1999, 2001, 2002, 2003, 2004, 2006, 2009, 2010,
* 2011 Free Software Foundation, Inc.
* Copyright (C) 2014 Werner Koch
* Copyright (C) 2015-2018 g10 Code GmbH
*
* This file is part of GnuPG.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of either
*
* - the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 3 of the License, or (at
* your option) any later version.
*
* or
*
* - 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.
*
* or both in parallel, as here.
*
* This file is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
/* Simple HTTP client implementation. We try to keep the code as
self-contained as possible. There are some constraints however:
- estream is required. We now require estream because it provides a
very useful and portable asprintf implementation and the fopencookie
function.
- stpcpy is required
- fixme: list other requirements.
- With HTTP_USE_NTBTLS or HTTP_USE_GNUTLS support for https is
provided (this also requires estream).
- With HTTP_NO_WSASTARTUP the socket initialization is not done
under Windows. This is useful if the socket layer has already
been initialized elsewhere. This also avoids the installation of
an exit handler to cleanup the socket layer.
*/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <unistd.h>
#ifdef HAVE_W32_SYSTEM
# ifdef HAVE_WINSOCK2_H
# include <winsock2.h>
# endif
# include <windows.h>
#else /*!HAVE_W32_SYSTEM*/
# include <sys/types.h>
# include <sys/socket.h>
# include <sys/time.h>
# include <time.h>
# include <fcntl.h>
# include <netinet/in.h>
# include <arpa/inet.h>
# include <netdb.h>
#endif /*!HAVE_W32_SYSTEM*/
#ifdef WITHOUT_NPTH /* Give the Makefile a chance to build without Pth. */
# undef USE_NPTH
#endif
#ifdef USE_NPTH
# include <npth.h>
#endif
#if defined (HTTP_USE_GNUTLS) && defined (HTTP_USE_NTBTLS)
# error Both, HTTP_USE_GNUTLS and HTTP_USE_NTBTLS, are defined.
#endif
#ifdef HTTP_USE_NTBTLS
# include <ntbtls.h>
#elif HTTP_USE_GNUTLS
# include <gnutls/gnutls.h>
# include <gnutls/x509.h>
#endif /*HTTP_USE_GNUTLS*/
#include <assuan.h> /* We need the socket wrapper. */
#include "../common/util.h"
#include "../common/i18n.h"
#include "../common/sysutils.h" /* (gnupg_fd_t) */
#include "dns-stuff.h"
#include "http.h"
#include "http-common.h"
#ifdef USE_NPTH
# define my_select(a,b,c,d,e) npth_select ((a), (b), (c), (d), (e))
# define my_accept(a,b,c) npth_accept ((a), (b), (c))
#else
# define my_select(a,b,c,d,e) select ((a), (b), (c), (d), (e))
# define my_accept(a,b,c) accept ((a), (b), (c))
#endif
#ifdef HAVE_W32_SYSTEM
#define sock_close(a) closesocket(a)
#else
#define sock_close(a) close(a)
#endif
#ifndef EAGAIN
#define EAGAIN EWOULDBLOCK
#endif
#ifndef INADDR_NONE /* Slowaris is missing that. */
#define INADDR_NONE ((unsigned long)(-1))
#endif /*INADDR_NONE*/
#define HTTP_PROXY_ENV "http_proxy"
#define MAX_LINELEN 20000 /* Max. length of a HTTP header line. */
#define VALID_URI_CHARS "abcdefghijklmnopqrstuvwxyz" \
"ABCDEFGHIJKLMNOPQRSTUVWXYZ" \
"01234567890@" \
"!\"#$%&'()*+,-./:;<=>?[\\]^_{|}~"
#if HTTP_USE_NTBTLS
typedef ntbtls_t tls_session_t;
# define USE_TLS 1
#elif HTTP_USE_GNUTLS
typedef gnutls_session_t tls_session_t;
# define USE_TLS 1
#else
typedef void *tls_session_t;
# undef USE_TLS
#endif
static gpg_err_code_t do_parse_uri (parsed_uri_t uri, int only_local_part,
int no_scheme_check, int force_tls);
static gpg_error_t parse_uri (parsed_uri_t *ret_uri, const char *uri,
int no_scheme_check, int force_tls);
static int remove_escapes (char *string);
static int insert_escapes (char *buffer, const char *string,
const char *special);
static uri_tuple_t parse_tuple (char *string);
static gpg_error_t send_request (ctrl_t ctrl, http_t hd, const char *httphost,
const char *auth,const char *proxy,
const char *srvtag, unsigned int timeout,
strlist_t headers);
static char *build_rel_path (parsed_uri_t uri);
static gpg_error_t parse_response (http_t hd);
static gpg_error_t connect_server (ctrl_t ctrl,
const char *server, unsigned short port,
unsigned int flags, const char *srvtag,
unsigned int timeout, assuan_fd_t *r_sock);
static gpgrt_ssize_t read_server (assuan_fd_t sock, void *buffer, size_t size);
static gpg_error_t write_server (assuan_fd_t sock, const char *data, size_t length);
static gpgrt_ssize_t cookie_read (void *cookie, void *buffer, size_t size);
static gpgrt_ssize_t cookie_write (void *cookie,
const void *buffer, size_t size);
static int cookie_close (void *cookie);
#if defined(HAVE_W32_SYSTEM) && defined(HTTP_USE_NTBTLS)
static gpgrt_ssize_t simple_cookie_read (void *cookie,
void *buffer, size_t size);
static gpgrt_ssize_t simple_cookie_write (void *cookie,
const void *buffer, size_t size);
#endif
/* A socket object used to a allow ref counting of sockets. */
struct my_socket_s
{
assuan_fd_t fd; /* The actual socket - shall never be ASSUAN_INVALID_FD. */
int refcount; /* Number of references to this socket. */
};
typedef struct my_socket_s *my_socket_t;
/* Cookie function structure and cookie object. */
static es_cookie_io_functions_t cookie_functions =
{
cookie_read,
cookie_write,
NULL,
cookie_close
};
struct cookie_s
{
/* Socket object or NULL if already closed. */
my_socket_t sock;
/* The session object or NULL if not used. */
http_session_t session;
/* True if TLS is to be used. */
int use_tls;
/* The remaining content length and a flag telling whether to use
the content length. */
uint64_t content_length;
unsigned int content_length_valid:1;
};
typedef struct cookie_s *cookie_t;
/* Simple cookie functions. Here the cookie is an int with the
* socket. */
#if defined(HAVE_W32_SYSTEM) && defined(HTTP_USE_NTBTLS)
static es_cookie_io_functions_t simple_cookie_functions =
{
simple_cookie_read,
simple_cookie_write,
NULL,
NULL
};
#endif
#if SIZEOF_UNSIGNED_LONG == 8
# define HTTP_SESSION_MAGIC 0x0068545470534553 /* "hTTpSES" */
#else
# define HTTP_SESSION_MAGIC 0x68547365 /* "hTse" */
#endif
/* The session object. */
struct http_session_s
{
unsigned long magic;
int refcount; /* Number of references to this object. */
#ifdef HTTP_USE_GNUTLS
gnutls_certificate_credentials_t certcred;
#endif /*HTTP_USE_GNUTLS*/
#ifdef USE_TLS
tls_session_t tls_session;
struct {
int done; /* Verifciation has been done. */
int rc; /* TLS verification return code. */
unsigned int status; /* Verification status. */
} verify;
char *servername; /* Malloced server name. */
#endif /*USE_TLS*/
/* A callback function to log details of TLS certifciates. */
void (*cert_log_cb) (http_session_t, gpg_error_t, const char *,
const void **, size_t *);
/* The flags passed to the session object. */
unsigned int flags;
/* A per-session TLS verification callback. */
http_verify_cb_t verify_cb;
void *verify_cb_value;
/* The connect timeout */
unsigned int connect_timeout;
};
/* An object to save header lines. */
struct header_s
{
struct header_s *next;
char *value; /* The value of the header (malloced). */
char name[1]; /* The name of the header (canonicalized). */
};
typedef struct header_s *header_t;
#if SIZEOF_UNSIGNED_LONG == 8
# define HTTP_CONTEXT_MAGIC 0x0068545470435458 /* "hTTpCTX" */
#else
# define HTTP_CONTEXT_MAGIC 0x68546378 /* "hTcx" */
#endif
/* Our handle context. */
struct http_context_s
{
unsigned long magic;
unsigned int status_code;
my_socket_t sock;
unsigned int in_data:1;
unsigned int is_http_0_9:1;
estream_t fp_read;
estream_t fp_write;
void *write_cookie;
void *read_cookie;
http_session_t session;
parsed_uri_t uri;
http_req_t req_type;
char *buffer; /* Line buffer. */
size_t buffer_size;
unsigned int flags;
header_t headers; /* Received headers. */
};
/* Two flags to enable verbose and debug mode. Although currently not
* set-able a value > 1 for OPT_DEBUG enables debugging of the session
* reference counting. */
static int opt_verbose;
static int opt_debug;
/* The global callback for the verification function. */
static gpg_error_t (*tls_callback) (http_t, http_session_t, int);
/* The list of files with trusted CA certificates. */
static strlist_t tls_ca_certlist;
/* The list of files with extra trusted CA certificates. */
static strlist_t cfg_ca_certlist;
/* The global callback for net activity. */
static void (*netactivity_cb)(void);
#if defined(HAVE_W32_SYSTEM) && !defined(HTTP_NO_WSASTARTUP)
#if GNUPG_MAJOR_VERSION == 1
#define REQ_WINSOCK_MAJOR 1
#define REQ_WINSOCK_MINOR 1
#else
#define REQ_WINSOCK_MAJOR 2
#define REQ_WINSOCK_MINOR 2
#endif
static void
deinit_sockets (void)
{
WSACleanup();
}
static void
init_sockets (void)
{
static int initialized;
static WSADATA wsdata;
if (initialized)
return;
if ( WSAStartup( MAKEWORD (REQ_WINSOCK_MINOR, REQ_WINSOCK_MAJOR), &wsdata ) )
{
log_error ("error initializing socket library: ec=%d\n",
(int)WSAGetLastError () );
return;
}
if ( LOBYTE(wsdata.wVersion) != REQ_WINSOCK_MAJOR
|| HIBYTE(wsdata.wVersion) != REQ_WINSOCK_MINOR )
{
log_error ("socket library version is %x.%x - but %d.%d needed\n",
LOBYTE(wsdata.wVersion), HIBYTE(wsdata.wVersion),
REQ_WINSOCK_MAJOR, REQ_WINSOCK_MINOR);
WSACleanup();
return;
}
atexit ( deinit_sockets );
initialized = 1;
}
#endif /*HAVE_W32_SYSTEM && !HTTP_NO_WSASTARTUP*/
/* Create a new socket object. Returns NULL and closes FD if not
enough memory is available. */
static my_socket_t
_my_socket_new (int lnr, assuan_fd_t fd)
{
my_socket_t so;
so = xtrymalloc (sizeof *so);
if (!so)
{
int save_errno = errno;
assuan_sock_close (fd);
gpg_err_set_errno (save_errno);
return NULL;
}
so->fd = fd;
so->refcount = 1;
if (opt_debug)
log_debug ("http.c:%d:socket_new: object %p for fd %d created\n",
lnr, so, (int)so->fd);
return so;
}
#define my_socket_new(a) _my_socket_new (__LINE__, (a))
/* Bump up the reference counter for the socket object SO. */
static my_socket_t
_my_socket_ref (int lnr, my_socket_t so)
{
so->refcount++;
if (opt_debug > 1)
log_debug ("http.c:%d:socket_ref: object %p for fd %d refcount now %d\n",
lnr, so, (int)so->fd, so->refcount);
return so;
}
#define my_socket_ref(a) _my_socket_ref (__LINE__,(a))
/* Bump down the reference counter for the socket object SO. If SO
has no more references, close the socket and release the
object. */
static void
_my_socket_unref (int lnr, my_socket_t so,
void (*preclose)(void*), void *preclosearg)
{
if (so)
{
so->refcount--;
if (opt_debug > 1)
log_debug ("http.c:%d:socket_unref: object %p for fd %d ref now %d\n",
lnr, so, (int)so->fd, so->refcount);
if (!so->refcount)
{
if (preclose)
preclose (preclosearg);
assuan_sock_close (so->fd);
xfree (so);
}
}
}
#define my_socket_unref(a,b,c) _my_socket_unref (__LINE__,(a),(b),(c))
#ifdef HTTP_USE_GNUTLS
static ssize_t
my_gnutls_read (gnutls_transport_ptr_t ptr, void *buffer, size_t size)
{
my_socket_t sock = ptr;
#if USE_NPTH
return npth_read (sock->fd, buffer, size);
#else
return read (sock->fd, buffer, size);
#endif
}
static ssize_t
my_gnutls_write (gnutls_transport_ptr_t ptr, const void *buffer, size_t size)
{
my_socket_t sock = ptr;
#if USE_NPTH
return npth_write (sock->fd, buffer, size);
#else
return write (sock->fd, buffer, size);
#endif
}
#endif /*HTTP_USE_GNUTLS*/
#ifdef HTTP_USE_NTBTLS
/* Connect the ntbls callback to our generic callback. */
static gpg_error_t
my_ntbtls_verify_cb (void *opaque, ntbtls_t tls, unsigned int verify_flags)
{
http_t hd = opaque;
(void)verify_flags;
log_assert (hd && hd->session && hd->session->verify_cb);
log_assert (hd->magic == HTTP_CONTEXT_MAGIC);
log_assert (hd->session->magic == HTTP_SESSION_MAGIC);
return hd->session->verify_cb (hd->session->verify_cb_value,
hd, hd->session,
(hd->flags | hd->session->flags),
tls);
}
#endif /*HTTP_USE_NTBTLS*/
/* This notification function is called by estream whenever stream is
closed. Its purpose is to mark the closing in the handle so
that a http_close won't accidentally close the estream. The function
http_close removes this notification so that it won't be called if
http_close was used before an es_fclose. */
static void
fp_onclose_notification (estream_t stream, void *opaque)
{
http_t hd = opaque;
log_assert (hd->magic == HTTP_CONTEXT_MAGIC);
if (hd->fp_read && hd->fp_read == stream)
hd->fp_read = NULL;
else if (hd->fp_write && hd->fp_write == stream)
hd->fp_write = NULL;
}
/*
* Helper function to create an HTTP header with hex encoded data. A
* new buffer is returned. This buffer is the concatenation of the
* string PREFIX, the hex-encoded DATA of length LEN and the string
* SUFFIX. On error NULL is returned and ERRNO set.
*/
static char *
make_header_line (const char *prefix, const char *suffix,
const void *data, size_t len )
{
static unsigned char bintoasc[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789+/";
const unsigned char *s = data;
char *buffer, *p;
buffer = xtrymalloc (strlen (prefix) + (len+2)/3*4 + strlen (suffix) + 1);
if (!buffer)
return NULL;
p = stpcpy (buffer, prefix);
for ( ; len >= 3 ; len -= 3, s += 3 )
{
*p++ = bintoasc[(s[0] >> 2) & 077];
*p++ = bintoasc[(((s[0] <<4)&060)|((s[1] >> 4)&017))&077];
*p++ = bintoasc[(((s[1]<<2)&074)|((s[2]>>6)&03))&077];
*p++ = bintoasc[s[2]&077];
*p = 0;
}
if ( len == 2 )
{
*p++ = bintoasc[(s[0] >> 2) & 077];
*p++ = bintoasc[(((s[0] <<4)&060)|((s[1] >> 4)&017))&077];
*p++ = bintoasc[((s[1]<<2)&074)];
*p++ = '=';
}
else if ( len == 1 )
{
*p++ = bintoasc[(s[0] >> 2) & 077];
*p++ = bintoasc[(s[0] <<4)&060];
*p++ = '=';
*p++ = '=';
}
*p = 0;
strcpy (p, suffix);
return buffer;
}
/* Set verbosity and debug mode for this module. */
void
http_set_verbose (int verbose, int debug)
{
opt_verbose = verbose;
opt_debug = debug;
}
/* Register a non-standard global TLS callback function. If no
verification is desired a callback needs to be registered which
always returns NULL. */
void
http_register_tls_callback (gpg_error_t (*cb)(http_t, http_session_t, int))
{
tls_callback = cb;
}
/* Register a CA certificate for future use. The certificate is
expected to be in FNAME. PEM format is assume if FNAME has a
suffix of ".pem". If FNAME is NULL the list of CA files is
removed. */
void
http_register_tls_ca (const char *fname)
{
strlist_t sl;
if (!fname)
{
free_strlist (tls_ca_certlist);
tls_ca_certlist = NULL;
}
else
{
/* Warn if we can't access right now, but register it anyway in
case it becomes accessible later */
if (access (fname, F_OK))
log_info (_("can't access '%s': %s\n"), fname,
gpg_strerror (gpg_error_from_syserror()));
sl = add_to_strlist (&tls_ca_certlist, fname);
if (*sl->d && !strcmp (sl->d + strlen (sl->d) - 4, ".pem"))
sl->flags = 1;
}
}
/* Register a CA certificate for future use. The certificate is
* expected to be in FNAME. PEM format is assume if FNAME has a
* suffix of ".pem". If FNAME is NULL the list of CA files is
* removed. This is a variant of http_register_tls_ca which puts the
* certificate into a separate list enabled using HTTP_FLAG_TRUST_CFG. */
void
http_register_cfg_ca (const char *fname)
{
strlist_t sl;
if (!fname)
{
free_strlist (cfg_ca_certlist);
cfg_ca_certlist = NULL;
}
else
{
/* Warn if we can't access right now, but register it anyway in
case it becomes accessible later */
if (access (fname, F_OK))
log_info (_("can't access '%s': %s\n"), fname,
gpg_strerror (gpg_error_from_syserror()));
sl = add_to_strlist (&cfg_ca_certlist, fname);
if (*sl->d && !strcmp (sl->d + strlen (sl->d) - 4, ".pem"))
sl->flags = 1;
}
}
/* Register a callback which is called every time the HTTP mode has
* made a successful connection to some server. */
void
http_register_netactivity_cb (void (*cb)(void))
{
netactivity_cb = cb;
}
/* Call the netactivity callback if any. */
static void
notify_netactivity (void)
{
if (netactivity_cb)
netactivity_cb ();
}
#ifdef USE_TLS
/* Free the TLS session associated with SESS, if any. */
static void
close_tls_session (http_session_t sess)
{
if (sess->tls_session)
{
# if HTTP_USE_NTBTLS
/* FIXME!!
Possibly, ntbtls_get_transport and close those streams.
Somehow get SOCK to call my_socket_unref.
*/
ntbtls_release (sess->tls_session);
# elif HTTP_USE_GNUTLS
my_socket_t sock = gnutls_transport_get_ptr (sess->tls_session);
my_socket_unref (sock, NULL, NULL);
gnutls_deinit (sess->tls_session);
if (sess->certcred)
gnutls_certificate_free_credentials (sess->certcred);
# endif /*HTTP_USE_GNUTLS*/
xfree (sess->servername);
sess->tls_session = NULL;
}
}
#endif /*USE_TLS*/
/* Release a session. Take care not to release it while it is being
used by a http context object. */
static void
session_unref (int lnr, http_session_t sess)
{
if (!sess)
return;
log_assert (sess->magic == HTTP_SESSION_MAGIC);
sess->refcount--;
if (opt_debug > 1)
log_debug ("http.c:%d:session_unref: sess %p ref now %d\n",
lnr, sess, sess->refcount);
if (sess->refcount)
return;
#ifdef USE_TLS
close_tls_session (sess);
#endif /*USE_TLS*/
sess->magic = 0xdeadbeef;
xfree (sess);
}
#define http_session_unref(a) session_unref (__LINE__, (a))
void
http_session_release (http_session_t sess)
{
http_session_unref (sess);
}
/* Create a new session object which is currently used to enable TLS
* support. It may eventually allow reusing existing connections.
* Valid values for FLAGS are:
* HTTP_FLAG_TRUST_DEF - Use the CAs set with http_register_tls_ca
* HTTP_FLAG_TRUST_SYS - Also use the CAs defined by the system
* HTTP_FLAG_TRUST_CFG - Also use CAs set with http_register_cfg_ca
* HTTP_FLAG_NO_CRL - Do not consult CRLs for https.
*/
gpg_error_t
http_session_new (http_session_t *r_session,
const char *intended_hostname, unsigned int flags,
http_verify_cb_t verify_cb, void *verify_cb_value)
{
gpg_error_t err;
http_session_t sess;
*r_session = NULL;
sess = xtrycalloc (1, sizeof *sess);
if (!sess)
return gpg_error_from_syserror ();
sess->magic = HTTP_SESSION_MAGIC;
sess->refcount = 1;
sess->flags = flags;
sess->verify_cb = verify_cb;
sess->verify_cb_value = verify_cb_value;
sess->connect_timeout = 0;
#if HTTP_USE_NTBTLS
{
(void)intended_hostname; /* Not needed because we do not preload
* certificates. */
err = ntbtls_new (&sess->tls_session, NTBTLS_CLIENT);
if (err)
{
log_error ("ntbtls_new failed: %s\n", gpg_strerror (err));
goto leave;
}
}
#elif HTTP_USE_GNUTLS
{
const char *errpos;
int rc;
strlist_t sl;
int add_system_cas = !!(flags & HTTP_FLAG_TRUST_SYS);
int is_hkps_pool;
rc = gnutls_certificate_allocate_credentials (&sess->certcred);
if (rc < 0)
{
log_error ("gnutls_certificate_allocate_credentials failed: %s\n",
gnutls_strerror (rc));
err = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
is_hkps_pool = (intended_hostname
&& !ascii_strcasecmp (intended_hostname,
get_default_keyserver (1)));
/* If the user has not specified a CA list, and they are looking
* for the hkps pool from sks-keyservers.net, then default to
* Kristian's certificate authority: */
if (!tls_ca_certlist && is_hkps_pool)
{
char *pemname = make_filename_try (gnupg_datadir (),
"sks-keyservers.netCA.pem", NULL);
if (!pemname)
{
err = gpg_error_from_syserror ();
log_error ("setting CA from file '%s' failed: %s\n",
pemname, gpg_strerror (err));
}
else
{
rc = gnutls_certificate_set_x509_trust_file
(sess->certcred, pemname, GNUTLS_X509_FMT_PEM);
if (rc < 0)
log_info ("setting CA from file '%s' failed: %s\n",
pemname, gnutls_strerror (rc));
xfree (pemname);
}
}
/* Add configured certificates to the session. */
if ((flags & HTTP_FLAG_TRUST_DEF))
{
for (sl = tls_ca_certlist; sl; sl = sl->next)
{
rc = gnutls_certificate_set_x509_trust_file
(sess->certcred, sl->d,
(sl->flags & 1)? GNUTLS_X509_FMT_PEM : GNUTLS_X509_FMT_DER);
if (rc < 0)
log_info ("setting CA from file '%s' failed: %s\n",
sl->d, gnutls_strerror (rc));
}
if (!tls_ca_certlist && !is_hkps_pool)
add_system_cas = 1;
}
/* Add system certificates to the session. */
if (add_system_cas)
{
#if GNUTLS_VERSION_NUMBER >= 0x030014
static int shown;
rc = gnutls_certificate_set_x509_system_trust (sess->certcred);
if (rc < 0)
log_info ("setting system CAs failed: %s\n", gnutls_strerror (rc));
else if (!shown)
{
shown = 1;
log_info ("number of system provided CAs: %d\n", rc);
}
#endif /* gnutls >= 3.0.20 */
}
/* Add other configured certificates to the session. */
if ((flags & HTTP_FLAG_TRUST_CFG))
{
for (sl = cfg_ca_certlist; sl; sl = sl->next)
{
rc = gnutls_certificate_set_x509_trust_file
(sess->certcred, sl->d,
(sl->flags & 1)? GNUTLS_X509_FMT_PEM : GNUTLS_X509_FMT_DER);
if (rc < 0)
log_info ("setting extra CA from file '%s' failed: %s\n",
sl->d, gnutls_strerror (rc));
}
}
rc = gnutls_init (&sess->tls_session, GNUTLS_CLIENT);
if (rc < 0)
{
log_error ("gnutls_init failed: %s\n", gnutls_strerror (rc));
err = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
/* A new session has the transport ptr set to (void*(-1), we need
it to be NULL. */
gnutls_transport_set_ptr (sess->tls_session, NULL);
rc = gnutls_priority_set_direct (sess->tls_session,
"NORMAL",
&errpos);
if (rc < 0)
{
log_error ("gnutls_priority_set_direct failed at '%s': %s\n",
errpos, gnutls_strerror (rc));
err = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
rc = gnutls_credentials_set (sess->tls_session,
GNUTLS_CRD_CERTIFICATE, sess->certcred);
if (rc < 0)
{
log_error ("gnutls_credentials_set failed: %s\n", gnutls_strerror (rc));
err = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
}
#else /*!HTTP_USE_GNUTLS && !HTTP_USE_NTBTLS*/
{
(void)intended_hostname;
(void)flags;
}
#endif /*!HTTP_USE_GNUTLS && !HTTP_USE_NTBTLS*/
if (opt_debug > 1)
log_debug ("http.c:session_new: sess %p created\n", sess);
err = 0;
#if USE_TLS
leave:
#endif /*USE_TLS*/
if (err)
http_session_unref (sess);
else
*r_session = sess;
return err;
}
/* Increment the reference count for session SESS. Passing NULL for
SESS is allowed. */
http_session_t
http_session_ref (http_session_t sess)
{
if (sess)
{
sess->refcount++;
if (opt_debug > 1)
log_debug ("http.c:session_ref: sess %p ref now %d\n",
sess, sess->refcount);
}
return sess;
}
void
http_session_set_log_cb (http_session_t sess,
void (*cb)(http_session_t, gpg_error_t,
const char *hostname,
const void **certs, size_t *certlens))
{
sess->cert_log_cb = cb;
}
/* Set the TIMEOUT in milliseconds for the connection's connect
* calls. Using 0 disables the timeout. */
void
http_session_set_timeout (http_session_t sess, unsigned int timeout)
{
sess->connect_timeout = timeout;
}
/* Start a HTTP retrieval and on success store at R_HD a context
pointer for completing the request and to wait for the response.
If HTTPHOST is not NULL it is used for the Host header instead of a
Host header derived from the URL. */
gpg_error_t
http_open (ctrl_t ctrl, http_t *r_hd, http_req_t reqtype, const char *url,
const char *httphost,
const char *auth, unsigned int flags, const char *proxy,
http_session_t session, const char *srvtag, strlist_t headers)
{
gpg_error_t err;
http_t hd;
*r_hd = NULL;
if (!(reqtype == HTTP_REQ_GET || reqtype == HTTP_REQ_POST))
return gpg_err_make (default_errsource, GPG_ERR_INV_ARG);
/* Create the handle. */
hd = xtrycalloc (1, sizeof *hd);
if (!hd)
return gpg_error_from_syserror ();
hd->magic = HTTP_CONTEXT_MAGIC;
hd->req_type = reqtype;
hd->flags = flags;
hd->session = http_session_ref (session);
err = parse_uri (&hd->uri, url, 0, !!(flags & HTTP_FLAG_FORCE_TLS));
if (!err)
err = send_request (ctrl, hd, httphost, auth, proxy, srvtag,
hd->session? hd->session->connect_timeout : 0,
headers);
if (err)
{
my_socket_unref (hd->sock, NULL, NULL);
if (hd->fp_read)
es_fclose (hd->fp_read);
if (hd->fp_write)
es_fclose (hd->fp_write);
http_session_unref (hd->session);
xfree (hd);
}
else
*r_hd = hd;
return err;
}
/* This function is useful to connect to a generic TCP service using
this http abstraction layer. This has the advantage of providing
service tags and an estream interface. TIMEOUT is in milliseconds. */
gpg_error_t
http_raw_connect (ctrl_t ctrl, http_t *r_hd,
const char *server, unsigned short port,
unsigned int flags, const char *srvtag, unsigned int timeout)
{
gpg_error_t err = 0;
http_t hd;
cookie_t cookie;
*r_hd = NULL;
if ((flags & HTTP_FLAG_FORCE_TOR))
{
int mode;
if (assuan_sock_get_flag (ASSUAN_INVALID_FD, "tor-mode", &mode) || !mode)
{
log_error ("Tor support is not available\n");
return gpg_err_make (default_errsource, GPG_ERR_NOT_IMPLEMENTED);
}
/* Non-blocking connects do not work with our Tor proxy because
* we can't continue the Socks protocol after the EINPROGRESS.
* Disable the timeout to use a blocking connect. */
timeout = 0;
}
/* Create the handle. */
hd = xtrycalloc (1, sizeof *hd);
if (!hd)
return gpg_error_from_syserror ();
hd->magic = HTTP_CONTEXT_MAGIC;
hd->req_type = HTTP_REQ_OPAQUE;
hd->flags = flags;
/* Connect. */
{
assuan_fd_t sock;
err = connect_server (ctrl, server, port,
hd->flags, srvtag, timeout, &sock);
if (err)
{
xfree (hd);
return err;
}
hd->sock = my_socket_new (sock);
if (!hd->sock)
{
err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
xfree (hd);
return err;
}
}
/* Setup estreams for reading and writing. */
cookie = xtrycalloc (1, sizeof *cookie);
if (!cookie)
{
err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
goto leave;
}
cookie->sock = my_socket_ref (hd->sock);
hd->fp_write = es_fopencookie (cookie, "w", cookie_functions);
if (!hd->fp_write)
{
err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
my_socket_unref (cookie->sock, NULL, NULL);
xfree (cookie);
goto leave;
}
hd->write_cookie = cookie; /* Cookie now owned by FP_WRITE. */
cookie = xtrycalloc (1, sizeof *cookie);
if (!cookie)
{
err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
goto leave;
}
cookie->sock = my_socket_ref (hd->sock);
hd->fp_read = es_fopencookie (cookie, "r", cookie_functions);
if (!hd->fp_read)
{
err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
my_socket_unref (cookie->sock, NULL, NULL);
xfree (cookie);
goto leave;
}
hd->read_cookie = cookie; /* Cookie now owned by FP_READ. */
/* Register close notification to interlock the use of es_fclose in
http_close and in user code. */
err = es_onclose (hd->fp_write, 1, fp_onclose_notification, hd);
if (!err)
err = es_onclose (hd->fp_read, 1, fp_onclose_notification, hd);
leave:
if (err)
{
if (hd->fp_read)
es_fclose (hd->fp_read);
if (hd->fp_write)
es_fclose (hd->fp_write);
my_socket_unref (hd->sock, NULL, NULL);
xfree (hd);
}
else
*r_hd = hd;
return err;
}
void
http_start_data (http_t hd)
{
if (!hd->in_data)
{
if (opt_debug || (hd->flags & HTTP_FLAG_LOG_RESP))
log_debug_string ("\r\n", "http.c:request-header:");
es_fputs ("\r\n", hd->fp_write);
es_fflush (hd->fp_write);
hd->in_data = 1;
}
else
es_fflush (hd->fp_write);
}
gpg_error_t
http_wait_response (http_t hd)
{
gpg_error_t err;
cookie_t cookie;
int use_tls;
/* Make sure that we are in the data. */
http_start_data (hd);
/* Close the write stream. Note that the reference counted socket
object keeps the actual system socket open. */
cookie = hd->write_cookie;
if (!cookie)
return gpg_err_make (default_errsource, GPG_ERR_INTERNAL);
use_tls = cookie->use_tls;
es_fclose (hd->fp_write);
hd->fp_write = NULL;
/* The close has released the cookie and thus we better set it to NULL. */
hd->write_cookie = NULL;
/* Shutdown one end of the socket is desired. As per HTTP/1.0 this
is not required but some very old servers (e.g. the original pksd
keyserver didn't worked without it. */
if ((hd->flags & HTTP_FLAG_SHUTDOWN))
shutdown (FD2INT (hd->sock->fd), 1);
hd->in_data = 0;
/* Create a new cookie and a stream for reading. */
cookie = xtrycalloc (1, sizeof *cookie);
if (!cookie)
return gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
cookie->sock = my_socket_ref (hd->sock);
cookie->session = http_session_ref (hd->session);
cookie->use_tls = use_tls;
hd->read_cookie = cookie;
hd->fp_read = es_fopencookie (cookie, "r", cookie_functions);
if (!hd->fp_read)
{
err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
my_socket_unref (cookie->sock, NULL, NULL);
http_session_unref (cookie->session);
xfree (cookie);
hd->read_cookie = NULL;
return err;
}
err = parse_response (hd);
if (!err)
err = es_onclose (hd->fp_read, 1, fp_onclose_notification, hd);
return err;
}
/* Convenience function to send a request and wait for the response.
Closes the handle on error. If PROXY is not NULL, this value will
be used as an HTTP proxy and any enabled $http_proxy gets
ignored. */
gpg_error_t
http_open_document (ctrl_t ctrl, http_t *r_hd, const char *document,
const char *auth, unsigned int flags, const char *proxy,
http_session_t session,
const char *srvtag, strlist_t headers)
{
gpg_error_t err;
err = http_open (ctrl, r_hd, HTTP_REQ_GET, document, NULL, auth, flags,
proxy, session, srvtag, headers);
if (err)
return err;
err = http_wait_response (*r_hd);
if (err)
http_close (*r_hd, 0);
return err;
}
void
http_close (http_t hd, int keep_read_stream)
{
if (!hd)
return;
log_assert (hd->magic == HTTP_CONTEXT_MAGIC);
/* First remove the close notifications for the streams. */
if (hd->fp_read)
es_onclose (hd->fp_read, 0, fp_onclose_notification, hd);
if (hd->fp_write)
es_onclose (hd->fp_write, 0, fp_onclose_notification, hd);
/* Now we can close the streams. */
my_socket_unref (hd->sock, NULL, NULL);
if (hd->fp_read && !keep_read_stream)
es_fclose (hd->fp_read);
if (hd->fp_write)
es_fclose (hd->fp_write);
http_session_unref (hd->session);
hd->magic = 0xdeadbeef;
http_release_parsed_uri (hd->uri);
while (hd->headers)
{
header_t tmp = hd->headers->next;
xfree (hd->headers->value);
xfree (hd->headers);
hd->headers = tmp;
}
xfree (hd->buffer);
xfree (hd);
}
estream_t
http_get_read_ptr (http_t hd)
{
return hd?hd->fp_read:NULL;
}
estream_t
http_get_write_ptr (http_t hd)
{
return hd?hd->fp_write:NULL;
}
unsigned int
http_get_status_code (http_t hd)
{
return hd?hd->status_code:0;
}
/* Return information pertaining to TLS. If TLS is not in use for HD,
NULL is returned. WHAT is used ask for specific information:
(NULL) := Only check whether TLS is in use. Returns an
unspecified string if TLS is in use. That string may
even be the empty string.
*/
const char *
http_get_tls_info (http_t hd, const char *what)
{
(void)what;
if (!hd)
return NULL;
return hd->uri->use_tls? "":NULL;
}
static gpg_error_t
parse_uri (parsed_uri_t *ret_uri, const char *uri,
int no_scheme_check, int force_tls)
{
gpg_err_code_t ec;
*ret_uri = xtrycalloc (1, sizeof **ret_uri + 2 * strlen (uri) + 1);
if (!*ret_uri)
return gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
strcpy ((*ret_uri)->buffer, uri);
strcpy ((*ret_uri)->buffer + strlen (uri) + 1, uri);
(*ret_uri)->original = (*ret_uri)->buffer + strlen (uri) + 1;
ec = do_parse_uri (*ret_uri, 0, no_scheme_check, force_tls);
if (ec)
{
http_release_parsed_uri (*ret_uri);
*ret_uri = NULL;
}
return gpg_err_make (default_errsource, ec);
}
/*
* Parse an URI and put the result into the newly allocated RET_URI.
* On success the caller must use http_release_parsed_uri() to
* releases the resources. If NO_SCHEME_CHECK is set, the function
* tries to parse the URL in the same way it would do for an HTTP
* style URI.
*/
gpg_error_t
http_parse_uri (parsed_uri_t *ret_uri, const char *uri,
int no_scheme_check)
{
return parse_uri (ret_uri, uri, no_scheme_check, 0);
}
void
http_release_parsed_uri (parsed_uri_t uri)
{
if (uri)
{
uri_tuple_t r, r2;
for (r = uri->params; r; r = r2)
{
r2 = r->next;
xfree (r);
}
for (r = uri->query; r; r = r2)
{
r2 = r->next;
xfree (r);
}
xfree (uri);
}
}
static gpg_err_code_t
do_parse_uri (parsed_uri_t uri, int only_local_part,
int no_scheme_check, int force_tls)
{
uri_tuple_t *tail;
char *p, *p2, *p3, *pp;
int n;
p = uri->buffer;
n = strlen (uri->buffer);
/* Initialize all fields to an empty string or an empty list. */
uri->scheme = uri->host = uri->path = p + n;
uri->port = 0;
uri->params = uri->query = NULL;
uri->use_tls = 0;
uri->is_http = 0;
uri->opaque = 0;
uri->v6lit = 0;
uri->onion = 0;
uri->explicit_port = 0;
uri->off_host = 0;
uri->off_path = 0;
/* A quick validity check. */
if (strspn (p, VALID_URI_CHARS) != n)
return GPG_ERR_BAD_URI; /* Invalid characters found. */
if (!only_local_part)
{
/* Find the scheme. */
if (!(p2 = strchr (p, ':')) || p2 == p)
return GPG_ERR_BAD_URI; /* No scheme. */
*p2++ = 0;
for (pp=p; *pp; pp++)
*pp = tolower (*(unsigned char*)pp);
uri->scheme = p;
if (!strcmp (uri->scheme, "http") && !force_tls)
{
uri->port = 80;
uri->is_http = 1;
}
else if (!strcmp (uri->scheme, "hkp") && !force_tls)
{
uri->port = 11371;
uri->is_http = 1;
}
#ifdef USE_TLS
else if (!strcmp (uri->scheme, "https") || !strcmp (uri->scheme,"hkps")
|| (force_tls && (!strcmp (uri->scheme, "http")
|| !strcmp (uri->scheme,"hkp"))))
{
uri->port = 443;
uri->is_http = 1;
uri->use_tls = 1;
}
#endif /*USE_TLS*/
else if (!no_scheme_check)
return GPG_ERR_INV_URI; /* Unsupported scheme */
p = p2;
if (*p == '/' && p[1] == '/' ) /* There seems to be a hostname. */
{
p += 2;
if ((p2 = strchr (p, '/')))
{
if (p2 - uri->buffer > 10000)
return GPG_ERR_BAD_URI;
uri->off_path = p2 - uri->buffer;
*p2++ = 0;
}
else
{
n = (p - uri->buffer) + strlen (p);
if (n > 10000)
return GPG_ERR_BAD_URI;
uri->off_path = n;
}
/* Check for username/password encoding */
if ((p3 = strchr (p, '@')))
{
uri->auth = p;
*p3++ = '\0';
p = p3;
}
for (pp=p; *pp; pp++)
*pp = tolower (*(unsigned char*)pp);
/* Handle an IPv6 literal */
if( *p == '[' && (p3=strchr( p, ']' )) )
{
*p3++ = '\0';
/* worst case, uri->host should have length 0, points to \0 */
uri->host = p + 1;
if (p - uri->buffer > 10000)
return GPG_ERR_BAD_URI;
uri->off_host = (p + 1) - uri->buffer;
uri->v6lit = 1;
p = p3;
}
else
{
uri->host = p;
if (p - uri->buffer > 10000)
return GPG_ERR_BAD_URI;
uri->off_host = p - uri->buffer;
}
if ((p3 = strchr (p, ':')))
{
*p3++ = '\0';
uri->port = atoi (p3);
uri->explicit_port = 1;
}
if ((n = remove_escapes (uri->host)) < 0)
return GPG_ERR_BAD_URI;
if (n != strlen (uri->host))
return GPG_ERR_BAD_URI; /* Hostname includes a Nul. */
p = p2 ? p2 : NULL;
}
else if (uri->is_http)
return GPG_ERR_INV_URI; /* No Leading double slash for HTTP. */
else
{
uri->opaque = 1;
uri->path = p;
if (is_onion_address (uri->path))
uri->onion = 1;
return 0;
}
} /* End global URI part. */
/* Parse the pathname part if any. */
if (p && *p)
{
/* TODO: Here we have to check params. */
/* Do we have a query part? */
if ((p2 = strchr (p, '?')))
*p2++ = 0;
uri->path = p;
if ((n = remove_escapes (p)) < 0)
return GPG_ERR_BAD_URI;
if (n != strlen (p))
return GPG_ERR_BAD_URI; /* Path includes a Nul. */
p = p2 ? p2 : NULL;
/* Parse a query string if any. */
if (p && *p)
{
tail = &uri->query;
for (;;)
{
uri_tuple_t elem;
if ((p2 = strchr (p, '&')))
*p2++ = 0;
if (!(elem = parse_tuple (p)))
return GPG_ERR_BAD_URI;
*tail = elem;
tail = &elem->next;
if (!p2)
break; /* Ready. */
p = p2;
}
}
}
if (is_onion_address (uri->host))
uri->onion = 1;
return 0;
}
/*
* Remove all %xx escapes; this is done in-place. Returns: New length
* of the string.
*/
static int
remove_escapes (char *string)
{
int n = 0;
unsigned char *p, *s;
for (p = s = (unsigned char*)string; *s; s++)
{
if (*s == '%')
{
if (s[1] && s[2] && isxdigit (s[1]) && isxdigit (s[2]))
{
s++;
*p = *s >= '0' && *s <= '9' ? *s - '0' :
*s >= 'A' && *s <= 'F' ? *s - 'A' + 10 : *s - 'a' + 10;
*p <<= 4;
s++;
*p |= *s >= '0' && *s <= '9' ? *s - '0' :
*s >= 'A' && *s <= 'F' ? *s - 'A' + 10 : *s - 'a' + 10;
p++;
n++;
}
else
{
*p++ = *s++;
if (*s)
*p++ = *s++;
if (*s)
*p++ = *s++;
if (*s)
*p = 0;
return -1; /* Bad URI. */
}
}
else
{
*p++ = *s;
n++;
}
}
*p = 0; /* Make sure to keep a string terminator. */
return n;
}
/* If SPECIAL is NULL this function escapes in forms mode. */
static size_t
escape_data (char *buffer, const void *data, size_t datalen,
const char *special)
{
int forms = !special;
const unsigned char *s;
size_t n = 0;
if (forms)
special = "%;?&=";
for (s = data; datalen; s++, datalen--)
{
if (forms && *s == ' ')
{
if (buffer)
*buffer++ = '+';
n++;
}
else if (forms && *s == '\n')
{
if (buffer)
memcpy (buffer, "%0D%0A", 6);
n += 6;
}
else if (forms && *s == '\r' && datalen > 1 && s[1] == '\n')
{
if (buffer)
memcpy (buffer, "%0D%0A", 6);
n += 6;
s++;
datalen--;
}
else if (strchr (VALID_URI_CHARS, *s) && !strchr (special, *s))
{
if (buffer)
*(unsigned char*)buffer++ = *s;
n++;
}
else
{
if (buffer)
{
snprintf (buffer, 4, "%%%02X", *s);
buffer += 3;
}
n += 3;
}
}
return n;
}
static int
insert_escapes (char *buffer, const char *string,
const char *special)
{
return escape_data (buffer, string, strlen (string), special);
}
/* Allocate a new string from STRING using standard HTTP escaping as
well as escaping of characters given in SPECIALS. A common pattern
for SPECIALS is "%;?&=". However it depends on the needs, for
example "+" and "/: often needs to be escaped too. Returns NULL on
failure and sets ERRNO. If SPECIAL is NULL a dedicated forms
encoding mode is used. */
char *
http_escape_string (const char *string, const char *specials)
{
int n;
char *buf;
n = insert_escapes (NULL, string, specials);
buf = xtrymalloc (n+1);
if (buf)
{
insert_escapes (buf, string, specials);
buf[n] = 0;
}
return buf;
}
/* Allocate a new string from {DATA,DATALEN} using standard HTTP
escaping as well as escaping of characters given in SPECIALS. A
common pattern for SPECIALS is "%;?&=". However it depends on the
needs, for example "+" and "/: often needs to be escaped too.
Returns NULL on failure and sets ERRNO. If SPECIAL is NULL a
dedicated forms encoding mode is used. */
char *
http_escape_data (const void *data, size_t datalen, const char *specials)
{
int n;
char *buf;
n = escape_data (NULL, data, datalen, specials);
buf = xtrymalloc (n+1);
if (buf)
{
escape_data (buf, data, datalen, specials);
buf[n] = 0;
}
return buf;
}
static uri_tuple_t
parse_tuple (char *string)
{
char *p = string;
char *p2;
int n;
uri_tuple_t tuple;
if ((p2 = strchr (p, '=')))
*p2++ = 0;
if ((n = remove_escapes (p)) < 0)
return NULL; /* Bad URI. */
if (n != strlen (p))
return NULL; /* Name with a Nul in it. */
tuple = xtrycalloc (1, sizeof *tuple);
if (!tuple)
return NULL; /* Out of core. */
tuple->name = p;
if (!p2) /* We have only the name, so we assume an empty value string. */
{
tuple->value = p + strlen (p);
tuple->valuelen = 0;
tuple->no_value = 1; /* Explicitly mark that we have seen no '='. */
}
else /* Name and value. */
{
if ((n = remove_escapes (p2)) < 0)
{
xfree (tuple);
return NULL; /* Bad URI. */
}
tuple->value = p2;
tuple->valuelen = n;
}
return tuple;
}
/* Return true if STRING is likely "hostname:port" or only "hostname". */
static int
is_hostname_port (const char *string)
{
int colons = 0;
if (!string || !*string)
return 0;
for (; *string; string++)
{
if (*string == ':')
{
if (colons)
return 0;
if (!string[1])
return 0;
colons++;
}
else if (!colons && strchr (" \t\f\n\v_@[]/", *string))
return 0; /* Invalid characters in hostname. */
else if (colons && !digitp (string))
return 0; /* Not a digit in the port. */
}
return 1;
}
/*
* Send a HTTP request to the server
* Returns 0 if the request was successful
*/
static gpg_error_t
send_request (ctrl_t ctrl, http_t hd, const char *httphost, const char *auth,
const char *proxy, const char *srvtag, unsigned int timeout,
strlist_t headers)
{
gpg_error_t err;
const char *server;
char *request, *p;
unsigned short port;
const char *http_proxy = NULL;
char *proxy_authstr = NULL;
char *authstr = NULL;
assuan_fd_t sock;
#ifdef USE_TLS
int have_http_proxy = 0;
#endif
if (hd->uri->use_tls && !hd->session)
{
log_error ("TLS requested but no session object provided\n");
return gpg_err_make (default_errsource, GPG_ERR_INTERNAL);
}
#ifdef USE_TLS
if (hd->uri->use_tls && !hd->session->tls_session)
{
log_error ("TLS requested but no TLS context available\n");
return gpg_err_make (default_errsource, GPG_ERR_INTERNAL);
}
if (opt_debug)
log_debug ("Using TLS library: %s %s\n",
# if HTTP_USE_NTBTLS
"NTBTLS", ntbtls_check_version (NULL)
# elif HTTP_USE_GNUTLS
"GNUTLS", gnutls_check_version (NULL)
# else
"?", "?"
# endif /*HTTP_USE_*TLS*/
);
#endif /*USE_TLS*/
if ((hd->flags & HTTP_FLAG_FORCE_TOR))
{
int mode;
if (assuan_sock_get_flag (ASSUAN_INVALID_FD, "tor-mode", &mode) || !mode)
{
log_error ("Tor support is not available\n");
return gpg_err_make (default_errsource, GPG_ERR_NOT_IMPLEMENTED);
}
/* Non-blocking connects do not work with our Tor proxy because
* we can't continue the Socks protocol after the EINPROGRESS.
* Disable the timeout to use a blocking connect. */
timeout = 0;
}
server = *hd->uri->host ? hd->uri->host : "localhost";
port = hd->uri->port ? hd->uri->port : 80;
/* Try to use SNI. */
#ifdef USE_TLS
if (hd->uri->use_tls)
{
# if HTTP_USE_GNUTLS
int rc;
# endif
xfree (hd->session->servername);
hd->session->servername = xtrystrdup (httphost? httphost : server);
if (!hd->session->servername)
{
err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
return err;
}
# if HTTP_USE_NTBTLS
err = ntbtls_set_hostname (hd->session->tls_session,
hd->session->servername);
if (err)
{
log_info ("ntbtls_set_hostname failed: %s\n", gpg_strerror (err));
return err;
}
# elif HTTP_USE_GNUTLS
rc = gnutls_server_name_set (hd->session->tls_session,
GNUTLS_NAME_DNS,
hd->session->servername,
strlen (hd->session->servername));
if (rc < 0)
log_info ("gnutls_server_name_set failed: %s\n", gnutls_strerror (rc));
# endif /*HTTP_USE_GNUTLS*/
}
#endif /*USE_TLS*/
if ( (proxy && *proxy)
|| ( (hd->flags & HTTP_FLAG_TRY_PROXY)
&& (http_proxy = getenv (HTTP_PROXY_ENV))
&& *http_proxy ))
{
parsed_uri_t uri;
if (proxy)
http_proxy = proxy;
err = parse_uri (&uri, http_proxy, 0, 0);
if (gpg_err_code (err) == GPG_ERR_INV_URI
&& is_hostname_port (http_proxy))
{
/* Retry assuming a "hostname:port" string. */
char *tmpname = strconcat ("http://", http_proxy, NULL);
if (tmpname && !parse_uri (&uri, tmpname, 0, 0))
err = 0;
xfree (tmpname);
}
if (err)
;
#ifdef USE_TLS
else if (!strcmp (uri->scheme, "http"))
have_http_proxy = 1;
#endif
else if (!strcmp (uri->scheme, "socks4")
|| !strcmp (uri->scheme, "socks5h"))
err = gpg_err_make (default_errsource, GPG_ERR_NOT_IMPLEMENTED);
else
err = gpg_err_make (default_errsource, GPG_ERR_INV_URI);
if (err)
{
log_error ("invalid HTTP proxy (%s): %s\n",
http_proxy, gpg_strerror (err));
return gpg_err_make (default_errsource, GPG_ERR_CONFIGURATION);
}
if (uri->auth)
{
remove_escapes (uri->auth);
proxy_authstr = make_header_line ("Proxy-Authorization: Basic ",
"\r\n",
uri->auth, strlen(uri->auth));
if (!proxy_authstr)
{
err = gpg_err_make (default_errsource,
gpg_err_code_from_syserror ());
http_release_parsed_uri (uri);
return err;
}
}
err = connect_server (ctrl,
*uri->host ? uri->host : "localhost",
uri->port ? uri->port : 80,
hd->flags, NULL, timeout, &sock);
http_release_parsed_uri (uri);
}
else
{
err = connect_server (ctrl,
server, port, hd->flags, srvtag, timeout, &sock);
}
if (err)
{
xfree (proxy_authstr);
return err;
}
hd->sock = my_socket_new (sock);
if (!hd->sock)
{
xfree (proxy_authstr);
return gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
}
#if USE_TLS
if (have_http_proxy && hd->uri->use_tls)
{
int saved_flags;
cookie_t cookie;
/* Try to use the CONNECT method to proxy our TLS stream. */
request = es_bsprintf
("CONNECT %s:%hu HTTP/1.0\r\nHost: %s:%hu\r\n%s",
httphost ? httphost : server,
port,
httphost ? httphost : server,
port,
proxy_authstr ? proxy_authstr : "");
xfree (proxy_authstr);
proxy_authstr = NULL;
if (! request)
return gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
if (opt_debug || (hd->flags & HTTP_FLAG_LOG_RESP))
log_debug_string (request, "http.c:request:");
cookie = xtrycalloc (1, sizeof *cookie);
if (! cookie)
{
err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
xfree (request);
return err;
}
cookie->sock = my_socket_ref (hd->sock);
hd->write_cookie = cookie;
hd->fp_write = es_fopencookie (cookie, "w", cookie_functions);
if (! hd->fp_write)
{
err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
my_socket_unref (cookie->sock, NULL, NULL);
xfree (cookie);
xfree (request);
hd->write_cookie = NULL;
return err;
}
else if (es_fputs (request, hd->fp_write) || es_fflush (hd->fp_write))
err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
xfree (request);
request = NULL;
/* Make sure http_wait_response doesn't close the stream. */
saved_flags = hd->flags;
hd->flags &= ~HTTP_FLAG_SHUTDOWN;
/* Get the response. */
err = http_wait_response (hd);
/* Restore flags, destroy stream. */
hd->flags = saved_flags;
es_fclose (hd->fp_read);
hd->fp_read = NULL;
hd->read_cookie = NULL;
/* Reset state. */
hd->in_data = 0;
if (err)
return err;
if (hd->status_code != 200)
{
request = es_bsprintf
("CONNECT %s:%hu",
httphost ? httphost : server,
port);
log_error (_("error accessing '%s': http status %u\n"),
request ? request : "out of core",
http_get_status_code (hd));
xfree (request);
return gpg_error (GPG_ERR_NO_DATA);
}
/* We are done with the proxy, the code below will establish a
* TLS session and talk directly to the target server. */
http_proxy = NULL;
}
#endif /* USE_TLS */
#if HTTP_USE_NTBTLS
if (hd->uri->use_tls)
{
estream_t in, out;
my_socket_ref (hd->sock);
/* Until we support send/recv in estream under Windows we need
* to use es_fopencookie. */
#ifdef HAVE_W32_SYSTEM
in = es_fopencookie ((void*)(unsigned int)hd->sock->fd, "rb",
simple_cookie_functions);
#else
in = es_fdopen_nc (hd->sock->fd, "rb");
#endif
if (!in)
{
err = gpg_error_from_syserror ();
xfree (proxy_authstr);
return err;
}
#ifdef HAVE_W32_SYSTEM
out = es_fopencookie ((void*)(unsigned int)hd->sock->fd, "wb",
simple_cookie_functions);
#else
out = es_fdopen_nc (hd->sock->fd, "wb");
#endif
if (!out)
{
err = gpg_error_from_syserror ();
es_fclose (in);
xfree (proxy_authstr);
return err;
}
err = ntbtls_set_transport (hd->session->tls_session, in, out);
if (err)
{
log_info ("TLS set_transport failed: %s <%s>\n",
gpg_strerror (err), gpg_strsource (err));
xfree (proxy_authstr);
return err;
}
#ifdef HTTP_USE_NTBTLS
if (hd->session->verify_cb)
{
err = ntbtls_set_verify_cb (hd->session->tls_session,
my_ntbtls_verify_cb, hd);
if (err)
{
log_error ("ntbtls_set_verify_cb failed: %s\n",
gpg_strerror (err));
xfree (proxy_authstr);
return err;
}
}
#endif /*HTTP_USE_NTBTLS*/
while ((err = ntbtls_handshake (hd->session->tls_session)))
{
switch (err)
{
default:
log_info ("TLS handshake failed: %s <%s>\n",
gpg_strerror (err), gpg_strsource (err));
xfree (proxy_authstr);
return err;
}
}
hd->session->verify.done = 0;
/* Try the available verify callbacks until one returns success
* or a real error. Note that NTBTLS does the verification
* during the handshake via */
#ifdef HTTP_USE_NTBTLS
err = 0; /* Fixme check that the CB has been called. */
#else
err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
#endif
if (hd->session->verify_cb
&& gpg_err_source (err) == GPG_ERR_SOURCE_DIRMNGR
&& gpg_err_code (err) == GPG_ERR_NOT_IMPLEMENTED)
err = hd->session->verify_cb (hd->session->verify_cb_value,
hd, hd->session,
(hd->flags | hd->session->flags),
hd->session->tls_session);
if (tls_callback
&& gpg_err_source (err) == GPG_ERR_SOURCE_DIRMNGR
&& gpg_err_code (err) == GPG_ERR_NOT_IMPLEMENTED)
err = tls_callback (hd, hd->session, 0);
if (gpg_err_source (err) == GPG_ERR_SOURCE_DIRMNGR
&& gpg_err_code (err) == GPG_ERR_NOT_IMPLEMENTED)
err = http_verify_server_credentials (hd->session);
if (err)
{
log_info ("TLS connection authentication failed: %s <%s>\n",
gpg_strerror (err), gpg_strsource (err));
xfree (proxy_authstr);
return err;
}
}
#elif HTTP_USE_GNUTLS
if (hd->uri->use_tls)
{
int rc;
my_socket_ref (hd->sock);
gnutls_transport_set_ptr (hd->session->tls_session, hd->sock);
gnutls_transport_set_pull_function (hd->session->tls_session,
my_gnutls_read);
gnutls_transport_set_push_function (hd->session->tls_session,
my_gnutls_write);
handshake_again:
do
{
rc = gnutls_handshake (hd->session->tls_session);
}
while (rc == GNUTLS_E_INTERRUPTED || rc == GNUTLS_E_AGAIN);
if (rc < 0)
{
if (rc == GNUTLS_E_WARNING_ALERT_RECEIVED
|| rc == GNUTLS_E_FATAL_ALERT_RECEIVED)
{
gnutls_alert_description_t alertno;
const char *alertstr;
alertno = gnutls_alert_get (hd->session->tls_session);
alertstr = gnutls_alert_get_name (alertno);
log_info ("TLS handshake %s: %s (alert %d)\n",
rc == GNUTLS_E_WARNING_ALERT_RECEIVED
? "warning" : "failed",
alertstr, (int)alertno);
if (alertno == GNUTLS_A_UNRECOGNIZED_NAME && server)
log_info (" (sent server name '%s')\n", server);
if (rc == GNUTLS_E_WARNING_ALERT_RECEIVED)
goto handshake_again;
}
else
log_info ("TLS handshake failed: %s\n", gnutls_strerror (rc));
xfree (proxy_authstr);
return gpg_err_make (default_errsource, GPG_ERR_NETWORK);
}
hd->session->verify.done = 0;
if (tls_callback)
err = tls_callback (hd, hd->session, 0);
else
err = http_verify_server_credentials (hd->session);
if (err)
{
log_info ("TLS connection authentication failed: %s\n",
gpg_strerror (err));
xfree (proxy_authstr);
return err;
}
}
#endif /*HTTP_USE_GNUTLS*/
if (auth || hd->uri->auth)
{
char *myauth;
if (auth)
{
myauth = xtrystrdup (auth);
if (!myauth)
{
xfree (proxy_authstr);
return gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
}
remove_escapes (myauth);
}
else
{
remove_escapes (hd->uri->auth);
myauth = hd->uri->auth;
}
authstr = make_header_line ("Authorization: Basic ", "\r\n",
myauth, strlen (myauth));
if (auth)
xfree (myauth);
if (!authstr)
{
xfree (proxy_authstr);
return gpg_err_make (default_errsource,
gpg_err_code_from_syserror ());
}
}
p = build_rel_path (hd->uri);
if (!p)
return gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
if (http_proxy && *http_proxy)
{
request = es_bsprintf
("%s %s://%s:%hu%s%s HTTP/1.0\r\n%s%s",
hd->req_type == HTTP_REQ_GET ? "GET" :
hd->req_type == HTTP_REQ_HEAD ? "HEAD" :
hd->req_type == HTTP_REQ_POST ? "POST" : "OOPS",
hd->uri->use_tls? "https" : "http",
httphost? httphost : server,
port, *p == '/' ? "" : "/", p,
authstr ? authstr : "",
proxy_authstr ? proxy_authstr : "");
}
else
{
char portstr[35];
if (port == (hd->uri->use_tls? 443 : 80))
*portstr = 0;
else
snprintf (portstr, sizeof portstr, ":%u", port);
request = es_bsprintf
("%s %s%s HTTP/1.0\r\nHost: %s%s\r\n%s",
hd->req_type == HTTP_REQ_GET ? "GET" :
hd->req_type == HTTP_REQ_HEAD ? "HEAD" :
hd->req_type == HTTP_REQ_POST ? "POST" : "OOPS",
*p == '/' ? "" : "/", p,
httphost? httphost : server,
portstr,
authstr? authstr:"");
}
xfree (p);
if (!request)
{
err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
xfree (authstr);
xfree (proxy_authstr);
return err;
}
if (opt_debug || (hd->flags & HTTP_FLAG_LOG_RESP))
log_debug_string (request, "http.c:request:");
/* First setup estream so that we can write even the first line
using estream. This is also required for the sake of gnutls. */
{
cookie_t cookie;
cookie = xtrycalloc (1, sizeof *cookie);
if (!cookie)
{
err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
goto leave;
}
cookie->sock = my_socket_ref (hd->sock);
hd->write_cookie = cookie;
cookie->use_tls = hd->uri->use_tls;
cookie->session = http_session_ref (hd->session);
hd->fp_write = es_fopencookie (cookie, "w", cookie_functions);
if (!hd->fp_write)
{
err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
my_socket_unref (cookie->sock, NULL, NULL);
xfree (cookie);
hd->write_cookie = NULL;
}
else if (es_fputs (request, hd->fp_write) || es_fflush (hd->fp_write))
err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
else
err = 0;
if (!err)
{
for (;headers; headers=headers->next)
{
if (opt_debug || (hd->flags & HTTP_FLAG_LOG_RESP))
log_debug_string (headers->d, "http.c:request-header:");
if ((es_fputs (headers->d, hd->fp_write) || es_fflush (hd->fp_write))
|| (es_fputs("\r\n",hd->fp_write) || es_fflush(hd->fp_write)))
{
err = gpg_err_make (default_errsource,
gpg_err_code_from_syserror ());
break;
}
}
}
}
leave:
es_free (request);
xfree (authstr);
xfree (proxy_authstr);
return err;
}
/*
* Build the relative path from the parsed URI. Minimal
* implementation. May return NULL in case of memory failure; errno
* is then set accordingly.
*/
static char *
build_rel_path (parsed_uri_t uri)
{
uri_tuple_t r;
char *rel_path, *p;
int n;
/* Count the needed space. */
n = insert_escapes (NULL, uri->path, "%;?&");
/* TODO: build params. */
for (r = uri->query; r; r = r->next)
{
n++; /* '?'/'&' */
n += insert_escapes (NULL, r->name, "%;?&=");
if (!r->no_value)
{
n++; /* '=' */
n += insert_escapes (NULL, r->value, "%;?&=");
}
}
n++;
/* Now allocate and copy. */
p = rel_path = xtrymalloc (n);
if (!p)
return NULL;
n = insert_escapes (p, uri->path, "%;?&");
p += n;
/* TODO: add params. */
for (r = uri->query; r; r = r->next)
{
*p++ = r == uri->query ? '?' : '&';
n = insert_escapes (p, r->name, "%;?&=");
p += n;
if (!r->no_value)
{
*p++ = '=';
/* TODO: Use valuelen. */
n = insert_escapes (p, r->value, "%;?&=");
p += n;
}
}
*p = 0;
return rel_path;
}
/* Transform a header name into a standard capitalized format; e.g.
"Content-Type". Conversion stops at the colon. As usual we don't
use the localized versions of ctype.h. */
static void
capitalize_header_name (char *name)
{
int first = 1;
for (; *name && *name != ':'; name++)
{
if (*name == '-')
first = 1;
else if (first)
{
if (*name >= 'a' && *name <= 'z')
*name = *name - 'a' + 'A';
first = 0;
}
else if (*name >= 'A' && *name <= 'Z')
*name = *name - 'A' + 'a';
}
}
/* Store an HTTP header line in LINE away. Line continuation is
supported as well as merging of headers with the same name. This
function may modify LINE. */
static gpg_err_code_t
store_header (http_t hd, char *line)
{
size_t n;
char *p, *value;
header_t h;
n = strlen (line);
if (n && line[n-1] == '\n')
{
line[--n] = 0;
if (n && line[n-1] == '\r')
line[--n] = 0;
}
if (!n) /* we are never called to hit this. */
return GPG_ERR_BUG;
if (*line == ' ' || *line == '\t')
{
/* Continuation. This won't happen too often as it is not
recommended. We use a straightforward implementation. */
if (!hd->headers)
return GPG_ERR_PROTOCOL_VIOLATION;
n += strlen (hd->headers->value);
p = xtrymalloc (n+1);
if (!p)
return gpg_err_code_from_syserror ();
strcpy (stpcpy (p, hd->headers->value), line);
xfree (hd->headers->value);
hd->headers->value = p;
return 0;
}
capitalize_header_name (line);
p = strchr (line, ':');
if (!p)
return GPG_ERR_PROTOCOL_VIOLATION;
*p++ = 0;
while (*p == ' ' || *p == '\t')
p++;
value = p;
for (h=hd->headers; h; h = h->next)
if ( !strcmp (h->name, line) )
break;
if (h)
{
/* We have already seen a line with that name. Thus we assume
* it is a comma separated list and merge them. */
p = strconcat (h->value, ",", value, NULL);
if (!p)
return gpg_err_code_from_syserror ();
xfree (h->value);
h->value = p;
return 0;
}
/* Append a new header. */
h = xtrymalloc (sizeof *h + strlen (line));
if (!h)
return gpg_err_code_from_syserror ();
strcpy (h->name, line);
h->value = xtrymalloc (strlen (value)+1);
if (!h->value)
{
xfree (h);
return gpg_err_code_from_syserror ();
}
strcpy (h->value, value);
h->next = hd->headers;
hd->headers = h;
return 0;
}
/* Return the header NAME from the last response. The returned value
is valid as along as HD has not been closed and no other request
has been send. If the header was not found, NULL is returned. NAME
must be canonicalized, that is the first letter of each dash
delimited part must be uppercase and all other letters lowercase. */
const char *
http_get_header (http_t hd, const char *name)
{
header_t h;
for (h=hd->headers; h; h = h->next)
if ( !strcmp (h->name, name) )
return h->value;
return NULL;
}
/* Return a newly allocated and NULL terminated array with pointers to
header names. The array must be released with xfree() and its
content is only values as long as no other request has been
send. */
const char **
http_get_header_names (http_t hd)
{
const char **array;
size_t n;
header_t h;
for (n=0, h = hd->headers; h; h = h->next)
n++;
array = xtrycalloc (n+1, sizeof *array);
if (array)
{
for (n=0, h = hd->headers; h; h = h->next)
array[n++] = h->name;
}
return array;
}
/*
* Parse the response from a server.
* Returns: Errorcode and sets some files in the handle
*/
static gpg_err_code_t
parse_response (http_t hd)
{
char *line, *p, *p2;
size_t maxlen, len;
cookie_t cookie = hd->read_cookie;
const char *s;
/* Delete old header lines. */
while (hd->headers)
{
header_t tmp = hd->headers->next;
xfree (hd->headers->value);
xfree (hd->headers);
hd->headers = tmp;
}
/* Wait for the status line. */
do
{
maxlen = MAX_LINELEN;
len = es_read_line (hd->fp_read, &hd->buffer, &hd->buffer_size, &maxlen);
line = hd->buffer;
if (!line)
return gpg_err_code_from_syserror (); /* Out of core. */
if (!maxlen)
return GPG_ERR_TRUNCATED; /* Line has been truncated. */
if (!len)
return GPG_ERR_EOF;
if (opt_debug || (hd->flags & HTTP_FLAG_LOG_RESP))
log_debug_string (line, "http.c:response:\n");
}
while (!*line);
if ((p = strchr (line, '/')))
*p++ = 0;
if (!p || strcmp (line, "HTTP"))
return 0; /* Assume http 0.9. */
if ((p2 = strpbrk (p, " \t")))
{
*p2++ = 0;
p2 += strspn (p2, " \t");
}
if (!p2)
return 0; /* Also assume http 0.9. */
p = p2;
/* TODO: Add HTTP version number check. */
if ((p2 = strpbrk (p, " \t")))
*p2++ = 0;
if (!isdigit ((unsigned int)p[0]) || !isdigit ((unsigned int)p[1])
|| !isdigit ((unsigned int)p[2]) || p[3])
{
/* Malformed HTTP status code - assume http 0.9. */
hd->is_http_0_9 = 1;
hd->status_code = 200;
return 0;
}
hd->status_code = atoi (p);
/* Skip all the header lines and wait for the empty line. */
do
{
maxlen = MAX_LINELEN;
len = es_read_line (hd->fp_read, &hd->buffer, &hd->buffer_size, &maxlen);
line = hd->buffer;
if (!line)
return gpg_err_code_from_syserror (); /* Out of core. */
/* Note, that we can silently ignore truncated lines. */
if (!len)
return GPG_ERR_EOF;
/* Trim line endings of empty lines. */
if ((*line == '\r' && line[1] == '\n') || *line == '\n')
*line = 0;
if (opt_debug || (hd->flags & HTTP_FLAG_LOG_RESP))
log_info ("http.c:RESP: '%.*s'\n",
(int)strlen(line)-(*line&&line[1]?2:0),line);
if (*line)
{
gpg_err_code_t ec = store_header (hd, line);
if (ec)
return ec;
}
}
while (len && *line);
cookie->content_length_valid = 0;
if (!(hd->flags & HTTP_FLAG_IGNORE_CL))
{
s = http_get_header (hd, "Content-Length");
if (s)
{
cookie->content_length_valid = 1;
cookie->content_length = string_to_u64 (s);
}
}
return 0;
}
#if 0
static int
start_server ()
{
struct sockaddr_in mya;
struct sockaddr_in peer;
int fd, client;
fd_set rfds;
int addrlen;
int i;
if ((fd = socket (AF_INET, SOCK_STREAM, 0)) == -1)
{
log_error ("socket() failed: %s\n", strerror (errno));
return -1;
}
i = 1;
if (setsockopt (fd, SOL_SOCKET, SO_REUSEADDR, (byte *) & i, sizeof (i)))
log_info ("setsockopt(SO_REUSEADDR) failed: %s\n", strerror (errno));
mya.sin_family = AF_INET;
memset (&mya.sin_addr, 0, sizeof (mya.sin_addr));
mya.sin_port = htons (11371);
if (bind (fd, (struct sockaddr *) &mya, sizeof (mya)))
{
log_error ("bind to port 11371 failed: %s\n", strerror (errno));
sock_close (fd);
return -1;
}
if (listen (fd, 5))
{
log_error ("listen failed: %s\n", strerror (errno));
sock_close (fd);
return -1;
}
for (;;)
{
FD_ZERO (&rfds);
FD_SET (fd, &rfds);
if (my_select (fd + 1, &rfds, NULL, NULL, NULL) <= 0)
continue; /* ignore any errors */
if (!FD_ISSET (fd, &rfds))
continue;
addrlen = sizeof peer;
client = my_accept (fd, (struct sockaddr *) &peer, &addrlen);
if (client == -1)
continue; /* oops */
log_info ("connect from %s\n", inet_ntoa (peer.sin_addr));
fflush (stdout);
fflush (stderr);
if (!fork ())
{
int c;
FILE *fp;
fp = fdopen (client, "r");
while ((c = getc (fp)) != EOF)
putchar (c);
fclose (fp);
exit (0);
}
sock_close (client);
}
return 0;
}
#endif
/* Return true if SOCKS shall be used. This is the case if tor_mode
* is enabled and the desired address is not the loopback address.
* This function is basically a copy of the same internal function in
* Libassuan. */
static int
use_socks (struct sockaddr_storage *addr)
{
int mode;
if (assuan_sock_get_flag (ASSUAN_INVALID_FD, "tor-mode", &mode) || !mode)
return 0; /* Not in Tor mode. */
else if (addr->ss_family == AF_INET6)
{
struct sockaddr_in6 *addr_in6 = (struct sockaddr_in6 *)addr;
const unsigned char *s;
int i;
s = (unsigned char *)&addr_in6->sin6_addr.s6_addr;
if (s[15] != 1)
return 1; /* Last octet is not 1 - not the loopback address. */
for (i=0; i < 15; i++, s++)
if (*s)
return 1; /* Non-zero octet found - not the loopback address. */
return 0; /* This is the loopback address. */
}
else if (addr->ss_family == AF_INET)
{
struct sockaddr_in *addr_in = (struct sockaddr_in *)addr;
if (*(unsigned char*)&addr_in->sin_addr.s_addr == 127)
return 0; /* Loopback (127.0.0.0/8) */
return 1;
}
else
return 0;
}
/* Wrapper around assuan_sock_new which takes the domain from an
* address parameter. */
static assuan_fd_t
my_sock_new_for_addr (struct sockaddr_storage *addr, int type, int proto)
{
int domain;
if (use_socks (addr))
{
/* Libassaun always uses 127.0.0.1 to connect to the socks
* server (i.e. the Tor daemon). */
domain = AF_INET;
}
else
domain = addr->ss_family;
return assuan_sock_new (domain, type, proto);
}
/* Call WSAGetLastError and map it to a libgpg-error. */
#ifdef HAVE_W32_SYSTEM
static gpg_error_t
my_wsagetlasterror (void)
{
int wsaerr;
gpg_err_code_t ec;
wsaerr = WSAGetLastError ();
switch (wsaerr)
{
case WSAENOTSOCK: ec = GPG_ERR_EINVAL; break;
case WSAEWOULDBLOCK: ec = GPG_ERR_EAGAIN; break;
case ERROR_BROKEN_PIPE: ec = GPG_ERR_EPIPE; break;
case WSANOTINITIALISED: ec = GPG_ERR_ENOSYS; break;
case WSAENOBUFS: ec = GPG_ERR_ENOBUFS; break;
case WSAEMSGSIZE: ec = GPG_ERR_EMSGSIZE; break;
case WSAECONNREFUSED: ec = GPG_ERR_ECONNREFUSED; break;
case WSAEISCONN: ec = GPG_ERR_EISCONN; break;
case WSAEALREADY: ec = GPG_ERR_EALREADY; break;
case WSAETIMEDOUT: ec = GPG_ERR_ETIMEDOUT; break;
default: ec = GPG_ERR_EIO; break;
}
return gpg_err_make (default_errsource, ec);
}
#endif /*HAVE_W32_SYSTEM*/
/* Connect SOCK and return GPG_ERR_ETIMEOUT if a connection could not
* be established within TIMEOUT milliseconds. 0 indicates the
* system's default timeout. The other args are the usual connect
* args. On success 0 is returned, on timeout GPG_ERR_ETIMEDOUT, and
* another error code for other errors. On timeout the caller needs
* to close the socket as soon as possible to stop an ongoing
* handshake.
*
* This implementation is for well-behaving systems; see Stevens,
* Network Programming, 2nd edition, Vol 1, 15.4. */
static gpg_error_t
connect_with_timeout (assuan_fd_t sock,
struct sockaddr *addr, int addrlen,
unsigned int timeout)
{
gpg_error_t err;
int syserr;
socklen_t slen;
fd_set rset, wset;
struct timeval tval;
int n;
#ifndef HAVE_W32_SYSTEM
int oflags;
# define RESTORE_BLOCKING() do { \
fcntl (sock, F_SETFL, oflags); \
} while (0)
#else /*HAVE_W32_SYSTEM*/
# define RESTORE_BLOCKING() do { \
unsigned long along = 0; \
ioctlsocket (FD2INT (sock), FIONBIO, &along); \
} while (0)
#endif /*HAVE_W32_SYSTEM*/
if (!timeout)
{
/* Shortcut. */
if (assuan_sock_connect (sock, addr, addrlen))
err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
else
err = 0;
return err;
}
/* Switch the socket into non-blocking mode. */
#ifdef HAVE_W32_SYSTEM
{
unsigned long along = 1;
if (ioctlsocket (FD2INT (sock), FIONBIO, &along))
return my_wsagetlasterror ();
}
#else
oflags = fcntl (sock, F_GETFL, 0);
if (fcntl (sock, F_SETFL, oflags | O_NONBLOCK))
return gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
#endif
/* Do the connect. */
if (!assuan_sock_connect (sock, addr, addrlen))
{
/* Immediate connect. Restore flags. */
RESTORE_BLOCKING ();
return 0; /* Success. */
}
err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
if (gpg_err_code (err) != GPG_ERR_EINPROGRESS
#ifdef HAVE_W32_SYSTEM
&& gpg_err_code (err) != GPG_ERR_EAGAIN
#endif
)
{
RESTORE_BLOCKING ();
return err;
}
FD_ZERO (&rset);
FD_SET (FD2INT (sock), &rset);
wset = rset;
tval.tv_sec = timeout / 1000;
tval.tv_usec = (timeout % 1000) * 1000;
n = my_select (FD2INT(sock)+1, &rset, &wset, NULL, &tval);
if (n < 0)
{
err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
RESTORE_BLOCKING ();
return err;
}
if (!n)
{
/* Timeout: We do not restore the socket flags on timeout
* because the caller is expected to close the socket. */
return gpg_err_make (default_errsource, GPG_ERR_ETIMEDOUT);
}
if (!FD_ISSET (sock, &rset) && !FD_ISSET (sock, &wset))
{
/* select misbehaved. */
return gpg_err_make (default_errsource, GPG_ERR_SYSTEM_BUG);
}
slen = sizeof (syserr);
if (getsockopt (FD2INT(sock), SOL_SOCKET, SO_ERROR,
(void*)&syserr, &slen) < 0)
{
/* Assume that this is Solaris which returns the error in ERRNO. */
err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
}
else if (syserr)
err = gpg_err_make (default_errsource, gpg_err_code_from_errno (syserr));
else
err = 0; /* Connected. */
RESTORE_BLOCKING ();
return err;
#undef RESTORE_BLOCKING
}
/* Actually connect to a server. On success 0 is returned and the
* file descriptor for the socket is stored at R_SOCK; on error an
* error code is returned and ASSUAN_INVALID_FD is stored at R_SOCK.
* TIMEOUT is the connect timeout in milliseconds. Note that the
* function tries to connect to all known addresses and the timeout is
* for each one. */
static gpg_error_t
connect_server (ctrl_t ctrl, const char *server, unsigned short port,
unsigned int flags, const char *srvtag, unsigned int timeout,
assuan_fd_t *r_sock)
{
gpg_error_t err;
assuan_fd_t sock = ASSUAN_INVALID_FD;
unsigned int srvcount = 0;
int hostfound = 0;
int anyhostaddr = 0;
int srv, connected;
gpg_error_t last_err = 0;
struct srventry *serverlist = NULL;
*r_sock = ASSUAN_INVALID_FD;
#if defined(HAVE_W32_SYSTEM) && !defined(HTTP_NO_WSASTARTUP)
init_sockets ();
#endif /*Windows*/
/* Onion addresses require special treatment. */
if (is_onion_address (server))
{
#ifdef ASSUAN_SOCK_TOR
if (opt_debug)
log_debug ("http.c:connect_server:onion: name='%s' port=%hu\n",
server, port);
sock = assuan_sock_connect_byname (server, port, 0, NULL,
ASSUAN_SOCK_TOR);
if (sock == ASSUAN_INVALID_FD)
{
err = gpg_err_make (default_errsource,
(errno == EHOSTUNREACH)? GPG_ERR_UNKNOWN_HOST
: gpg_err_code_from_syserror ());
log_error ("can't connect to '%s': %s\n", server, gpg_strerror (err));
return err;
}
notify_netactivity ();
*r_sock = sock;
return 0;
#else /*!ASSUAN_SOCK_TOR*/
err = gpg_err_make (default_errsource, GPG_ERR_ENETUNREACH);
return ASSUAN_INVALID_FD;
#endif /*!HASSUAN_SOCK_TOR*/
}
/* Do the SRV thing */
if (srvtag)
{
err = get_dns_srv (ctrl, server, srvtag, NULL, &serverlist, &srvcount);
if (err)
log_info ("getting '%s' SRV for '%s' failed: %s\n",
srvtag, server, gpg_strerror (err));
/* Note that on error SRVCOUNT is zero. */
err = 0;
}
if (!serverlist)
{
/* Either we're not using SRV, or the SRV lookup failed. Make
up a fake SRV record. */
serverlist = xtrycalloc (1, sizeof *serverlist);
if (!serverlist)
return gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
serverlist->port = port;
strncpy (serverlist->target, server, DIMof (struct srventry, target));
serverlist->target[DIMof (struct srventry, target)-1] = '\0';
srvcount = 1;
}
connected = 0;
for (srv=0; srv < srvcount && !connected; srv++)
{
dns_addrinfo_t aibuf, ai;
if (opt_debug)
log_debug ("http.c:connect_server: trying name='%s' port=%hu\n",
serverlist[srv].target, port);
err = resolve_dns_name (ctrl,
serverlist[srv].target, port, 0, SOCK_STREAM,
&aibuf, NULL);
if (err)
{
log_info ("resolving '%s' failed: %s\n",
serverlist[srv].target, gpg_strerror (err));
last_err = err;
continue; /* Not found - try next one. */
}
hostfound = 1;
for (ai = aibuf; ai && !connected; ai = ai->next)
{
if (ai->family == AF_INET && (flags & HTTP_FLAG_IGNORE_IPv4))
continue;
if (ai->family == AF_INET6 && (flags & HTTP_FLAG_IGNORE_IPv6))
continue;
if (sock != ASSUAN_INVALID_FD)
assuan_sock_close (sock);
sock = my_sock_new_for_addr (ai->addr, ai->socktype, ai->protocol);
if (sock == ASSUAN_INVALID_FD)
{
err = gpg_err_make (default_errsource,
gpg_err_code_from_syserror ());
log_error ("error creating socket: %s\n", gpg_strerror (err));
free_dns_addrinfo (aibuf);
xfree (serverlist);
return err;
}
anyhostaddr = 1;
err = connect_with_timeout (sock, (struct sockaddr *)ai->addr,
ai->addrlen, timeout);
if (err)
{
last_err = err;
}
else
{
connected = 1;
notify_netactivity ();
}
}
free_dns_addrinfo (aibuf);
}
xfree (serverlist);
if (!connected)
{
if (!hostfound)
log_error ("can't connect to '%s': %s\n",
server, "host not found");
else if (!anyhostaddr)
log_error ("can't connect to '%s': %s\n",
server, "no IP address for host");
else
{
#ifdef HAVE_W32_SYSTEM
log_error ("can't connect to '%s': ec=%d\n",
server, (int)WSAGetLastError());
#else
log_error ("can't connect to '%s': %s\n",
server, gpg_strerror (last_err));
#endif
}
err = last_err? last_err : gpg_err_make (default_errsource,
GPG_ERR_UNKNOWN_HOST);
if (sock != ASSUAN_INVALID_FD)
assuan_sock_close (sock);
return err;
}
*r_sock = sock;
return 0;
}
/* Helper to read from a socket. This handles npth things and
* EINTR. */
static gpgrt_ssize_t
read_server (assuan_fd_t sock, void *buffer, size_t size)
{
int nread;
do
{
#ifdef HAVE_W32_SYSTEM
/* Under Windows we need to use recv for a socket. */
# if defined(USE_NPTH)
npth_unprotect ();
# endif
nread = recv (FD2INT (sock), buffer, size, 0);
# if defined(USE_NPTH)
npth_protect ();
# endif
#else /*!HAVE_W32_SYSTEM*/
# ifdef USE_NPTH
nread = npth_read (sock, buffer, size);
# else
nread = read (sock, buffer, size);
# endif
#endif /*!HAVE_W32_SYSTEM*/
}
while (nread == -1 && errno == EINTR);
return nread;
}
static gpg_error_t
write_server (assuan_fd_t sock, const char *data, size_t length)
{
int nleft;
int nwritten;
nleft = length;
while (nleft > 0)
{
#if defined(HAVE_W32_SYSTEM)
# if defined(USE_NPTH)
npth_unprotect ();
# endif
nwritten = send (FD2INT (sock), data, nleft, 0);
# if defined(USE_NPTH)
npth_protect ();
# endif
if ( nwritten == SOCKET_ERROR )
{
log_info ("network write failed: ec=%d\n", (int)WSAGetLastError ());
return gpg_error (GPG_ERR_NETWORK);
}
#else /*!HAVE_W32_SYSTEM*/
# ifdef USE_NPTH
nwritten = npth_write (sock, data, nleft);
# else
nwritten = write (sock, data, nleft);
# endif
if (nwritten == -1)
{
if (errno == EINTR)
continue;
if (errno == EAGAIN)
{
struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = 50000;
my_select (0, NULL, NULL, NULL, &tv);
continue;
}
log_info ("network write failed: %s\n", strerror (errno));
return gpg_error_from_syserror ();
}
#endif /*!HAVE_W32_SYSTEM*/
nleft -= nwritten;
data += nwritten;
}
return 0;
}
/* Read handler for estream. */
static gpgrt_ssize_t
cookie_read (void *cookie, void *buffer, size_t size)
{
cookie_t c = cookie;
int nread;
if (c->content_length_valid)
{
if (!c->content_length)
return 0; /* EOF */
if (c->content_length < size)
size = c->content_length;
}
#if HTTP_USE_NTBTLS
if (c->use_tls && c->session && c->session->tls_session)
{
estream_t in, out;
ntbtls_get_stream (c->session->tls_session, &in, &out);
nread = es_fread (buffer, 1, size, in);
if (opt_debug)
log_debug ("TLS network read: %d/%zu\n", nread, size);
}
else
#elif HTTP_USE_GNUTLS
if (c->use_tls && c->session && c->session->tls_session)
{
again:
nread = gnutls_record_recv (c->session->tls_session, buffer, size);
if (nread < 0)
{
if (nread == GNUTLS_E_INTERRUPTED)
goto again;
if (nread == GNUTLS_E_AGAIN)
{
struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = 50000;
my_select (0, NULL, NULL, NULL, &tv);
goto again;
}
if (nread == GNUTLS_E_REHANDSHAKE)
goto again; /* A client is allowed to just ignore this request. */
if (nread == GNUTLS_E_PREMATURE_TERMINATION)
{
/* The server terminated the connection. Close the TLS
session, and indicate EOF using a short read. */
close_tls_session (c->session);
return 0;
}
log_info ("TLS network read failed: %s\n", gnutls_strerror (nread));
gpg_err_set_errno (EIO);
return -1;
}
}
else
#endif /*HTTP_USE_GNUTLS*/
{
nread = read_server (c->sock->fd, buffer, size);
}
if (c->content_length_valid && nread > 0)
{
if (nread < c->content_length)
c->content_length -= nread;
else
c->content_length = 0;
}
return (gpgrt_ssize_t)nread;
}
/* Write handler for estream. */
static gpgrt_ssize_t
cookie_write (void *cookie, const void *buffer_arg, size_t size)
{
const char *buffer = buffer_arg;
cookie_t c = cookie;
int nwritten = 0;
#if HTTP_USE_NTBTLS
if (c->use_tls && c->session && c->session->tls_session)
{
estream_t in, out;
ntbtls_get_stream (c->session->tls_session, &in, &out);
if (size == 0)
es_fflush (out);
else
nwritten = es_fwrite (buffer, 1, size, out);
if (opt_debug)
log_debug ("TLS network write: %d/%zu\n", nwritten, size);
}
else
#elif HTTP_USE_GNUTLS
if (c->use_tls && c->session && c->session->tls_session)
{
int nleft = size;
while (nleft > 0)
{
nwritten = gnutls_record_send (c->session->tls_session,
buffer, nleft);
if (nwritten <= 0)
{
if (nwritten == GNUTLS_E_INTERRUPTED)
continue;
if (nwritten == GNUTLS_E_AGAIN)
{
struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = 50000;
my_select (0, NULL, NULL, NULL, &tv);
continue;
}
log_info ("TLS network write failed: %s\n",
gnutls_strerror (nwritten));
gpg_err_set_errno (EIO);
return -1;
}
nleft -= nwritten;
buffer += nwritten;
}
}
else
#endif /*HTTP_USE_GNUTLS*/
{
if ( write_server (c->sock->fd, buffer, size) )
{
gpg_err_set_errno (EIO);
nwritten = -1;
}
else
nwritten = size;
}
return (gpgrt_ssize_t)nwritten;
}
#if defined(HAVE_W32_SYSTEM) && defined(HTTP_USE_NTBTLS)
static gpgrt_ssize_t
simple_cookie_read (void *cookie, void *buffer, size_t size)
{
assuan_fd_t sock = (assuan_fd_t)cookie;
return read_server (sock, buffer, size);
}
static gpgrt_ssize_t
simple_cookie_write (void *cookie, const void *buffer_arg, size_t size)
{
assuan_fd_t sock = (assuan_fd_t)cookie;
const char *buffer = buffer_arg;
int nwritten;
if (write_server (sock, buffer, size))
{
gpg_err_set_errno (EIO);
nwritten = -1;
}
else
nwritten = size;
return (gpgrt_ssize_t)nwritten;
}
#endif /*HAVE_W32_SYSTEM*/
#ifdef HTTP_USE_GNUTLS
/* Wrapper for gnutls_bye used by my_socket_unref. */
static void
send_gnutls_bye (void *opaque)
{
tls_session_t tls_session = opaque;
int ret;
again:
do
ret = gnutls_bye (tls_session, GNUTLS_SHUT_RDWR);
while (ret == GNUTLS_E_INTERRUPTED);
if (ret == GNUTLS_E_AGAIN)
{
struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = 50000;
my_select (0, NULL, NULL, NULL, &tv);
goto again;
}
}
#endif /*HTTP_USE_GNUTLS*/
/* Close handler for estream. */
static int
cookie_close (void *cookie)
{
cookie_t c = cookie;
if (!c)
return 0;
#if HTTP_USE_NTBTLS
if (c->use_tls && c->session && c->session->tls_session)
{
/* FIXME!! Possibly call ntbtls_close_notify for close
of write stream. */
my_socket_unref (c->sock, NULL, NULL);
}
else
#elif HTTP_USE_GNUTLS
if (c->use_tls && c->session && c->session->tls_session)
my_socket_unref (c->sock, send_gnutls_bye, c->session->tls_session);
else
#endif /*HTTP_USE_GNUTLS*/
if (c->sock)
my_socket_unref (c->sock, NULL, NULL);
if (c->session)
http_session_unref (c->session);
xfree (c);
return 0;
}
/* Verify the credentials of the server. Returns 0 on success and
store the result in the session object. */
gpg_error_t
http_verify_server_credentials (http_session_t sess)
{
#if HTTP_USE_GNUTLS
static const char errprefix[] = "TLS verification of peer failed";
int rc;
unsigned int status;
const char *hostname;
const gnutls_datum_t *certlist;
unsigned int certlistlen;
gnutls_x509_crt_t cert;
gpg_error_t err = 0;
sess->verify.done = 1;
sess->verify.status = 0;
sess->verify.rc = GNUTLS_E_CERTIFICATE_ERROR;
if (gnutls_certificate_type_get (sess->tls_session) != GNUTLS_CRT_X509)
{
log_error ("%s: %s\n", errprefix, "not an X.509 certificate");
sess->verify.rc = GNUTLS_E_UNSUPPORTED_CERTIFICATE_TYPE;
return gpg_error (GPG_ERR_GENERAL);
}
rc = gnutls_certificate_verify_peers2 (sess->tls_session, &status);
if (rc)
{
log_error ("%s: %s\n", errprefix, gnutls_strerror (rc));
if (!err)
err = gpg_error (GPG_ERR_GENERAL);
}
else if (status)
{
log_error ("%s: status=0x%04x\n", errprefix, status);
#if GNUTLS_VERSION_NUMBER >= 0x030104
{
gnutls_datum_t statusdat;
if (!gnutls_certificate_verification_status_print
(status, GNUTLS_CRT_X509, &statusdat, 0))
{
log_info ("%s: %s\n", errprefix, statusdat.data);
gnutls_free (statusdat.data);
}
}
#endif /*gnutls >= 3.1.4*/
sess->verify.status = status;
if (!err)
err = gpg_error (GPG_ERR_GENERAL);
}
hostname = sess->servername;
if (!hostname || !strchr (hostname, '.'))
{
log_error ("%s: %s\n", errprefix, "hostname missing");
if (!err)
err = gpg_error (GPG_ERR_GENERAL);
}
certlist = gnutls_certificate_get_peers (sess->tls_session, &certlistlen);
if (!certlistlen)
{
log_error ("%s: %s\n", errprefix, "server did not send a certificate");
if (!err)
err = gpg_error (GPG_ERR_GENERAL);
/* Need to stop here. */
if (err)
return err;
}
rc = gnutls_x509_crt_init (&cert);
if (rc < 0)
{
if (!err)
err = gpg_error (GPG_ERR_GENERAL);
if (err)
return err;
}
rc = gnutls_x509_crt_import (cert, &certlist[0], GNUTLS_X509_FMT_DER);
if (rc < 0)
{
log_error ("%s: %s: %s\n", errprefix, "error importing certificate",
gnutls_strerror (rc));
if (!err)
err = gpg_error (GPG_ERR_GENERAL);
}
if (!gnutls_x509_crt_check_hostname (cert, hostname))
{
log_error ("%s: %s\n", errprefix, "hostname does not match");
if (!err)
err = gpg_error (GPG_ERR_GENERAL);
}
gnutls_x509_crt_deinit (cert);
if (!err)
sess->verify.rc = 0;
if (sess->cert_log_cb)
{
const void *bufarr[10];
size_t buflenarr[10];
size_t n;
for (n = 0; n < certlistlen && n < DIM (bufarr)-1; n++)
{
bufarr[n] = certlist[n].data;
buflenarr[n] = certlist[n].size;
}
bufarr[n] = NULL;
buflenarr[n] = 0;
sess->cert_log_cb (sess, err, hostname, bufarr, buflenarr);
}
return err;
#else /*!HTTP_USE_GNUTLS*/
(void)sess;
return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
#endif
}
/* Return the first query variable with the specified key. If there
is no such variable, return NULL. */
struct uri_tuple_s *
uri_query_lookup (parsed_uri_t uri, const char *key)
{
struct uri_tuple_s *t;
for (t = uri->query; t; t = t->next)
if (strcmp (t->name, key) == 0)
return t;
return NULL;
}
/* Return true if both URI point to the same host for the purpose of
* redirection check. A is the original host and B the host given in
* the Location header. As a temporary workaround a fixed list of
* exceptions is also consulted. */
static int
same_host_p (parsed_uri_t a, parsed_uri_t b)
{
static struct
{
const char *from; /* NULL uses the last entry from the table. */
const char *to;
} allow[] =
{
{ "protonmail.com", "api.protonmail.com" },
{ NULL, "api.protonmail.ch" },
{ "protonmail.ch", "api.protonmail.com" },
- { NULL, "api.protonmail.ch" }
+ { NULL, "api.protonmail.ch" },
+ { "pm.me", "api.protonmail.ch" }
};
+ static const char *subdomains[] =
+ {
+ "openpgpkey."
+ };
int i;
const char *from;
if (!a->host || !b->host)
return 0;
if (!ascii_strcasecmp (a->host, b->host))
return 1;
from = NULL;
for (i=0; i < DIM (allow); i++)
{
if (allow[i].from)
from = allow[i].from;
if (!from)
continue;
if (!ascii_strcasecmp (from, a->host)
&& !ascii_strcasecmp (allow[i].to, b->host))
return 1;
}
+ /* Also consider hosts the same if they differ only in a subdomain;
+ * in both direction. This allows to have redirection between the
+ * WKD advanced and direct lookup methods. */
+ for (i=0; i < DIM (subdomains); i++)
+ {
+ const char *subdom = subdomains[i];
+ size_t subdomlen = strlen (subdom);
+
+ if (!ascii_strncasecmp (a->host, subdom, subdomlen)
+ && !ascii_strcasecmp (a->host + subdomlen, b->host))
+ return 1;
+ if (!ascii_strncasecmp (b->host, subdom, subdomlen)
+ && !ascii_strcasecmp (b->host + subdomlen, a->host))
+ return 1;
+ }
+
return 0;
}
/* Prepare a new URL for a HTTP redirect. INFO has flags controlling
* the operation, STATUS_CODE is used for diagnostics, LOCATION is the
* value of the "Location" header, and R_URL reveives the new URL on
* success or NULL or error. Note that INFO->ORIG_URL is
* required. */
gpg_error_t
http_prepare_redirect (http_redir_info_t *info, unsigned int status_code,
const char *location, char **r_url)
{
gpg_error_t err;
parsed_uri_t locuri;
parsed_uri_t origuri;
char *newurl;
char *p;
*r_url = NULL;
if (!info || !info->orig_url)
return gpg_error (GPG_ERR_INV_ARG);
if (!info->silent)
log_info (_("URL '%s' redirected to '%s' (%u)\n"),
info->orig_url, location? location:"[none]", status_code);
if (!info->redirects_left)
{
if (!info->silent)
log_error (_("too many redirections\n"));
return gpg_error (GPG_ERR_NO_DATA);
}
info->redirects_left--;
if (!location || !*location)
return gpg_error (GPG_ERR_NO_DATA);
err = http_parse_uri (&locuri, location, 0);
if (err)
return err;
/* Make sure that an onion address only redirects to another
* onion address, or that a https address only redirects to a
* https address. */
if (info->orig_onion && !locuri->onion)
{
http_release_parsed_uri (locuri);
return gpg_error (GPG_ERR_FORBIDDEN);
}
if (!info->allow_downgrade && info->orig_https && !locuri->use_tls)
{
http_release_parsed_uri (locuri);
return gpg_error (GPG_ERR_FORBIDDEN);
}
if (info->trust_location)
{
/* We trust the Location - return it verbatim. */
http_release_parsed_uri (locuri);
newurl = xtrystrdup (location);
if (!newurl)
{
err = gpg_error_from_syserror ();
http_release_parsed_uri (locuri);
return err;
}
}
else if ((err = http_parse_uri (&origuri, info->orig_url, 0)))
{
http_release_parsed_uri (locuri);
return err;
}
else if (same_host_p (origuri, locuri))
{
/* The host is the same or on an exception list and thus we can
* take the location verbatim. */
http_release_parsed_uri (origuri);
http_release_parsed_uri (locuri);
newurl = xtrystrdup (location);
if (!newurl)
{
err = gpg_error_from_syserror ();
http_release_parsed_uri (locuri);
return err;
}
}
else
{
/* We take only the host and port from the URL given in the
* Location. This limits the effects of redirection attacks by
* rogue hosts returning an URL to servers in the client's own
* network. We don't even include the userinfo because they
* should be considered similar to the path and query parts.
*/
if (!(locuri->off_path - locuri->off_host))
{
http_release_parsed_uri (origuri);
http_release_parsed_uri (locuri);
return gpg_error (GPG_ERR_BAD_URI);
}
if (!(origuri->off_path - origuri->off_host))
{
http_release_parsed_uri (origuri);
http_release_parsed_uri (locuri);
return gpg_error (GPG_ERR_BAD_URI);
}
newurl = xtrymalloc (strlen (origuri->original)
+ (locuri->off_path - locuri->off_host) + 1);
if (!newurl)
{
err = gpg_error_from_syserror ();
http_release_parsed_uri (origuri);
http_release_parsed_uri (locuri);
return err;
}
/* Build new URL from
* uriguri: scheme userinfo ---- ---- path rest
* locuri: ------ -------- host port ---- ----
*/
p = newurl;
memcpy (p, origuri->original, origuri->off_host);
p += origuri->off_host;
memcpy (p, locuri->original + locuri->off_host,
(locuri->off_path - locuri->off_host));
p += locuri->off_path - locuri->off_host;
strcpy (p, origuri->original + origuri->off_path);
http_release_parsed_uri (origuri);
http_release_parsed_uri (locuri);
if (!info->silent)
log_info (_("redirection changed to '%s'\n"), newurl);
}
*r_url = newurl;
return 0;
}
/* Return string describing the http STATUS. Returns an empty string
* for an unknown status. */
const char *
http_status2string (unsigned int status)
{
switch (status)
{
case 500: return "Internal Server Error";
case 501: return "Not Implemented";
case 502: return "Bad Gateway";
case 503: return "Service Unavailable";
case 504: return "Gateway Timeout";
case 505: return "HTTP version Not Supported";
case 506: return "Variant Also Negation";
case 507: return "Insufficient Storage";
case 508: return "Loop Detected";
case 510: return "Not Extended";
case 511: return "Network Authentication Required";
}
return "";
}
diff --git a/dirmngr/ks-engine-hkp.c b/dirmngr/ks-engine-hkp.c
index 4d660b87e..f8814ecd0 100644
--- a/dirmngr/ks-engine-hkp.c
+++ b/dirmngr/ks-engine-hkp.c
@@ -1,1807 +1,1836 @@
/* ks-engine-hkp.c - HKP keyserver engine
* Copyright (C) 2011, 2012 Free Software Foundation, Inc.
* Copyright (C) 2011, 2012, 2014 Werner Koch
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#ifdef HAVE_W32_SYSTEM
# ifdef HAVE_WINSOCK2_H
# include <winsock2.h>
# endif
# include <windows.h>
#else /*!HAVE_W32_SYSTEM*/
# include <sys/types.h>
# include <sys/socket.h>
# include <netdb.h>
#endif /*!HAVE_W32_SYSTEM*/
#include <npth.h>
#include "dirmngr.h"
#include "misc.h"
#include "../common/userids.h"
#include "dns-stuff.h"
#include "ks-engine.h"
/* Substitutes for missing Mingw macro. The EAI_SYSTEM mechanism
seems not to be available (probably because there is only one set
of error codes anyway). For now we use WSAEINVAL. */
#ifndef EAI_OVERFLOW
# define EAI_OVERFLOW EAI_FAIL
#endif
#ifdef HAVE_W32_SYSTEM
# ifndef EAI_SYSTEM
# define EAI_SYSTEM WSAEINVAL
# endif
#endif
/* Number of seconds after a host is marked as resurrected. */
#define RESURRECT_INTERVAL (3600+1800) /* 1.5 hours */
/* To match the behaviour of our old gpgkeys helper code we escape
more characters than actually needed. */
#define EXTRA_ESCAPE_CHARS "@!\"#$%&'()*+,-./:;<=>?[\\]^_{|}~"
/* How many redirections do we allow. */
#define MAX_REDIRECTS 2
/* Number of retries done for a dead host etc. */
#define SEND_REQUEST_RETRIES 3
+/* Number of retries done in case of transient errors. */
+#define SEND_REQUEST_EXTRA_RETRIES 5
+
+
enum ks_protocol { KS_PROTOCOL_HKP, KS_PROTOCOL_HKPS, KS_PROTOCOL_MAX };
/* Objects used to maintain information about hosts. */
struct hostinfo_s;
typedef struct hostinfo_s *hostinfo_t;
struct hostinfo_s
{
time_t lastfail; /* Time we tried to connect and failed. */
time_t lastused; /* Time of last use. */
int *pool; /* An array with indices into HOSTTABLE or NULL
if NAME is not a pool name. */
size_t pool_len; /* Length of POOL. */
size_t pool_size; /* Allocated size of POOL. */
#define MAX_POOL_SIZE 128
int poolidx; /* Index into POOL with the used host. -1 if not set. */
unsigned int v4:1; /* Host supports AF_INET. */
unsigned int v6:1; /* Host supports AF_INET6. */
unsigned int onion:1;/* NAME is an onion (Tor HS) address. */
unsigned int dead:1; /* Host is currently unresponsive. */
unsigned int iporname_valid:1; /* The field IPORNAME below is valid */
/* (but may be NULL) */
unsigned int did_a_lookup:1; /* Have we done an A lookup yet? */
unsigned int did_srv_lookup:2; /* One bit per protocol indicating
whether we already did a SRV
lookup. */
time_t died_at; /* The time the host was marked dead. If this is
0 the host has been manually marked dead. */
char *cname; /* Canonical name of the host. Only set if this
is a pool or NAME has a numerical IP address. */
char *iporname; /* Numeric IP address or name for printing. */
unsigned short port[KS_PROTOCOL_MAX];
/* The port used by the host for all protocols, 0
if unknown. */
char name[1]; /* The hostname. */
};
/* An array of hostinfo_t for all hosts requested by the caller or
resolved from a pool name and its allocated size.*/
static hostinfo_t *hosttable;
static int hosttable_size;
/* A mutex used to serialize access to the hosttable. */
static npth_mutex_t hosttable_lock;
/* The number of host slots we initially allocate for HOSTTABLE. */
#define INITIAL_HOSTTABLE_SIZE 50
/* Create a new hostinfo object, fill in NAME and put it into
HOSTTABLE. Return the index into hosttable on success or -1 on
error. */
static int
create_new_hostinfo (const char *name)
{
hostinfo_t hi, *newtable;
int newsize;
int idx, rc;
hi = xtrymalloc (sizeof *hi + strlen (name));
if (!hi)
return -1;
strcpy (hi->name, name);
hi->pool = NULL;
hi->pool_len = 0;
hi->pool_size = 0;
hi->poolidx = -1;
hi->lastused = (time_t)(-1);
hi->lastfail = (time_t)(-1);
hi->v4 = 0;
hi->v6 = 0;
hi->onion = 0;
hi->dead = 0;
hi->did_a_lookup = 0;
hi->did_srv_lookup = 0;
hi->iporname_valid = 0;
hi->died_at = 0;
hi->cname = NULL;
hi->iporname = NULL;
hi->port[KS_PROTOCOL_HKP] = 0;
hi->port[KS_PROTOCOL_HKPS] = 0;
/* Add it to the hosttable. */
for (idx=0; idx < hosttable_size; idx++)
if (!hosttable[idx])
{
hosttable[idx] = hi;
return idx;
}
/* Need to extend the hosttable. */
newsize = hosttable_size + INITIAL_HOSTTABLE_SIZE;
newtable = xtryrealloc (hosttable, newsize * sizeof *hosttable);
if (!newtable)
{
xfree (hi);
return -1;
}
hosttable = newtable;
idx = hosttable_size;
hosttable_size = newsize;
rc = idx;
hosttable[idx++] = hi;
while (idx < hosttable_size)
hosttable[idx++] = NULL;
return rc;
}
/* Find the host NAME in our table. Return the index into the
hosttable or -1 if not found. */
static int
find_hostinfo (const char *name)
{
int idx;
for (idx=0; idx < hosttable_size; idx++)
if (hosttable[idx] && !ascii_strcasecmp (hosttable[idx]->name, name))
return idx;
return -1;
}
static int
sort_hostpool (const void *xa, const void *xb)
{
int a = *(int *)xa;
int b = *(int *)xb;
assert (a >= 0 && a < hosttable_size);
assert (b >= 0 && b < hosttable_size);
assert (hosttable[a]);
assert (hosttable[b]);
return ascii_strcasecmp (hosttable[a]->name, hosttable[b]->name);
}
/* Return true if the host with the hosttable index TBLIDX is in HI->pool. */
static int
host_in_pool_p (hostinfo_t hi, int tblidx)
{
int i, pidx;
for (i = 0; i < hi->pool_len && (pidx = hi->pool[i]) != -1; i++)
if (pidx == tblidx && hosttable[pidx])
return 1;
return 0;
}
/* Select a random host. Consult HI->pool which indices into the global
hosttable. Returns index into HI->pool or -1 if no host could be
selected. */
static int
select_random_host (hostinfo_t hi)
{
int *tbl;
size_t tblsize;
int pidx, idx;
/* We create a new table so that we randomly select only from
currently alive hosts. */
for (idx = 0, tblsize = 0;
idx < hi->pool_len && (pidx = hi->pool[idx]) != -1;
idx++)
if (hosttable[pidx] && !hosttable[pidx]->dead)
tblsize++;
if (!tblsize)
return -1; /* No hosts. */
tbl = xtrymalloc (tblsize * sizeof *tbl);
if (!tbl)
return -1;
for (idx = 0, tblsize = 0;
idx < hi->pool_len && (pidx = hi->pool[idx]) != -1;
idx++)
if (hosttable[pidx] && !hosttable[pidx]->dead)
tbl[tblsize++] = pidx;
if (tblsize == 1) /* Save a get_uint_nonce. */
pidx = tbl[0];
else
pidx = tbl[get_uint_nonce () % tblsize];
xfree (tbl);
return pidx;
}
/* Figure out if a set of DNS records looks like a pool. */
static int
arecords_is_pool (dns_addrinfo_t aibuf)
{
dns_addrinfo_t ai;
int n_v6, n_v4;
n_v6 = n_v4 = 0;
for (ai = aibuf; ai; ai = ai->next)
{
if (ai->family == AF_INET6)
n_v6++;
else if (ai->family == AF_INET)
n_v4++;
}
return n_v6 > 1 || n_v4 > 1;
}
/* Print a warning iff Tor is not running but Tor has been requested.
* Also return true if it is not running. */
static int
tor_not_running_p (ctrl_t ctrl)
{
assuan_fd_t sock;
if (!dirmngr_use_tor ())
return 0;
sock = assuan_sock_connect_byname (NULL, 0, 0, NULL, ASSUAN_SOCK_TOR);
if (sock != ASSUAN_INVALID_FD)
{
assuan_sock_close (sock);
return 0;
}
log_info ("(it seems Tor is not running)\n");
dirmngr_status (ctrl, "WARNING", "tor_not_running 0",
"Tor is enabled but the local Tor daemon"
" seems to be down", NULL);
return 1;
}
/* Add the host AI under the NAME into the HOSTTABLE. If PORT is not
zero, it specifies which port to use to talk to the host for
PROTOCOL. If NAME specifies a pool (as indicated by IS_POOL),
update the given reference table accordingly. */
static void
add_host (ctrl_t ctrl, const char *name, int is_pool,
const dns_addrinfo_t ai,
enum ks_protocol protocol, unsigned short port)
{
gpg_error_t tmperr;
char *tmphost;
int idx, tmpidx;
hostinfo_t host;
int i;
idx = find_hostinfo (name);
host = hosttable[idx];
if (is_pool)
{
/* For a pool immediately convert the address to a string. */
tmperr = resolve_dns_addr (ctrl, ai->addr, ai->addrlen,
(DNS_NUMERICHOST | DNS_WITHBRACKET), &tmphost);
}
else if (!is_ip_address (name))
{
/* This is a hostname. Use the name as given without going
* through resolve_dns_addr. */
tmphost = xtrystrdup (name);
if (!tmphost)
tmperr = gpg_error_from_syserror ();
else
tmperr = 0;
}
else
{
/* Do a PTR lookup on AI. If a name was not found the function
* returns the numeric address (with brackets). */
tmperr = resolve_dns_addr (ctrl, ai->addr, ai->addrlen,
DNS_WITHBRACKET, &tmphost);
}
if (tmperr)
{
log_info ("resolve_dns_addr failed while checking '%s': %s\n",
name, gpg_strerror (tmperr));
}
else if (host->pool_len + 1 >= MAX_POOL_SIZE)
{
log_error ("resolve_dns_addr for '%s': '%s'"
" [index table full - ignored]\n", name, tmphost);
}
else
{
if (!is_pool && is_ip_address (name))
/* Update the original entry. */
tmpidx = idx;
else
tmpidx = find_hostinfo (tmphost);
log_info ("resolve_dns_addr for '%s': '%s'%s\n",
name, tmphost,
tmpidx == -1? "" : " [already known]");
if (tmpidx == -1) /* Create a new entry. */
tmpidx = create_new_hostinfo (tmphost);
if (tmpidx == -1)
{
log_error ("map_host for '%s' problem: %s - '%s' [ignored]\n",
name, strerror (errno), tmphost);
}
else /* Set or update the entry. */
{
if (port)
hosttable[tmpidx]->port[protocol] = port;
if (ai->family == AF_INET6)
{
hosttable[tmpidx]->v6 = 1;
}
else if (ai->family == AF_INET)
{
hosttable[tmpidx]->v4 = 1;
}
else
BUG ();
/* If we updated the main entry, we're done. */
if (idx == tmpidx)
goto leave;
/* If we updated an existing entry, we're done. */
for (i = 0; i < host->pool_len; i++)
if (host->pool[i] == tmpidx)
goto leave;
/* Otherwise, we need to add it to the pool. Check if there
is space. */
if (host->pool_len + 1 > host->pool_size)
{
int *new_pool;
size_t new_size;
if (host->pool_size == 0)
new_size = 4;
else
new_size = host->pool_size * 2;
new_pool = xtryrealloc (host->pool,
new_size * sizeof *new_pool);
if (new_pool == NULL)
goto leave;
host->pool = new_pool;
host->pool_size = new_size;
}
/* Finally, add it. */
log_assert (host->pool_len < host->pool_size);
host->pool[host->pool_len++] = tmpidx;
}
}
leave:
xfree (tmphost);
}
/* Sort the pool of the given hostinfo HI. */
static void
hostinfo_sort_pool (hostinfo_t hi)
{
qsort (hi->pool, hi->pool_len, sizeof *hi->pool, sort_hostpool);
}
/* Map the host name NAME to the actual to be used host name. This
* allows us to manage round robin DNS names. We use our own strategy
* to choose one of the hosts. For example we skip those hosts which
* failed for some time and we stick to one host for a time
* independent of DNS retry times. If FORCE_RESELECT is true a new
* host is always selected. If SRVTAG is NULL no service record
* lookup will be done, if it is set that service name is used. The
* selected host is stored as a malloced string at R_HOST; on error
* NULL is stored. If we know the port used by the selected host from
* a service record, a string representation is written to R_PORTSTR,
* otherwise it is left untouched. If R_HTTPFLAGS is not NULL it will
* receive flags which are to be passed to http_open. If R_HTTPHOST
* is not NULL a malloced name of the host is stored there; this might
* be different from R_HOST in case it has been selected from a
* pool. */
static gpg_error_t
map_host (ctrl_t ctrl, const char *name, const char *srvtag, int force_reselect,
enum ks_protocol protocol, char **r_host, char *r_portstr,
unsigned int *r_httpflags, char **r_httphost)
{
gpg_error_t err = 0;
hostinfo_t hi;
int idx;
dns_addrinfo_t aibuf, ai;
int is_pool;
int new_hosts = 0;
char *cname;
*r_host = NULL;
if (r_httpflags)
*r_httpflags = 0;
if (r_httphost)
*r_httphost = NULL;
/* No hostname means localhost. */
if (!name || !*name)
{
*r_host = xtrystrdup ("localhost");
return *r_host? 0 : gpg_error_from_syserror ();
}
/* See whether the host is in our table. */
idx = find_hostinfo (name);
if (idx == -1)
{
idx = create_new_hostinfo (name);
if (idx == -1)
return gpg_error_from_syserror ();
hi = hosttable[idx];
hi->onion = is_onion_address (name);
}
else
hi = hosttable[idx];
is_pool = hi->pool != NULL;
if (srvtag && !is_ip_address (name)
&& ! hi->onion
&& ! (hi->did_srv_lookup & 1 << protocol))
{
struct srventry *srvs;
unsigned int srvscount;
/* Check for SRV records. */
err = get_dns_srv (ctrl, name, srvtag, NULL, &srvs, &srvscount);
if (err)
{
if (gpg_err_code (err) == GPG_ERR_ECONNREFUSED)
tor_not_running_p (ctrl);
return err;
}
if (srvscount > 0)
{
int i;
if (! is_pool)
is_pool = srvscount > 1;
for (i = 0; i < srvscount; i++)
{
err = resolve_dns_name (ctrl, srvs[i].target, 0,
AF_UNSPEC, SOCK_STREAM,
&ai, &cname);
if (err)
continue;
dirmngr_tick (ctrl);
add_host (ctrl, name, is_pool, ai, protocol, srvs[i].port);
new_hosts = 1;
}
xfree (srvs);
}
hi->did_srv_lookup |= 1 << protocol;
}
if (! hi->did_a_lookup
&& ! hi->onion)
{
/* Find all A records for this entry and put them into the pool
list - if any. */
err = resolve_dns_name (ctrl, name, 0, 0, SOCK_STREAM, &aibuf, &cname);
if (err)
{
log_error ("resolving '%s' failed: %s\n", name, gpg_strerror (err));
err = 0;
}
else
{
/* First figure out whether this is a pool. For a pool we
use a different strategy than for a plain server: We use
the canonical name of the pool as the virtual host along
with the IP addresses. If it is not a pool, we use the
specified name. */
if (! is_pool)
is_pool = arecords_is_pool (aibuf);
if (is_pool && cname)
{
hi->cname = cname;
cname = NULL;
}
for (ai = aibuf; ai; ai = ai->next)
{
if (ai->family != AF_INET && ai->family != AF_INET6)
continue;
if (opt.disable_ipv4 && ai->family == AF_INET)
continue;
if (opt.disable_ipv6 && ai->family == AF_INET6)
continue;
dirmngr_tick (ctrl);
add_host (ctrl, name, is_pool, ai, 0, 0);
new_hosts = 1;
}
hi->did_a_lookup = 1;
}
xfree (cname);
free_dns_addrinfo (aibuf);
}
if (new_hosts)
hostinfo_sort_pool (hi);
if (hi->pool)
{
/* Deal with the pool name before selecting a host. */
if (r_httphost)
{
*r_httphost = xtrystrdup (hi->name);
if (!*r_httphost)
return gpg_error_from_syserror ();
}
/* If the currently selected host is now marked dead, force a
re-selection . */
if (force_reselect)
hi->poolidx = -1;
else if (hi->poolidx >= 0 && hi->poolidx < hosttable_size
&& hosttable[hi->poolidx] && hosttable[hi->poolidx]->dead)
hi->poolidx = -1;
/* Select a host if needed. */
if (hi->poolidx == -1)
{
hi->poolidx = select_random_host (hi);
if (hi->poolidx == -1)
{
log_error ("no alive host found in pool '%s'\n", name);
if (r_httphost)
{
xfree (*r_httphost);
*r_httphost = NULL;
}
return gpg_error (GPG_ERR_NO_KEYSERVER);
}
}
assert (hi->poolidx >= 0 && hi->poolidx < hosttable_size);
hi = hosttable[hi->poolidx];
assert (hi);
}
else if (r_httphost && is_ip_address (hi->name))
{
/* This is a numerical IP address and not a pool. We want to
* find the canonical name so that it can be used in the HTTP
* Host header. Fixme: We should store that name in the
* hosttable. */
char *host;
err = resolve_dns_name (ctrl, hi->name, 0, 0, SOCK_STREAM, &aibuf, NULL);
if (!err)
{
for (ai = aibuf; ai; ai = ai->next)
{
if ((!opt.disable_ipv6 && ai->family == AF_INET6)
|| (!opt.disable_ipv4 && ai->family == AF_INET))
{
err = resolve_dns_addr (ctrl,
ai->addr, ai->addrlen, 0, &host);
if (!err)
{
/* Okay, we return the first found name. */
*r_httphost = host;
break;
}
}
}
}
free_dns_addrinfo (aibuf);
}
if (hi->dead)
{
log_error ("host '%s' marked as dead\n", hi->name);
if (r_httphost)
{
xfree (*r_httphost);
*r_httphost = NULL;
}
return gpg_error (GPG_ERR_NO_KEYSERVER);
}
if (r_httpflags)
{
/* If the hosttable does not indicate that a certain host
supports IPv<N>, we explicit set the corresponding http
flags. The reason for this is that a host might be listed in
a pool as not v6 only but actually support v6 when later
the name is resolved by our http layer. */
if (!hi->v4)
*r_httpflags |= HTTP_FLAG_IGNORE_IPv4;
if (!hi->v6)
*r_httpflags |= HTTP_FLAG_IGNORE_IPv6;
/* Note that we do not set the HTTP_FLAG_FORCE_TOR for onion
addresses because the http module detects this itself. This
also allows us to use an onion address without Tor mode being
enabled. */
}
*r_host = xtrystrdup (hi->name);
if (!*r_host)
{
err = gpg_error_from_syserror ();
if (r_httphost)
{
xfree (*r_httphost);
*r_httphost = NULL;
}
return err;
}
if (hi->port[protocol])
snprintf (r_portstr, 6 /* five digits and the sentinel */,
"%hu", hi->port[protocol]);
return 0;
}
/* Mark the host NAME as dead. NAME may be given as an URL. Returns
true if a host was really marked as dead or was already marked dead
(e.g. by a concurrent session). */
static int
mark_host_dead (const char *name)
{
const char *host;
char *host_buffer = NULL;
parsed_uri_t parsed_uri = NULL;
int done = 0;
if (name && *name && !http_parse_uri (&parsed_uri, name, 1))
{
if (parsed_uri->v6lit)
{
host_buffer = strconcat ("[", parsed_uri->host, "]", NULL);
if (!host_buffer)
log_error ("out of core in mark_host_dead");
host = host_buffer;
}
else
host = parsed_uri->host;
}
else
host = name;
if (host && *host && strcmp (host, "localhost"))
{
hostinfo_t hi;
int idx;
idx = find_hostinfo (host);
if (idx != -1)
{
hi = hosttable[idx];
log_info ("marking host '%s' as dead%s\n",
hi->name, hi->dead? " (again)":"");
hi->dead = 1;
hi->died_at = gnupg_get_time ();
if (!hi->died_at)
hi->died_at = 1;
done = 1;
}
}
http_release_parsed_uri (parsed_uri);
xfree (host_buffer);
return done;
}
/* Mark a host in the hosttable as dead or - if ALIVE is true - as
alive. */
gpg_error_t
ks_hkp_mark_host (ctrl_t ctrl, const char *name, int alive)
{
gpg_error_t err = 0;
hostinfo_t hi, hi2;
int idx, idx2, idx3, n;
if (!name || !*name || !strcmp (name, "localhost"))
return 0;
if (npth_mutex_lock (&hosttable_lock))
log_fatal ("failed to acquire mutex\n");
idx = find_hostinfo (name);
if (idx == -1)
{
err = gpg_error (GPG_ERR_NOT_FOUND);
goto leave;
}
hi = hosttable[idx];
if (alive && hi->dead)
{
hi->dead = 0;
err = ks_printf_help (ctrl, "marking '%s' as alive", name);
}
else if (!alive && !hi->dead)
{
hi->dead = 1;
hi->died_at = 0; /* Manually set dead. */
err = ks_printf_help (ctrl, "marking '%s' as dead", name);
}
/* If the host is a pool mark all member hosts. */
if (!err && hi->pool)
{
for (idx2 = 0;
!err && idx2 < hi->pool_len && (n = hi->pool[idx2]) != -1;
idx2++)
{
assert (n >= 0 && n < hosttable_size);
if (!alive)
{
/* Do not mark a host from a pool dead if it is also a
member in another pool. */
for (idx3=0; idx3 < hosttable_size; idx3++)
{
if (hosttable[idx3]
&& hosttable[idx3]->pool
&& idx3 != idx
&& host_in_pool_p (hosttable[idx3], n))
break;
}
if (idx3 < hosttable_size)
continue; /* Host is also a member of another pool. */
}
hi2 = hosttable[n];
if (!hi2)
;
else if (alive && hi2->dead)
{
hi2->dead = 0;
err = ks_printf_help (ctrl, "marking '%s' as alive",
hi2->name);
}
else if (!alive && !hi2->dead)
{
hi2->dead = 1;
hi2->died_at = 0; /* Manually set dead. */
err = ks_printf_help (ctrl, "marking '%s' as dead",
hi2->name);
}
}
}
leave:
if (npth_mutex_unlock (&hosttable_lock))
log_fatal ("failed to release mutex\n");
return err;
}
/* Debug function to print the entire hosttable. */
gpg_error_t
ks_hkp_print_hosttable (ctrl_t ctrl)
{
gpg_error_t err;
int idx, idx2;
hostinfo_t hi;
membuf_t mb;
time_t curtime;
char *p, *died;
const char *diedstr;
err = ks_print_help (ctrl, "hosttable (idx, ipv6, ipv4, dead, name, time):");
if (err)
return err;
if (npth_mutex_lock (&hosttable_lock))
log_fatal ("failed to acquire mutex\n");
curtime = gnupg_get_time ();
for (idx=0; idx < hosttable_size; idx++)
if ((hi=hosttable[idx]))
{
if (hi->dead && hi->died_at)
{
died = elapsed_time_string (hi->died_at, curtime);
diedstr = died? died : "error";
}
else
diedstr = died = NULL;
if (!hi->iporname_valid)
{
char *canon = NULL;
xfree (hi->iporname);
hi->iporname = NULL;
/* Do a lookup just for the display purpose. */
if (hi->onion || hi->pool)
;
else if (is_ip_address (hi->name))
{
dns_addrinfo_t aibuf, ai;
/* Turn the numerical IP address string into an AI and
* then do a DNS PTR lookup. */
if (!resolve_dns_name (ctrl, hi->name, 0, 0,
SOCK_STREAM,
&aibuf, &canon))
{
if (canon && is_ip_address (canon))
{
xfree (canon);
canon = NULL;
}
for (ai = aibuf; !canon && ai; ai = ai->next)
{
resolve_dns_addr (ctrl, ai->addr, ai->addrlen,
DNS_WITHBRACKET, &canon);
if (canon && is_ip_address (canon))
{
/* We already have the numeric IP - no need to
* display it a second time. */
xfree (canon);
canon = NULL;
}
}
}
free_dns_addrinfo (aibuf);
}
else
{
dns_addrinfo_t aibuf, ai;
/* Get the IP address as a string from a name. Note
* that resolve_dns_addr allocates CANON on success
* and thus terminates the loop. */
if (!resolve_dns_name (ctrl, hi->name, 0,
hi->v6? AF_INET6 : AF_INET,
SOCK_STREAM,
&aibuf, NULL))
{
for (ai = aibuf; !canon && ai; ai = ai->next)
{
resolve_dns_addr (ctrl, ai->addr, ai->addrlen,
DNS_NUMERICHOST|DNS_WITHBRACKET,
&canon);
}
}
free_dns_addrinfo (aibuf);
}
hi->iporname = canon;
hi->iporname_valid = 1;
}
err = ks_printf_help (ctrl, "%3d %s %s %s %s%s%s%s%s%s%s\n",
idx,
hi->onion? "O" : hi->v6? "6":" ",
hi->v4? "4":" ",
hi->dead? "d":" ",
hi->name,
hi->iporname? " (":"",
hi->iporname? hi->iporname : "",
hi->iporname? ")":"",
diedstr? " (":"",
diedstr? diedstr:"",
diedstr? ")":"" );
xfree (died);
if (err)
goto leave;
if (hi->cname)
err = ks_printf_help (ctrl, " . %s", hi->cname);
if (err)
goto leave;
if (hi->pool)
{
init_membuf (&mb, 256);
put_membuf_printf (&mb, " . -->");
for (idx2 = 0; idx2 < hi->pool_len && hi->pool[idx2] != -1; idx2++)
{
put_membuf_printf (&mb, " %d", hi->pool[idx2]);
if (hi->poolidx == hi->pool[idx2])
put_membuf_printf (&mb, "*");
}
put_membuf( &mb, "", 1);
p = get_membuf (&mb, NULL);
if (!p)
{
err = gpg_error_from_syserror ();
goto leave;
}
err = ks_print_help (ctrl, p);
xfree (p);
if (err)
goto leave;
}
}
leave:
if (npth_mutex_unlock (&hosttable_lock))
log_fatal ("failed to release mutex\n");
return err;
}
/* Print a help output for the schemata supported by this module. */
gpg_error_t
ks_hkp_help (ctrl_t ctrl, parsed_uri_t uri)
{
const char data[] =
"Handler for HKP URLs:\n"
" hkp://\n"
#if HTTP_USE_GNUTLS || HTTP_USE_NTBTLS
" hkps://\n"
#endif
"Supported methods: search, get, put\n";
gpg_error_t err;
#if HTTP_USE_GNUTLS || HTTP_USE_NTBTLS
const char data2[] = " hkp\n hkps";
#else
const char data2[] = " hkp";
#endif
if (!uri)
err = ks_print_help (ctrl, data2);
else if (uri->is_http && (!strcmp (uri->scheme, "hkp")
|| !strcmp (uri->scheme, "hkps")))
err = ks_print_help (ctrl, data);
else
err = 0;
return err;
}
/* Build the remote part of the URL from SCHEME, HOST and an optional
* PORT. If NO_SRV is set no SRV record lookup will be done. Returns
* an allocated string at R_HOSTPORT or NULL on failure. If
* R_HTTPHOST is not NULL it receives a malloced string with the
* hostname; this may be different from HOST if HOST is selected from
* a pool. */
static gpg_error_t
make_host_part (ctrl_t ctrl,
const char *scheme, const char *host, unsigned short port,
int force_reselect, int no_srv,
char **r_hostport, unsigned int *r_httpflags, char **r_httphost)
{
gpg_error_t err;
const char *srvtag;
char portstr[10];
char *hostname;
enum ks_protocol protocol;
*r_hostport = NULL;
if (!strcmp (scheme, "hkps") || !strcmp (scheme,"https"))
{
scheme = "https";
srvtag = no_srv? NULL : "pgpkey-https";
protocol = KS_PROTOCOL_HKPS;
}
else /* HKP or HTTP. */
{
scheme = "http";
srvtag = no_srv? NULL : "pgpkey-http";
protocol = KS_PROTOCOL_HKP;
}
if (npth_mutex_lock (&hosttable_lock))
log_fatal ("failed to acquire mutex\n");
portstr[0] = 0;
err = map_host (ctrl, host, srvtag, force_reselect, protocol,
&hostname, portstr, r_httpflags, r_httphost);
if (npth_mutex_unlock (&hosttable_lock))
log_fatal ("failed to release mutex\n");
if (err)
return err;
/* If map_host did not return a port (from a SRV record) but a port
* has been specified (implicitly or explicitly) then use that port.
* In the case that a port was not specified (which is probably a
* bug in https.c) we will set up defaults. */
if (*portstr)
;
else if (!*portstr && port)
snprintf (portstr, sizeof portstr, "%hu", port);
else if (!strcmp (scheme,"https"))
strcpy (portstr, "443");
else
strcpy (portstr, "11371");
if (*hostname != '[' && is_ip_address (hostname) == 6)
*r_hostport = strconcat (scheme, "://[", hostname, "]:", portstr, NULL);
else
*r_hostport = strconcat (scheme, "://", hostname, ":", portstr, NULL);
xfree (hostname);
if (!*r_hostport)
{
if (r_httphost)
{
xfree (*r_httphost);
*r_httphost = NULL;
}
return gpg_error_from_syserror ();
}
return 0;
}
/* Resolve all known keyserver names and update the hosttable. This
is mainly useful for debugging because the resolving is anyway done
on demand. */
gpg_error_t
ks_hkp_resolve (ctrl_t ctrl, parsed_uri_t uri)
{
gpg_error_t err;
char *hostport = NULL;
/* NB: With an explicitly given port we do not want to consult a
* service record because that might be in conflict with the port
* from such a service record. */
err = make_host_part (ctrl, uri->scheme, uri->host, uri->port,
1, uri->explicit_port,
&hostport, NULL, NULL);
if (err)
{
err = ks_printf_help (ctrl, "%s://%s:%hu: resolve failed: %s",
uri->scheme, uri->host, uri->port,
gpg_strerror (err));
}
else
{
err = ks_printf_help (ctrl, "%s", hostport);
xfree (hostport);
}
return err;
}
/* Housekeeping function called from the housekeeping thread. It is
used to mark dead hosts alive so that they may be tried again after
some time. */
void
ks_hkp_housekeeping (time_t curtime)
{
int idx;
hostinfo_t hi;
if (npth_mutex_lock (&hosttable_lock))
log_fatal ("failed to acquire mutex\n");
for (idx=0; idx < hosttable_size; idx++)
{
hi = hosttable[idx];
if (!hi)
continue;
if (!hi->dead)
continue;
if (!hi->died_at)
continue; /* Do not resurrect manually shot hosts. */
if (hi->died_at + RESURRECT_INTERVAL <= curtime
|| hi->died_at > curtime)
{
hi->dead = 0;
log_info ("resurrected host '%s'", hi->name);
}
}
if (npth_mutex_unlock (&hosttable_lock))
log_fatal ("failed to release mutex\n");
}
/* Reload (SIGHUP) action for this module. We mark all host alive
* even those which have been manually shot. */
void
ks_hkp_reload (void)
{
int idx, count;
hostinfo_t hi;
if (npth_mutex_lock (&hosttable_lock))
log_fatal ("failed to acquire mutex\n");
for (idx=count=0; idx < hosttable_size; idx++)
{
hi = hosttable[idx];
if (!hi)
continue;
hi->iporname_valid = 0;
if (!hi->dead)
continue;
hi->dead = 0;
count++;
}
if (count)
log_info ("number of resurrected hosts: %d", count);
if (npth_mutex_unlock (&hosttable_lock))
log_fatal ("failed to release mutex\n");
}
/* Send an HTTP request. On success returns an estream object at
R_FP. HOSTPORTSTR is only used for diagnostics. If HTTPHOST is
not NULL it will be used as HTTP "Host" header. If POST_CB is not
NULL a post request is used and that callback is called to allow
writing the post data. If R_HTTP_STATUS is not NULL, the http
status code will be stored there. */
static gpg_error_t
send_request (ctrl_t ctrl, const char *request, const char *hostportstr,
const char *httphost, unsigned int httpflags,
gpg_error_t (*post_cb)(void *, http_t), void *post_cb_value,
estream_t *r_fp, unsigned int *r_http_status)
{
gpg_error_t err;
http_session_t session = NULL;
http_t http = NULL;
http_redir_info_t redirinfo = { MAX_REDIRECTS };
estream_t fp = NULL;
char *request_buffer = NULL;
parsed_uri_t uri = NULL;
*r_fp = NULL;
err = http_parse_uri (&uri, request, 0);
if (err)
goto leave;
redirinfo.orig_url = request;
redirinfo.orig_onion = uri->onion;
redirinfo.allow_downgrade = 1;
/* FIXME: I am not sure whey we allow a downgrade for hkp requests.
* Needs at least an explanation here.. */
+ once_more:
err = http_session_new (&session, httphost,
((ctrl->http_no_crl? HTTP_FLAG_NO_CRL : 0)
| HTTP_FLAG_TRUST_DEF),
gnupg_http_tls_verify_cb, ctrl);
if (err)
goto leave;
http_session_set_log_cb (session, cert_log_cb);
http_session_set_timeout (session, ctrl->timeout);
- once_more:
err = http_open (ctrl, &http,
post_cb? HTTP_REQ_POST : HTTP_REQ_GET,
request,
httphost,
/* fixme: AUTH */ NULL,
(httpflags
|(opt.honor_http_proxy? HTTP_FLAG_TRY_PROXY:0)
|(dirmngr_use_tor ()? HTTP_FLAG_FORCE_TOR:0)
|(opt.disable_ipv4? HTTP_FLAG_IGNORE_IPv4 : 0)
|(opt.disable_ipv6? HTTP_FLAG_IGNORE_IPv6 : 0)),
ctrl->http_proxy,
session,
NULL,
/*FIXME curl->srvtag*/NULL);
if (!err)
{
fp = http_get_write_ptr (http);
/* Avoid caches to get the most recent copy of the key. We set
both the Pragma and Cache-Control versions of the header, so
we're good with both HTTP 1.0 and 1.1. */
es_fputs ("Pragma: no-cache\r\n"
"Cache-Control: no-cache\r\n", fp);
if (post_cb)
err = post_cb (post_cb_value, http);
if (!err)
{
http_start_data (http);
if (es_ferror (fp))
err = gpg_error_from_syserror ();
}
}
if (err)
{
/* Fixme: After a redirection we show the old host name. */
log_error (_("error connecting to '%s': %s\n"),
hostportstr, gpg_strerror (err));
goto leave;
}
/* Wait for the response. */
dirmngr_tick (ctrl);
err = http_wait_response (http);
if (err)
{
log_error (_("error reading HTTP response for '%s': %s\n"),
hostportstr, gpg_strerror (err));
goto leave;
}
if (http_get_tls_info (http, NULL))
{
/* Update the httpflags so that a redirect won't fallback to an
unencrypted connection. */
httpflags |= HTTP_FLAG_FORCE_TLS;
}
if (r_http_status)
*r_http_status = http_get_status_code (http);
switch (http_get_status_code (http))
{
case 200:
err = 0;
break; /* Success. */
case 301:
case 302:
case 307:
{
xfree (request_buffer);
err = http_prepare_redirect (&redirinfo, http_get_status_code (http),
http_get_header (http, "Location"),
&request_buffer);
if (err)
goto leave;
request = request_buffer;
http_close (http, 0);
http = NULL;
+ http_session_release (session);
+ session = NULL;
}
goto once_more;
case 501:
err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
goto leave;
+ case 413: /* Payload too large */
+ err = gpg_error (GPG_ERR_TOO_LARGE);
+ goto leave;
+
default:
log_error (_("error accessing '%s': http status %u\n"),
request, http_get_status_code (http));
err = gpg_error (GPG_ERR_NO_DATA);
goto leave;
}
/* FIXME: We should register a permanent redirection and whether a
host has ever used TLS so that future calls will always use
TLS. */
fp = http_get_read_ptr (http);
if (!fp)
{
err = gpg_error (GPG_ERR_BUG);
goto leave;
}
/* Return the read stream and close the HTTP context. */
*r_fp = fp;
http_close (http, 1);
http = NULL;
leave:
http_close (http, 0);
http_session_release (session);
xfree (request_buffer);
http_release_parsed_uri (uri);
return err;
}
/* Helper to evaluate the error code ERR from a send_request() call
with REQUEST. The function returns true if the caller shall try
again. TRIES_LEFT points to a variable to track the number of
retries; this function decrements it and won't return true if it is
- down to zero. */
+ down to zero. EXTRA_TRIES_LEFT does the same but only for
+ transient http status codes. */
static int
handle_send_request_error (ctrl_t ctrl, gpg_error_t err, const char *request,
- unsigned int http_status, unsigned int *tries_left)
+ unsigned int http_status, unsigned int *tries_left,
+ unsigned int *extra_tries_left)
{
int retry = 0;
/* Fixme: Should we disable all hosts of a protocol family if a
* request for an address of that family returned ENETDOWN? */
switch (gpg_err_code (err))
{
case GPG_ERR_ECONNREFUSED:
if (tor_not_running_p (ctrl))
break; /* A retry does not make sense. */
/* Okay: Tor is up or --use-tor is not used. */
/*FALLTHRU*/
case GPG_ERR_ENETUNREACH:
case GPG_ERR_ENETDOWN:
case GPG_ERR_UNKNOWN_HOST:
case GPG_ERR_NETWORK:
case GPG_ERR_EIO: /* Sometimes used by estream cookie functions. */
case GPG_ERR_EADDRNOTAVAIL: /* e.g. when IPv6 is disabled */
case GPG_ERR_EAFNOSUPPORT: /* e.g. when IPv6 is not compiled in */
if (mark_host_dead (request) && *tries_left)
retry = 1;
break;
case GPG_ERR_ETIMEDOUT:
if (*tries_left)
{
log_info ("selecting a different host due to a timeout\n");
retry = 1;
}
break;
case GPG_ERR_EACCES:
if (dirmngr_use_tor ())
{
log_info ("(Tor configuration problem)\n");
dirmngr_status (ctrl, "WARNING", "tor_config_problem 0",
"Please check that the \"SocksPort\" flag "
"\"IPv6Traffic\" is set in torrc", NULL);
}
break;
case GPG_ERR_NO_DATA:
{
switch (http_status)
{
case 502: /* Bad Gateway */
log_info ("marking host dead due to a %u (%s)\n",
http_status, http_status2string (http_status));
if (mark_host_dead (request) && *tries_left)
retry = 1;
break;
case 503: /* Service Unavailable */
case 504: /* Gateway Timeout */
- log_info ("selecting a different host due to a %u (%s)",
- http_status, http_status2string (http_status));
- retry = 1;
+ if (*extra_tries_left)
+ {
+ log_info ("selecting a different host due to a %u (%s)",
+ http_status, http_status2string (http_status));
+ retry = 2;
+ }
break;
}
}
break;
default:
break;
}
- if (*tries_left)
- --*tries_left;
+ if (retry == 2)
+ {
+ if (*extra_tries_left)
+ --*extra_tries_left;
+ }
+ else
+ {
+ if (*tries_left)
+ --*tries_left;
+ }
return retry;
}
/* Search the keyserver identified by URI for keys matching PATTERN.
On success R_FP has an open stream to read the data. If
R_HTTP_STATUS is not NULL, the http status code will be stored
there. */
gpg_error_t
ks_hkp_search (ctrl_t ctrl, parsed_uri_t uri, const char *pattern,
estream_t *r_fp, unsigned int *r_http_status)
{
gpg_error_t err;
KEYDB_SEARCH_DESC desc;
char fprbuf[2+64+1];
char *hostport = NULL;
char *request = NULL;
estream_t fp = NULL;
int reselect;
unsigned int httpflags;
char *httphost = NULL;
unsigned int http_status;
unsigned int tries = SEND_REQUEST_RETRIES;
+ unsigned int extra_tries = SEND_REQUEST_EXTRA_RETRIES;
*r_fp = NULL;
/* Remove search type indicator and adjust PATTERN accordingly.
Note that HKP keyservers like the 0x to be present when searching
by keyid. We need to re-format the fingerprint and keyids so to
remove the gpg specific force-use-of-this-key flag ("!"). */
err = classify_user_id (pattern, &desc, 1);
if (err)
return err;
log_assert (desc.fprlen <= 64);
switch (desc.mode)
{
case KEYDB_SEARCH_MODE_EXACT:
case KEYDB_SEARCH_MODE_SUBSTR:
case KEYDB_SEARCH_MODE_MAIL:
case KEYDB_SEARCH_MODE_MAILSUB:
pattern = desc.u.name;
break;
case KEYDB_SEARCH_MODE_SHORT_KID:
snprintf (fprbuf, sizeof fprbuf, "0x%08lX", (ulong)desc.u.kid[1]);
pattern = fprbuf;
break;
case KEYDB_SEARCH_MODE_LONG_KID:
snprintf (fprbuf, sizeof fprbuf, "0x%08lX%08lX",
(ulong)desc.u.kid[0], (ulong)desc.u.kid[1]);
pattern = fprbuf;
break;
case KEYDB_SEARCH_MODE_FPR:
fprbuf[0] = '0';
fprbuf[1] = 'x';
bin2hex (desc.u.fpr, desc.fprlen, fprbuf+2);
pattern = fprbuf;
break;
default:
return gpg_error (GPG_ERR_INV_USER_ID);
}
/* Build the request string. */
reselect = 0;
again:
{
char *searchkey;
xfree (hostport); hostport = NULL;
xfree (httphost); httphost = NULL;
err = make_host_part (ctrl, uri->scheme, uri->host, uri->port,
reselect, uri->explicit_port,
&hostport, &httpflags, &httphost);
if (err)
goto leave;
searchkey = http_escape_string (pattern, EXTRA_ESCAPE_CHARS);
if (!searchkey)
{
err = gpg_error_from_syserror ();
goto leave;
}
xfree (request);
request = strconcat (hostport,
"/pks/lookup?op=index&options=mr&search=",
searchkey,
NULL);
xfree (searchkey);
if (!request)
{
err = gpg_error_from_syserror ();
goto leave;
}
}
/* Send the request. */
err = send_request (ctrl, request, hostport, httphost, httpflags,
NULL, NULL, &fp, &http_status);
- if (handle_send_request_error (ctrl, err, request, http_status, &tries))
+ if (handle_send_request_error (ctrl, err, request, http_status,
+ &tries, &extra_tries))
{
reselect = 1;
goto again;
}
if (r_http_status)
*r_http_status = http_status;
if (err)
{
if (gpg_err_code (err) == GPG_ERR_NO_DATA)
dirmngr_status (ctrl, "SOURCE", hostport, NULL);
goto leave;
}
err = dirmngr_status (ctrl, "SOURCE", hostport, NULL);
if (err)
goto leave;
/* Peek at the response. */
{
int c = es_getc (fp);
if (c == -1)
{
err = es_ferror (fp)?gpg_error_from_syserror ():gpg_error (GPG_ERR_EOF);
log_error ("error reading response: %s\n", gpg_strerror (err));
goto leave;
}
if (c == '<')
{
/* The document begins with a '<': Assume a HTML response,
which we don't support. */
err = gpg_error (GPG_ERR_UNSUPPORTED_ENCODING);
goto leave;
}
es_ungetc (c, fp);
}
/* Return the read stream. */
*r_fp = fp;
fp = NULL;
leave:
es_fclose (fp);
xfree (request);
xfree (hostport);
xfree (httphost);
return err;
}
/* Get the key described key the KEYSPEC string from the keyserver
identified by URI. On success R_FP has an open stream to read the
data. The data will be provided in a format GnuPG can import
(either a binary OpenPGP message or an armored one). */
gpg_error_t
ks_hkp_get (ctrl_t ctrl, parsed_uri_t uri, const char *keyspec, estream_t *r_fp)
{
gpg_error_t err;
KEYDB_SEARCH_DESC desc;
char kidbuf[2+64+1];
const char *exactname = NULL;
char *searchkey = NULL;
char *hostport = NULL;
char *request = NULL;
estream_t fp = NULL;
int reselect;
char *httphost = NULL;
unsigned int httpflags;
unsigned int http_status;
unsigned int tries = SEND_REQUEST_RETRIES;
+ unsigned int extra_tries = SEND_REQUEST_EXTRA_RETRIES;
*r_fp = NULL;
/* Remove search type indicator and adjust PATTERN accordingly.
Note that HKP keyservers like the 0x to be present when searching
by keyid. We need to re-format the fingerprint and keyids so to
remove the gpg specific force-use-of-this-key flag ("!"). */
err = classify_user_id (keyspec, &desc, 1);
if (err)
return err;
log_assert (desc.fprlen <= 64);
switch (desc.mode)
{
case KEYDB_SEARCH_MODE_SHORT_KID:
snprintf (kidbuf, sizeof kidbuf, "0x%08lX", (ulong)desc.u.kid[1]);
break;
case KEYDB_SEARCH_MODE_LONG_KID:
snprintf (kidbuf, sizeof kidbuf, "0x%08lX%08lX",
(ulong)desc.u.kid[0], (ulong)desc.u.kid[1]);
break;
case KEYDB_SEARCH_MODE_FPR:
if (desc.fprlen < 20)
{
log_error ("HKP keyservers do not support v3 fingerprints\n");
return gpg_error (GPG_ERR_INV_USER_ID);
}
kidbuf[0] = '0';
kidbuf[1] = 'x';
bin2hex (desc.u.fpr, desc.fprlen, kidbuf+2);
break;
case KEYDB_SEARCH_MODE_EXACT:
exactname = desc.u.name;
break;
default:
return gpg_error (GPG_ERR_INV_USER_ID);
}
searchkey = http_escape_string (exactname? exactname : kidbuf,
EXTRA_ESCAPE_CHARS);
if (!searchkey)
{
err = gpg_error_from_syserror ();
goto leave;
}
reselect = 0;
again:
/* Build the request string. */
xfree (hostport); hostport = NULL;
xfree (httphost); httphost = NULL;
err = make_host_part (ctrl, uri->scheme, uri->host, uri->port,
reselect, uri->explicit_port,
&hostport, &httpflags, &httphost);
if (err)
goto leave;
xfree (request);
request = strconcat (hostport,
"/pks/lookup?op=get&options=mr&search=",
searchkey,
exactname? "&exact=on":"",
NULL);
if (!request)
{
err = gpg_error_from_syserror ();
goto leave;
}
/* Send the request. */
err = send_request (ctrl, request, hostport, httphost, httpflags,
NULL, NULL, &fp, &http_status);
- if (handle_send_request_error (ctrl, err, request, http_status, &tries))
+ if (handle_send_request_error (ctrl, err, request, http_status,
+ &tries, &extra_tries))
{
reselect = 1;
goto again;
}
if (err)
{
if (gpg_err_code (err) == GPG_ERR_NO_DATA)
dirmngr_status (ctrl, "SOURCE", hostport, NULL);
goto leave;
}
err = dirmngr_status (ctrl, "SOURCE", hostport, NULL);
if (err)
goto leave;
/* Return the read stream and close the HTTP context. */
*r_fp = fp;
fp = NULL;
leave:
es_fclose (fp);
xfree (request);
xfree (hostport);
xfree (httphost);
xfree (searchkey);
return err;
}
/* Callback parameters for put_post_cb. */
struct put_post_parm_s
{
char *datastring;
};
/* Helper for ks_hkp_put. */
static gpg_error_t
put_post_cb (void *opaque, http_t http)
{
struct put_post_parm_s *parm = opaque;
gpg_error_t err = 0;
estream_t fp;
size_t len;
fp = http_get_write_ptr (http);
len = strlen (parm->datastring);
es_fprintf (fp,
"Content-Type: application/x-www-form-urlencoded\r\n"
"Content-Length: %zu\r\n", len+8 /* 8 is for "keytext" */);
http_start_data (http);
if (es_fputs ("keytext=", fp) || es_write (fp, parm->datastring, len, NULL))
err = gpg_error_from_syserror ();
return err;
}
/* Send the key in {DATA,DATALEN} to the keyserver identified by URI. */
gpg_error_t
ks_hkp_put (ctrl_t ctrl, parsed_uri_t uri, const void *data, size_t datalen)
{
gpg_error_t err;
char *hostport = NULL;
char *request = NULL;
estream_t fp = NULL;
struct put_post_parm_s parm;
char *armored = NULL;
int reselect;
char *httphost = NULL;
unsigned int httpflags;
unsigned int http_status;
unsigned int tries = SEND_REQUEST_RETRIES;
+ unsigned int extra_tries = SEND_REQUEST_EXTRA_RETRIES;
parm.datastring = NULL;
err = armor_data (&armored, data, datalen);
if (err)
goto leave;
parm.datastring = http_escape_string (armored, EXTRA_ESCAPE_CHARS);
if (!parm.datastring)
{
err = gpg_error_from_syserror ();
goto leave;
}
xfree (armored);
armored = NULL;
/* Build the request string. */
reselect = 0;
again:
xfree (hostport); hostport = NULL;
xfree (httphost); httphost = NULL;
err = make_host_part (ctrl, uri->scheme, uri->host, uri->port,
reselect, uri->explicit_port,
&hostport, &httpflags, &httphost);
if (err)
goto leave;
xfree (request);
request = strconcat (hostport, "/pks/add", NULL);
if (!request)
{
err = gpg_error_from_syserror ();
goto leave;
}
/* Send the request. */
err = send_request (ctrl, request, hostport, httphost, 0,
put_post_cb, &parm, &fp, &http_status);
- if (handle_send_request_error (ctrl, err, request, http_status, &tries))
+ if (handle_send_request_error (ctrl, err, request, http_status,
+ &tries, &extra_tries))
{
reselect = 1;
goto again;
}
if (err)
goto leave;
leave:
es_fclose (fp);
xfree (parm.datastring);
xfree (armored);
xfree (request);
xfree (hostport);
xfree (httphost);
return err;
}
void
ks_hkp_init (void)
{
int err;
err = npth_mutex_init (&hosttable_lock, NULL);
if (err)
log_fatal ("error initializing mutex: %s\n", strerror (err));
}
diff --git a/dirmngr/ks-engine-http.c b/dirmngr/ks-engine-http.c
index 0f3e2db4a..a84a3a1ea 100644
--- a/dirmngr/ks-engine-http.c
+++ b/dirmngr/ks-engine-http.c
@@ -1,202 +1,206 @@
/* ks-engine-http.c - HTTP OpenPGP key access
* Copyright (C) 2011 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "dirmngr.h"
#include "misc.h"
#include "ks-engine.h"
/* How many redirections do we allow. */
#define MAX_REDIRECTS 2
/* Print a help output for the schemata supported by this module. */
gpg_error_t
ks_http_help (ctrl_t ctrl, parsed_uri_t uri)
{
const char data[] =
"Handler for HTTP URLs:\n"
" http://\n"
#if HTTP_USE_GNUTLS || HTTP_USE_NTBTLS
" https://\n"
#endif
"Supported methods: fetch\n";
gpg_error_t err;
#if HTTP_USE_GNUTLS || HTTP_USE_NTBTLS
const char data2[] = " http\n https";
#else
const char data2[] = " http";
#endif
if (!uri)
err = ks_print_help (ctrl, data2);
else if (uri->is_http && strcmp (uri->scheme, "hkp"))
err = ks_print_help (ctrl, data);
else
err = 0;
return err;
}
/* Get the key from URL which is expected to specify a http style
* scheme. On success R_FP has an open stream to read the data.
* Despite its name this function is also used to retrieve arbitrary
* data via https or http.
*/
gpg_error_t
ks_http_fetch (ctrl_t ctrl, const char *url, unsigned int flags,
estream_t *r_fp)
{
gpg_error_t err;
http_session_t session = NULL;
unsigned int session_flags;
http_t http = NULL;
http_redir_info_t redirinfo = { MAX_REDIRECTS };
estream_t fp = NULL;
char *request_buffer = NULL;
parsed_uri_t uri = NULL;
err = http_parse_uri (&uri, url, 0);
if (err)
goto leave;
redirinfo.orig_url = url;
redirinfo.orig_onion = uri->onion;
redirinfo.orig_https = uri->use_tls;
redirinfo.allow_downgrade = !!(flags & KS_HTTP_FETCH_ALLOW_DOWNGRADE);
/* By default we only use the system provided certificates with this
* fetch command. */
session_flags = HTTP_FLAG_TRUST_SYS;
if ((flags & KS_HTTP_FETCH_NO_CRL) || ctrl->http_no_crl)
session_flags |= HTTP_FLAG_NO_CRL;
if ((flags & KS_HTTP_FETCH_TRUST_CFG))
session_flags |= HTTP_FLAG_TRUST_CFG;
once_more:
err = http_session_new (&session, NULL, session_flags,
gnupg_http_tls_verify_cb, ctrl);
if (err)
goto leave;
http_session_set_log_cb (session, cert_log_cb);
http_session_set_timeout (session, ctrl->timeout);
*r_fp = NULL;
err = http_open (ctrl, &http,
HTTP_REQ_GET,
url,
/* httphost */ NULL,
/* fixme: AUTH */ NULL,
((opt.honor_http_proxy? HTTP_FLAG_TRY_PROXY:0)
| (DBG_LOOKUP? HTTP_FLAG_LOG_RESP:0)
| (dirmngr_use_tor ()? HTTP_FLAG_FORCE_TOR:0)
| (opt.disable_ipv4? HTTP_FLAG_IGNORE_IPv4 : 0)
| (opt.disable_ipv6? HTTP_FLAG_IGNORE_IPv6 : 0)),
ctrl->http_proxy,
session,
NULL,
/*FIXME curl->srvtag*/NULL);
if (!err)
{
fp = http_get_write_ptr (http);
/* Avoid caches to get the most recent copy of the key. We set
* both the Pragma and Cache-Control versions of the header, so
* we're good with both HTTP 1.0 and 1.1. */
if ((flags & KS_HTTP_FETCH_NOCACHE))
es_fputs ("Pragma: no-cache\r\n"
"Cache-Control: no-cache\r\n", fp);
http_start_data (http);
if (es_ferror (fp))
err = gpg_error_from_syserror ();
}
if (err)
{
/* Fixme: After a redirection we show the old host name. */
log_error (_("error connecting to '%s': %s\n"),
url, gpg_strerror (err));
goto leave;
}
/* Wait for the response. */
dirmngr_tick (ctrl);
err = http_wait_response (http);
if (err)
{
log_error (_("error reading HTTP response for '%s': %s\n"),
url, gpg_strerror (err));
goto leave;
}
switch (http_get_status_code (http))
{
case 200:
err = 0;
break; /* Success. */
case 301:
case 302:
case 307:
{
xfree (request_buffer);
err = http_prepare_redirect (&redirinfo, http_get_status_code (http),
http_get_header (http, "Location"),
&request_buffer);
if (err)
goto leave;
url = request_buffer;
http_close (http, 0);
http = NULL;
http_session_release (session);
session = NULL;
}
goto once_more;
+ case 413: /* Payload too large */
+ err = gpg_error (GPG_ERR_TOO_LARGE);
+ goto leave;
+
default:
log_error (_("error accessing '%s': http status %u\n"),
url, http_get_status_code (http));
err = gpg_error (GPG_ERR_NO_DATA);
goto leave;
}
fp = http_get_read_ptr (http);
if (!fp)
{
err = gpg_error (GPG_ERR_BUG);
goto leave;
}
/* Return the read stream and close the HTTP context. */
*r_fp = fp;
http_close (http, 1);
http = NULL;
leave:
http_close (http, 0);
http_session_release (session);
xfree (request_buffer);
http_release_parsed_uri (uri);
return err;
}
diff --git a/dirmngr/ocsp.c b/dirmngr/ocsp.c
index 79c252d87..e19779c59 100644
--- a/dirmngr/ocsp.c
+++ b/dirmngr/ocsp.c
@@ -1,830 +1,889 @@
/* ocsp.c - OCSP management
* Copyright (C) 2004, 2007 g10 Code GmbH
*
* This file is part of DirMngr.
*
* DirMngr is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* DirMngr is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <assert.h>
#include "dirmngr.h"
#include "misc.h"
#include "http.h"
#include "validate.h"
#include "certcache.h"
#include "ocsp.h"
/* The maximum size we allow as a response from an OCSP reponder. */
#define MAX_RESPONSE_SIZE 65536
static const char oidstr_ocsp[] = "1.3.6.1.5.5.7.48.1";
/* Telesec attribute used to implement a positive confirmation.
CertHash ::= SEQUENCE {
HashAlgorithm AlgorithmIdentifier,
certificateHash OCTET STRING }
*/
/* static const char oidstr_certHash[] = "1.3.36.8.3.13"; */
/* Read from FP and return a newly allocated buffer in R_BUFFER with the
entire data read from FP. */
static gpg_error_t
read_response (estream_t fp, unsigned char **r_buffer, size_t *r_buflen)
{
gpg_error_t err;
unsigned char *buffer;
size_t bufsize, nbytes;
*r_buffer = NULL;
*r_buflen = 0;
bufsize = 4096;
buffer = xtrymalloc (bufsize);
if (!buffer)
return gpg_error_from_errno (errno);
nbytes = 0;
for (;;)
{
unsigned char *tmp;
size_t nread = 0;
assert (nbytes < bufsize);
nread = es_fread (buffer+nbytes, 1, bufsize-nbytes, fp);
if (nread < bufsize-nbytes && es_ferror (fp))
{
err = gpg_error_from_errno (errno);
log_error (_("error reading from responder: %s\n"),
strerror (errno));
xfree (buffer);
return err;
}
if ( !(nread == bufsize-nbytes && !es_feof (fp)))
{ /* Response successfully received. */
nbytes += nread;
*r_buffer = buffer;
*r_buflen = nbytes;
return 0;
}
nbytes += nread;
/* Need to enlarge the buffer. */
if (bufsize >= MAX_RESPONSE_SIZE)
{
log_error (_("response from server too large; limit is %d bytes\n"),
MAX_RESPONSE_SIZE);
xfree (buffer);
return gpg_error (GPG_ERR_TOO_LARGE);
}
bufsize += 4096;
tmp = xtryrealloc (buffer, bufsize);
if (!tmp)
{
err = gpg_error_from_errno (errno);
xfree (buffer);
return err;
}
buffer = tmp;
}
}
/* Construct an OCSP request, send it to the configured OCSP responder
and parse the response. On success the OCSP context may be used to
- further process the response. */
+ further process the response. The signature value and the
+ production date are returned at R_SIGVAL and R_PRODUCED_AT; they
+ may be NULL or an empty string if not available. A new hash
+ context is returned at R_MD. */
static gpg_error_t
-do_ocsp_request (ctrl_t ctrl, ksba_ocsp_t ocsp, gcry_md_hd_t md,
- const char *url, ksba_cert_t cert, ksba_cert_t issuer_cert)
+do_ocsp_request (ctrl_t ctrl, ksba_ocsp_t ocsp,
+ const char *url, ksba_cert_t cert, ksba_cert_t issuer_cert,
+ ksba_sexp_t *r_sigval, ksba_isotime_t r_produced_at,
+ gcry_md_hd_t *r_md)
{
gpg_error_t err;
unsigned char *request, *response;
size_t requestlen, responselen;
http_t http;
ksba_ocsp_response_status_t response_status;
const char *t;
int redirects_left = 2;
char *free_this = NULL;
(void)ctrl;
+ *r_sigval = NULL;
+ *r_produced_at = 0;
+ *r_md = NULL;
+
if (dirmngr_use_tor ())
{
/* For now we do not allow OCSP via Tor due to possible privacy
concerns. Needs further research. */
log_error (_("OCSP request not possible due to Tor mode\n"));
return gpg_error (GPG_ERR_NOT_SUPPORTED);
}
if (opt.disable_http)
{
log_error (_("OCSP request not possible due to disabled HTTP\n"));
return gpg_error (GPG_ERR_NOT_SUPPORTED);
}
err = ksba_ocsp_add_target (ocsp, cert, issuer_cert);
if (err)
{
log_error (_("error setting OCSP target: %s\n"), gpg_strerror (err));
return err;
}
{
size_t n;
unsigned char nonce[32];
n = ksba_ocsp_set_nonce (ocsp, NULL, 0);
if (n > sizeof nonce)
n = sizeof nonce;
gcry_create_nonce (nonce, n);
ksba_ocsp_set_nonce (ocsp, nonce, n);
}
err = ksba_ocsp_build_request (ocsp, &request, &requestlen);
if (err)
{
log_error (_("error building OCSP request: %s\n"), gpg_strerror (err));
return err;
}
once_more:
err = http_open (ctrl, &http, HTTP_REQ_POST, url, NULL, NULL,
((opt.honor_http_proxy? HTTP_FLAG_TRY_PROXY:0)
| (dirmngr_use_tor ()? HTTP_FLAG_FORCE_TOR:0)
| (opt.disable_ipv4? HTTP_FLAG_IGNORE_IPv4 : 0)
| (opt.disable_ipv6? HTTP_FLAG_IGNORE_IPv6 : 0)),
ctrl->http_proxy, NULL, NULL, NULL);
if (err)
{
log_error (_("error connecting to '%s': %s\n"), url, gpg_strerror (err));
xfree (free_this);
return err;
}
es_fprintf (http_get_write_ptr (http),
"Content-Type: application/ocsp-request\r\n"
"Content-Length: %lu\r\n",
(unsigned long)requestlen );
http_start_data (http);
if (es_fwrite (request, requestlen, 1, http_get_write_ptr (http)) != 1)
{
err = gpg_error_from_errno (errno);
log_error ("error sending request to '%s': %s\n", url, strerror (errno));
http_close (http, 0);
xfree (request);
xfree (free_this);
return err;
}
xfree (request);
request = NULL;
err = http_wait_response (http);
if (err || http_get_status_code (http) != 200)
{
if (err)
log_error (_("error reading HTTP response for '%s': %s\n"),
url, gpg_strerror (err));
else
{
switch (http_get_status_code (http))
{
case 301:
case 302:
{
const char *s = http_get_header (http, "Location");
log_info (_("URL '%s' redirected to '%s' (%u)\n"),
url, s?s:"[none]", http_get_status_code (http));
if (s && *s && redirects_left-- )
{
xfree (free_this); url = NULL;
free_this = xtrystrdup (s);
if (!free_this)
err = gpg_error_from_errno (errno);
else
{
url = free_this;
http_close (http, 0);
goto once_more;
}
}
else
err = gpg_error (GPG_ERR_NO_DATA);
log_error (_("too many redirections\n"));
}
break;
+ case 413: /* Payload too large */
+ err = gpg_error (GPG_ERR_TOO_LARGE);
+ break;
+
default:
log_error (_("error accessing '%s': http status %u\n"),
url, http_get_status_code (http));
err = gpg_error (GPG_ERR_NO_DATA);
break;
}
}
http_close (http, 0);
xfree (free_this);
return err;
}
err = read_response (http_get_read_ptr (http), &response, &responselen);
http_close (http, 0);
if (err)
{
log_error (_("error reading HTTP response for '%s': %s\n"),
url, gpg_strerror (err));
xfree (free_this);
return err;
}
+ /* log_printhex (response, responselen, "ocsp response"); */
err = ksba_ocsp_parse_response (ocsp, response, responselen,
&response_status);
if (err)
{
log_error (_("error parsing OCSP response for '%s': %s\n"),
url, gpg_strerror (err));
xfree (response);
xfree (free_this);
return err;
}
switch (response_status)
{
case KSBA_OCSP_RSPSTATUS_SUCCESS: t = "success"; break;
case KSBA_OCSP_RSPSTATUS_MALFORMED: t = "malformed"; break;
case KSBA_OCSP_RSPSTATUS_INTERNAL: t = "internal error"; break;
case KSBA_OCSP_RSPSTATUS_TRYLATER: t = "try later"; break;
case KSBA_OCSP_RSPSTATUS_SIGREQUIRED: t = "must sign request"; break;
case KSBA_OCSP_RSPSTATUS_UNAUTHORIZED: t = "unauthorized"; break;
case KSBA_OCSP_RSPSTATUS_REPLAYED: t = "replay detected"; break;
case KSBA_OCSP_RSPSTATUS_OTHER: t = "other (unknown)"; break;
case KSBA_OCSP_RSPSTATUS_NONE: t = "no status"; break;
default: t = "[unknown status]"; break;
}
if (response_status == KSBA_OCSP_RSPSTATUS_SUCCESS)
{
+ int hash_algo;
+
if (opt.verbose)
log_info (_("OCSP responder at '%s' status: %s\n"), url, t);
+ /* Get the signature value now because we can all this fucntion
+ * only once. */
+ *r_sigval = ksba_ocsp_get_sig_val (ocsp, r_produced_at);
+
+ hash_algo = hash_algo_from_sigval (*r_sigval);
+ if (!hash_algo)
+ {
+ if (opt.verbose)
+ log_info ("ocsp: using SHA-256 as fallback hash algo.\n");
+ hash_algo = GCRY_MD_SHA256;
+ }
+ err = gcry_md_open (r_md, hash_algo, 0);
+ if (err)
+ {
+ log_error (_("failed to establish a hashing context for OCSP: %s\n"),
+ gpg_strerror (err));
+ goto leave;
+ }
+ if (DBG_HASHING)
+ gcry_md_debug (*r_md, "ocsp");
+
err = ksba_ocsp_hash_response (ocsp, response, responselen,
- HASH_FNC, md);
+ HASH_FNC, *r_md);
if (err)
log_error (_("hashing the OCSP response for '%s' failed: %s\n"),
url, gpg_strerror (err));
}
else
{
log_error (_("OCSP responder at '%s' status: %s\n"), url, t);
err = gpg_error (GPG_ERR_GENERAL);
}
+ leave:
xfree (response);
xfree (free_this);
+ if (err)
+ {
+ xfree (*r_sigval);
+ *r_sigval = NULL;
+ *r_produced_at = 0;
+ gcry_md_close (*r_md);
+ *r_md = NULL;
+ }
return err;
}
/* Validate that CERT is indeed valid to sign an OCSP response. If
SIGNER_FPR_LIST is not NULL we simply check that CERT matches one
of the fingerprints in this list. */
static gpg_error_t
validate_responder_cert (ctrl_t ctrl, ksba_cert_t cert,
fingerprint_list_t signer_fpr_list)
{
gpg_error_t err;
char *fpr;
if (signer_fpr_list)
{
fpr = get_fingerprint_hexstring (cert);
for (; signer_fpr_list && strcmp (signer_fpr_list->hexfpr, fpr);
signer_fpr_list = signer_fpr_list->next)
;
if (signer_fpr_list)
err = 0;
else
{
log_error (_("not signed by a default OCSP signer's certificate"));
err = gpg_error (GPG_ERR_BAD_CA_CERT);
}
xfree (fpr);
}
else
{
/* We avoid duplicating the entire certificate validation code
from gpgsm here. Because we have no way calling back to the
client and letting it compute the validity, we use the ugly
hack of telling the client that the response will only be
valid if the certificate given in this status message is
valid.
Note, that in theory we could simply ask the client via an
inquire to validate a certificate but this might involve
calling DirMngr again recursively - we can't do that as of now
(neither DirMngr nor gpgsm have the ability for concurrent
access to DirMngr. */
/* FIXME: We should cache this certificate locally, so that the next
call to dirmngr won't need to look it up - if this works at
all. */
fpr = get_fingerprint_hexstring (cert);
dirmngr_status (ctrl, "ONLY_VALID_IF_CERT_VALID", fpr, NULL);
xfree (fpr);
err = 0;
}
return err;
}
/* Helper for check_signature. */
static int
check_signature_core (ctrl_t ctrl, ksba_cert_t cert, gcry_sexp_t s_sig,
gcry_sexp_t s_hash, fingerprint_list_t signer_fpr_list)
{
gpg_error_t err;
ksba_sexp_t pubkey;
gcry_sexp_t s_pkey = NULL;
pubkey = ksba_cert_get_public_key (cert);
if (!pubkey)
err = gpg_error (GPG_ERR_INV_OBJ);
else
err = canon_sexp_to_gcry (pubkey, &s_pkey);
xfree (pubkey);
if (!err)
err = gcry_pk_verify (s_sig, s_hash, s_pkey);
if (!err)
err = validate_responder_cert (ctrl, cert, signer_fpr_list);
if (!err)
{
gcry_sexp_release (s_pkey);
return 0; /* Successfully verified the signature. */
}
/* We simply ignore all errors. */
gcry_sexp_release (s_pkey);
- return -1;
+ return err;
}
/* Check the signature of an OCSP response. OCSP is the context,
S_SIG the signature value and MD the handle of the hash we used for
the response. This function automagically finds the correct public
key. If SIGNER_FPR_LIST is not NULL, the default OCSP reponder has been
used and thus the certificate is one of those identified by
the fingerprints. */
static gpg_error_t
check_signature (ctrl_t ctrl,
ksba_ocsp_t ocsp, gcry_sexp_t s_sig, gcry_md_hd_t md,
fingerprint_list_t signer_fpr_list)
{
gpg_error_t err;
int algo, cert_idx;
gcry_sexp_t s_hash;
ksba_cert_t cert;
+ const char *s;
/* Create a suitable S-expression with the hash value of our response. */
gcry_md_final (md);
algo = gcry_md_get_algo (md);
- if (algo != GCRY_MD_SHA1 )
+ s = gcry_md_algo_name (algo);
+ if (algo && s && strlen (s) < 16)
{
- log_error (_("only SHA-1 is supported for OCSP responses\n"));
- return gpg_error (GPG_ERR_DIGEST_ALGO);
+ char hashalgostr[16+1];
+ int i;
+
+ for (i=0; s[i]; i++)
+ hashalgostr[i] = ascii_tolower (s[i]);
+ hashalgostr[i] = 0;
+ err = gcry_sexp_build (&s_hash, NULL, "(data(flags pkcs1)(hash %s %b))",
+ hashalgostr,
+ (int)gcry_md_get_algo_dlen (algo),
+ gcry_md_read (md, algo));
}
- err = gcry_sexp_build (&s_hash, NULL, "(data(flags pkcs1)(hash sha1 %b))",
- gcry_md_get_algo_dlen (algo),
- gcry_md_read (md, algo));
+ else
+ err = gpg_error (GPG_ERR_DIGEST_ALGO);
if (err)
{
log_error (_("creating S-expression failed: %s\n"), gcry_strerror (err));
return err;
}
/* Get rid of old OCSP specific certificate references. */
release_ctrl_ocsp_certs (ctrl);
if (signer_fpr_list && !signer_fpr_list->next)
{
/* There is exactly one signer fingerprint given. Thus we use
the default OCSP responder's certificate and instantly know
the certificate to use. */
cert = get_cert_byhexfpr (signer_fpr_list->hexfpr);
if (!cert)
cert = get_cert_local (ctrl, signer_fpr_list->hexfpr);
if (cert)
{
err = check_signature_core (ctrl, cert, s_sig, s_hash,
signer_fpr_list);
ksba_cert_release (cert);
cert = NULL;
if (!err)
{
gcry_sexp_release (s_hash);
return 0; /* Successfully verified the signature. */
}
}
}
else
{
char *name;
ksba_sexp_t keyid;
/* Put all certificates included in the response into the cache
and setup a list of those certificate which will later be
preferred used when locating certificates. */
for (cert_idx=0; (cert = ksba_ocsp_get_cert (ocsp, cert_idx));
cert_idx++)
{
cert_ref_t cref;
+ /* dump_cert ("from ocsp response", cert); */
cref = xtrymalloc (sizeof *cref);
if (!cref)
log_error (_("allocating list item failed: %s\n"),
gcry_strerror (err));
else if (!cache_cert_silent (cert, &cref->fpr))
{
cref->next = ctrl->ocsp_certs;
ctrl->ocsp_certs = cref;
}
else
xfree (cref);
}
/* Get the certificate by means of the responder ID. */
err = ksba_ocsp_get_responder_id (ocsp, &name, &keyid);
if (err)
{
log_error (_("error getting responder ID: %s\n"),
gcry_strerror (err));
return err;
}
cert = find_cert_bysubject (ctrl, name, keyid);
if (!cert)
{
log_error ("responder certificate ");
if (name)
log_printf ("'/%s' ", name);
if (keyid)
{
log_printf ("{");
dump_serial (keyid);
log_printf ("} ");
}
log_printf ("not found\n");
}
- ksba_free (name);
- ksba_free (keyid);
if (cert)
{
err = check_signature_core (ctrl, cert, s_sig, s_hash,
signer_fpr_list);
ksba_cert_release (cert);
if (!err)
{
+ ksba_free (name);
+ ksba_free (keyid);
gcry_sexp_release (s_hash);
return 0; /* Successfully verified the signature. */
}
+ log_error ("responder certificate ");
+ if (name)
+ log_printf ("'/%s' ", name);
+ if (keyid)
+ {
+ log_printf ("{");
+ dump_serial (keyid);
+ log_printf ("} ");
+ }
+ log_printf ("did not verify: %s\n", gpg_strerror (err));
}
+ ksba_free (name);
+ ksba_free (keyid);
}
gcry_sexp_release (s_hash);
log_error (_("no suitable certificate found to verify the OCSP response\n"));
return gpg_error (GPG_ERR_NO_PUBKEY);
}
/* Check whether the certificate either given by fingerprint CERT_FPR
or directly through the CERT object is valid by running an OCSP
transaction. With FORCE_DEFAULT_RESPONDER set only the configured
default responder is used. */
gpg_error_t
ocsp_isvalid (ctrl_t ctrl, ksba_cert_t cert, const char *cert_fpr,
int force_default_responder)
{
gpg_error_t err;
ksba_ocsp_t ocsp = NULL;
ksba_cert_t issuer_cert = NULL;
ksba_sexp_t sigval = NULL;
gcry_sexp_t s_sig = NULL;
ksba_isotime_t current_time;
ksba_isotime_t this_update, next_update, revocation_time, produced_at;
ksba_isotime_t tmp_time;
ksba_status_t status;
ksba_crl_reason_t reason;
char *url_buffer = NULL;
const char *url;
gcry_md_hd_t md = NULL;
int i, idx;
char *oid;
ksba_name_t name;
fingerprint_list_t default_signer = NULL;
/* Get the certificate. */
if (cert)
{
ksba_cert_ref (cert);
err = find_issuing_cert (ctrl, cert, &issuer_cert);
if (err)
{
log_error (_("issuer certificate not found: %s\n"),
gpg_strerror (err));
goto leave;
}
}
else
{
cert = get_cert_local (ctrl, cert_fpr);
if (!cert)
{
log_error (_("caller did not return the target certificate\n"));
err = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
issuer_cert = get_issuing_cert_local (ctrl, NULL);
if (!issuer_cert)
{
log_error (_("caller did not return the issuing certificate\n"));
err = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
}
/* Create an OCSP instance. */
err = ksba_ocsp_new (&ocsp);
if (err)
{
log_error (_("failed to allocate OCSP context: %s\n"),
gpg_strerror (err));
goto leave;
}
-
-
/* Figure out the OCSP responder to use.
1. Try to get the reponder from the certificate.
We do only take http and https style URIs into account.
2. If this fails use the default responder, if any.
*/
url = NULL;
for (idx=0; !url && !opt.ignore_ocsp_service_url && !force_default_responder
&& !(err=ksba_cert_get_authority_info_access (cert, idx,
&oid, &name)); idx++)
{
if ( !strcmp (oid, oidstr_ocsp) )
{
for (i=0; !url && ksba_name_enum (name, i); i++)
{
char *p = ksba_name_get_uri (name, i);
if (p && (!ascii_strncasecmp (p, "http:", 5)
|| !ascii_strncasecmp (p, "https:", 6)))
url = url_buffer = p;
else
xfree (p);
}
}
ksba_name_release (name);
ksba_free (oid);
}
if (err && gpg_err_code (err) != GPG_ERR_EOF)
{
log_error (_("can't get authorityInfoAccess: %s\n"), gpg_strerror (err));
goto leave;
}
if (!url)
{
if (!opt.ocsp_responder || !*opt.ocsp_responder)
{
log_info (_("no default OCSP responder defined\n"));
err = gpg_error (GPG_ERR_CONFIGURATION);
goto leave;
}
if (!opt.ocsp_signer)
{
log_info (_("no default OCSP signer defined\n"));
err = gpg_error (GPG_ERR_CONFIGURATION);
goto leave;
}
url = opt.ocsp_responder;
default_signer = opt.ocsp_signer;
if (opt.verbose)
log_info (_("using default OCSP responder '%s'\n"), url);
}
else
{
if (opt.verbose)
log_info (_("using OCSP responder '%s'\n"), url);
}
/* Ask the OCSP responder. */
- err = gcry_md_open (&md, GCRY_MD_SHA1, 0);
- if (err)
- {
- log_error (_("failed to establish a hashing context for OCSP: %s\n"),
- gpg_strerror (err));
- goto leave;
- }
- err = do_ocsp_request (ctrl, ocsp, md, url, cert, issuer_cert);
+ err = do_ocsp_request (ctrl, ocsp, url, cert, issuer_cert,
+ &sigval, produced_at, &md);
if (err)
goto leave;
/* It is sometimes useful to know the responder ID. */
if (opt.verbose)
{
char *resp_name;
ksba_sexp_t resp_keyid;
err = ksba_ocsp_get_responder_id (ocsp, &resp_name, &resp_keyid);
if (err)
log_info (_("error getting responder ID: %s\n"), gpg_strerror (err));
else
{
log_info ("responder id: ");
if (resp_name)
log_printf ("'/%s' ", resp_name);
if (resp_keyid)
{
log_printf ("{");
dump_serial (resp_keyid);
log_printf ("} ");
}
log_printf ("\n");
}
ksba_free (resp_name);
ksba_free (resp_keyid);
err = 0;
}
/* We got a useful answer, check that the answer has a valid signature. */
- sigval = ksba_ocsp_get_sig_val (ocsp, produced_at);
- if (!sigval || !*produced_at)
+ if (!sigval || !*produced_at || !md)
{
err = gpg_error (GPG_ERR_INV_OBJ);
goto leave;
}
if ( (err = canon_sexp_to_gcry (sigval, &s_sig)) )
goto leave;
xfree (sigval);
sigval = NULL;
err = check_signature (ctrl, ocsp, s_sig, md, default_signer);
if (err)
goto leave;
/* We only support one certificate per request. Check that the
answer matches the right certificate. */
err = ksba_ocsp_get_status (ocsp, cert,
&status, this_update, next_update,
revocation_time, &reason);
if (err)
{
log_error (_("error getting OCSP status for target certificate: %s\n"),
gpg_strerror (err));
goto leave;
}
/* In case the certificate has been revoked, we better invalidate
our cached validation status. */
if (status == KSBA_STATUS_REVOKED)
{
time_t validated_at = 0; /* That is: No cached validation available. */
err = ksba_cert_set_user_data (cert, "validated_at",
&validated_at, sizeof (validated_at));
if (err)
{
log_error ("set_user_data(validated_at) failed: %s\n",
gpg_strerror (err));
err = 0; /* The certificate is anyway revoked, and that is a
more important message than the failure of our
cache. */
}
}
if (opt.verbose)
{
log_info (_("certificate status is: %s (this=%s next=%s)\n"),
status == KSBA_STATUS_GOOD? _("good"):
status == KSBA_STATUS_REVOKED? _("revoked"):
status == KSBA_STATUS_UNKNOWN? _("unknown"):
status == KSBA_STATUS_NONE? _("none"): "?",
this_update, next_update);
if (status == KSBA_STATUS_REVOKED)
log_info (_("certificate has been revoked at: %s due to: %s\n"),
revocation_time,
reason == KSBA_CRLREASON_UNSPECIFIED? "unspecified":
reason == KSBA_CRLREASON_KEY_COMPROMISE? "key compromise":
reason == KSBA_CRLREASON_CA_COMPROMISE? "CA compromise":
reason == KSBA_CRLREASON_AFFILIATION_CHANGED?
"affiliation changed":
reason == KSBA_CRLREASON_SUPERSEDED? "superseded":
reason == KSBA_CRLREASON_CESSATION_OF_OPERATION?
"cessation of operation":
reason == KSBA_CRLREASON_CERTIFICATE_HOLD?
"certificate on hold":
reason == KSBA_CRLREASON_REMOVE_FROM_CRL?
"removed from CRL":
reason == KSBA_CRLREASON_PRIVILEGE_WITHDRAWN?
"privilege withdrawn":
reason == KSBA_CRLREASON_AA_COMPROMISE? "AA compromise":
reason == KSBA_CRLREASON_OTHER? "other":"?");
}
if (status == KSBA_STATUS_REVOKED)
err = gpg_error (GPG_ERR_CERT_REVOKED);
else if (status == KSBA_STATUS_UNKNOWN)
err = gpg_error (GPG_ERR_NO_DATA);
else if (status != KSBA_STATUS_GOOD)
err = gpg_error (GPG_ERR_GENERAL);
/* Allow for some clock skew. */
gnupg_get_isotime (current_time);
add_seconds_to_isotime (current_time, opt.ocsp_max_clock_skew);
if (strcmp (this_update, current_time) > 0 )
{
log_error (_("OCSP responder returned a status in the future\n"));
log_info ("used now: %s this_update: %s\n", current_time, this_update);
if (!err)
err = gpg_error (GPG_ERR_TIME_CONFLICT);
}
/* Check that THIS_UPDATE is not too far back in the past. */
gnupg_copy_time (tmp_time, this_update);
add_seconds_to_isotime (tmp_time,
opt.ocsp_max_period+opt.ocsp_max_clock_skew);
if (!*tmp_time || strcmp (tmp_time, current_time) < 0 )
{
log_error (_("OCSP responder returned a non-current status\n"));
log_info ("used now: %s this_update: %s\n",
current_time, this_update);
if (!err)
err = gpg_error (GPG_ERR_TIME_CONFLICT);
}
/* Check that we are not beyond NEXT_UPDATE (plus some extra time). */
if (*next_update)
{
gnupg_copy_time (tmp_time, next_update);
add_seconds_to_isotime (tmp_time,
opt.ocsp_current_period+opt.ocsp_max_clock_skew);
if (!*tmp_time && strcmp (tmp_time, current_time) < 0 )
{
log_error (_("OCSP responder returned an too old status\n"));
log_info ("used now: %s next_update: %s\n",
current_time, next_update);
if (!err)
err = gpg_error (GPG_ERR_TIME_CONFLICT);
}
}
leave:
gcry_md_close (md);
gcry_sexp_release (s_sig);
xfree (sigval);
ksba_cert_release (issuer_cert);
ksba_cert_release (cert);
ksba_ocsp_release (ocsp);
xfree (url_buffer);
return err;
}
/* Release the list of OCSP certificates hold in the CTRL object. */
void
release_ctrl_ocsp_certs (ctrl_t ctrl)
{
while (ctrl->ocsp_certs)
{
cert_ref_t tmp = ctrl->ocsp_certs->next;
xfree (ctrl->ocsp_certs);
ctrl->ocsp_certs = tmp;
}
}
diff --git a/doc/DETAILS b/doc/DETAILS
index 74a63ef00..3046523da 100644
--- a/doc/DETAILS
+++ b/doc/DETAILS
@@ -1,1580 +1,1581 @@
# doc/DETAILS -*- org -*-
#+TITLE: GnuPG Details
# Globally disable superscripts and subscripts:
#+OPTIONS: ^:{}
#
# Note: This file uses org-mode; it should be easy to read as plain
# text but be aware of some markup peculiarities: Verbatim code is
# enclosed in #+begin-example, #+end-example blocks or marked by a
# colon as the first non-white-space character, words bracketed with
# equal signs indicate a monospace font, and the usual /italics/,
# *bold*, and _underline_ conventions are recognized.
This is the DETAILS file for GnuPG which specifies some internals and
parts of the external API for GPG and GPGSM.
* Format of the colon listings
The format is a based on colon separated record, each recods starts
with a tag string and extends to the end of the line. Here is an
example:
#+begin_example
$ gpg --with-colons --list-keys \
--with-fingerprint --with-fingerprint wk@gnupg.org
pub:f:1024:17:6C7EE1B8621CC013:899817715:1055898235::m:::scESC:
fpr:::::::::ECAF7590EB3443B5C7CF3ACB6C7EE1B8621CC013:
uid:f::::::::Werner Koch <wk@g10code.com>:
uid:f::::::::Werner Koch <wk@gnupg.org>:
sub:f:1536:16:06AD222CADF6A6E1:919537416:1036177416:::::e:
fpr:::::::::CF8BCC4B18DE08FCD8A1615906AD222CADF6A6E1:
sub:r:1536:20:5CE086B5B5A18FF4:899817788:1025961788:::::esc:
fpr:::::::::AB059359A3B81F410FCFF97F5CE086B5B5A18FF4:
#+end_example
Note that new version of GnuPG or the use of certain options may add
new fields to the output. Parsers should not assume a limit on the
number of fields per line. Some fields are not yet used or only used
with certain record types; parsers should ignore fields they are not
aware of. New versions of GnuPG or the use of certain options may add
new types of records as well. Parsers should ignore any record whose
type they do not recognize for forward-compatibility.
The double =--with-fingerprint= prints the fingerprint for the subkeys
too. Old versions of gpg used a slightly different format and required
the use of the option =--fixed-list-mode= to conform to the format
described here.
** Description of the fields
*** Field 1 - Type of record
- pub :: Public key
- crt :: X.509 certificate
- crs :: X.509 certificate and private key available
- sub :: Subkey (secondary key)
- sec :: Secret key
- ssb :: Secret subkey (secondary key)
- uid :: User id
- uat :: User attribute (same as user id except for field 10).
- sig :: Signature
- rev :: Revocation signature
- rvs :: Revocation signature (standalone) [since 2.2.9]
- fpr :: Fingerprint (fingerprint is in field 10)
- pkd :: Public key data [*]
- grp :: Keygrip
- rvk :: Revocation key
- tfs :: TOFU statistics [*]
- tru :: Trust database information [*]
- spk :: Signature subpacket [*]
- cfg :: Configuration data [*]
Records marked with an asterisk are described at [[*Special%20field%20formats][*Special fields]].
*** Field 2 - Validity
This is a letter describing the computed validity of a key.
Currently this is a single letter, but be prepared that additional
information may follow in some future versions. Note that GnuPG <
2.1 does not set this field for secret key listings.
- o :: Unknown (this key is new to the system)
- i :: The key is invalid (e.g. due to a missing self-signature)
- d :: The key has been disabled
(deprecated - use the 'D' in field 12 instead)
- r :: The key has been revoked
- e :: The key has expired
- - :: Unknown validity (i.e. no value assigned)
- q :: Undefined validity. '-' and 'q' may safely be treated as
the same value for most purposes
- n :: The key is not valid
- m :: The key is marginal valid.
- f :: The key is fully valid
- u :: The key is ultimately valid. This often means that the
secret key is available, but any key may be marked as
ultimately valid.
- w :: The key has a well known private part.
- s :: The key has special validity. This means that it might be
self-signed and expected to be used in the STEED system.
If the validity information is given for a UID or UAT record, it
describes the validity calculated based on this user ID. If given
for a key record it describes the validity taken from the best
rated user ID.
For X.509 certificates a 'u' is used for a trusted root
certificate (i.e. for the trust anchor) and an 'f' for all other
valid certificates.
In "sig" records, this field may have one of these values as first
character:
- ! :: Signature is good.
- - :: Signature is bad.
- ? :: No public key to verify signature or public key is not usable.
- % :: Other error verifying a signature
More values may be added later. The field may also be empty if
gpg has been invoked in a non-checking mode (--list-sigs) or in a
fast checking mode. Since 2.2.7 '?' will also be printed by the
command --list-sigs if the key is not in the local keyring.
*** Field 3 - Key length
The length of key in bits.
*** Field 4 - Public key algorithm
The values here are those from the OpenPGP specs or if they are
greater than 255 the algorithm ids as used by Libgcrypt.
*** Field 5 - KeyID
This is the 64 bit keyid as specified by OpenPGP and the last 64
bit of the SHA-1 fingerprint of an X.509 certifciate.
*** Field 6 - Creation date
The creation date of the key is given in UTC. For UID and UAT
records, this is used for the self-signature date. Note that the
date is usually printed in seconds since epoch, however, we are
migrating to an ISO 8601 format (e.g. "19660205T091500"). This is
currently only relevant for X.509. A simple way to detect the new
format is to scan for the 'T'. Note that old versions of gpg
without using the =--fixed-list-mode= option used a "yyyy-mm-tt"
format.
*** Field 7 - Expiration date
Key or UID/UAT expiration date or empty if it does not expire.
*** Field 8 - Certificate S/N, UID hash, trust signature info
Used for serial number in crt records. For UID and UAT records,
this is a hash of the user ID contents used to represent that
exact user ID. For trust signatures, this is the trust depth
separated by the trust value by a space.
*** Field 9 - Ownertrust
This is only used on primary keys. This is a single letter, but
be prepared that additional information may follow in future
versions. For trust signatures with a regular expression, this is
the regular expression value, quoted as in field 10.
*** Field 10 - User-ID
The value is quoted like a C string to avoid control characters
(the colon is quoted =\x3a=). For a "pub" record this field is
not used on --fixed-list-mode. A UAT record puts the attribute
subpacket count here, a space, and then the total attribute
subpacket size. In gpgsm the issuer name comes here. A FPR
record stores the fingerprint here. The fingerprint of a
revocation key is stored here.
*** Field 11 - Signature class
Signature class as per RFC-4880. This is a 2 digit hexnumber
followed by either the letter 'x' for an exportable signature or
the letter 'l' for a local-only signature. The class byte of an
revocation key is also given here, 'x' and 'l' is used the same
way. This field if not used for X.509.
"rev" and "rvs" may be followed by a comma and a 2 digit hexnumber
with the revocation reason.
*** Field 12 - Key capabilities
The defined capabilities are:
- e :: Encrypt
- s :: Sign
- c :: Certify
- a :: Authentication
- ? :: Unknown capability
A key may have any combination of them in any order. In addition
to these letters, the primary key has uppercase versions of the
letters to denote the _usable_ capabilities of the entire key, and
a potential letter 'D' to indicate a disabled key.
*** Field 13 - Issuer certificate fingerprint or other info
Used in FPR records for S/MIME keys to store the fingerprint of
the issuer certificate. This is useful to build the certificate
path based on certificates stored in the local key database it is
only filled if the issuer certificate is available. The root has
been reached if this is the same string as the fingerprint. The
advantage of using this value is that it is guaranteed to have
been built by the same lookup algorithm as gpgsm uses.
For "uid" records this field lists the preferences in the same way
gpg's --edit-key menu does.
For "sig", "rev" and "rvs" records, this is the fingerprint of the
key that issued the signature. Note that this may only be filled
if the signature verified correctly. Note also that for various
technical reasons, this fingerprint is only available if
--no-sig-cache is used. Since 2.2.7 this field will also be set
if the key is missing but the signature carries an issuer
fingerprint as meta data.
*** Field 14 - Flag field
Flag field used in the --edit menu output
*** Field 15 - S/N of a token
Used in sec/ssb to print the serial number of a token (internal
protect mode 1002) or a '#' if that key is a simple stub (internal
protect mode 1001). If the option --with-secret is used and a
secret key is available for the public key, a '+' indicates this.
*** Field 16 - Hash algorithm
For sig records, this is the used hash algorithm. For example:
2 = SHA-1, 8 = SHA-256.
*** Field 17 - Curve name
For pub, sub, sec, and ssb records this field is used for the ECC
curve name.
*** Field 18 - Compliance flags
Space separated list of asserted compliance modes and
screening result for this key.
Valid values are:
- 8 :: The key is compliant with RFC4880bis
- 23 :: The key is compliant with compliance mode "de-vs".
- 6001 :: Screening hit on the ROCA vulnerability.
*** Field 19 - Last update
The timestamp of the last update of a key or user ID. The update
time of a key is defined a lookup of the key via its unique
identifier (fingerprint); the field is empty if not known. The
update time of a user ID is defined by a lookup of the key using a
trusted mapping from mail address to key.
*** Field 20 - Origin
The origin of the key or the user ID. This is an integer
optionally followed by a space and an URL. This goes along with
the previous field. The URL is quoted in C style.
*** Field 21 - Comment
This is currently only used in "rev" and "rvs" records to carry
the the comment field of the recocation reason. The value is
quoted in C style.
** Special fields
*** PKD - Public key data
If field 1 has the tag "pkd", a listing looks like this:
#+begin_example
pkd:0:1024:B665B1435F4C2 .... FF26ABB:
! ! !-- the value
! !------ for information number of bits in the value
!--------- index (eg. DSA goes from 0 to 3: p,q,g,y)
#+end_example
*** TFS - TOFU statistics
This field may follows a UID record to convey information about
the TOFU database. The information is similar to a TOFU_STATS
status line.
- Field 2 :: tfs record version (must be 1)
- Field 3 :: validity - A number with validity code.
- Field 4 :: signcount - The number of signatures seen.
- Field 5 :: encrcount - The number of encryptions done.
- Field 6 :: policy - A string with the policy
- Field 7 :: signture-first-seen - a timestamp or 0 if not known.
- Field 8 :: signature-most-recent-seen - a timestamp or 0 if not known.
- Field 9 :: encryption-first-done - a timestamp or 0 if not known.
- Field 10 :: encryption-most-recent-done - a timestamp or 0 if not known.
*** TRU - Trust database information
Example for a "tru" trust base record:
#+begin_example
tru:o:0:1166697654:1:3:1:5
#+end_example
- Field 2 :: Reason for staleness of trust. If this field is
empty, then the trustdb is not stale. This field may
have multiple flags in it:
- o :: Trustdb is old
- t :: Trustdb was built with a different trust model
than the one we are using now.
- Field 3 :: Trust model
- 0 :: Classic trust model, as used in PGP 2.x.
- 1 :: PGP trust model, as used in PGP 6 and later.
This is the same as the classic trust model,
except for the addition of trust signatures.
GnuPG before version 1.4 used the classic trust model
by default. GnuPG 1.4 and later uses the PGP trust
model by default.
- Field 4 :: Date trustdb was created in seconds since Epoch.
- Field 5 :: Date trustdb will expire in seconds since Epoch.
- Field 6 :: Number of marginally trusted users to introduce a new
key signer (gpg's option --marginals-needed).
- Field 7 :: Number of completely trusted users to introduce a new
key signer. (gpg's option --completes-needed)
- Field 8 :: Maximum depth of a certification chain. (gpg's option
--max-cert-depth)
*** SPK - Signature subpacket records
- Field 2 :: Subpacket number as per RFC-4880 and later.
- Field 3 :: Flags in hex. Currently the only two bits assigned
are 1, to indicate that the subpacket came from the
hashed part of the signature, and 2, to indicate the
subpacket was marked critical.
- Field 4 :: Length of the subpacket. Note that this is the
length of the subpacket, and not the length of field
5 below. Due to the need for %-encoding, the length
of field 5 may be up to 3x this value.
- Field 5 :: The subpacket data. Printable ASCII is shown as
ASCII, but other values are rendered as %XX where XX
is the hex value for the byte.
*** CFG - Configuration data
--list-config outputs information about the GnuPG configuration
for the benefit of frontends or other programs that call GnuPG.
There are several list-config items, all colon delimited like the
rest of the --with-colons output. The first field is always "cfg"
to indicate configuration information. The second field is one of
(with examples):
- version :: The third field contains the version of GnuPG.
: cfg:version:1.3.5
- pubkey :: The third field contains the public key algorithms
this version of GnuPG supports, separated by
semicolons. The algorithm numbers are as specified in
RFC-4880. Note that in contrast to the --status-fd
interface these are _not_ the Libgcrypt identifiers.
Using =pubkeyname= prints names instead of numbers.
: cfg:pubkey:1;2;3;16;17
- cipher :: The third field contains the symmetric ciphers this
version of GnuPG supports, separated by semicolons.
The cipher numbers are as specified in RFC-4880.
Using =ciphername= prints names instead of numbers.
: cfg:cipher:2;3;4;7;8;9;10
- digest :: The third field contains the digest (hash) algorithms
this version of GnuPG supports, separated by
semicolons. The digest numbers are as specified in
RFC-4880. Using =digestname= prints names instead of
numbers.
: cfg:digest:1;2;3;8;9;10
- compress :: The third field contains the compression algorithms
this version of GnuPG supports, separated by
semicolons. The algorithm numbers are as specified
in RFC-4880.
: cfg:compress:0;1;2;3
- group :: The third field contains the name of the group, and the
fourth field contains the values that the group expands
to, separated by semicolons.
For example, a group of:
: group mynames = paige 0x12345678 joe patti
would result in:
: cfg:group:mynames:patti;joe;0x12345678;paige
- curve :: The third field contains the curve names this version
of GnuPG supports, separated by semicolons. Using
=curveoid= prints OIDs instead of numbers.
: cfg:curve:ed25519;nistp256;nistp384;nistp521
* Format of the --status-fd output
Every line is prefixed with "[GNUPG:] ", followed by a keyword with
the type of the status line and some arguments depending on the type
(maybe none); an application should always be willing to ignore
unknown keywords that may be emitted by future versions of GnuPG.
Also, new versions of GnuPG may add arguments to existing keywords.
Any additional arguments should be ignored for forward-compatibility.
** General status codes
*** NEWSIG [<signers_uid>]
Is issued right before a signature verification starts. This is
useful to define a context for parsing ERROR status messages.
If SIGNERS_UID is given and is not "-" this is the percent-escaped
value of the OpenPGP Signer's User ID signature sub-packet.
*** GOODSIG <long_keyid_or_fpr> <username>
The signature with the keyid is good. For each signature only one
of the codes GOODSIG, BADSIG, EXPSIG, EXPKEYSIG, REVKEYSIG or
ERRSIG will be emitted. In the past they were used as a marker
for a new signature; new code should use the NEWSIG status
instead. The username is the primary one encoded in UTF-8 and %XX
escaped. The fingerprint may be used instead of the long keyid if
it is available. This is the case with CMS and might eventually
also be available for OpenPGP.
*** EXPSIG <long_keyid_or_fpr> <username>
The signature with the keyid is good, but the signature is
expired. The username is the primary one encoded in UTF-8 and %XX
escaped. The fingerprint may be used instead of the long keyid if
it is available. This is the case with CMS and might eventually
also be available for OpenPGP.
*** EXPKEYSIG <long_keyid_or_fpr> <username>
The signature with the keyid is good, but the signature was made
by an expired key. The username is the primary one encoded in
UTF-8 and %XX escaped. The fingerprint may be used instead of the
long keyid if it is available. This is the case with CMS and
might eventually also be available for OpenPGP.
*** REVKEYSIG <long_keyid_or_fpr> <username>
The signature with the keyid is good, but the signature was made
by a revoked key. The username is the primary one encoded in UTF-8
and %XX escaped. The fingerprint may be used instead of the long
keyid if it is available. This is the case with CMS and might
eventually also beñ available for OpenPGP.
*** BADSIG <long_keyid_or_fpr> <username>
The signature with the keyid has not been verified okay. The
username is the primary one encoded in UTF-8 and %XX escaped. The
fingerprint may be used instead of the long keyid if it is
available. This is the case with CMS and might eventually also be
available for OpenPGP.
*** ERRSIG <keyid> <pkalgo> <hashalgo> <sig_class> <time> <rc> <fpr>
It was not possible to check the signature. This may be caused by
a missing public key or an unsupported algorithm. A RC of 4
indicates unknown algorithm, a 9 indicates a missing public
key. The other fields give more information about this signature.
sig_class is a 2 byte hex-value. The fingerprint may be used
instead of the long_keyid_or_fpr if it is available. This is the
case with gpgsm and might eventually also be available for
OpenPGP. The ERRSIG line has FPR filed which is only available
since 2.2.7; that FPR may either be missing or - if the signature
has no fingerprint as meta data.
Note, that TIME may either be the number of seconds since Epoch or
an ISO 8601 string. The latter can be detected by the presence of
the letter 'T'.
*** VALIDSIG <args>
The args are:
- <fingerprint_in_hex>
- <sig_creation_date>
- <sig-timestamp>
- <expire-timestamp>
- <sig-version>
- <reserved>
- <pubkey-algo>
- <hash-algo>
- <sig-class>
- [ <primary-key-fpr> ]
This status indicates that the signature is cryptographically
valid. This is similar to GOODSIG, EXPSIG, EXPKEYSIG, or REVKEYSIG
(depending on the date and the state of the signature and signing
key) but has the fingerprint as the argument. Multiple status
lines (VALIDSIG and the other appropriate *SIG status) are emitted
for a valid signature. All arguments here are on one long line.
sig-timestamp is the signature creation time in seconds after the
epoch. expire-timestamp is the signature expiration time in
seconds after the epoch (zero means "does not
expire"). sig-version, pubkey-algo, hash-algo, and sig-class (a
2-byte hex value) are all straight from the signature packet.
PRIMARY-KEY-FPR is the fingerprint of the primary key or identical
to the first argument. This is useful to get back to the primary
key without running gpg again for this purpose.
The primary-key-fpr parameter is used for OpenPGP and not
available for CMS signatures. The sig-version as well as the sig
class is not defined for CMS and currently set to 0 and 00.
Note, that *-TIMESTAMP may either be a number of seconds since
Epoch or an ISO 8601 string which can be detected by the presence
of the letter 'T'.
*** SIG_ID <radix64_string> <sig_creation_date> <sig-timestamp>
This is emitted only for signatures of class 0 or 1 which have
been verified okay. The string is a signature id and may be used
in applications to detect replay attacks of signed messages. Note
that only DLP algorithms give unique ids - others may yield
duplicated ones when they have been created in the same second.
Note, that SIG-TIMESTAMP may either be a number of seconds since
Epoch or an ISO 8601 string which can be detected by the presence
of the letter 'T'.
*** ENC_TO <long_keyid> <keytype> <keylength>
The message is encrypted to this LONG_KEYID. KEYTYPE is the
numerical value of the public key algorithm or 0 if it is not
known, KEYLENGTH is the length of the key or 0 if it is not known
(which is currently always the case). Gpg prints this line
always; Gpgsm only if it knows the certificate.
*** BEGIN_DECRYPTION
Mark the start of the actual decryption process. This is also
emitted when in --list-only mode.
*** END_DECRYPTION
Mark the end of the actual decryption process. This are also
emitted when in --list-only mode.
*** DECRYPTION_KEY <fpr> <fpr2> <otrust>
This line is emitted when a public key decryption succeeded in
providing a session key. <fpr> is the hexified fingerprint of the
actual key used for decryption. <fpr2> is the fingerprint of the
primary key. <otrust> is the letter with the ownertrust; this is
in general a 'u' which stands for ultimately trusted.
*** DECRYPTION_INFO <mdc_method> <sym_algo> [<aead_algo>]
Print information about the symmetric encryption algorithm and the
MDC method. This will be emitted even if the decryption fails.
For an AEAD algorithm AEAD_ALGO is not 0. GPGSM currently does
not print such a status.
*** DECRYPTION_FAILED
The symmetric decryption failed - one reason could be a wrong
passphrase for a symmetrical encrypted message.
*** DECRYPTION_OKAY
The decryption process succeeded. This means, that either the
correct secret key has been used or the correct passphrase for a
symmetric encrypted message was given. The program itself may
return an errorcode because it may not be possible to verify a
signature for some reasons.
*** SESSION_KEY <algo>:<hexdigits>
The session key used to decrypt the message. This message will
only be emitted if the option --show-session-key is used. The
format is suitable to be passed as value for the option
--override-session-key. It is not an indication that the
decryption will or has succeeded.
*** BEGIN_ENCRYPTION <mdc_method> <sym_algo> [<aead_algo>]
Mark the start of the actual encryption process.
MDC_METHOD shall be 0 if an AEAD_ALGO is not 0. Users should
however ignore MDC_METHOD if AEAD_ALGO is not 0.
*** END_ENCRYPTION
Mark the end of the actual encryption process.
*** FILE_START <what> <filename>
Start processing a file <filename>. <what> indicates the performed
operation:
- 1 :: verify
- 2 :: encrypt
- 3 :: decrypt
*** FILE_DONE
Marks the end of a file processing which has been started
by FILE_START.
*** BEGIN_SIGNING
Mark the start of the actual signing process. This may be used as
an indication that all requested secret keys are ready for use.
*** ALREADY_SIGNED <long-keyid>
Warning: This is experimental and might be removed at any time.
*** SIG_CREATED <type> <pk_algo> <hash_algo> <class> <timestamp> <keyfpr>
A signature has been created using these parameters.
Values for type <type> are:
- D :: detached
- C :: cleartext
- S :: standard
(only the first character should be checked)
<class> are 2 hex digits with the OpenPGP signature class.
Note, that TIMESTAMP may either be a number of seconds since Epoch
or an ISO 8601 string which can be detected by the presence of the
letter 'T'.
*** NOTATION_
There are actually three related status codes to convey notation
data:
- NOTATION_NAME <name>
- NOTATION_FLAGS <critical> <human_readable>
- NOTATION_DATA <string>
<name> and <string> are %XX escaped. The data may be split among
several NOTATION_DATA lines. NOTATION_FLAGS is emitted after
NOTATION_NAME and gives the critical and human readable flags;
the flag values are either 0 or 1.
*** POLICY_URL <string>
Note that URL in <string> is %XX escaped.
*** PLAINTEXT <format> <timestamp> <filename>
This indicates the format of the plaintext that is about to be
written. The format is a 1 byte hex code that shows the format of
the plaintext: 62 ('b') is binary data, 74 ('t') is text data with
no character set specified, and 75 ('u') is text data encoded in
the UTF-8 character set. The timestamp is in seconds since the
epoch. If a filename is available it gets printed as the third
argument, percent-escaped as usual.
*** PLAINTEXT_LENGTH <length>
This indicates the length of the plaintext that is about to be
written. Note that if the plaintext packet has partial length
encoding it is not possible to know the length ahead of time. In
that case, this status tag does not appear.
*** ATTRIBUTE <arguments>
The list or arguments are:
- <fpr>
- <octets>
- <type>
- <index>
- <count>
- <timestamp>
- <expiredate>
- <flags>
This is one long line issued for each attribute subpacket when an
attribute packet is seen during key listing. <fpr> is the
fingerprint of the key. <octets> is the length of the attribute
subpacket. <type> is the attribute type (e.g. 1 for an image).
<index> and <count> indicate that this is the N-th indexed
subpacket of count total subpackets in this attribute packet.
<timestamp> and <expiredate> are from the self-signature on the
attribute packet. If the attribute packet does not have a valid
self-signature, then the timestamp is 0. <flags> are a bitwise OR
of:
- 0x01 :: this attribute packet is a primary uid
- 0x02 :: this attribute packet is revoked
- 0x04 :: this attribute packet is expired
*** SIG_SUBPACKET <type> <flags> <len> <data>
This indicates that a signature subpacket was seen. The format is
the same as the "spk" record above.
*** ENCRYPTION_COMPLIANCE_MODE <flags>
Indicates that the current encryption operation was in compliance
with the given set of modes for all recipients. "flags" is a
space separated list of numerical flags, see "Field 18 -
Compliance flags" above.
*** DECRYPTION_COMPLIANCE_MODE <flags>
Indicates that the current decryption operation is in compliance
with the given set of modes. "flags" is a space separated list of
numerical flags, see "Field 18 - Compliance flags" above.
*** VERIFICATION_COMPLIANCE_MODE <flags>
Indicates that the current signature verification operation is in
compliance with the given set of modes. "flags" is a space
separated list of numerical flags, see "Field 18 - Compliance
flags" above.
** Key related
*** INV_RECP, INV_SGNR
The two similar status codes:
- INV_RECP <reason> <requested_recipient>
- INV_SGNR <reason> <requested_sender>
are issued for each unusable recipient/sender. The reasons codes
currently in use are:
- 0 :: No specific reason given
- 1 :: Not Found
- 2 :: Ambiguous specification
- 3 :: Wrong key usage
- 4 :: Key revoked
- 5 :: Key expired
- 6 :: No CRL known
- 7 :: CRL too old
- 8 :: Policy mismatch
- 9 :: Not a secret key
- 10 :: Key not trusted
- 11 :: Missing certificate
- 12 :: Missing issuer certificate
- 13 :: Key disabled
- 14 :: Syntax error in specification
If no specific reason was given a previously emitted status code
KEY_CONSIDERED may be used to analyzed the problem.
Note that for historical reasons the INV_RECP status is also used
for gpgsm's SIGNER command where it relates to signer's of course.
Newer GnuPG versions are using INV_SGNR; applications should
ignore the INV_RECP during the sender's command processing once
they have seen an INV_SGNR. Different codes are used so that they
can be distinguish while doing an encrypt+sign operation.
*** NO_RECP <reserved>
Issued if no recipients are usable.
*** NO_SGNR <reserved>
Issued if no senders are usable.
*** KEY_CONSIDERED <fpr> <flags>
Issued to explian the lookup of a key. FPR is the hexified
fingerprint of the primary key. The bit values for FLAGS are:
- 1 :: The key has not been selected.
- 2 :: All subkeys of the key are expired or have been revoked.
*** KEYEXPIRED <expire-timestamp>
The key has expired. expire-timestamp is the expiration time in
seconds since Epoch. This status line is not very useful because
it will also be emitted for expired subkeys even if this subkey is
not used. To check whether a key used to sign a message has
expired, the EXPKEYSIG status line is to be used.
Note, that the TIMESTAMP may either be a number of seconds since
Epoch or an ISO 8601 string which can be detected by the presence
of the letter 'T'.
*** KEYREVOKED
The used key has been revoked by its owner. No arguments yet.
*** NO_PUBKEY <long keyid>
The public key is not available. Note the arg should in general
not be used because it is better to take it from the ERRSIG
status line which is printed right before this one.
*** NO_SECKEY <long keyid>
The secret key is not available
*** KEY_CREATED <type> <fingerprint> [<handle>]
A key has been created. Values for <type> are:
- B :: primary and subkey
- P :: primary
- S :: subkey
The fingerprint is one of the primary key for type B and P and the
one of the subkey for S. Handle is an arbitrary non-whitespace
string used to match key parameters from batch key creation run.
*** KEY_NOT_CREATED [<handle>]
The key from batch run has not been created due to errors.
*** TRUST_
These are several similar status codes:
- TRUST_UNDEFINED <error_token>
- TRUST_NEVER <error_token>
- TRUST_MARGINAL [0 [<validation_model>]]
- TRUST_FULLY [0 [<validation_model>]]
- TRUST_ULTIMATE [0 [<validation_model>]]
For good signatures one of these status lines are emitted to
indicate the validity of the key used to create the signature.
The error token values are currently only emitted by gpgsm.
VALIDATION_MODEL describes the algorithm used to check the
validity of the key. The defaults are the standard Web of Trust
model for gpg and the standard X.509 model for gpgsm. The
defined values are
- pgp :: The standard PGP WoT.
- shell :: The standard X.509 model.
- chain :: The chain model.
- steed :: The STEED model.
- tofu :: The TOFU model
Note that the term =TRUST_= in the status names is used for
historic reasons; we now speak of validity.
*** TOFU_USER <fingerprint_in_hex> <mbox>
This status identifies the key and the userid for all following
Tofu information. The fingerprint is the fingerprint of the
primary key and the mbox is in general the addr-spec part of the
userid encoded in UTF-8 and percent escaped. The fingerprint is
identical for all TOFU_USER lines up to a NEWSIG line.
*** TOFU_STATS <MANY_ARGS>
Statistics for the current user id.
The <MANY_ARGS> are the usual space delimited arguments. Here we
have too many of them to fit on one printed line and thus they are
given on 3 printed lines:
: <summary> <sign-count> <encryption-count>
: [<policy> [<tm1> <tm2> <tm3> <tm4>
: [<validity> [<sign-days> <encrypt-days>]]]]
Values for SUMMARY are:
- 0 :: attention, an interaction with the user is required (conflict)
- 1 :: key with no verification/encryption history
- 2 :: key with little history
- 3 :: key with enough history for basic trust
- 4 :: key with a lot of history
Values for POLICY are:
- none :: No Policy set
- auto :: Policy is "auto"
- good :: Policy is "good"
- bad :: Policy is "bad"
- ask :: Policy is "ask"
- unknown :: Policy is "unknown" (TOFU information does not
contribute to the key's validity)
TM1 is the time the first message was verified. TM2 is the time
the most recent message was verified. TM3 is the time the first
message was encrypted. TM4 is the most recent encryption. All may
either be seconds since Epoch or an ISO time string
(yyyymmddThhmmss).
VALIDITY is the same as SUMMARY with the exception that VALIDITY
doesn't reflect whether the key needs attention. That is it never
takes on value 0. Instead, if there is a conflict, VALIDITY still
reflects the key's validity (values: 1-4).
SUMMARY values use the euclidean distance (m = sqrt(a² + b²)) rather
then the sum of the magnitudes (m = a + b) to ensure a balance between
verified signatures and encrypted messages.
Values are calculated based on the number of days where a key was used
for verifying a signature or to encrypt to it.
The ranges for the values are:
- 1 :: signature_days + encryption_days == 0
- 2 :: 1 <= sqrt(signature_days² + encryption_days²) < 8
- 3 :: 8 <= sqrt(signature_days² + encryption_days²) < 42
- 4 :: sqrt(signature_days² + encryption_days²) >= 42
SIGN-COUNT and ENCRYPTION-COUNT are the number of messages that we
have seen that have been signed by this key / encryption to this
key.
SIGN-DAYS and ENCRYPTION-DAYS are similar, but the number of days
(in UTC) on which we have seen messages signed by this key /
encrypted to this key.
*** TOFU_STATS_SHORT <long_string>
Information about the TOFU binding for the signature.
Example: "15 signatures verified. 10 messages encrypted"
*** TOFU_STATS_LONG <long_string>
Information about the TOFU binding for the signature in verbose
format. The LONG_STRING is percent escaped.
Example: 'Verified 9 messages signed by "Werner Koch
(dist sig)" in the past 3 minutes, 40 seconds. The most
recent message was verified 4 seconds ago.'
*** PKA_TRUST_
This is one of:
- PKA_TRUST_GOOD <addr-spec>
- PKA_TRUST_BAD <addr-spec>
Depending on the outcome of the PKA check one of the above status
codes is emitted in addition to a =TRUST_*= status.
** Remote control
*** GET_BOOL, GET_LINE, GET_HIDDEN, GOT_IT
These status line are used with --command-fd for interactive
control of the process.
*** USERID_HINT <long main keyid> <string>
Give a hint about the user ID for a certain keyID.
*** NEED_PASSPHRASE <long keyid> <long main keyid> <keytype> <keylength>
Issued whenever a passphrase is needed. KEYTYPE is the numerical
value of the public key algorithm or 0 if this is not applicable,
KEYLENGTH is the length of the key or 0 if it is not known (this
is currently always the case).
*** NEED_PASSPHRASE_SYM <cipher_algo> <s2k_mode> <s2k_hash>
Issued whenever a passphrase for symmetric encryption is needed.
*** NEED_PASSPHRASE_PIN <card_type> <chvno> [<serialno>]
Issued whenever a PIN is requested to unlock a card.
*** MISSING_PASSPHRASE
No passphrase was supplied. An application which encounters this
message may want to stop parsing immediately because the next
message will probably be a BAD_PASSPHRASE. However, if the
application is a wrapper around the key edit menu functionality it
might not make sense to stop parsing but simply ignoring the
following BAD_PASSPHRASE.
*** BAD_PASSPHRASE <long keyid>
The supplied passphrase was wrong or not given. In the latter
case you may have seen a MISSING_PASSPHRASE.
*** GOOD_PASSPHRASE
The supplied passphrase was good and the secret key material
is therefore usable.
** Import/Export
*** IMPORT_CHECK <long keyid> <fingerprint> <user ID>
This status is emitted in interactive mode right before
the "import.okay" prompt.
*** IMPORTED <long keyid> <username>
The keyid and name of the signature just imported
*** IMPORT_OK <reason> [<fingerprint>]
The key with the primary key's FINGERPRINT has been imported.
REASON flags are:
- 0 :: Not actually changed
- 1 :: Entirely new key.
- 2 :: New user IDs
- 4 :: New signatures
- 8 :: New subkeys
- 16 :: Contains private key.
The flags may be ORed.
*** IMPORT_PROBLEM <reason> [<fingerprint>]
Issued for each import failure. Reason codes are:
- 0 :: No specific reason given.
- 1 :: Invalid Certificate.
- 2 :: Issuer Certificate missing.
- 3 :: Certificate Chain too long.
- 4 :: Error storing certificate.
*** IMPORT_RES <args>
Final statistics on import process (this is one long line). The
args are a list of unsigned numbers separated by white space:
- <count>
- <no_user_id>
- <imported>
- always 0 (formerly used for the number of RSA keys)
- <unchanged>
- <n_uids>
- <n_subk>
- <n_sigs>
- <n_revoc>
- <sec_read>
- <sec_imported>
- <sec_dups>
- <skipped_new_keys>
- <not_imported>
- <skipped_v3_keys>
*** EXPORTED <fingerprint>
The key with <fingerprint> has been exported. The fingerprint is
the fingerprint of the primary key even if the primary key has
been replaced by a stub key during secret key export.
*** EXPORT_RES <args>
Final statistics on export process (this is one long line). The
args are a list of unsigned numbers separated by white space:
- <count>
- <secret_count>
- <exported>
** Smartcard related
*** CARDCTRL <what> [<serialno>]
This is used to control smartcard operations. Defined values for
WHAT are:
- 1 :: Request insertion of a card. Serialnumber may be given
to request a specific card. Used by gpg 1.4 w/o
scdaemon
- 2 :: Request removal of a card. Used by gpg 1.4 w/o scdaemon.
- 3 :: Card with serialnumber detected
- 4 :: No card available
- 5 :: No card reader available
- 6 :: No card support available
- 7 :: Card is in termination state
*** SC_OP_FAILURE [<code>]
An operation on a smartcard definitely failed. Currently there is
no indication of the actual error code, but application should be
prepared to later accept more arguments. Defined values for
<code> are:
- 0 :: unspecified error (identically to a missing CODE)
- 1 :: canceled
- 2 :: bad PIN
*** SC_OP_SUCCESS
A smart card operation succeeded. This status is only printed for
certain operation and is mostly useful to check whether a PIN
change really worked.
** Miscellaneous status codes
*** NODATA <what>
No data has been found. Codes for WHAT are:
- 1 :: No armored data.
- 2 :: Expected a packet but did not found one.
- 3 :: Invalid packet found, this may indicate a non OpenPGP
message.
- 4 :: Signature expected but not found
You may see more than one of these status lines.
*** UNEXPECTED <what>
Unexpected data has been encountered. Codes for WHAT are:
- 0 :: Not further specified
- 1 :: Corrupted message structure
*** TRUNCATED <maxno>
The output was truncated to MAXNO items. This status code is
issued for certain external requests.
*** ERROR <error location> <error code> [<more>]
This is a generic error status message, it might be followed by
error location specific data. <error code> and <error_location>
should not contain spaces. The error code is a either a string
commencing with a letter or such a string prefixed with a
numerical error code and an underscore; e.g.: "151011327_EOF".
*** WARNING <location> <error code> [<text>]
This is a generic warning status message, it might be followed by
error location specific data. <error code> and <location>
should not contain spaces. The error code is a either a string
commencing with a letter or such a string prefixed with a
numerical error code and an underscore; e.g.: "151011327_EOF".
*** SUCCESS [<location>]
Positive confirmation that an operation succeeded. It is used
similar to ISO-C's EXIT_SUCCESS. <location> is optional but if
given should not contain spaces. Used only with a few commands.
*** FAILURE <location> <error_code>
This is the counterpart to SUCCESS and used to indicate a program
failure. It is used similar to ISO-C's EXIT_FAILURE but allows
conveying more information, in particular a gpg-error error code.
That numerical error code may optionally have a suffix made of an
underscore and a string with an error symbol like "151011327_EOF".
A dash may be used instead of <location>.
*** BADARMOR
The ASCII armor is corrupted. No arguments yet.
*** DELETE_PROBLEM <reason_code>
Deleting a key failed. Reason codes are:
- 1 :: No such key
- 2 :: Must delete secret key first
- 3 :: Ambiguous specification
- 4 :: Key is stored on a smartcard.
*** PROGRESS <what> <char> <cur> <total> [<units>]
Used by the primegen and public key functions to indicate
progress. <char> is the character displayed with no --status-fd
enabled, with the linefeed replaced by an 'X'. <cur> is the
current amount done and <total> is amount to be done; a <total> of
0 indicates that the total amount is not known. Both are
non-negative integers. The condition
: TOTAL && CUR == TOTAL
may be used to detect the end of an operation.
Well known values for <what> are:
- pk_dsa :: DSA key generation
- pk_elg :: Elgamal key generation
- primegen :: Prime generation
- need_entropy :: Waiting for new entropy in the RNG
- tick :: Generic tick without any special meaning - useful
for letting clients know that the server is still
working.
- starting_agent :: A gpg-agent was started because it is not
running as a daemon.
- learncard :: Send by the agent and gpgsm while learing
the data of a smartcard.
- card_busy :: A smartcard is still working
When <what> refers to a file path, it may be truncated.
<units> is sometimes used to describe the units for <current> and
<total>. For example "B", "KiB", or "MiB".
*** BACKUP_KEY_CREATED <fingerprint> <fname>
A backup of a key identified by <fingerprint> has been writte to
the file <fname>; <fname> is percent-escaped.
*** MOUNTPOINT <name>
<name> is a percent-plus escaped filename describing the
mountpoint for the current operation (e.g. used by "g13 --mount").
This may either be the specified mountpoint or one randomly
chosen by g13.
*** PINENTRY_LAUNCHED <pid>[:<extra>]
This status line is emitted by gpg to notify a client that a
Pinentry has been launched. <pid> is the PID of the Pinentry. It
may be used to display a hint to the user but can't be used to
synchronize with Pinentry. Note that there is also an Assuan
inquiry line with the same name used internally or, if enabled,
send to the client instead of this status line. Such an inquiry
may be used to sync with Pinentry
** Obsolete status codes
*** SIGEXPIRED
Removed on 2011-02-04. This is deprecated in favor of KEYEXPIRED.
*** RSA_OR_IDEA
Obsolete. This status message used to be emitted for requests to
use the IDEA or RSA algorithms. It has been dropped from GnuPG
2.1 after the respective patents expired.
*** SHM_INFO, SHM_GET, SHM_GET_BOOL, SHM_GET_HIDDEN
These were used for the ancient shared memory based co-processing.
*** BEGIN_STREAM, END_STREAM
Used to issued by the experimental pipemode.
* Format of the --attribute-fd output
When --attribute-fd is set, during key listings (--list-keys,
--list-secret-keys) GnuPG dumps each attribute packet to the file
descriptor specified. --attribute-fd is intended for use with
--status-fd as part of the required information is carried on the
ATTRIBUTE status tag (see above).
The contents of the attribute data is specified by RFC 4880. For
convenience, here is the Photo ID format, as it is currently the
only attribute defined:
- Byte 0-1 :: The length of the image header. Due to a historical
accident (i.e. oops!) back in the NAI PGP days, this
is a little-endian number. Currently 16 (0x10 0x00).
- Byte 2 :: The image header version. Currently 0x01.
- Byte 3 :: Encoding format. 0x01 == JPEG.
- Byte 4-15 :: Reserved, and currently unused.
All other data after this header is raw image (JPEG) data.
* Layout of the TrustDB
The TrustDB is built from fixed length records, where the first byte
describes the record type. All numeric values are stored in network
byte order. The length of each record is 40 bytes. The first
record of the DB is always of type 1 and this is the only record of
this type.
The record types: directory(2), key(3), uid(4), pref(5), sigrec(6),
and shadow directory(8) are not anymore used by version 2 of the
TrustDB.
** Record type 0
Unused record or deleted, can be reused for any purpose. Such
records should in general not exist because deleted records are of
type 254 and kept in a linked list.
** Version info (RECTYPE_VER, 1)
Version information for this TrustDB. This is always the first
record of the DB and the only one of this type.
- 1 u8 :: Record type (value: 1).
- 3 byte :: Magic value ("gpg")
- 1 u8 :: TrustDB version (value: 2).
- 1 u8 :: =marginals=. How many marginal trusted keys are required.
- 1 u8 :: =completes=. How many completely trusted keys are
required.
- 1 u8 :: =max_cert_depth=. How deep is the WoT evaluated. Along
with =marginals= and =completes=, this value is used to
check whether the cached validity value from a [FIXME
dir] record can be used.
- 1 u8 :: =trust_model=
- 1 u8 :: =min_cert_level=
- 2 byte :: Not used
- 1 u32 :: =created=. Timestamp of trustdb creation.
- 1 u32 :: =nextcheck=. Timestamp of last modification which may
affect the validity of keys in the trustdb. This value
is checked against the validity timestamp in the dir
records.
- 1 u32 :: =reserved=. Not used.
- 1 u32 :: =reserved2=. Not used.
- 1 u32 :: =firstfree=. Number of the record with the head record
of the RECTYPE_FREE linked list.
- 1 u32 :: =reserved3=. Not used.
- 1 u32 :: =trusthashtbl=. Record number of the trusthashtable.
** Hash table (RECTYPE_HTBL, 10)
Due to the fact that we use fingerprints to lookup keys, we can
implement quick access by some simple hash methods, and avoid the
overhead of gdbm. A property of fingerprints is that they can be
used directly as hash values. What we use is a dynamic multilevel
architecture, which combines hash tables, record lists, and linked
lists.
This record is a hash table of 256 entries with the property that
all these records are stored consecutively to make one big
table. The hash value is simple the 1st, 2nd, ... byte of the
fingerprint (depending on the indirection level).
- 1 u8 :: Record type (value: 10).
- 1 u8 :: Reserved
- n u32 :: =recnum=. A table with the hash table items fitting into
this record. =n= depends on the record length:
$n=(reclen-2)/4$ which yields 9 for oure current record
length of 40 bytes.
The total number of hash table records to form the table is:
$m=(256+n-1)/n$. This is 29 for our record length of 40.
To look up a key we use the first byte of the fingerprint to get
the recnum from this hash table and then look up the addressed
record:
- If that record is another hash table, we use 2nd byte to index
that hash table and so on;
- if that record is a hash list, we walk all entries until we find
a matching one; or
- if that record is a key record, we compare the fingerprint to
decide whether it is the requested key;
** Hash list (RECTYPE_HLST, 11)
See hash table above on how it is used. It may also be used for
other purposes.
- 1 u8 :: Record type (value: 11).
- 1 u8 :: Reserved.
- 1 u32 :: =next=. Record number of the next hash list record or 0
if none.
- n u32 :: =rnum=. Array with record numbers to values. With
$n=(reclen-5)/5$ and our record length of 40, n is 7.
** Trust record (RECTYPE_TRUST, 12)
- 1 u8 :: Record type (value: 12).
- 1 u8 :: Reserved.
- 20 byte :: =fingerprint=.
- 1 u8 :: =ownertrust=.
- 1 u8 :: =depth=.
- 1 u8 :: =min_ownertrust=.
- 1 byte :: Not used.
- 1 u32 :: =validlist=.
- 10 byte :: Not used.
** Validity record (RECTYPE_VALID, 13)
- 1 u8 :: Record type (value: 13).
- 1 u8 :: Reserved.
- 20 byte :: =namehash=.
- 1 u8 :: =validity=
- 1 u32 :: =next=.
- 1 u8 :: =full_count=.
- 1 u8 :: =marginal_count=.
- 11 byte :: Not used.
** Free record (RECTYPE_FREE, 254)
All these records form a linked list of unused records in the TrustDB.
- 1 u8 :: Record type (value: 254)
- 1 u8 :: Reserved.
- 1 u32 :: =next=. Record number of the next rcord of this type.
The record number to the head of this linked list is
stored in the version info record.
* Database scheme for the TOFU info
#+begin_src sql
--
-- The VERSION table holds the version of our TOFU data structures.
--
CREATE TABLE version (
version integer -- As of now this is always 1
);
--
-- The BINDINGS table associates mail addresses with keys.
--
CREATE TABLE bindings (
oid integer primary key autoincrement,
fingerprint text, -- The key's fingerprint in hex
email text, -- The normalized mail address destilled from user_id
user_id text, -- The unmodified user id
time integer, -- The time this binding was first observed.
policy boolean check
(policy in (1, 2, 3, 4, 5)), -- The trust policy with the values:
-- 1 := Auto
-- 2 := Good
-- 3 := Unknown
-- 4 := Bad
-- 5 := Ask
conflict string, -- NULL or a hex formatted fingerprint.
unique (fingerprint, email)
);
CREATE INDEX bindings_fingerprint_email on bindings (fingerprint, email);
CREATE INDEX bindings_email on bindings (email);
--
-- The SIGNATURES table records all data signatures we verified
--
CREATE TABLE signatures (
binding integer not null, -- Link to bindings table,
-- references bindings.oid.
sig_digest text, -- The digest of the signed message.
origin text, -- String describing who initially fed
-- the signature to gpg (e.g. "email:claws").
sig_time integer, -- Timestamp from the signature.
time integer, -- Time this record was created.
primary key (binding, sig_digest, origin)
);
#+end_src
* GNU extensions to the S2K algorithm
1 octet - S2K Usage: either 254 or 255.
1 octet - S2K Cipher Algo: 0
1 octet - S2K Specifier: 101
3 octets - "GNU"
1 octet - GNU S2K Extension Number.
If such a GNU extension is used neither an IV nor any kind of
checksum is used. The defined GNU S2K Extension Numbers are:
- 1 :: Do not store the secret part at all. No specific data
follows.
- 2 :: A stub to access smartcards. This data follows:
- One octet with the length of the following serial number.
- The serial number. Regardless of what the length octet
indicates no more than 16 octets are stored.
Note that gpg stores the GNU S2K Extension Number internally as an
S2K Specifier with an offset of 1000.
* Format of the OpenPGP TRUST packet
According to RFC4880 (5.10), the trust packet (aka ring trust) is
only used within keyrings and contains data that records the user's
specifications of which key holds trusted introducers. The RFC also
states that the format of this packet is implementation defined and
SHOULD NOT be emitted to output streams or should be ignored on
import. GnuPG uses this packet in several additional ways:
- 1 octet :: Trust-Value (only used by Subtype SIG)
- 1 octet :: Signature-Cache (only used by Subtype SIG; value must
be less than 128)
- 3 octets :: Fixed value: "gpg"
- 1 octet :: Subtype
- 0 :: Signature cache (SIG)
- 1 :: Key source on the primary key (KEY)
- 2 :: Key source on a user id (UID)
- 1 octet :: Key Source; i.e. the origin of the key:
- 0 :: Unknown source.
- 1 :: Public keyserver.
- 2 :: Preferred keyserver.
- 3 :: OpenPGP DANE.
- 4 :: Web Key Directory.
- 5 :: Import from a trusted URL.
- 6 :: Import from a trusted file.
- 7 :: Self generated.
- 4 octets :: Time of last update. This is a a four-octet scalar
with the seconds since Epoch.
- 1 octet :: Scalar with the length of the following field.
- N octets :: String with the URL of the source. This may be a
zero-length string.
If the packets contains only two octets a Subtype of 0 is assumed;
this is the only format recognized by GnuPG versions < 2.1.18.
Trust-Value and Signature-Cache must be zero for all subtypes other
than SIG.
* Keyserver helper message format
*This information is obsolete*
(Keyserver helpers have been replaced by dirmngr)
The keyserver may be contacted by a Unix Domain socket or via TCP.
The format of a request is:
#+begin_example
command-tag
"Content-length:" digits
CRLF
#+end_example
Where command-tag is
#+begin_example
NOOP
GET <user-name>
PUT
DELETE <user-name>
#+end_example
The format of a response is:
#+begin_example
"GNUPG/1.0" status-code status-text
"Content-length:" digits
CRLF
#+end_example
followed by <digits> bytes of data
Status codes are:
- 1xx :: Informational - Request received, continuing process
- 2xx :: Success - The action was successfully received, understood,
and accepted
- 4xx :: Client Error - The request contains bad syntax or cannot be
fulfilled
- 5xx :: Server Error - The server failed to fulfill an apparently
valid request
* Object identifiers
OIDs below the GnuPG arc:
#+begin_example
1.3.6.1.4.1.11591.2 GnuPG
1.3.6.1.4.1.11591.2.1 notation
1.3.6.1.4.1.11591.2.1.1 pkaAddress
1.3.6.1.4.1.11591.2.2 X.509 extensions
1.3.6.1.4.1.11591.2.2.1 standaloneCertificate
1.3.6.1.4.1.11591.2.2.2 wellKnownPrivateKey
1.3.6.1.4.1.11591.2.12242973 invalid encoded OID
#+end_example
* Debug flags
This tables gives the flag values for the --debug option along with
the alternative names used by the components.
| | gpg | gpgsm | agent | scd | dirmngr | g13 | wks |
|-------+---------+---------+---------+---------+---------+---------+---------|
| 1 | packet | x509 | | | x509 | mount | mime |
| 2 | mpi | mpi | mpi | mpi | | | parser |
| 4 | crypto | crypto | crypto | crypto | crypto | crypto | crypto |
| 8 | filter | | | | | | |
| 16 | iobuf | | | | dns | | |
| 32 | memory | memory | memory | memory | memory | memory | memory |
| 64 | cache | cache | cache | cache | cache | | |
| 128 | memstat | memstat | memstat | memstat | memstat | memstat | memstat |
| 256 | trust | | | | | | |
| 512 | hashing | hashing | hashing | hashing | hashing | | |
| 1024 | ipc | ipc | ipc | ipc | ipc | ipc | ipc |
| 2048 | | | | cardio | network | | |
| 4096 | clock | | | reader | | | |
| 8192 | lookup | | | | lookup | | |
| 16384 | extprog | | | | | | extprog |
Description of some debug flags:
- cardio :: Used by scdaemon to trace the APDUs exchange with the
card.
- clock :: Show execution times of certain functions.
- crypto :: Trace crypto operations.
- hashing :: Create files with the hashed data.
- ipc :: Trace the Assuan commands.
- mpi :: Show the values of the MPIs.
- reader :: Used by scdaemon to trace card reader related code. For
example: Open and close reader.
* Miscellaneous notes
** v3 fingerprints
For packet version 3 we calculate the keyids this way:
- RSA :: Low 64 bits of n
- ELGAMAL :: Build a v3 pubkey packet (with CTB 0x99) and
calculate a RMD160 hash value from it. This is used
as the fingerprint and the low 64 bits are the keyid.
** Simplified revocation certificates
Revocation certificates consist only of the signature packet;
"--import" knows how to handle this. The rationale behind it is to
keep them small.
** Documentation on HKP (the http keyserver protocol):
A minimalistic HTTP server on port 11371 recognizes a GET for
/pks/lookup. The standard http URL encoded query parameters are
this (always key=value):
- op=index (like pgp -kv), op=vindex (like pgp -kvv) and op=get (like
pgp -kxa)
- search=<stringlist>. This is a list of words that must occur in the key.
The words are delimited with space, points, @ and so on. The delimiters
are not searched for and the order of the words doesn't matter (but see
next option).
- exact=on. This switch tells the hkp server to only report exact matching
keys back. In this case the order and the "delimiters" are important.
- fingerprint=on. Also reports the fingerprints when used with 'index' or
'vindex'
The keyserver also recognizes http-POSTs to /pks/add. Use this to upload
keys.
A better way to do this would be a request like:
/pks/lookup/<gnupg_formatierte_user_id>?op=<operation>
This can be implemented using Hurd's translator mechanism.
However, I think the whole keyserver stuff has to be re-thought;
I have some ideas and probably create a white paper.
** Algorithm names for the "keygen.algo" prompt
When using a --command-fd controlled key generation or "addkey"
there is way to know the number to enter on the "keygen.algo"
prompt. The displayed numbers are for human reception and may
change with releases. To provide a stable way to enter a desired
algorithm choice the prompt also accepts predefined names for the
algorithms, which will not change.
| Name | No | Description |
|---------+----+---------------------------------|
| rsa+rsa | 1 | RSA and RSA (default) |
| dsa+elg | 2 | DSA and Elgamal |
| dsa | 3 | DSA (sign only) |
| rsa/s | 4 | RSA (sign only) |
| elg | 5 | Elgamal (encrypt only) |
| rsa/e | 6 | RSA (encrypt only) |
| dsa/* | 7 | DSA (set your own capabilities) |
| rsa/* | 8 | RSA (set your own capabilities) |
| ecc+ecc | 9 | ECC and ECC |
| ecc/s | 10 | ECC (sign only) |
| ecc/* | 11 | ECC (set your own capabilities) |
| ecc/e | 12 | ECC (encrypt only) |
| keygrip | 13 | Existing key |
+ | cardkey | 14 | Existing key from card |
If one of the "foo/*" names are used a "keygen.flags" prompt needs
to be answered as well. Instead of toggling the predefined flags,
it is also possible to set them direct: Use a "=" character
directly followed by a combination of "a" (for authentication), "s"
(for signing), or "c" (for certification).
diff --git a/doc/Makefile.am b/doc/Makefile.am
index 0720dd366..0c44217d0 100644
--- a/doc/Makefile.am
+++ b/doc/Makefile.am
@@ -1,201 +1,203 @@
# Copyright (C) 2002, 2004 Free Software Foundation, Inc.
#
# This file is part of GnuPG.
#
# GnuPG is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# GnuPG is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, see <https://www.gnu.org/licenses/>.
## Process this file with automake to produce Makefile.in
AM_CPPFLAGS =
include $(top_srcdir)/am/cmacros.am
examples = examples/README examples/scd-event examples/trustlist.txt \
examples/vsnfd.prf examples/debug.prf examples/qualified.txt \
examples/systemd-user/README \
examples/systemd-user/dirmngr.service \
examples/systemd-user/dirmngr.socket \
examples/systemd-user/gpg-agent.service \
examples/systemd-user/gpg-agent.socket \
examples/systemd-user/gpg-agent-ssh.socket \
examples/systemd-user/gpg-agent-browser.socket \
examples/systemd-user/gpg-agent-extra.socket \
examples/gpgconf.conf examples/pwpattern.list
helpfiles = help.txt help.be.txt help.ca.txt help.cs.txt \
help.da.txt help.de.txt help.el.txt help.eo.txt \
help.es.txt help.et.txt help.fi.txt help.fr.txt \
help.gl.txt help.hu.txt help.id.txt help.it.txt \
help.ja.txt help.nb.txt help.pl.txt help.pt.txt \
help.pt_BR.txt help.ro.txt help.ru.txt help.sk.txt \
help.sv.txt help.tr.txt help.zh_CN.txt help.zh_TW.txt
profiles =
EXTRA_DIST = samplekeys.asc mksamplekeys com-certs.pem \
gnupg-logo.eps gnupg-logo.pdf gnupg-logo.png gnupg-logo-tr.png \
gnupg-module-overview.png gnupg-module-overview.pdf \
gnupg-card-architecture.png gnupg-card-architecture.pdf \
FAQ gnupg7.texi mkdefsinc.c defsincdate \
opt-homedir.texi see-also-note.texi specify-user-id.texi \
gpgv.texi yat2m.c ChangeLog-2011 whats-new-in-2.1.txt \
trust-values.texi
BUILT_SOURCES = gnupg-module-overview.png gnupg-module-overview.pdf \
gnupg-card-architecture.png gnupg-card-architecture.pdf \
defsincdate defs.inc
info_TEXINFOS = gnupg.texi
dist_pkgdata_DATA = $(helpfiles) $(profiles)
nobase_dist_doc_DATA = FAQ DETAILS HACKING DCO TRANSLATE OpenPGP KEYSERVER \
$(examples)
#dist_html_DATA =
gnupg_TEXINFOS = \
gpg.texi gpgsm.texi gpg-agent.texi scdaemon.texi instguide.texi \
tools.texi debugging.texi glossary.texi contrib.texi gpl.texi \
sysnotes.texi dirmngr.texi wks.texi gpg-card.texi \
gnupg-module-overview.svg \
gnupg-card-architecture.fig \
howtos.texi howto-create-a-server-cert.texi
gnupg.texi : defs.inc
# We need EPS files for "make distcheck" but we do not want to distribute
# them due to their size. Let's build them as needed.
gnupg.dvi : gnupg-module-overview.eps gnupg-card-architecture.eps
DVIPS = TEXINPUTS="$(srcdir)$(PATH_SEPARATOR)$$TEXINPUTS" dvips
AM_MAKEINFOFLAGS = -I $(srcdir) --css-ref=/share/site.css
YAT2M_OPTIONS = -I $(srcdir) \
--release "GnuPG @PACKAGE_VERSION@" --source "GNU Privacy Guard 2.2"
myman_sources = gnupg7.texi gpg.texi gpgsm.texi gpg-agent.texi \
dirmngr.texi scdaemon.texi tools.texi wks.texi \
gpg-card.texi
myman_pages = gpgsm.1 gpg-agent.1 dirmngr.8 scdaemon.1 \
watchgnupg.1 gpgconf.1 addgnupghome.8 gpg-preset-passphrase.1 \
gpg-connect-agent.1 gpgparsemail.1 symcryptrun.1 gpgtar.1 \
applygnupgdefaults.8 gpg-wks-client.1 gpg-wks-server.1 \
- dirmngr-client.1 gpg-card.1
+ dirmngr-client.1 gpg-card.1 gpg-check-pattern.1
if USE_GPG2_HACK
myman_pages += gpg2.1 gpgv2.1
else
myman_pages += gpg.1 gpgv.1
endif
man_MANS = $(myman_pages) gnupg.7
watchgnupg_SOURCE = gnupg.texi
CLEANFILES = yat2m mkdefsinc defs.inc
DISTCLEANFILES = gnupg.tmp gnupg.ops yat2m-stamp.tmp yat2m-stamp \
gnupg-card-architecture.eps \
gnupg-module-overview.eps \
$(myman_pages) gnupg.7
yat2m: yat2m.c
$(CC_FOR_BUILD) -o $@ $(srcdir)/yat2m.c
mkdefsinc: mkdefsinc.c Makefile ../config.h
$(CC_FOR_BUILD) -I. -I.. -I$(srcdir) $(AM_CPPFLAGS) \
-o $@ $(srcdir)/mkdefsinc.c
+if MAINTAINER_MODE
.svg.eps:
convert `test -f '$<' || echo '$(srcdir)/'`$< $@
.svg.png:
convert `test -f '$<' || echo '$(srcdir)/'`$< $@
.svg.pdf:
convert `test -f '$<' || echo '$(srcdir)/'`$< $@
.fig.png:
fig2dev -L png `test -f '$<' || echo '$(srcdir)/'`$< $@
.fig.jpg:
fig2dev -L jpeg `test -f '$<' || echo '$(srcdir)/'`$< $@
.fig.eps:
fig2dev -L eps `test -f '$<' || echo '$(srcdir)/'`$< $@
.fig.pdf:
fig2dev -L pdf `test -f '$<' || echo '$(srcdir)/'`$< $@
+endif
yat2m-stamp: $(myman_sources) defs.inc
@rm -f yat2m-stamp.tmp
@touch yat2m-stamp.tmp
incd="`test -f defsincdate || echo '$(srcdir)/'`defsincdate"; \
for file in $(myman_sources) ; do \
$(YAT2M) $(YAT2M_OPTIONS) --store \
--date "`cat $$incd 2>/dev/null`" \
`test -f '$$file' || echo '$(srcdir)/'`$$file ; done
@mv -f yat2m-stamp.tmp $@
yat2m-stamp: $(YAT2M)
$(myman_pages) gnupg.7 : yat2m-stamp defs.inc
@if test -f $@; then :; else \
trap 'rm -rf yat2m-stamp yat2m-lock' 1 2 13 15; \
if mkdir yat2m-lock 2>/dev/null; then \
rm -f yat2m-stamp; \
$(MAKE) $(AM_MAKEFLAGS) yat2m-stamp; \
rmdir yat2m-lock; \
else \
while test -d yat2m-lock; do sleep 1; done; \
test -f yat2m-stamp; exit $$?; \
fi; \
fi
dist-hook: defsincdate
defsincdate: $(gnupg_TEXINFOS)
: >defsincdate ; \
if test -e $(top_srcdir)/.git; then \
(cd $(srcdir) && git log -1 --format='%ct' \
-- $(gnupg_TEXINFOS) 2>/dev/null) >>defsincdate; \
fi
defs.inc : defsincdate Makefile mkdefsinc
incd="`test -f defsincdate || echo '$(srcdir)/'`defsincdate"; \
./mkdefsinc -C $(srcdir) --date "`cat $$incd 2>/dev/null`" \
$(gnupg_TEXINFOS) >$@
online: gnupg.html gnupg.pdf gnupg-module-overview.png \
gnupg-card-architecture.png
set -e; \
echo "Uploading current manuals to www.gnupg.org ..."; \
cp $(srcdir)/gnupg-logo-tr.png gnupg.html/; \
cp gnupg-module-overview.png gnupg.html/; \
cp gnupg-card-architecture.png gnupg.html/; \
user=werner ; webhost="ftp.gnupg.org" ; dashdevel="" ; \
if echo "@PACKAGE_VERSION@" | grep -- "-beta" >/dev/null; then \
dashdevel="-devel" ; \
else \
rsync -v gnupg.pdf $${user}@$${webhost}:webspace/manuals/ ; \
fi ; \
cd gnupg.html ; \
rsync -vr --exclude='.git' . \
$${user}@$${webhost}:webspace/manuals/gnupg$${dashdevel}/
diff --git a/doc/dirmngr.texi b/doc/dirmngr.texi
index f5910a884..eb49ad96c 100644
--- a/doc/dirmngr.texi
+++ b/doc/dirmngr.texi
@@ -1,1182 +1,1180 @@
@c Copyright (C) 2002 Klar"alvdalens Datakonsult AB
@c Copyright (C) 2004, 2005, 2006, 2007 g10 Code GmbH
@c This is part of the GnuPG manual.
@c For copying conditions, see the file gnupg.texi.
@include defs.inc
@node Invoking DIRMNGR
@chapter Invoking DIRMNGR
@cindex DIRMNGR command options
@cindex command options
@cindex options, DIRMNGR command
@manpage dirmngr.8
@ifset manverb
.B dirmngr
\- CRL and OCSP daemon
@end ifset
@mansect synopsis
@ifset manverb
.B dirmngr
.RI [ options ]
.I command
.RI [ args ]
@end ifset
@mansect description
Since version 2.1 of GnuPG, @command{dirmngr} takes care of accessing
the OpenPGP keyservers. As with previous versions it is also used as
a server for managing and downloading certificate revocation lists
(CRLs) for X.509 certificates, downloading X.509 certificates, and
providing access to OCSP providers. Dirmngr is invoked internally by
@command{gpg}, @command{gpgsm}, or via the @command{gpg-connect-agent}
tool.
@manpause
@noindent
@xref{Option Index},for an index to @command{DIRMNGR}'s commands and
options.
@mancont
@menu
* Dirmngr Commands:: List of all commands.
* Dirmngr Options:: List of all options.
* Dirmngr Configuration:: Configuration files.
* Dirmngr Signals:: Use of signals.
* Dirmngr Examples:: Some usage examples.
* Dirmngr Protocol:: The protocol dirmngr uses.
@end menu
@node Dirmngr Commands
@section Commands
@mansect commands
Commands are not distinguished from options except for the fact that
only one command is allowed.
@table @gnupgtabopt
@item --version
@opindex version
Print the program version and licensing information. Note that you cannot
abbreviate this command.
@item --help, -h
@opindex help
Print a usage message summarizing the most useful command-line options.
Note that you cannot abbreviate this command.
@item --dump-options
@opindex dump-options
Print a list of all available options and commands. Note that you cannot
abbreviate this command.
@item --server
@opindex server
Run in server mode and wait for commands on the @code{stdin}. The
default mode is to create a socket and listen for commands there.
This is only used for testing.
@item --daemon
@opindex daemon
Run in background daemon mode and listen for commands on a socket.
This is the way @command{dirmngr} is started on demand by the other
GnuPG components. To force starting @command{dirmngr} it is in
general best to use @code{gpgconf --launch dirmngr}.
@item --supervised
@opindex supervised
Run in the foreground, sending logs to stderr, and listening on file
descriptor 3, which must already be bound to a listening socket. This
is useful when running under systemd or other similar process
supervision schemes. This option is not supported on Windows.
@item --list-crls
@opindex list-crls
List the contents of the CRL cache on @code{stdout}. This is probably
only useful for debugging purposes.
@item --load-crl @var{file}
@opindex load-crl
This command requires a filename as additional argument, and it will
make Dirmngr try to import the CRL in @var{file} into it's cache.
Note, that this is only possible if Dirmngr is able to retrieve the
CA's certificate directly by its own means. In general it is better
to use @code{gpgsm}'s @code{--call-dirmngr loadcrl filename} command
so that @code{gpgsm} can help dirmngr.
@item --fetch-crl @var{url}
@opindex fetch-crl
This command requires an URL as additional argument, and it will make
dirmngr try to retrieve and import the CRL from that @var{url} into
it's cache. This is mainly useful for debugging purposes. The
@command{dirmngr-client} provides the same feature for a running dirmngr.
@item --shutdown
@opindex shutdown
This commands shuts down an running instance of Dirmngr. This command
has currently no effect.
@item --flush
@opindex flush
This command removes all CRLs from Dirmngr's cache. Client requests
will thus trigger reading of fresh CRLs.
@end table
@mansect options
@node Dirmngr Options
@section Option Summary
Note that all long options with the exception of @option{--options}
and @option{--homedir} may also be given in the configuration file
after stripping off the two leading dashes.
@table @gnupgtabopt
@item --options @var{file}
@opindex options
Reads configuration from @var{file} instead of from the default
per-user configuration file. The default configuration file is named
@file{dirmngr.conf} and expected in the home directory.
@item --homedir @var{dir}
@opindex options
Set the name of the home directory to @var{dir}. This option is only
effective when used on the command line. The default is
the directory named @file{.gnupg} directly below the home directory
of the user unless the environment variable @code{GNUPGHOME} has been set
in which case its value will be used. Many kinds of data are stored within
this directory.
@item -v
@item --verbose
@opindex v
@opindex verbose
Outputs additional information while running.
You can increase the verbosity by giving several
verbose commands to @sc{dirmngr}, such as @option{-vv}.
@item --log-file @var{file}
@opindex log-file
Append all logging output to @var{file}. This is very helpful in
seeing what the agent actually does. Use @file{socket://} to log to
socket.
@item --debug-level @var{level}
@opindex debug-level
Select the debug level for investigating problems. @var{level} may be a
numeric value or by a keyword:
@table @code
@item none
No debugging at all. A value of less than 1 may be used instead of
the keyword.
@item basic
Some basic debug messages. A value between 1 and 2 may be used
instead of the keyword.
@item advanced
More verbose debug messages. A value between 3 and 5 may be used
instead of the keyword.
@item expert
Even more detailed messages. A value between 6 and 8 may be used
instead of the keyword.
@item guru
All of the debug messages you can get. A value greater than 8 may be
used instead of the keyword. The creation of hash tracing files is
only enabled if the keyword is used.
@end table
How these messages are mapped to the actual debugging flags is not
specified and may change with newer releases of this program. They are
however carefully selected to best aid in debugging.
@item --debug @var{flags}
@opindex debug
Set debugging flags. This option is only useful for debugging and its
behavior may change with a new release. All flags are or-ed and may
be given in C syntax (e.g. 0x0042) or as a comma separated list of
flag names. To get a list of all supported flags the single word
"help" can be used.
@item --debug-all
@opindex debug-all
Same as @code{--debug=0xffffffff}
@item --tls-debug @var{level}
@opindex tls-debug
Enable debugging of the TLS layer at @var{level}. The details of the
debug level depend on the used TLS library and are not set in stone.
@item --debug-wait @var{n}
@opindex debug-wait
When running in server mode, wait @var{n} seconds before entering the
actual processing loop and print the pid. This gives time to attach a
debugger.
@item --disable-check-own-socket
@opindex disable-check-own-socket
On some platforms @command{dirmngr} is able to detect the removal of
its socket file and shutdown itself. This option disable this
self-test for debugging purposes.
@item -s
@itemx --sh
@itemx -c
@itemx --csh
@opindex s
@opindex sh
@opindex c
@opindex csh
Format the info output in daemon mode for use with the standard Bourne
shell respective the C-shell. The default is to guess it based on the
environment variable @code{SHELL} which is in almost all cases
sufficient.
@item --force
@opindex force
Enabling this option forces loading of expired CRLs; this is only
useful for debugging.
@item --use-tor
@itemx --no-use-tor
@opindex use-tor
@opindex no-use-tor
The option @option{--use-tor} switches Dirmngr and thus GnuPG into
``Tor mode'' to route all network access via Tor (an anonymity
network). Certain other features are disabled in this mode. The
effect of @option{--use-tor} cannot be overridden by any other command
-or even be reloading gpg-agent. The use of @option{--no-use-tor}
+or even by reloading dirmngr. The use of @option{--no-use-tor}
disables the use of Tor. The default is to use Tor if it is available
on startup or after reloading dirmngr.
@item --standard-resolver
@opindex standard-resolver
This option forces the use of the system's standard DNS resolver code.
This is mainly used for debugging. Note that on Windows a standard
resolver is not used and all DNS access will return the error ``Not
Implemented'' if this function is used.
@item --recursive-resolver
@opindex recursive-resolver
When possible use a recursive resolver instead of a stub resolver.
@item --resolver-timeout @var{n}
@opindex resolver-timeout
Set the timeout for the DNS resolver to N seconds. The default are 30
seconds.
@item --connect-timeout @var{n}
@item --connect-quick-timeout @var{n}
@opindex connect-timeout
@opindex connect-quick-timeout
Set the timeout for HTTP and generic TCP connection attempts to N
seconds. The value set with the quick variant is used when the
--quick option has been given to certain Assuan commands. The quick
value is capped at the value of the regular connect timeout. The
default values are 15 and 2 seconds. Note that the timeout values are
for each connection attempt; the connection code will attempt to
connect all addresses listed for a server.
@item --listen-backlog @var{n}
@opindex listen-backlog
Set the size of the queue for pending connections. The default is 64.
@item --allow-version-check
@opindex allow-version-check
Allow Dirmngr to connect to @code{https://versions.gnupg.org} to get
the list of current software versions. If this option is enabled
the list is retrieved in case the local
copy does not exist or is older than 5 to 7 days. See the option
@option{--query-swdb} of the command @command{gpgconf} for more
details. Note, that regardless of this option a version check can
always be triggered using this command:
@example
gpg-connect-agent --dirmngr 'loadswdb --force' /bye
@end example
@item --keyserver @var{name}
@opindex keyserver
Use @var{name} as your keyserver. This is the server that @command{gpg}
communicates with to receive keys, send keys, and search for
keys. The format of the @var{name} is a URI:
`scheme:[//]keyservername[:port]' The scheme is the type of keyserver:
"hkp" for the HTTP (or compatible) keyservers, "ldap" for the LDAP
keyservers, or "mailto" for the Graff email keyserver. Note that your
particular installation of GnuPG may have other keyserver types
available as well. Keyserver schemes are case-insensitive. After the
keyserver name, optional keyserver configuration options may be
provided. These are the same as the @option{--keyserver-options} of
@command{gpg}, but apply only to this particular keyserver.
Most keyservers synchronize with each other, so there is generally no
need to send keys to more than one server. The keyserver
@code{hkp://keys.gnupg.net} uses round robin DNS to give a different
keyserver each time you use it.
If exactly two keyservers are configured and only one is a Tor hidden
service (.onion), Dirmngr selects the keyserver to use depending on
whether Tor is locally running or not. The check for a running Tor is
done for each new connection.
If no keyserver is explicitly configured, dirmngr will use the
built-in default of hkps://hkps.pool.sks-keyservers.net.
@item --nameserver @var{ipaddr}
@opindex nameserver
In ``Tor mode'' Dirmngr uses a public resolver via Tor to resolve DNS
names. If the default public resolver, which is @code{8.8.8.8}, shall
not be used a different one can be given using this option. Note that
a numerical IP address must be given (IPv6 or IPv4) and that no error
checking is done for @var{ipaddr}.
@item --disable-ipv4
@item --disable-ipv6
@opindex disable-ipv4
@opindex disable-ipv6
Disable the use of all IPv4 or IPv6 addresses.
@item --disable-ldap
@opindex disable-ldap
Entirely disables the use of LDAP.
@item --disable-http
@opindex disable-http
Entirely disables the use of HTTP.
@item --ignore-http-dp
@opindex ignore-http-dp
When looking for the location of a CRL, the to be tested certificate
usually contains so called @dfn{CRL Distribution Point} (DP) entries
which are URLs describing the way to access the CRL. The first found DP
entry is used. With this option all entries using the @acronym{HTTP}
scheme are ignored when looking for a suitable DP.
@item --ignore-ldap-dp
@opindex ignore-ldap-dp
This is similar to @option{--ignore-http-dp} but ignores entries using
the @acronym{LDAP} scheme. Both options may be combined resulting in
ignoring DPs entirely.
@item --ignore-ocsp-service-url
@opindex ignore-ocsp-service-url
Ignore all OCSP URLs contained in the certificate. The effect is to
force the use of the default responder.
@item --honor-http-proxy
@opindex honor-http-proxy
If the environment variable @env{http_proxy} has been set, use its
value to access HTTP servers.
@item --http-proxy @var{host}[:@var{port}]
@opindex http-proxy
@efindex http_proxy
Use @var{host} and @var{port} to access HTTP servers. The use of this
option overrides the environment variable @env{http_proxy} regardless
whether @option{--honor-http-proxy} has been set.
@item --ldap-proxy @var{host}[:@var{port}]
@opindex ldap-proxy
Use @var{host} and @var{port} to connect to LDAP servers. If @var{port}
is omitted, port 389 (standard LDAP port) is used. This overrides any
specified host and port part in a LDAP URL and will also be used if host
and port have been omitted from the URL.
@item --only-ldap-proxy
@opindex only-ldap-proxy
Never use anything else but the LDAP "proxy" as configured with
@option{--ldap-proxy}. Usually @command{dirmngr} tries to use other
configured LDAP server if the connection using the "proxy" failed.
@item --ldapserverlist-file @var{file}
@opindex ldapserverlist-file
Read the list of LDAP servers to consult for CRLs and certificates from
file instead of the default per-user ldap server list file. The default
value for @var{file} is @file{dirmngr_ldapservers.conf}.
This server list file contains one LDAP server per line in the format
@sc{hostname:port:username:password:base_dn}
Lines starting with a @samp{#} are comments.
Note that as usual all strings entered are expected to be UTF-8 encoded.
Obviously this will lead to problems if the password has originally been
encoded as Latin-1. There is no other solution here than to put such a
password in the binary encoding into the file (i.e. non-ascii characters
won't show up readable).@footnote{The @command{gpgconf} tool might be
helpful for frontends as it enables editing this configuration file using
percent-escaped strings.}
@item --ldaptimeout @var{secs}
@opindex ldaptimeout
Specify the number of seconds to wait for an LDAP query before timing
out. The default are 15 seconds. 0 will never timeout.
@item --add-servers
@opindex add-servers
This option makes dirmngr add any servers it discovers when validating
certificates against CRLs to the internal list of servers to consult for
certificates and CRLs.
This option is useful when trying to validate a certificate that has
a CRL distribution point that points to a server that is not already
listed in the ldapserverlist. Dirmngr will always go to this server and
try to download the CRL, but chances are high that the certificate used
to sign the CRL is located on the same server. So if dirmngr doesn't add
that new server to list, it will often not be able to verify the
signature of the CRL unless the @code{--add-servers} option is used.
Note: The current version of dirmngr has this option disabled by default.
@item --allow-ocsp
@opindex allow-ocsp
This option enables OCSP support if requested by the client.
OCSP requests are rejected by default because they may violate the
privacy of the user; for example it is possible to track the time when
a user is reading a mail.
@item --ocsp-responder @var{url}
@opindex ocsp-responder
Use @var{url} as the default OCSP Responder if the certificate does
not contain information about an assigned responder. Note, that
@code{--ocsp-signer} must also be set to a valid certificate.
@item --ocsp-signer @var{fpr}|@var{file}
@opindex ocsp-signer
Use the certificate with the fingerprint @var{fpr} to check the
responses of the default OCSP Responder. Alternatively a filename can be
given in which case the response is expected to be signed by one of the
certificates described in that file. Any argument which contains a
slash, dot or tilde is considered a filename. Usual filename expansion
takes place: A tilde at the start followed by a slash is replaced by the
content of @env{HOME}, no slash at start describes a relative filename
which will be searched at the home directory. To make sure that the
@var{file} is searched in the home directory, either prepend the name
with "./" or use a name which contains a dot.
If a response has been signed by a certificate described by these
fingerprints no further check upon the validity of this certificate is
done.
The format of the @var{FILE} is a list of SHA-1 fingerprint, one per
line with optional colons between the bytes. Empty lines and lines
prefix with a hash mark are ignored.
@item --ocsp-max-clock-skew @var{n}
@opindex ocsp-max-clock-skew
The number of seconds a skew between the OCSP responder and them local
clock is accepted. Default is 600 (10 minutes).
@item --ocsp-max-period @var{n}
@opindex ocsp-max-period
Seconds a response is at maximum considered valid after the time given
in the thisUpdate field. Default is 7776000 (90 days).
@item --ocsp-current-period @var{n}
@opindex ocsp-current-period
The number of seconds an OCSP response is considered valid after the
time given in the NEXT_UPDATE datum. Default is 10800 (3 hours).
@item --max-replies @var{n}
@opindex max-replies
Do not return more that @var{n} items in one query. The default is
10.
@item --ignore-cert-extension @var{oid}
@opindex ignore-cert-extension
Add @var{oid} to the list of ignored certificate extensions. The
@var{oid} is expected to be in dotted decimal form, like
@code{2.5.29.3}. This option may be used more than once. Critical
flagged certificate extensions matching one of the OIDs in the list
are treated as if they are actually handled and thus the certificate
won't be rejected due to an unknown critical extension. Use this
option with care because extensions are usually flagged as critical
for a reason.
@item --hkp-cacert @var{file}
Use the root certificates in @var{file} for verification of the TLS
certificates used with @code{hkps} (keyserver access over TLS). If
the file is in PEM format a suffix of @code{.pem} is expected for
@var{file}. This option may be given multiple times to add more
root certificates. Tilde expansion is supported.
If no @code{hkp-cacert} directive is present, dirmngr will make a
reasonable choice: if the keyserver in question is the special pool
@code{hkps.pool.sks-keyservers.net}, it will use the bundled root
certificate for that pool. Otherwise, it will use the system CAs.
@end table
@c
@c Dirmngr Configuration
@c
@mansect files
@node Dirmngr Configuration
@section Configuration
Dirmngr makes use of several directories when running in daemon mode:
There are a few configuration files whih control the operation of
dirmngr. By default they may all be found in the current home
directory (@pxref{option --homedir}).
@table @file
@item dirmngr.conf
@efindex dirmngr.conf
This is the standard configuration file read by @command{dirmngr} on
startup. It may contain any valid long option; the leading two dashes
may not be entered and the option may not be abbreviated. This file
is also read after a @code{SIGHUP} however not all options will
actually have an effect. This default name may be changed on the
command line (@pxref{option --options}). You should backup this file.
@item /etc/gnupg/trusted-certs
This directory should be filled with certificates of Root CAs you
are trusting in checking the CRLs and signing OCSP Responses.
Usually these are the same certificates you use with the applications
making use of dirmngr. It is expected that each of these certificate
files contain exactly one @acronym{DER} encoded certificate in a file
with the suffix @file{.crt} or @file{.der}. @command{dirmngr} reads
those certificates on startup and when given a SIGHUP. Certificates
which are not readable or do not make up a proper X.509 certificate
are ignored; see the log file for details.
Applications using dirmngr (e.g. gpgsm) can request these
certificates to complete a trust chain in the same way as with the
extra-certs directory (see below).
Note that for OCSP responses the certificate specified using the option
@option{--ocsp-signer} is always considered valid to sign OCSP requests.
@item /etc/gnupg/extra-certs
This directory may contain extra certificates which are preloaded
into the internal cache on startup. Applications using dirmngr (e.g. gpgsm)
can request cached certificates to complete a trust chain.
This is convenient in cases you have a couple intermediate CA certificates
or certificates usually used to sign OCSP responses.
These certificates are first tried before going
out to the net to look for them. These certificates must also be
@acronym{DER} encoded and suffixed with @file{.crt} or @file{.der}.
@item ~/.gnupg/crls.d
This directory is used to store cached CRLs. The @file{crls.d}
part will be created by dirmngr if it does not exists but you need to
make sure that the upper directory exists.
@end table
@manpause
To be able to see what's going on you should create the configure file
@file{~/gnupg/dirmngr.conf} with at least one line:
@example
log-file ~/dirmngr.log
@end example
To be able to perform OCSP requests you probably want to add the line:
@example
allow-ocsp
@end example
To make sure that new options are read and that after the installation
of a new GnuPG versions the installed dirmngr is running, you may want
to kill an existing dirmngr first:
@example
gpgconf --kill dirmngr
@end example
You may check the log file to see whether all desired root
certificates have been loaded correctly.
@c
@c Dirmngr Signals
@c
@mansect signals
@node Dirmngr Signals
@section Use of signals
A running @command{dirmngr} may be controlled by signals, i.e. using
the @command{kill} command to send a signal to the process.
Here is a list of supported signals:
@table @gnupgtabopt
@item SIGHUP
@cpindex SIGHUP
This signal flushes all internally cached CRLs as well as any cached
certificates. Then the certificate cache is reinitialized as on
startup. Options are re-read from the configuration file. Instead of
sending this signal it is better to use
@example
gpgconf --reload dirmngr
@end example
@item SIGTERM
@cpindex SIGTERM
Shuts down the process but waits until all current requests are
fulfilled. If the process has received 3 of these signals and requests
are still pending, a shutdown is forced. You may also use
@example
gpgconf --kill dirmngr
@end example
instead of this signal
@item SIGINT
@cpindex SIGINT
Shuts down the process immediately.
@item SIGUSR1
@cpindex SIGUSR1
This prints some caching statistics to the log file.
@end table
@c
@c Examples
@c
@mansect examples
@node Dirmngr Examples
@section Examples
Here is an example on how to show dirmngr's internal table of OpenPGP
keyserver addresses. The output is intended for debugging purposes
and not part of a defined API.
@example
gpg-connect-agent --dirmngr 'keyserver --hosttable' /bye
@end example
To inhibit the use of a particular host you have noticed in one of the
keyserver pools, you may use
@example
gpg-connect-agent --dirmngr 'keyserver --dead pgpkeys.bnd.de' /bye
@end example
The description of the @code{keyserver} command can be printed using
@example
gpg-connect-agent --dirmngr 'help keyserver' /bye
@end example
@c
@c Assuan Protocol
@c
@manpause
@node Dirmngr Protocol
@section Dirmngr's Assuan Protocol
Assuan is the IPC protocol used to access dirmngr. This is a
description of the commands implemented by dirmngr.
@menu
* Dirmngr LOOKUP:: Look up a certificate via LDAP
* Dirmngr ISVALID:: Validate a certificate using a CRL or OCSP.
* Dirmngr CHECKCRL:: Validate a certificate using a CRL.
* Dirmngr CHECKOCSP:: Validate a certificate using OCSP.
* Dirmngr CACHECERT:: Put a certificate into the internal cache.
* Dirmngr VALIDATE:: Validate a certificate for debugging.
@end menu
@node Dirmngr LOOKUP
@subsection Return the certificate(s) found
Lookup certificate. To allow multiple patterns (which are ORed)
quoting is required: Spaces are to be translated into "+" or into
"%20"; obviously this requires that the usual escape quoting rules
are applied. The server responds with:
@example
S: D <DER encoded certificate>
S: END
S: D <second DER encoded certificate>
S: END
S: OK
@end example
In this example 2 certificates are returned. The server may return
any number of certificates; OK will also be returned when no
certificates were found. The dirmngr might return a status line
@example
S: S TRUNCATED <n>
@end example
To indicate that the output was truncated to N items due to a
limitation of the server or by an arbitrary set limit.
The option @option{--url} may be used if instead of a search pattern a
complete URL to the certificate is known:
@example
C: LOOKUP --url CN%3DWerner%20Koch,o%3DIntevation%20GmbH,c%3DDE?userCertificate
@end example
If the option @option{--cache-only} is given, no external lookup is done
so that only certificates from the cache are returned.
With the option @option{--single}, the first and only the first match
will be returned. Unless option @option{--cache-only} is also used, no
local lookup will be done in this case.
@node Dirmngr ISVALID
@subsection Validate a certificate using a CRL or OCSP
@example
ISVALID [--only-ocsp] [--force-default-responder] @var{certid}|@var{certfpr}
@end example
Check whether the certificate described by the @var{certid} has been
revoked. Due to caching, the Dirmngr is able to answer immediately in
most cases.
The @var{certid} is a hex encoded string consisting of two parts,
delimited by a single dot. The first part is the SHA-1 hash of the
issuer name and the second part the serial number.
Alternatively the certificate's SHA-1 fingerprint @var{certfpr} may be
given in which case an OCSP request is done before consulting the CRL.
If the option @option{--only-ocsp} is given, no fallback to a CRL check
will be used. If the option @option{--force-default-responder} is
given, only the default OCSP responder will be used and any other
methods of obtaining an OCSP responder URL won't be used.
@noindent
Common return values are:
@table @code
@item GPG_ERR_NO_ERROR (0)
This is the positive answer: The certificate is not revoked and we have
an up-to-date revocation list for that certificate. If OCSP was used
the responder confirmed that the certificate has not been revoked.
@item GPG_ERR_CERT_REVOKED
This is the negative answer: The certificate has been revoked. Either
it is in a CRL and that list is up to date or an OCSP responder informed
us that it has been revoked.
@item GPG_ERR_NO_CRL_KNOWN
No CRL is known for this certificate or the CRL is not valid or out of
date.
@item GPG_ERR_NO_DATA
The OCSP responder returned an ``unknown'' status. This means that it
is not aware of the certificate's status.
@item GPG_ERR_NOT_SUPPORTED
This is commonly seen if OCSP support has not been enabled in the
configuration.
@end table
If DirMngr has not enough information about the given certificate (which
is the case for not yet cached certificates), it will inquire the
missing data:
@example
S: INQUIRE SENDCERT <CertID>
C: D <DER encoded certificate>
C: END
@end example
A client should be aware that DirMngr may ask for more than one
certificate.
If Dirmngr has a certificate but the signature of the certificate
could not been validated because the root certificate is not known to
dirmngr as trusted, it may ask back to see whether the client trusts
this the root certificate:
@example
S: INQUIRE ISTRUSTED <CertHexfpr>
C: D 1
C: END
@end example
Only this answer will let Dirmngr consider the certificate as valid.
@node Dirmngr CHECKCRL
@subsection Validate a certificate using a CRL
Check whether the certificate with FINGERPRINT (SHA-1 hash of the
entire X.509 certificate blob) is valid or not by consulting the CRL
responsible for this certificate. If the fingerprint has not been
given or the certificate is not known, the function inquires the
certificate using:
@example
S: INQUIRE TARGETCERT
C: D <DER encoded certificate>
C: END
@end example
Thus the caller is expected to return the certificate for the request
(which should match FINGERPRINT) as a binary blob. Processing then
takes place without further interaction; in particular dirmngr tries
to locate other required certificate by its own mechanism which
includes a local certificate store as well as a list of trusted root
certificates.
@noindent
The return code is 0 for success; i.e. the certificate has not been
revoked or one of the usual error codes from libgpg-error.
@node Dirmngr CHECKOCSP
@subsection Validate a certificate using OCSP
@example
CHECKOCSP [--force-default-responder] [@var{fingerprint}]
@end example
Check whether the certificate with @var{fingerprint} (the SHA-1 hash of
the entire X.509 certificate blob) is valid by consulting the appropriate
OCSP responder. If the fingerprint has not been given or the
certificate is not known by Dirmngr, the function inquires the
certificate using:
@example
S: INQUIRE TARGETCERT
C: D <DER encoded certificate>
C: END
@end example
Thus the caller is expected to return the certificate for the request
(which should match @var{fingerprint}) as a binary blob. Processing
then takes place without further interaction; in particular dirmngr
tries to locate other required certificates by its own mechanism which
includes a local certificate store as well as a list of trusted root
certificates.
If the option @option{--force-default-responder} is given, only the
default OCSP responder is used. This option is the per-command variant
of the global option @option{--ignore-ocsp-service-url}.
@noindent
The return code is 0 for success; i.e. the certificate has not been
revoked or one of the usual error codes from libgpg-error.
@node Dirmngr CACHECERT
@subsection Put a certificate into the internal cache
Put a certificate into the internal cache. This command might be
useful if a client knows in advance certificates required for a test and
wants to make sure they get added to the internal cache. It is also
helpful for debugging. To get the actual certificate, this command
immediately inquires it using
@example
S: INQUIRE TARGETCERT
C: D <DER encoded certificate>
C: END
@end example
Thus the caller is expected to return the certificate for the request
as a binary blob.
@noindent
The return code is 0 for success; i.e. the certificate has not been
successfully cached or one of the usual error codes from libgpg-error.
@node Dirmngr VALIDATE
@subsection Validate a certificate for debugging
Validate a certificate using the certificate validation function used
internally by dirmngr. This command is only useful for debugging. To
get the actual certificate, this command immediately inquires it using
@example
S: INQUIRE TARGETCERT
C: D <DER encoded certificate>
C: END
@end example
Thus the caller is expected to return the certificate for the request
as a binary blob.
@mansect see also
@ifset isman
@command{gpgsm}(1),
@command{dirmngr-client}(1)
@end ifset
@include see-also-note.texi
@c
@c !!! UNDER CONSTRUCTION !!!
@c
@c
@c @section Verifying a Certificate
@c
@c There are several ways to request services from Dirmngr. Almost all of
@c them are done using the Assuan protocol. What we describe here is the
@c Assuan command CHECKCRL as used for example by the dirmnr-client tool if
@c invoked as
@c
@c @example
@c dirmngr-client foo.crt
@c @end example
@c
@c This command will send an Assuan request to an already running Dirmngr
@c instance. foo.crt is expected to be a standard X.509 certificate and
@c dirmngr will receive the Assuan command
@c
@c @example
@c CHECKCRL @var [{fingerprint}]
@c @end example
@c
@c @var{fingerprint} is optional and expected to be the SHA-1 has of the
@c DER encoding of the certificate under question. It is to be HEX
@c encoded. The rationale for sending the fingerprint is that it allows
@c dirmngr to reply immediately if it has already cached such a request. If
@c this is not the case and no certificate has been found in dirmngr's
@c internal certificate storage, dirmngr will request the certificate using
@c the Assuan inquiry
@c
@c @example
@c INQUIRE TARGETCERT
@c @end example
@c
@c The caller (in our example dirmngr-client) is then expected to return
@c the certificate for the request (which should match @var{fingerprint})
@c as a binary blob.
@c
@c Dirmngr now passes control to @code{crl_cache_cert_isvalid}. This
@c function checks whether a CRL item exists for target certificate. These
@c CRL items are kept in a database of already loaded and verified CRLs.
@c This mechanism is called the CRL cache. Obviously timestamps are kept
@c there with each item to cope with the expiration date of the CRL. The
@c possible return values are: @code{0} to indicate that a valid CRL is
@c available for the certificate and the certificate itself is not listed
@c in this CRL, @code{GPG_ERR_CERT_REVOKED} to indicate that the certificate is
@c listed in the CRL or @code{GPG_ERR_NO_CRL_KNOWN} in cases where no CRL or no
@c information is available. The first two codes are immediately returned to
@c the caller and the processing of this request has been done.
@c
@c Only the @code{GPG_ERR_NO_CRL_KNOWN} needs more attention: Dirmngr now
@c calls @code{clr_cache_reload_crl} and if this succeeds calls
@c @code{crl_cache_cert_isvald) once more. All further errors are
@c immediately returned to the caller.
@c
@c @code{crl_cache_reload_crl} is the actual heart of the CRL management.
@c It locates the corresponding CRL for the target certificate, reads and
@c verifies this CRL and stores it in the CRL cache. It works like this:
@c
@c * Loop over all crlDPs in the target certificate.
@c * If the crlDP is invalid immediately terminate the loop.
@c * Loop over all names in the current crlDP.
@c * If the URL scheme is unknown or not enabled
@c (--ignore-http-dp, --ignore-ldap-dp) continues with
@c the next name.
@c * @code{crl_fetch} is called to actually retrieve the CRL.
@c In case of problems this name is ignore and we continue with
@c the next name. Note that @code{crl_fetch} does only return
@c a descriptor for the CRL for further reading so does the CRL
@c does not yet end up in memory.
@c * @code{crl_cache_insert} is called with that descriptor to
@c actually read the CRL into the cache. See below for a
@c description of this function. If there is any error (e.g. read
@c problem, CRL not correctly signed or verification of signature
@c not possible), this descriptor is rejected and we continue
@c with the next name. If the CRL has been successfully loaded,
@c the loop is terminated.
@c * If no crlDP has been found in the previous loop use a default CRL.
@c Note, that if any crlDP has been found but loading of the CRL failed,
@c this condition is not true.
@c * Try to load a CRL from all configured servers (ldapservers.conf)
@c in turn. The first server returning a CRL is used.
@c * @code(crl_cache_insert) is then used to actually insert the CRL
@c into the cache. If this failed we give up immediately without
@c checking the rest of the servers from the first step.
@c * Ready.
@c
@c
@c The @code{crl_cache_insert} function takes care of reading the bulk of
@c the CRL, parsing it and checking the signature. It works like this: A
@c new database file is created using a temporary file name. The CRL
@c parsing machinery is started and all items of the CRL are put into
@c this database file. At the end the issuer certificate of the CRL
@c needs to be retrieved. Three cases are to be distinguished:
@c
@c a) An authorityKeyIdentifier with an issuer and serialno exits: The
@c certificate is retrieved using @code{find_cert_bysn}. If
@c the certificate is in the certificate cache, it is directly
@c returned. Then the requester (i.e. the client who requested the
@c CRL check) is asked via the Assuan inquiry ``SENDCERT'' whether
@c he can provide this certificate. If this succeed the returned
@c certificate gets cached and returned. Note, that dirmngr does not
@c verify in any way whether the expected certificate is returned.
@c It is in the interest of the client to return a useful certificate
@c as otherwise the service request will fail due to a bad signature.
@c The last way to get the certificate is by looking it up at
@c external resources. This is done using the @code{ca_cert_fetch}
@c and @code{fetch_next_ksba_cert} and comparing the returned
@c certificate to match the requested issuer and seriano (This is
@c needed because the LDAP layer may return several certificates as
@c LDAP as no standard way to retrieve by serial number).
@c
@c b) An authorityKeyIdentifier with a key ID exists: The certificate is
@c retrieved using @code{find_cert_bysubject}. If the certificate is
@c in the certificate cache, it is directly returned. Then the
@c requester is asked via the Assuan inquiry ``SENDCERT_SKI'' whether
@c he can provide this certificate. If this succeed the returned
@c certificate gets cached and returned. Note, that dirmngr does not
@c verify in any way whether the expected certificate is returned.
@c It is in the interest of the client to return a useful certificate
@c as otherwise the service request will fail due to a bad signature.
@c The last way to get the certificate is by looking it up at
@c external resources. This is done using the @code{ca_cert_fetch}
@c and @code{fetch_next_ksba_cert} and comparing the returned
@c certificate to match the requested subject and key ID.
@c
@c c) No authorityKeyIdentifier exits: The certificate is retrieved
@c using @code{find_cert_bysubject} without the key ID argument. If
@c the certificate is in the certificate cache the first one with a
@c matching subject is directly returned. Then the requester is
@c asked via the Assuan inquiry ``SENDCERT'' and an exact
@c specification of the subject whether he can
@c provide this certificate. If this succeed the returned
@c certificate gets cached and returned. Note, that dirmngr does not
@c verify in any way whether the expected certificate is returned.
@c It is in the interest of the client to return a useful certificate
@c as otherwise the service request will fail due to a bad signature.
@c The last way to get the certificate is by looking it up at
@c external resources. This is done using the @code{ca_cert_fetch}
@c and @code{fetch_next_ksba_cert} and comparing the returned
@c certificate to match the requested subject; the first certificate
@c with a matching subject is then returned.
@c
@c If no certificate was found, the function returns with the error
@c GPG_ERR_MISSING_CERT. Now the signature is verified. If this fails,
@c the erro is returned. On success the @code{validate_cert_chain} is
@c used to verify that the certificate is actually valid.
@c
@c Here we may encounter a recursive situation:
@c @code{validate_cert_chain} needs to look at other certificates and
@c also at CRLs to check whether these other certificates and well, the
@c CRL issuer certificate itself are not revoked. FIXME: We need to make
@c sure that @code{validate_cert_chain} does not try to lookup the CRL we
@c are currently processing. This would be a catch-22 and may indicate a
@c broken PKI. However, due to overlapping expiring times and imprecise
@c clocks this may actually happen.
@c
@c For historical reasons the Assuan command ISVALID is a bit different
@c to CHECKCRL but this is mainly due to different calling conventions.
@c In the end the same fucntionality is used, albeit hidden by a couple
@c of indirection and argument and result code mangling. It furthere
@c ingetrages OCSP checking depending on options are the way it is
@c called. GPGSM still uses this command but might eventually switch over
@c to CHECKCRL and CHECKOCSP so that ISVALID can be retired.
@c
@c
@c @section Validating a certificate
@c
@c We describe here how the internal function @code{validate_cert_chain}
@c works. Note that mainly testing purposes this functionality may be
@c called directly using @cmd{dirmngr-client --validate @file{foo.crt}}.
@c
@c The function takes the target certificate and a mode argument as
@c parameters and returns an error code and optionally the closes
@c expiration time of all certificates in the chain.
@c
@c We first check that the certificate may be used for the requested
@c purpose (i.e. OCSP or CRL signing). If this is not the case
@c GPG_ERR_WRONG_KEY_USAGE is returned.
@c
@c The next step is to find the trust anchor (root certificate) and to
@c assemble the chain in memory: Starting with the target certificate,
@c the expiration time is checked against the current date, unknown
@c critical extensions are detected and certificate policies are matched
@c (We only allow 2.289.9.9 but I have no clue about that OID and from
@c where I got it - it does not even seem to be assigned - debug cruft?).
@c
@c Now if this certificate is a self-signed one, we have reached the
@c trust anchor. In this case we check that the signature is good, the
@c certificate is allowed to act as a CA, that it is a trusted one (by
@c checking whether it is has been put into the trusted-certs
@c configuration directory) and finally prepend into to our list
@c representing the certificate chain. This steps ends then.
@c
@c If it is not a self-signed certificate, we check that the chain won't
@c get too long (current limit is 100), if this is the case we terminate
@c with the error GPG_ERR_BAD_CERT_CHAIN.
@c
@c Now the issuer's certificate is looked up: If an
@c authorityKeyIdentifier is available, this one is used to locate the
@c certificate either using issuer and serialnumber or subject DN
@c (i.e. the issuer's DN) and the keyID. The functions
@c @code{find_cert_bysn) and @code{find_cert_bysubject} are used
@c respectively. The have already been described above under the
@c description of @code{crl_cache_insert}. If no certificate was found
@c or with no authorityKeyIdentifier, only the cache is consulted using
@c @code{get_cert_bysubject}. The latter is done under the assumption
@c that a matching certificate has explicitly been put into the
@c certificate cache. If the issuer's certificate could not be found,
@c the validation terminates with the error code @code{GPG_ERR_MISSING_CERT}.
@c
@c If the issuer's certificate has been found, the signature of the
@c actual certificate is checked and in case this fails the error
@c #code{GPG_ERR_BAD_CERT_CHAIN} is returned. If the signature checks out, the
@c maximum chain length of the issuing certificate is checked as well as
@c the capability of the certificate (i.e. whether he may be used for
@c certificate signing). Then the certificate is prepended to our list
@c representing the certificate chain. Finally the loop is continued now
@c with the issuer's certificate as the current certificate.
@c
@c After the end of the loop and if no error as been encountered
@c (i.e. the certificate chain has been assempled correctly), a check is
@c done whether any certificate expired or a critical policy has not been
@c met. In any of these cases the validation terminates with an
@c appropriate error.
@c
@c Finally the function @code{check_revocations} is called to verify no
@c certificate in the assempled chain has been revoked: This is an
@c recursive process because a CRL has to be checked for each certificate
@c in the chain except for the root certificate, of which we already know
@c that it is trusted and we avoid checking a CRL here due to common
@c setup problems and the assumption that a revoked root certificate has
@c been removed from the list of trusted certificates.
@c
@c
@c
@c
@c @section Looking up certificates through LDAP.
@c
@c This describes the LDAP layer to retrieve certificates.
@c the functions @code{ca_cert_fetch} and @code{fetch_next_ksba_cert} are
@c used for this. The first one starts a search and the second one is
@c used to retrieve certificate after certificate.
@c
-
-
diff --git a/doc/gpg-card.texi b/doc/gpg-card.texi
index aa49f81e7..fcc1792f1 100644
--- a/doc/gpg-card.texi
+++ b/doc/gpg-card.texi
@@ -1,517 +1,652 @@
@c card-tool.texi - man page for gpg-card-tool
@c Copyright (C) 2019 g10 Code GmbH
@c This is part of the GnuPG manual.
@c For copying conditions, see the file GnuPG.texi.
@include defs.inc
@node Smart Card Tool
@chapter Smart Card Tool
-GnuPG comes with tool to administrate smart cards and USB tokens. This
-tool is an extension of the @option{--edit-key} command available with
-@command{gpg}.
+GnuPG comes with a tool to administrate smart cards and USB tokens.
+This tool is an enhanced version of the @option{--edit-key} command
+available with @command{gpg}.
@menu
* gpg-card:: Administrate smart cards.
@end menu
@c
@c GPG-CARD-TOOL
@c
@manpage gpg-card.1
@node gpg-card
@section Administrate smart cards.
@ifset manverb
.B gpg-card
\- Administrate Smart Cards
@end ifset
@mansect synopsis
@ifset manverb
.B gpg-card
.RI [ options ]
.br
.B gpg-card
.RI [ options ]
.I command
.RI {
.B --
.I command
.RI }
@end ifset
@mansect description
The @command{gpg-card} is used to administrate smart cards and USB
tokens. It provides a superset of features from @command{gpg
--card-edit} an can be considered a frontend to @command{scdaemon}
which is a daemon started by @command{gpg-agent} to handle smart
cards.
If @command{gpg-card} is invoked without commands an interactive
mode is used.
If @command{gpg-card} is invoked with one or more commands the
same commands as available in the interactive mode are run from the
command line. These commands need to be delimited with a double-dash.
If a double-dash or a shell specific character is required as part of
a command the entire command needs to be put in quotes. If one of
those commands returns an error the remaining commands are mot anymore
run unless the command was prefixed with a single dash.
A list of commands is available by using the command @code{help} and a
detailed description of each command is printed by using @code{help
COMMAND}.
See the NOTES sections for instructions pertaining to specific cards
or card applications.
@mansect options
@noindent
@command{gpg-card} understands these options:
@table @gnupgtabopt
@item --with-colons
@opindex with-colons
This option has currently no effect.
@item --status-fd @var{n}
@opindex status-fd
Write special status strings to the file descriptor @var{n}. This
program returns only the status messages SUCCESS or FAILURE which are
helpful when the caller uses a double fork approach and can't easily
get the return code of the process.
@item --verbose
@opindex verbose
Enable extra informational output.
@item --quiet
@opindex quiet
Disable almost all informational output.
@item --version
@opindex version
Print version of the program and exit.
@item --help
@opindex help
Display a brief help page and exit.
@item --no-autostart
@opindex no-autostart
Do not start the gpg-agent if it has not yet been started and its
service is required. This option is mostly useful on machines where
the connection to gpg-agent has been redirected to another machines.
@item --agent-program @var{file}
@opindex agent-program
Specify the agent program to be started if none is running. The
default value is determined by running @command{gpgconf} with the
option @option{--list-dirs}.
@item --gpg-program @var{file}
@opindex gpg-program
Specify a non-default gpg binary to be used by certain commands.
@item --gpgsm-program @var{file}
@opindex gpgsm-program
Specify a non-default gpgsm binary to be used by certain commands.
@end table
@mansect notes (OpenPGP)
The support for OpenPGP cards in @command{gpg-card} is not yet
complete. For missing features, please continue to use @code{gpg
--card-edit}.
@mansect notes (PIV)
@noindent
GnuPG has support for PIV cards (``Personal Identity Verification''
as specified by NIST Special Publication 800-73-4). This section
describes how to initialize (personalize) a fresh Yubikey token
featuring the PIV application (requires Yubikey-5). We assume that
the credentials have not yet been changed and thus are:
@table @asis
@item Authentication key
-This is a 24 byte key described by the hex string
+This is a 24 byte key described by the hex string @*
@code{010203040506070801020304050607080102030405060708}.
@item PIV Application PIN
This is the string @code{123456}.
@item PIN Unblocking Key
This is the string @code{12345678}.
@end table
See the example section on how to change these defaults. For
production use it is important to use secure values for them. Note that
the Authentication Key is not queried via the usual Pinentry dialog
but needs to be entered manually or read from a file. The use of a
dedicated machine to personalize tokens is strongly suggested.
To see what is on the card, the command @code{list} can be given. We
will use the interactive mode in the following (the string
@emph{gpg/card>} is the prompt). An example output for a fresh card
is:
@example
gpg/card> list
Reader ...........: 1050:0407:X:0
Card type ........: yubikey
Card firmware ....: 5.1.2
Serial number ....: D2760001240102010006090746250000
Application type .: OpenPGP
Version ..........: 2.1
[...]
@end example
-It can be seen by the ``Application type'' line that GnuPG selected the
-OpenPGP application of the Yubikey. This is because GnuPG assigns the
-highest priority to the OpenPGP application. To use the PIV
-application of the Yubikey, the OpenPGP application needs to be
-disabled:
+It can be seen by the ``Application type'' line that GnuPG selected
+the OpenPGP application of the Yubikey. This is because GnuPG assigns
+the highest priority to the OpenPGP application. To use the PIV
+application of the Yubikey several methods can be used:
+
+With a Yubikey 5 or later the OpenPGP application on the Yubikey can
+be disabled:
@example
gpg/card> yubikey disable all opgp
gpg/card> yubikey list
Application USB NFC
-----------------------
OTP yes yes
U2F yes yes
OPGP no no
PIV yes no
OATH yes yes
FIDO2 yes yes
gpg/card> reset
@end example
The @code{reset} is required so that the GnuPG system rereads the
card. Note that disabled applications keep all their data and can at
-any time be re-enabled (see @emph{help yubikey}). Now a @emph{list}
-command shows this:
+any time be re-enabled (use @kbd{help yubikey}).
+
+Another option, which works for all Yubikey versions, is to disable
+the support for OpenPGP cards in scdaemon. This is done by adding the
+line
+
+@smallexample
+disable-application openpgp
+@end smallexample
+
+to @file{~/.gnupg/scdaemon.conf} and by restarting scdaemon, either by
+killing the process or by using @kbd{gpgconf --kill scdaemon}. Finally
+the default order in which card applications are tried by scdaemon can
+be changed. For example to prefer PIV over OpenPGP it is sufficient
+to add
+
+@smallexample
+application-priority piv
+@end smallexample
+
+to @file{~/.gnupg/scdaemon.conf} and to restart @command{scdaemon}.
+This has an effect only on tokens which support both, PIV and OpenPGP,
+but does not hamper the use of OpenPGP only tokens.
+
+With one of these methods employed the @code{list} command of
+@command{gpg-card} shows this:
@example
gpg/card> list
Reader ...........: 1050:0407:X:0
Card type ........: yubikey
Card firmware ....: 5.1.2
Serial number ....: FF020001008A77C1
Application type .: PIV
Version ..........: 1.0
Displayed s/n ....: yk-9074625
PIN usage policy .: app-pin
PIN retry counter : - 3 -
PIV authentication: [none]
keyref .....: PIV.9A
Card authenticat. : [none]
keyref .....: PIV.9E
Digital signature : [none]
keyref .....: PIV.9C
Key management ...: [none]
keyref .....: PIV.9D
@end example
-Note that the ``Displayed s/sn'' is printed on the token and also
+In case several tokens are plugged into the computer, gpg-card will
+show only one. To show another token the number of the token (0, 1,
+2, ...) can be given as an argument to the @code{list} command. The
+command @kbd{list --cards} prints a list of all inserted tokens.
+
+Note that the ``Displayed s/n'' is printed on the token and also
shown in Pinentry prompts asking for the PIN. The four standard key
slots are always shown, if other key slots are initialized they are
shown as well. The @emph{PIV authentication} key (internal reference
@emph{PIV.9A}) is used to authenticate the card and the card holder.
The use of the associated private key is protected by the Application
PIN which needs to be provided once and the key can the be used until
the card is reset or removed from the reader or USB port. GnuPG uses
this key with its @emph{Secure Shell} support. The @emph{Card
authentication} key (@emph{PIV.9E}) is also known as the CAK and used
to support physical access applications. The private key is not
protected by a PIN and can thus immediately be used. The @emph{Digital
signature} key (@emph{PIV.9C}) is used to digitally sign documents.
The use of the associated private key is protected by the Application
PIN which needs to be provided for each signing operation. The
@emph{Key management} key (@emph{PIV.9D}) is used for encryption. The
use of the associated private key is protected by the Application PIN
which needs to be provided only once so that decryption operations can
then be done until the card is reset or removed from the reader or USB
port.
-We now generate tree of the four keys. Note that GnuPG does currently
-not use the the @emph{Card authentication} key but because it is
-mandatory by the specs we create it anyway. Key generation requires
-that we authenticate to the card. This can be done either on the
-command line (which would reveal the key):
+We now generate three of the four keys. Note that GnuPG does
+currently not use the the @emph{Card authentication} key; however,
+that key is mandatory by the PIV standard and thus we create it too.
+Key generation requires that we authenticate to the card. This can be
+done either on the command line (which would reveal the key):
@example
gpg/card> auth 010203040506070801020304050607080102030405060708
@end example
or by reading the key from a file. That file needs to consist of one
LF terminated line with the hex encoded key (as above):
@example
gpg/card> auth < myauth.key
@end example
As usual @samp{help auth} gives help for this command. An error
message is printed if a non-matching key is used. The authentication
is valid until a reset of the card or until the card is removed from
the reader or the USB port. Note that that in non-interactive mode
the @samp{<} needs to be quoted so that the shell does not interpret
it as a its own redirection symbol.
@noindent
Here are the actual commands to generate the keys:
@example
gpg/card> generate --algo=nistp384 PIV.9A
PIV card no. yk-9074625 detected
gpg/card> generate --algo=nistp256 PIV.9E
PIV card no. yk-9074625 detected
gpg/card> generate --algo=rsa2048 PIV.9C
PIV card no. yk-9074625 detected
@end example
If a key has already been created for one of the slots an error will
be printed; to create a new key anyway the option @samp{--force} can be
used. Note that only the private and public keys have been created
but no certificates are stored in the key slots. In fact, GnuPG uses
its own non-standard method to store just the public key in place of
the the certificate. Other application will not be able to make use
these keys until @command{gpgsm} or another tool has been used to
create and store the respective certificates. Let us see what the
list command now shows:
@example
gpg/card> list
Reader ...........: 1050:0407:X:0
Card type ........: yubikey
Card firmware ....: 5.1.2
Serial number ....: FF020001008A77C1
Application type .: PIV
Version ..........: 1.0
Displayed s/n ....: yk-9074625
PIN usage policy .: app-pin
PIN retry counter : - 3 -
PIV authentication: 213D1825FDE0F8240CB4E4229F01AF90AC658C2E
keyref .....: PIV.9A (auth)
algorithm ..: nistp384
Card authenticat. : 7A53E6CFFE7220A0E646B4632EE29E5A7104499C
keyref .....: PIV.9E (auth)
algorithm ..: nistp256
Digital signature : 32A6C6FAFCB8421878608AAB452D5470DD3223ED
keyref .....: PIV.9C (sign,cert)
algorithm ..: rsa2048
Key management ...: [none]
keyref .....: PIV.9D
@end example
The primary information for each key is the @emph{keygrip}, a 40 byte
hex-string identifying the key. This keygrip is a unique identifier
for the specific parameters of a key. It is used by
@command{gpg-agent} and other parts of GnuPG to associate a private
key to its protocol specific certificate format (X.509, OpenPGP, or
SecureShell). Below the keygrip the key reference along with the key
usage capabilities are show. Finally the algorithm is printed in the
format used by @command {gpg}. At that point no other information is
shown because for these new keys gpg won't be able to find matching
certificates.
Although we could have created the @emph{Key management} key also with
the generate command, we will create that key off-card so that a
backup exists. To accomplish this a key needs to be created with
either @command{gpg} or @command{gpgsm} or imported in one of these
tools. In our example we create a self-signed X.509 certificate (exit
the gpg-card tool, first):
@example
$ gpgsm --gen-key -o encr.crt
(1) RSA
(2) Existing key
(3) Existing key from card
Your selection? 1
What keysize do you want? (3072) 2048
Requested keysize is 2048 bits
Possible actions for a RSA key:
(1) sign, encrypt
(2) sign
(3) encrypt
Your selection? 3
Enter the X.509 subject name: CN=Encryption key for yk-9074625,O=example,C=DE
Enter email addresses (end with an empty line):
> otto@@example.net
>
Enter DNS names (optional; end with an empty line):
>
Enter URIs (optional; end with an empty line):
>
Create self-signed certificate? (y/N) y
These parameters are used:
Key-Type: RSA
Key-Length: 2048
Key-Usage: encrypt
Serial: random
Name-DN: CN=Encryption key for yk-9074625,O=example,C=DE
Name-Email: otto@@example.net
Proceed with creation? (y/N)
Now creating self-signed certificate. This may take a while ...
gpgsm: about to sign the certificate for key: &34798AAFE0A7565088101CC4AE31C5C8C74461CB
gpgsm: certificate created
Ready.
$ gpgsm --import encr.crt
gpgsm: certificate imported
gpgsm: total number processed: 1
gpgsm: imported: 1
@end example
-Note the last steps which imported the created certificate. If you
+Note the last step which imported the created certificate. If you
you instead created a certificate signing request (CSR) instead of a
self-signed certificate and sent this off to a CA you would do the
same import step with the certificate received from the CA. Take note
of the keygrip (prefixed with an ampersand) as shown during the
certificate creation or listed it again using @samp{gpgsm
--with-keygrip -k otto@@example.net}. Now to move the key and
certificate to the card start @command{gpg-card} again and enter:
@example
gpg/card> writekey PIV.9D 34798AAFE0A7565088101CC4AE31C5C8C74461CB
gpg/card> writecert PIV.9D < encr.crt
@end example
If you entered a passphrase to protect the private key, you will be
asked for it via the Pinentry prompt. On success the key and the
certificate has been written to the card and a @code{list} command
shows:
@example
[...]
Key management ...: 34798AAFE0A7565088101CC4AE31C5C8C74461CB
keyref .....: PIV.9D (encr)
algorithm ..: rsa2048
used for ...: X.509
user id ..: CN=Encryption key for yk-9074625,O=example,C=DE
user id ..: <otto@@example.net>
@end example
In case the same key (identified by the keygrip) has been used for
several certificates you will see several ``used for'' parts. With
this the encryption key is now fully functional and can be used to
decrypt messages encrypted to this certificate. @sc{Take care:} the
original key is still stored on-disk and should be moved to a backup
medium. This can simply be done by copying the file
@file{34798AAFE0A7565088101CC4AE31C5C8C74461CB.key} from the directory
@file{~/.gnupg/private-keys-v1.d/} to the backup medium and deleting
the file at its original place.
The final example is to create a self-signed certificate for digital
signatures. Leave @command{gpg-card} using @code{quit} or by pressing
Control-D and use gpgsm:
@example
$ gpgsm --learn
$ gpgsm --gen-key -o sign.crt
Please select what kind of key you want:
(1) RSA
(2) Existing key
(3) Existing key from card
Your selection? 3
Serial number of the card: FF020001008A77C1
Available keys:
(1) 213D1825FDE0F8240CB4E4229F01AF90AC658C2E PIV.9A nistp384
(2) 7A53E6CFFE7220A0E646B4632EE29E5A7104499C PIV.9E nistp256
(3) 32A6C6FAFCB8421878608AAB452D5470DD3223ED PIV.9C rsa2048
(4) 34798AAFE0A7565088101CC4AE31C5C8C74461CB PIV.9D rsa2048
Your selection? 3
Possible actions for a RSA key:
(1) sign, encrypt
(2) sign
(3) encrypt
Your selection? 2
Enter the X.509 subject name: CN=Signing key for yk-9074625,O=example,C=DE
Enter email addresses (end with an empty line):
> otto@@example.net
>
Enter DNS names (optional; end with an empty line):
>
Enter URIs (optional; end with an empty line):
>
Create self-signed certificate? (y/N)
These parameters are used:
Key-Type: card:PIV.9C
Key-Length: 1024
Key-Usage: sign
Serial: random
Name-DN: CN=Signing key for yk-9074625,O=example,C=DE
Name-Email: otto@@example.net
Proceed with creation? (y/N) y
Now creating self-signed certificate. This may take a while ...
gpgsm: about to sign the certificate for key: &32A6C6FAFCB8421878608AAB452D5470DD3223ED
gpgsm: certificate created
Ready.
$ gpgsm --import sign.crt
gpgsm: certificate imported
gpgsm: total number processed: 1
gpgsm: imported: 1
@end example
The use of @samp{gpgsm --learn} is currently necessary so that
gpg-agent knows what keys are available on the card. The need for
this command will eventually be removed. The remaining commands are
similar to the creation of an on-disk key. However, here we select
the @samp{Digital signature} key. During the creation process you
will be asked for the Application PIN of the card. The final step is
to write the certificate to the card using @command{gpg-card}:
@example
gpg/card> writecert PIV.9C < sign.crt
@end example
By running list again we will see the fully initialized card:
@example
Reader ...........: 1050:0407:X:0
Card type ........: yubikey
Card firmware ....: 5.1.2
Serial number ....: FF020001008A77C1
Application type .: PIV
Version ..........: 1.0
Displayed s/n ....: yk-9074625
PIN usage policy .: app-pin
PIN retry counter : - [verified] -
PIV authentication: 213D1825FDE0F8240CB4E4229F01AF90AC658C2E
keyref .....: PIV.9A (auth)
algorithm ..: nistp384
Card authenticat. : 7A53E6CFFE7220A0E646B4632EE29E5A7104499C
keyref .....: PIV.9E (auth)
algorithm ..: nistp256
Digital signature : 32A6C6FAFCB8421878608AAB452D5470DD3223ED
keyref .....: PIV.9C (sign,cert)
algorithm ..: rsa2048
used for ...: X.509
user id ..: CN=Signing key for yk-9074625,O=example,C=DE
user id ..: <otto@@example.net>
Key management ...: 34798AAFE0A7565088101CC4AE31C5C8C74461CB
keyref .....: PIV.9D (encr)
algorithm ..: rsa2048
used for ...: X.509
user id ..: CN=Encryption key for yk-9074625,O=example,C=DE
user id ..: <otto@@example.net>
@end example
It is now possible to sign and to encrypt with this card using gpgsm
and to use the @samp{PIV authentication} key with ssh:
@example
$ ssh-add -l
384 SHA256:0qnJ0Y0ehWxKcx2frLfEljf6GCdlO55OZed5HqGHsaU cardno:yk-9074625 (ECDSA)
@end example
As usual use ssh-add with the uppercase @samp{-L} to list the public
ssh key. To use the certificates with Thunderbird or Mozilla, please
consult the Scute manual for details.
+If you want to use the same PIV keys also for OpenPGP (for example on
+a Yubikey to avoid switching between OpenPGP and PIV), this is also
+possible:
+
+@example
+$ gpgsm --learn
+$ gpg --full-gen-key
+Please select what kind of key you want:
+ (1) RSA and RSA (default)
+ (2) DSA and Elgamal
+ (3) DSA (sign only)
+ (4) RSA (sign only)
+ (14) Existing key from card
+Your selection? 14
+Serial number of the card: FF020001008A77C1
+Available keys:
+ (1) 213D1825FDE0F8240CB4E4229F01AF90AC658C2E PIV.9A nistp384 (auth)
+ (2) 7A53E6CFFE7220A0E646B4632EE29E5A7104499C PIV.9E nistp256 (auth)
+ (3) 32A6C6FAFCB8421878608AAB452D5470DD3223ED PIV.9C rsa2048 (cert,sign)
+ (4) 34798AAFE0A7565088101CC4AE31C5C8C74461CB PIV.9D rsa2048 (encr)
+Your selection? 3
+Please specify how long the key should be valid.
+ 0 = key does not expire
+ <n> = key expires in n days
+ <n>w = key expires in n weeks
+ <n>m = key expires in n months
+ <n>y = key expires in n years
+Key is valid for? (0)
+Key does not expire at all
+Is this correct? (y/N) y
+
+GnuPG needs to construct a user ID to identify your key.
+
+Real name:
+Email address: otto@@example.net
+Comment:
+You selected this USER-ID:
+ "otto@@example.net"
+
+Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? o
+gpg: key C3AFA9ED971BB365 marked as ultimately trusted
+gpg: revocation certificate stored as '[...]D971BB365.rev'
+public and secret key created and signed.
+
+Note that this key cannot be used for encryption. You may want to use
+the command "--edit-key" to generate a subkey for this purpose.
+pub rsa2048 2019-04-04 [SC]
+ 7F899AE2FB73159DD68A1B20C3AFA9ED971BB365
+uid otto@@example.net
+@end example
+
+Note that you will be asked two times to enter the PIN of your PIV
+card. If you run @command{gpg} in @option{--expert} mode you will
+also ge given the option to change the usage flags of the key. The next
+typescript shows how to add the encryption subkey:
+
+@example
+$ gpg --edit-key 7F899AE2FB73159DD68A1B20C3AFA9ED971BB365
+Secret key is available.
+
+sec rsa2048/C3AFA9ED971BB365
+ created: 2019-04-04 expires: never usage: SC
+ card-no: FF020001008A77C1
+ trust: ultimate validity: ultimate
+[ultimate] (1). otto@@example.net
+gpg> addkey
+Secret parts of primary key are stored on-card.
+Please select what kind of key you want:
+ (3) DSA (sign only)
+ (4) RSA (sign only)
+ (5) Elgamal (encrypt only)
+ (6) RSA (encrypt only)
+ (14) Existing key from card
+Your selection? 14
+Serial number of the card: FF020001008A77C1
+Available keys:
+ (1) 213D1825FDE0F8240CB4E4229F01AF90AC658C2E PIV.9A nistp384 (auth)
+ (2) 7A53E6CFFE7220A0E646B4632EE29E5A7104499C PIV.9E nistp256 (auth)
+ (3) 32A6C6FAFCB8421878608AAB452D5470DD3223ED PIV.9C rsa2048 (cert,sign)
+ (4) 34798AAFE0A7565088101CC4AE31C5C8C74461CB PIV.9D rsa2048 (encr)
+Your selection? 4
+Please specify how long the key should be valid.
+ 0 = key does not expire
+ <n> = key expires in n days
+ <n>w = key expires in n weeks
+ <n>m = key expires in n months
+ <n>y = key expires in n years
+Key is valid for? (0)
+Key does not expire at all
+Is this correct? (y/N) y
+Really create? (y/N) y
+
+sec rsa2048/C3AFA9ED971BB365
+ created: 2019-04-04 expires: never usage: SC
+ card-no: FF020001008A77C1
+ trust: ultimate validity: ultimate
+ssb rsa2048/7067860A98FCE6E1
+ created: 2019-04-04 expires: never usage: E
+ card-no: FF020001008A77C1
+[ultimate] (1). otto@@example.net
+
+gpg> save
+@end example
+Now you can use your PIV card also with @command{gpg}.
@c @mansect examples
@mansect see also
@ifset isman
@command{scdaemon}(1)
@end ifset
diff --git a/doc/gpg.texi b/doc/gpg.texi
index e6829b911..80c7f48f5 100644
--- a/doc/gpg.texi
+++ b/doc/gpg.texi
@@ -1,4246 +1,4294 @@
@c Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007,
@c 2008, 2009, 2010 Free Software Foundation, Inc.
@c This is part of the GnuPG manual.
@c For copying conditions, see the file gnupg.texi.
@include defs.inc
@node Invoking GPG
@chapter Invoking GPG
@cindex GPG command options
@cindex command options
@cindex options, GPG command
@c Begin standard stuff
@ifclear gpgtwohack
@manpage gpg.1
@ifset manverb
.B gpg
\- OpenPGP encryption and signing tool
@end ifset
@mansect synopsis
@ifset manverb
.B gpg
.RB [ \-\-homedir
.IR dir ]
.RB [ \-\-options
.IR file ]
.RI [ options ]
.I command
.RI [ args ]
@end ifset
@end ifclear
@c End standard stuff
@c Begin gpg2 hack stuff
@ifset gpgtwohack
@manpage gpg2.1
@ifset manverb
.B gpg2
\- OpenPGP encryption and signing tool
@end ifset
@mansect synopsis
@ifset manverb
.B gpg2
.RB [ \-\-homedir
.IR dir ]
.RB [ \-\-options
.IR file ]
.RI [ options ]
.I command
.RI [ args ]
@end ifset
@end ifset
@c End gpg2 hack stuff
@mansect description
@command{@gpgname} is the OpenPGP part of the GNU Privacy Guard (GnuPG). It
is a tool to provide digital encryption and signing services using the
OpenPGP standard. @command{@gpgname} features complete key management and
all the bells and whistles you would expect from a full OpenPGP
implementation.
There are two main versions of GnuPG: GnuPG 1.x and GnuPG 2.x. GnuPG
2.x supports modern encryption algorithms and thus should be preferred
over GnuPG 1.x. You only need to use GnuPG 1.x if your platform
doesn't support GnuPG 2.x, or you need support for some features that
GnuPG 2.x has deprecated, e.g., decrypting data created with PGP-2
keys.
@ifclear gpgtwohack
If you are looking for version 1 of GnuPG, you may find that version
installed under the name @command{gpg1}.
@end ifclear
@ifset gpgtwohack
In contrast to the standalone command @command{gpg} from GnuPG 1.x,
the 2.x version is commonly installed under the name
@command{@gpgname}.
@end ifset
@manpause
@xref{Option Index}, for an index to @command{@gpgname}'s commands and options.
@mancont
@menu
* GPG Commands:: List of all commands.
* GPG Options:: List of all options.
* GPG Configuration:: Configuration files.
* GPG Examples:: Some usage examples.
Developer information:
* Unattended Usage of GPG:: Using @command{gpg} from other programs.
@end menu
@c * GPG Protocol:: The protocol the server mode uses.
@c *******************************************
@c *************** ****************
@c *************** COMMANDS ****************
@c *************** ****************
@c *******************************************
@mansect commands
@node GPG Commands
@section Commands
Commands are not distinguished from options except for the fact that
only one command is allowed. Generally speaking, irrelevant options
are silently ignored, and may not be checked for correctness.
@command{@gpgname} may be run with no commands. In this case it will
perform a reasonable action depending on the type of file it is given
as input (an encrypted message is decrypted, a signature is verified,
a file containing keys is listed, etc.).
@menu
* General GPG Commands:: Commands not specific to the functionality.
* Operational GPG Commands:: Commands to select the type of operation.
* OpenPGP Key Management:: How to manage your keys.
@end menu
@c *******************************************
@c ********** GENERAL COMMANDS *************
@c *******************************************
@node General GPG Commands
@subsection Commands not specific to the function
@table @gnupgtabopt
@item --version
@opindex version
Print the program version and licensing information. Note that you
cannot abbreviate this command.
@item --help
@itemx -h
@opindex help
Print a usage message summarizing the most useful command-line options.
Note that you cannot arbitrarily abbreviate this command
(though you can use its short form @option{-h}).
@item --warranty
@opindex warranty
Print warranty information.
@item --dump-options
@opindex dump-options
Print a list of all available options and commands. Note that you cannot
abbreviate this command.
@end table
@c *******************************************
@c ******** OPERATIONAL COMMANDS ***********
@c *******************************************
@node Operational GPG Commands
@subsection Commands to select the type of operation
@table @gnupgtabopt
@item --sign
@itemx -s
@opindex sign
Sign a message. This command may be combined with @option{--encrypt}
(to sign and encrypt a message), @option{--symmetric} (to sign and
symmetrically encrypt a message), or both @option{--encrypt} and
@option{--symmetric} (to sign and encrypt a message that can be
decrypted using a secret key or a passphrase). The signing key is
chosen by default or can be set explicitly using the
@option{--local-user} and @option{--default-key} options.
@item --clear-sign
@opindex clear-sign
@itemx --clearsign
@opindex clearsign
Make a cleartext signature. The content in a cleartext signature is
readable without any special software. OpenPGP software is only needed
to verify the signature. cleartext signatures may modify end-of-line
whitespace for platform independence and are not intended to be
reversible. The signing key is chosen by default or can be set
explicitly using the @option{--local-user} and @option{--default-key}
options.
@item --detach-sign
@itemx -b
@opindex detach-sign
Make a detached signature.
@item --encrypt
@itemx -e
@opindex encrypt
Encrypt data to one or more public keys. This command may be combined
with @option{--sign} (to sign and encrypt a message),
@option{--symmetric} (to encrypt a message that can decrypted using a
secret key or a passphrase), or @option{--sign} and
@option{--symmetric} together (for a signed message that can be
decrypted using a secret key or a passphrase). @option{--recipient}
and related options specify which public keys to use for encryption.
@item --symmetric
@itemx -c
@opindex symmetric
Encrypt with a symmetric cipher using a passphrase. The default
symmetric cipher used is @value{GPGSYMENCALGO}, but may be chosen with the
@option{--cipher-algo} option. This command may be combined with
@option{--sign} (for a signed and symmetrically encrypted message),
@option{--encrypt} (for a message that may be decrypted via a secret key
or a passphrase), or @option{--sign} and @option{--encrypt} together
(for a signed message that may be decrypted via a secret key or a
passphrase). @command{@gpgname} caches the passphrase used for
symmetric encryption so that a decrypt operation may not require that
the user needs to enter the passphrase. The option
@option{--no-symkey-cache} can be used to disable this feature.
@item --store
@opindex store
Store only (make a simple literal data packet).
@item --decrypt
@itemx -d
@opindex decrypt
Decrypt the file given on the command line (or STDIN if no file
is specified) and write it to STDOUT (or the file specified with
@option{--output}). If the decrypted file is signed, the signature is also
verified. This command differs from the default operation, as it never
writes to the filename which is included in the file and it rejects
files that don't begin with an encrypted message.
@item --verify
@opindex verify
Assume that the first argument is a signed file and verify it without
generating any output. With no arguments, the signature packet is
read from STDIN. If only one argument is given, the specified file is
expected to include a complete signature.
With more than one argument, the first argument should specify a file
with a detached signature and the remaining files should contain the
signed data. To read the signed data from STDIN, use @samp{-} as the
second filename. For security reasons, a detached signature will not
read the signed material from STDIN if not explicitly specified.
Note: If the option @option{--batch} is not used, @command{@gpgname}
may assume that a single argument is a file with a detached signature,
and it will try to find a matching data file by stripping certain
suffixes. Using this historical feature to verify a detached
signature is strongly discouraged; you should always specify the data file
explicitly.
Note: When verifying a cleartext signature, @command{@gpgname} verifies
only what makes up the cleartext signed data and not any extra data
outside of the cleartext signature or the header lines directly following
the dash marker line. The option @code{--output} may be used to write
out the actual signed data, but there are other pitfalls with this
format as well. It is suggested to avoid cleartext signatures in
favor of detached signatures.
Note: Sometimes the use of the @command{gpgv} tool is easier than
using the full-fledged @command{gpg} with this option. @command{gpgv}
is designed to compare signed data against a list of trusted keys and
returns with success only for a good signature. It has its own manual
page.
@item --multifile
@opindex multifile
This modifies certain other commands to accept multiple files for
processing on the command line or read from STDIN with each filename on
a separate line. This allows for many files to be processed at
once. @option{--multifile} may currently be used along with
@option{--verify}, @option{--encrypt}, and @option{--decrypt}. Note that
@option{--multifile --verify} may not be used with detached signatures.
@item --verify-files
@opindex verify-files
Identical to @option{--multifile --verify}.
@item --encrypt-files
@opindex encrypt-files
Identical to @option{--multifile --encrypt}.
@item --decrypt-files
@opindex decrypt-files
Identical to @option{--multifile --decrypt}.
@item --list-keys
@itemx -k
@itemx --list-public-keys
@opindex list-keys
List the specified keys. If no keys are specified, then all keys from
the configured public keyrings are listed.
Never use the output of this command in scripts or other programs.
The output is intended only for humans and its format is likely to
change. The @option{--with-colons} option emits the output in a
stable, machine-parseable format, which is intended for use by scripts
and other programs.
@item --list-secret-keys
@itemx -K
@opindex list-secret-keys
List the specified secret keys. If no keys are specified, then all
known secret keys are listed. A @code{#} after the initial tags
@code{sec} or @code{ssb} means that the secret key or subkey is
currently not usable. We also say that this key has been taken
offline (for example, a primary key can be taken offline by exporting
the key using the command @option{--export-secret-subkeys}). A
@code{>} after these tags indicate that the key is stored on a
smartcard. See also @option{--list-keys}.
@item --check-signatures
@opindex check-signatures
@itemx --check-sigs
@opindex check-sigs
Same as @option{--list-keys}, but the key signatures are verified and
listed too. Note that for performance reasons the revocation status
of a signing key is not shown. This command has the same effect as
using @option{--list-keys} with @option{--with-sig-check}.
The status of the verification is indicated by a flag directly
following the "sig" tag (and thus before the flags described below. A
"!" indicates that the signature has been successfully verified, a "-"
denotes a bad signature and a "%" is used if an error occurred while
checking the signature (e.g. a non supported algorithm). Signatures
where the public key is not available are not listed; to see their
keyids the command @option{--list-sigs} can be used.
For each signature listed, there are several flags in between the
signature status flag and keyid. These flags give additional
information about each key signature. From left to right, they are
the numbers 1-3 for certificate check level (see
@option{--ask-cert-level}), "L" for a local or non-exportable
signature (see @option{--lsign-key}), "R" for a nonRevocable signature
(see the @option{--edit-key} command "nrsign"), "P" for a signature
that contains a policy URL (see @option{--cert-policy-url}), "N" for a
signature that contains a notation (see @option{--cert-notation}), "X"
for an eXpired signature (see @option{--ask-cert-expire}), and the
numbers 1-9 or "T" for 10 and above to indicate trust signature levels
(see the @option{--edit-key} command "tsign").
@item --locate-keys
+@itemx --locate-external-keys
@opindex locate-keys
+@opindex locate-external-keys
Locate the keys given as arguments. This command basically uses the
-same algorithm as used when locating keys for encryption or signing and
-may thus be used to see what keys @command{@gpgname} might use. In
-particular external methods as defined by @option{--auto-key-locate} may
-be used to locate a key. Only public keys are listed.
+same algorithm as used when locating keys for encryption or signing
+and may thus be used to see what keys @command{@gpgname} might use.
+In particular external methods as defined by
+@option{--auto-key-locate} may be used to locate a key. Only public
+keys are listed. The variant @option{--locate-external-keys} does not
+consider a locally existing key and can thus be used to force the
+refresh of a key via the defined external methods.
@item --show-keys
@opindex show-keys
This commands takes OpenPGP keys as input and prints information about
them in the same way the command @option{--list-keys} does for locally
stored key. In addition the list options @code{show-unusable-uids},
@code{show-unusable-subkeys}, @code{show-notations} and
@code{show-policy-urls} are also enabled. As usual for automated
processing, this command should be combined with the option
@option{--with-colons}.
@item --fingerprint
@opindex fingerprint
List all keys (or the specified ones) along with their
fingerprints. This is the same output as @option{--list-keys} but with
the additional output of a line with the fingerprint. May also be
combined with @option{--check-signatures}. If this
command is given twice, the fingerprints of all secondary keys are
listed too. This command also forces pretty printing of fingerprints
if the keyid format has been set to "none".
@item --list-packets
@opindex list-packets
List only the sequence of packets. This command is only useful for
debugging. When used with option @option{--verbose} the actual MPI
values are dumped and not only their lengths. Note that the output of
this command may change with new releases.
@item --edit-card
@opindex edit-card
@itemx --card-edit
@opindex card-edit
Present a menu to work with a smartcard. The subcommand "help" provides
an overview on available commands. For a detailed description, please
see the Card HOWTO at
https://gnupg.org/documentation/howtos.html#GnuPG-cardHOWTO .
@item --card-status
@opindex card-status
Show the content of the smart card.
@item --change-pin
@opindex change-pin
Present a menu to allow changing the PIN of a smartcard. This
functionality is also available as the subcommand "passwd" with the
@option{--edit-card} command.
@item --delete-keys @var{name}
@opindex delete-keys
Remove key from the public keyring. In batch mode either @option{--yes} is
required or the key must be specified by fingerprint. This is a
-safeguard against accidental deletion of multiple keys.
+safeguard against accidental deletion of multiple keys. If the
+exclamation mark syntax is used with the fingerprint of a subkey only
+that subkey is deleted; if the exclamation mark is used with the
+fingerprint of the primary key the entire public key is deleted.
@item --delete-secret-keys @var{name}
@opindex delete-secret-keys
Remove key from the secret keyring. In batch mode the key must be
specified by fingerprint. The option @option{--yes} can be used to
advice gpg-agent not to request a confirmation. This extra
pre-caution is done because @command{@gpgname} can't be sure that the
secret key (as controlled by gpg-agent) is only used for the given
-OpenPGP public key.
+OpenPGP public key. If the exclamation mark syntax is used with the
+fingerprint of a subkey only the secret part of that subkey is
+deleted; if the exclamation mark is used with the fingerprint of the
+primary key only the secret part of the primary key is deleted.
@item --delete-secret-and-public-key @var{name}
@opindex delete-secret-and-public-key
Same as @option{--delete-key}, but if a secret key exists, it will be
removed first. In batch mode the key must be specified by fingerprint.
The option @option{--yes} can be used to advice gpg-agent not to
request a confirmation.
@item --export
@opindex export
Either export all keys from all keyrings (default keyrings and those
registered via option @option{--keyring}), or if at least one name is given,
those of the given name. The exported keys are written to STDOUT or to the
file given with option @option{--output}. Use together with
@option{--armor} to mail those keys.
@item --send-keys @var{keyIDs}
@opindex send-keys
Similar to @option{--export} but sends the keys to a keyserver.
-Fingerprints may be used instead of key IDs. Option
-@option{--keyserver} must be used to give the name of this
-keyserver. Don't send your complete keyring to a keyserver --- select
+Fingerprints may be used instead of key IDs.
+Don't send your complete keyring to a keyserver --- select
only those keys which are new or changed by you. If no @var{keyIDs}
are given, @command{@gpgname} does nothing.
@item --export-secret-keys
@itemx --export-secret-subkeys
@opindex export-secret-keys
@opindex export-secret-subkeys
Same as @option{--export}, but exports the secret keys instead. The
exported keys are written to STDOUT or to the file given with option
@option{--output}. This command is often used along with the option
@option{--armor} to allow for easy printing of the key for paper backup;
however the external tool @command{paperkey} does a better job of
creating backups on paper. Note that exporting a secret key can be a
security risk if the exported keys are sent over an insecure channel.
The second form of the command has the special property to render the
secret part of the primary key useless; this is a GNU extension to
OpenPGP and other implementations can not be expected to successfully
import such a key. Its intended use is in generating a full key with
an additional signing subkey on a dedicated machine. This command
then exports the key without the primary key to the main machine.
GnuPG may ask you to enter the passphrase for the key. This is
required, because the internal protection method of the secret key is
different from the one specified by the OpenPGP protocol.
@item --export-ssh-key
@opindex export-ssh-key
This command is used to export a key in the OpenSSH public key format.
It requires the specification of one key by the usual means and
exports the latest valid subkey which has an authentication capability
to STDOUT or to the file given with option @option{--output}. That
output can directly be added to ssh's @file{authorized_key} file.
By specifying the key to export using a key ID or a fingerprint
suffixed with an exclamation mark (!), a specific subkey or the
primary key can be exported. This does not even require that the key
has the authentication capability flag set.
@item --import
@itemx --fast-import
@opindex import
Import/merge keys. This adds the given keys to the
keyring. The fast version is currently just a synonym.
There are a few other options which control how this command works.
Most notable here is the @option{--import-options merge-only} option
which does not insert new keys but does only the merging of new
signatures, user-IDs and subkeys.
@item --receive-keys @var{keyIDs}
@opindex receive-keys
@itemx --recv-keys @var{keyIDs}
@opindex recv-keys
-Import the keys with the given @var{keyIDs} from a keyserver. Option
-@option{--keyserver} must be used to give the name of this keyserver.
+Import the keys with the given @var{keyIDs} from a keyserver.
@item --refresh-keys
@opindex refresh-keys
Request updates from a keyserver for keys that already exist on the
local keyring. This is useful for updating a key with the latest
signatures, user IDs, etc. Calling this with no arguments will refresh
-the entire keyring. Option @option{--keyserver} must be used to give the
-name of the keyserver for all keys that do not have preferred keyservers
-set (see @option{--keyserver-options honor-keyserver-url}).
+the entire keyring.
@item --search-keys @var{names}
@opindex search-keys
-Search the keyserver for the given @var{names}. Multiple names given here will
-be joined together to create the search string for the keyserver.
-Option @option{--keyserver} must be used to give the name of this
-keyserver. Keyservers that support different search methods allow using
-the syntax specified in "How to specify a user ID" below. Note that
-different keyserver types support different search methods. Currently
-only LDAP supports them all.
+Search the keyserver for the given @var{names}. Multiple names given
+here will be joined together to create the search string for the
+keyserver. Note that keyservers search for @var{names} in a different
+and simpler way than gpg does. The best choice is to use a mail
+address. Due to data privacy reasons keyservers may even not even
+allow searching by user id or mail address and thus may only return
+results when being used with the @option{--recv-key} command to
+search by key fingerprint or keyid.
@item --fetch-keys @var{URIs}
@opindex fetch-keys
Retrieve keys located at the specified @var{URIs}. Note that different
installations of GnuPG may support different protocols (HTTP, FTP,
LDAP, etc.). When using HTTPS the system provided root certificates
are used by this command.
@item --update-trustdb
@opindex update-trustdb
Do trust database maintenance. This command iterates over all keys and
builds the Web of Trust. This is an interactive command because it may
have to ask for the "ownertrust" values for keys. The user has to give
an estimation of how far she trusts the owner of the displayed key to
correctly certify (sign) other keys. GnuPG only asks for the ownertrust
value if it has not yet been assigned to a key. Using the
@option{--edit-key} menu, the assigned value can be changed at any time.
@item --check-trustdb
@opindex check-trustdb
Do trust database maintenance without user interaction. From time to
time the trust database must be updated so that expired keys or
signatures and the resulting changes in the Web of Trust can be
tracked. Normally, GnuPG will calculate when this is required and do it
automatically unless @option{--no-auto-check-trustdb} is set. This
command can be used to force a trust database check at any time. The
processing is identical to that of @option{--update-trustdb} but it
skips keys with a not yet defined "ownertrust".
For use with cron jobs, this command can be used together with
@option{--batch} in which case the trust database check is done only if
a check is needed. To force a run even in batch mode add the option
@option{--yes}.
@anchor{option --export-ownertrust}
@item --export-ownertrust
@opindex export-ownertrust
Send the ownertrust values to STDOUT. This is useful for backup purposes
as these values are the only ones which can't be re-created from a
corrupted trustdb. Example:
@c man:.RS
@example
@gpgname{} --export-ownertrust > otrust.txt
@end example
@c man:.RE
@item --import-ownertrust
@opindex import-ownertrust
Update the trustdb with the ownertrust values stored in @code{files} (or
STDIN if not given); existing values will be overwritten. In case of a
severely damaged trustdb and if you have a recent backup of the
ownertrust values (e.g. in the file @file{otrust.txt}), you may re-create
the trustdb using these commands:
@c man:.RS
@example
cd ~/.gnupg
rm trustdb.gpg
@gpgname{} --import-ownertrust < otrust.txt
@end example
@c man:.RE
@item --rebuild-keydb-caches
@opindex rebuild-keydb-caches
When updating from version 1.0.6 to 1.0.7 this command should be used
to create signature caches in the keyring. It might be handy in other
situations too.
@item --print-md @var{algo}
@itemx --print-mds
@opindex print-md
Print message digest of algorithm @var{algo} for all given files or STDIN.
With the second form (or a deprecated "*" for @var{algo}) digests for all
available algorithms are printed.
@item --gen-random @var{0|1|2} @var{count}
@opindex gen-random
Emit @var{count} random bytes of the given quality level 0, 1 or 2. If
@var{count} is not given or zero, an endless sequence of random bytes
will be emitted. If used with @option{--armor} the output will be
base64 encoded. PLEASE, don't use this command unless you know what
you are doing; it may remove precious entropy from the system!
@item --gen-prime @var{mode} @var{bits}
@opindex gen-prime
Use the source, Luke :-). The output format is subject to change
with ant release.
@item --enarmor
@itemx --dearmor
@opindex enarmor
@opindex dearmor
Pack or unpack an arbitrary input into/from an OpenPGP ASCII armor.
This is a GnuPG extension to OpenPGP and in general not very useful.
@item --tofu-policy @{auto|good|unknown|bad|ask@} @var{keys}
@opindex tofu-policy
Set the TOFU policy for all the bindings associated with the specified
@var{keys}. For more information about the meaning of the policies,
@pxref{trust-model-tofu}. The @var{keys} may be specified either by their
fingerprint (preferred) or their keyid.
@c @item --server
@c @opindex server
@c Run gpg in server mode. This feature is not yet ready for use and
@c thus not documented.
@end table
@c ********************************************
@c ******* KEY MANAGEMENT COMMANDS **********
@c ********************************************
@node OpenPGP Key Management
@subsection How to manage your keys
This section explains the main commands for key management.
@table @gnupgtabopt
@item --quick-generate-key @var{user-id} [@var{algo} [@var{usage} [@var{expire}]]]
@itemx --quick-gen-key
@opindex quick-generate-key
@opindex quick-gen-key
This is a simple command to generate a standard key with one user id.
In contrast to @option{--generate-key} the key is generated directly
without the need to answer a bunch of prompts. Unless the option
@option{--yes} is given, the key creation will be canceled if the
given user id already exists in the keyring.
If invoked directly on the console without any special options an
answer to a ``Continue?'' style confirmation prompt is required. In
case the user id already exists in the keyring a second prompt to
force the creation of the key will show up.
If @var{algo} or @var{usage} are given, only the primary key is
created and no prompts are shown. To specify an expiration date but
still create a primary and subkey use ``default'' or
``future-default'' for @var{algo} and ``default'' for @var{usage}.
For a description of these optional arguments see the command
@code{--quick-add-key}. The @var{usage} accepts also the value
``cert'' which can be used to create a certification only primary key;
the default is to a create certification and signing key.
The @var{expire} argument can be used to specify an expiration date
for the key. Several formats are supported; commonly the ISO formats
``YYYY-MM-DD'' or ``YYYYMMDDThhmmss'' are used. To make the key
expire in N seconds, N days, N weeks, N months, or N years use
``seconds=N'', ``Nd'', ``Nw'', ``Nm'', or ``Ny'' respectively. Not
specifying a value, or using ``-'' results in a key expiring in a
reasonable default interval. The values ``never'', ``none'' can be
used for no expiration date.
If this command is used with @option{--batch},
@option{--pinentry-mode} has been set to @code{loopback}, and one of
the passphrase options (@option{--passphrase},
@option{--passphrase-fd}, or @option{passphrase-file}) is used, the
supplied passphrase is used for the new key and the agent does not ask
for it. To create a key without any protection @code{--passphrase ''}
may be used.
Note that it is possible to create a primary key and a subkey using
non-default algorithms by using ``default'' and changing the default
parameters using the option @option{--default-new-key-algo}.
@item --quick-set-expire @var{fpr} @var{expire} [*|@var{subfprs}]
@opindex quick-set-expire
With two arguments given, directly set the expiration time of the
primary key identified by @var{fpr} to @var{expire}. To remove the
expiration time @code{0} can be used. With three arguments and the
third given as an asterisk, the expiration time of all non-revoked and
not yet expired subkeys are set to @var{expire}. With more than two
arguments and a list of fingerprints given for @var{subfprs}, all
non-revoked subkeys matching these fingerprints are set to
@var{expire}.
@item --quick-add-key @var{fpr} [@var{algo} [@var{usage} [@var{expire}]]]
@opindex quick-add-key
Directly add a subkey to the key identified by the fingerprint
@var{fpr}. Without the optional arguments an encryption subkey is
added. If any of the arguments are given a more specific subkey is
added.
@var{algo} may be any of the supported algorithms or curve names
given in the format as used by key listings. To use the default
algorithm the string ``default'' or ``-'' can be used. Supported
algorithms are ``rsa'', ``dsa'', ``elg'', ``ed25519'', ``cv25519'',
and other ECC curves. For example the string ``rsa'' adds an RSA key
with the default key length; a string ``rsa4096'' requests that the
key length is 4096 bits. The string ``future-default'' is an alias
for the algorithm which will likely be used as default algorithm in
future versions of gpg. To list the supported ECC curves the command
@code{gpg --with-colons --list-config curve} can be used.
Depending on the given @var{algo} the subkey may either be an
encryption subkey or a signing subkey. If an algorithm is capable of
signing and encryption and such a subkey is desired, a @var{usage}
string must be given. This string is either ``default'' or ``-'' to
keep the default or a comma delimited list (or space delimited list)
of keywords: ``sign'' for a signing subkey, ``auth'' for an
authentication subkey, and ``encr'' for an encryption subkey
(``encrypt'' can be used as alias for ``encr''). The valid
combinations depend on the algorithm.
The @var{expire} argument can be used to specify an expiration date
for the key. Several formats are supported; commonly the ISO formats
``YYYY-MM-DD'' or ``YYYYMMDDThhmmss'' are used. To make the key
expire in N seconds, N days, N weeks, N months, or N years use
``seconds=N'', ``Nd'', ``Nw'', ``Nm'', or ``Ny'' respectively. Not
specifying a value, or using ``-'' results in a key expiring in a
reasonable default interval. The values ``never'', ``none'' can be
used for no expiration date.
@item --generate-key
@opindex generate-key
@itemx --gen-key
@opindex gen-key
Generate a new key pair using the current default parameters. This is
the standard command to create a new key. In addition to the key a
revocation certificate is created and stored in the
@file{openpgp-revocs.d} directory below the GnuPG home directory.
@item --full-generate-key
@opindex full-generate-key
@itemx --full-gen-key
@opindex full-gen-key
Generate a new key pair with dialogs for all options. This is an
extended version of @option{--generate-key}.
There is also a feature which allows you to create keys in batch
mode. See the manual section ``Unattended key generation'' on how
to use this.
@item --generate-revocation @var{name}
@opindex generate-revocation
@itemx --gen-revoke @var{name}
@opindex gen-revoke
Generate a revocation certificate for the complete key. To only revoke
a subkey or a key signature, use the @option{--edit} command.
This command merely creates the revocation certificate so that it can
be used to revoke the key if that is ever needed. To actually revoke
a key the created revocation certificate needs to be merged with the
key to revoke. This is done by importing the revocation certificate
using the @option{--import} command. Then the revoked key needs to be
published, which is best done by sending the key to a keyserver
(command @option{--send-key}) and by exporting (@option{--export}) it
to a file which is then send to frequent communication partners.
@item --generate-designated-revocation @var{name}
@opindex generate-designated-revocation
@itemx --desig-revoke @var{name}
@opindex desig-revoke
Generate a designated revocation certificate for a key. This allows a
user (with the permission of the keyholder) to revoke someone else's
key.
@item --edit-key
@opindex edit-key
Present a menu which enables you to do most of the key management
related tasks. It expects the specification of a key on the command
line.
@c ******** Begin Edit-key Options **********
@table @asis
@item uid @var{n}
@opindex keyedit:uid
Toggle selection of user ID or photographic user ID with index @var{n}.
Use @code{*} to select all and @code{0} to deselect all.
@item key @var{n}
@opindex keyedit:key
Toggle selection of subkey with index @var{n} or key ID @var{n}.
Use @code{*} to select all and @code{0} to deselect all.
@item sign
@opindex keyedit:sign
Make a signature on key of user @code{name}. If the key is not yet
signed by the default user (or the users given with @option{-u}), the program
displays the information of the key again, together with its
fingerprint and asks whether it should be signed. This question is
repeated for all users specified with
@option{-u}.
@item lsign
@opindex keyedit:lsign
Same as "sign" but the signature is marked as non-exportable and will
therefore never be used by others. This may be used to make keys
valid only in the local environment.
@item nrsign
@opindex keyedit:nrsign
Same as "sign" but the signature is marked as non-revocable and can
therefore never be revoked.
@item tsign
@opindex keyedit:tsign
Make a trust signature. This is a signature that combines the notions
of certification (like a regular signature), and trust (like the
"trust" command). It is generally only useful in distinct communities
or groups. For more information please read the sections
``Trust Signature'' and ``Regular Expression'' in RFC-4880.
@end table
@c man:.RS
Note that "l" (for local / non-exportable), "nr" (for non-revocable,
and "t" (for trust) may be freely mixed and prefixed to "sign" to
create a signature of any type desired.
@c man:.RE
If the option @option{--only-sign-text-ids} is specified, then any
non-text based user ids (e.g., photo IDs) will not be selected for
signing.
@table @asis
@item delsig
@opindex keyedit:delsig
Delete a signature. Note that it is not possible to retract a signature,
once it has been send to the public (i.e. to a keyserver). In that case
you better use @code{revsig}.
@item revsig
@opindex keyedit:revsig
Revoke a signature. For every signature which has been generated by
one of the secret keys, GnuPG asks whether a revocation certificate
should be generated.
@item check
@opindex keyedit:check
Check the signatures on all selected user IDs. With the extra
option @code{selfsig} only self-signatures are shown.
@item adduid
@opindex keyedit:adduid
Create an additional user ID.
@item addphoto
@opindex keyedit:addphoto
Create a photographic user ID. This will prompt for a JPEG file that
will be embedded into the user ID. Note that a very large JPEG will make
for a very large key. Also note that some programs will display your
JPEG unchanged (GnuPG), and some programs will scale it to fit in a
dialog box (PGP).
@item showphoto
@opindex keyedit:showphoto
Display the selected photographic user ID.
@item deluid
@opindex keyedit:deluid
Delete a user ID or photographic user ID. Note that it is not
possible to retract a user id, once it has been send to the public
(i.e. to a keyserver). In that case you better use @code{revuid}.
@item revuid
@opindex keyedit:revuid
Revoke a user ID or photographic user ID.
@item primary
@opindex keyedit:primary
Flag the current user id as the primary one, removes the primary user
id flag from all other user ids and sets the timestamp of all affected
self-signatures one second ahead. Note that setting a photo user ID
as primary makes it primary over other photo user IDs, and setting a
regular user ID as primary makes it primary over other regular user
IDs.
@item keyserver
@opindex keyedit:keyserver
Set a preferred keyserver for the specified user ID(s). This allows
other users to know where you prefer they get your key from. See
@option{--keyserver-options honor-keyserver-url} for more on how this
works. Setting a value of "none" removes an existing preferred
keyserver.
@item notation
@opindex keyedit:notation
Set a name=value notation for the specified user ID(s). See
@option{--cert-notation} for more on how this works. Setting a value of
"none" removes all notations, setting a notation prefixed with a minus
sign (-) removes that notation, and setting a notation name (without the
=value) prefixed with a minus sign removes all notations with that name.
@item pref
@opindex keyedit:pref
List preferences from the selected user ID. This shows the actual
preferences, without including any implied preferences.
@item showpref
@opindex keyedit:showpref
More verbose preferences listing for the selected user ID. This shows
the preferences in effect by including the implied preferences of 3DES
(cipher), SHA-1 (digest), and Uncompressed (compression) if they are
not already included in the preference list. In addition, the
preferred keyserver and signature notations (if any) are shown.
@item setpref @var{string}
@opindex keyedit:setpref
Set the list of user ID preferences to @var{string} for all (or just
the selected) user IDs. Calling setpref with no arguments sets the
preference list to the default (either built-in or set via
@option{--default-preference-list}), and calling setpref with "none"
as the argument sets an empty preference list. Use @command{@gpgname
--version} to get a list of available algorithms. Note that while you
can change the preferences on an attribute user ID (aka "photo ID"),
GnuPG does not select keys via attribute user IDs so these preferences
will not be used by GnuPG.
When setting preferences, you should list the algorithms in the order
which you'd like to see them used by someone else when encrypting a
message to your key. If you don't include 3DES, it will be
automatically added at the end. Note that there are many factors that
go into choosing an algorithm (for example, your key may not be the
only recipient), and so the remote OpenPGP application being used to
send to you may or may not follow your exact chosen order for a given
message. It will, however, only choose an algorithm that is present
on the preference list of every recipient key. See also the
INTEROPERABILITY WITH OTHER OPENPGP PROGRAMS section below.
@item addkey
@opindex keyedit:addkey
Add a subkey to this key.
@item addcardkey
@opindex keyedit:addcardkey
Generate a subkey on a card and add it to this key.
@item keytocard
@opindex keyedit:keytocard
Transfer the selected secret subkey (or the primary key if no subkey
has been selected) to a smartcard. The secret key in the keyring will
be replaced by a stub if the key could be stored successfully on the
card and you use the save command later. Only certain key types may be
transferred to the card. A sub menu allows you to select on what card
to store the key. Note that it is not possible to get that key back
from the card - if the card gets broken your secret key will be lost
unless you have a backup somewhere.
@item bkuptocard @var{file}
@opindex keyedit:bkuptocard
Restore the given @var{file} to a card. This command may be used to restore a
backup key (as generated during card initialization) to a new card. In
almost all cases this will be the encryption key. You should use this
command only with the corresponding public key and make sure that the
file given as argument is indeed the backup to restore. You should then
select 2 to restore as encryption key. You will first be asked to enter
the passphrase of the backup key and then for the Admin PIN of the card.
@item delkey
@opindex keyedit:delkey
Remove a subkey (secondary key). Note that it is not possible to retract
a subkey, once it has been send to the public (i.e. to a keyserver). In
that case you better use @code{revkey}. Also note that this only
deletes the public part of a key.
@item revkey
@opindex keyedit:revkey
Revoke a subkey.
@item expire
@opindex keyedit:expire
Change the key or subkey expiration time. If a subkey is selected, the
expiration time of this subkey will be changed. With no selection, the
key expiration of the primary key is changed.
@item trust
@opindex keyedit:trust
Change the owner trust value for the key. This updates the trust-db
immediately and no save is required.
@item disable
@itemx enable
@opindex keyedit:disable
@opindex keyedit:enable
Disable or enable an entire key. A disabled key can not normally be
used for encryption.
@item addrevoker
@opindex keyedit:addrevoker
Add a designated revoker to the key. This takes one optional argument:
"sensitive". If a designated revoker is marked as sensitive, it will
not be exported by default (see export-options).
@item passwd
@opindex keyedit:passwd
Change the passphrase of the secret key.
@item toggle
@opindex keyedit:toggle
This is dummy command which exists only for backward compatibility.
@item clean
@opindex keyedit:clean
Compact (by removing all signatures except the selfsig) any user ID
that is no longer usable (e.g. revoked, or expired). Then, remove any
signatures that are not usable by the trust calculations.
Specifically, this removes any signature that does not validate, any
signature that is superseded by a later signature, revoked signatures,
and signatures issued by keys that are not present on the keyring.
@item minimize
@opindex keyedit:minimize
Make the key as small as possible. This removes all signatures from
each user ID except for the most recent self-signature.
@item change-usage
@opindex keyedit:change-usage
Change the usage flags (capabilities) of the primary key or of
subkeys. These usage flags (e.g. Certify, Sign, Authenticate,
Encrypt) are set during key creation. Sometimes it is useful to
have the opportunity to change them (for example to add
Authenticate) after they have been created. Please take care when
doing this; the allowed usage flags depend on the key algorithm.
@item cross-certify
@opindex keyedit:cross-certify
Add cross-certification signatures to signing subkeys that may not
currently have them. Cross-certification signatures protect against a
subtle attack against signing subkeys. See
@option{--require-cross-certification}. All new keys generated have
this signature by default, so this command is only useful to bring
older keys up to date.
@item save
@opindex keyedit:save
Save all changes to the keyrings and quit.
@item quit
@opindex keyedit:quit
Quit the program without updating the
keyrings.
@end table
@c man:.RS
The listing shows you the key with its secondary keys and all user
IDs. The primary user ID is indicated by a dot, and selected keys or
user IDs are indicated by an asterisk. The trust
value is displayed with the primary key: "trust" is the assigned owner
trust and "validity" is the calculated validity of the key. Validity
values are also displayed for all user IDs.
For possible values of trust, @pxref{trust-values}.
@c man:.RE
@c ******** End Edit-key Options **********
@item --sign-key @var{name}
@opindex sign-key
Signs a public key with your secret key. This is a shortcut version of
the subcommand "sign" from @option{--edit}.
@item --lsign-key @var{name}
@opindex lsign-key
Signs a public key with your secret key but marks it as
non-exportable. This is a shortcut version of the subcommand "lsign"
from @option{--edit-key}.
@item --quick-sign-key @var{fpr} [@var{names}]
@itemx --quick-lsign-key @var{fpr} [@var{names}]
@opindex quick-sign-key
@opindex quick-lsign-key
Directly sign a key from the passphrase without any further user
interaction. The @var{fpr} must be the verified primary fingerprint
of a key in the local keyring. If no @var{names} are given, all
useful user ids are signed; with given [@var{names}] only useful user
ids matching one of theses names are signed. By default, or if a name
is prefixed with a '*', a case insensitive substring match is used.
If a name is prefixed with a '=' a case sensitive exact match is done.
The command @option{--quick-lsign-key} marks the signatures as
non-exportable. If such a non-exportable signature already exists the
@option{--quick-sign-key} turns it into a exportable signature.
This command uses reasonable defaults and thus does not provide the
full flexibility of the "sign" subcommand from @option{--edit-key}.
Its intended use is to help unattended key signing by utilizing a list
of verified fingerprints.
@item --quick-add-uid @var{user-id} @var{new-user-id}
@opindex quick-add-uid
This command adds a new user id to an existing key. In contrast to
the interactive sub-command @code{adduid} of @option{--edit-key} the
@var{new-user-id} is added verbatim with only leading and trailing
white space removed, it is expected to be UTF-8 encoded, and no checks
on its form are applied.
@item --quick-revoke-uid @var{user-id} @var{user-id-to-revoke}
@opindex quick-revoke-uid
This command revokes a user ID on an existing key. It cannot be used
to revoke the last user ID on key (some non-revoked user ID must
remain), with revocation reason ``User ID is no longer valid''. If
you want to specify a different revocation reason, or to supply
supplementary revocation text, you should use the interactive
sub-command @code{revuid} of @option{--edit-key}.
@item --quick-set-primary-uid @var{user-id} @var{primary-user-id}
@opindex quick-set-primary-uid
This command sets or updates the primary user ID flag on an existing
key. @var{user-id} specifies the key and @var{primary-user-id} the
user ID which shall be flagged as the primary user ID. The primary
user ID flag is removed from all other user ids and the timestamp of
all affected self-signatures is set one second ahead.
@item --change-passphrase @var{user-id}
@opindex change-passphrase
@itemx --passwd @var{user-id}
@opindex passwd
Change the passphrase of the secret key belonging to the certificate
specified as @var{user-id}. This is a shortcut for the sub-command
@code{passwd} of the edit key menu. When using together with the
option @option{--dry-run} this will not actually change the passphrase
but check that the current passphrase is correct.
@end table
@c *******************************************
@c *************** ****************
@c *************** OPTIONS ****************
@c *************** ****************
@c *******************************************
@mansect options
@node GPG Options
@section Option Summary
@command{@gpgname} features a bunch of options to control the exact
behaviour and to change the default configuration.
@menu
* GPG Configuration Options:: How to change the configuration.
* GPG Key related Options:: Key related options.
* GPG Input and Output:: Input and Output.
* OpenPGP Options:: OpenPGP protocol specific options.
* Compliance Options:: Compliance options.
* GPG Esoteric Options:: Doing things one usually doesn't want to do.
* Deprecated Options:: Deprecated options.
@end menu
Long options can be put in an options file (default
"~/.gnupg/gpg.conf"). Short option names will not work - for example,
"armor" is a valid option for the options file, while "a" is not. Do not
write the 2 dashes, but simply the name of the option and any required
arguments. Lines with a hash ('#') as the first non-white-space
character are ignored. Commands may be put in this file too, but that is
not generally useful as the command will execute automatically with
every execution of gpg.
Please remember that option parsing stops as soon as a non-option is
encountered, you can explicitly stop parsing by using the special option
@option{--}.
@c *******************************************
@c ******** CONFIGURATION OPTIONS **********
@c *******************************************
@node GPG Configuration Options
@subsection How to change the configuration
These options are used to change the configuration and are usually found
in the option file.
@table @gnupgtabopt
@item --default-key @var{name}
@opindex default-key
Use @var{name} as the default key to sign with. If this option is not
used, the default key is the first key found in the secret keyring.
Note that @option{-u} or @option{--local-user} overrides this option.
This option may be given multiple times. In this case, the last key
for which a secret key is available is used. If there is no secret
key available for any of the specified values, GnuPG will not emit an
error message but continue as if this option wasn't given.
@item --default-recipient @var{name}
@opindex default-recipient
Use @var{name} as default recipient if option @option{--recipient} is
not used and don't ask if this is a valid one. @var{name} must be
non-empty.
@item --default-recipient-self
@opindex default-recipient-self
Use the default key as default recipient if option @option{--recipient} is not
used and don't ask if this is a valid one. The default key is the first
one from the secret keyring or the one set with @option{--default-key}.
@item --no-default-recipient
@opindex no-default-recipient
Reset @option{--default-recipient} and @option{--default-recipient-self}.
@item -v, --verbose
@opindex verbose
Give more information during processing. If used
twice, the input data is listed in detail.
@item --no-verbose
@opindex no-verbose
Reset verbose level to 0.
@item -q, --quiet
@opindex quiet
Try to be as quiet as possible.
@item --batch
@itemx --no-batch
@opindex batch
@opindex no-batch
Use batch mode. Never ask, do not allow interactive commands.
@option{--no-batch} disables this option. Note that even with a
filename given on the command line, gpg might still need to read from
STDIN (in particular if gpg figures that the input is a
detached signature and no data file has been specified). Thus if you
do not want to feed data via STDIN, you should connect STDIN to
g@file{/dev/null}.
It is highly recommended to use this option along with the options
@option{--status-fd} and @option{--with-colons} for any unattended use of
@command{gpg}.
@item --no-tty
@opindex no-tty
Make sure that the TTY (terminal) is never used for any output.
This option is needed in some cases because GnuPG sometimes prints
warnings to the TTY even if @option{--batch} is used.
@item --yes
@opindex yes
Assume "yes" on most questions.
@item --no
@opindex no
Assume "no" on most questions.
@item --list-options @var{parameters}
@opindex list-options
This is a space or comma delimited string that gives options used when
listing keys and signatures (that is, @option{--list-keys},
@option{--check-signatures}, @option{--list-public-keys},
@option{--list-secret-keys}, and the @option{--edit-key} functions).
Options can be prepended with a @option{no-} (after the two dashes) to
give the opposite meaning. The options are:
@table @asis
@item show-photos
@opindex list-options:show-photos
Causes @option{--list-keys}, @option{--check-signatures},
@option{--list-public-keys}, and @option{--list-secret-keys} to
display any photo IDs attached to the key. Defaults to no. See also
@option{--photo-viewer}. Does not work with @option{--with-colons}:
see @option{--attribute-fd} for the appropriate way to get photo data
for scripts and other frontends.
@item show-usage
@opindex list-options:show-usage
Show usage information for keys and subkeys in the standard key
listing. This is a list of letters indicating the allowed usage for a
key (@code{E}=encryption, @code{S}=signing, @code{C}=certification,
@code{A}=authentication). Defaults to yes.
@item show-policy-urls
@opindex list-options:show-policy-urls
Show policy URLs in the @option{--check-signatures}
listings. Defaults to no.
@item show-notations
@itemx show-std-notations
@itemx show-user-notations
@opindex list-options:show-notations
@opindex list-options:show-std-notations
@opindex list-options:show-user-notations
Show all, IETF standard, or user-defined signature notations in the
@option{--check-signatures} listings. Defaults to no.
@item show-keyserver-urls
@opindex list-options:show-keyserver-urls
Show any preferred keyserver URL in the
@option{--check-signatures} listings. Defaults to no.
@item show-uid-validity
@opindex list-options:show-uid-validity
Display the calculated validity of user IDs during key listings.
Defaults to yes.
@item show-unusable-uids
@opindex list-options:show-unusable-uids
Show revoked and expired user IDs in key listings. Defaults to no.
@item show-unusable-subkeys
@opindex list-options:show-unusable-subkeys
Show revoked and expired subkeys in key listings. Defaults to no.
@item show-keyring
@opindex list-options:show-keyring
Display the keyring name at the head of key listings to show which
keyring a given key resides on. Defaults to no.
@item show-sig-expire
@opindex list-options:show-sig-expire
Show signature expiration dates (if any) during
@option{--check-signatures} listings. Defaults to no.
@item show-sig-subpackets
@opindex list-options:show-sig-subpackets
Include signature subpackets in the key listing. This option can take an
optional argument list of the subpackets to list. If no argument is
passed, list all subpackets. Defaults to no. This option is only
meaningful when using @option{--with-colons} along with
@option{--check-signatures}.
@item show-only-fpr-mbox
@opindex list-options:show-only-fpr-mbox
- For each valid user-id which also has a valid mail address print
- only the fingerprint and the mail address.
+ For each user-id which has a valid mail address print
+ only the fingerprint followed by the mail address.
@end table
@item --verify-options @var{parameters}
@opindex verify-options
This is a space or comma delimited string that gives options used when
verifying signatures. Options can be prepended with a `no-' to give
the opposite meaning. The options are:
@table @asis
@item show-photos
@opindex verify-options:show-photos
Display any photo IDs present on the key that issued the signature.
Defaults to no. See also @option{--photo-viewer}.
@item show-policy-urls
@opindex verify-options:show-policy-urls
Show policy URLs in the signature being verified. Defaults to yes.
@item show-notations
@itemx show-std-notations
@itemx show-user-notations
@opindex verify-options:show-notations
@opindex verify-options:show-std-notations
@opindex verify-options:show-user-notations
Show all, IETF standard, or user-defined signature notations in the
signature being verified. Defaults to IETF standard.
@item show-keyserver-urls
@opindex verify-options:show-keyserver-urls
Show any preferred keyserver URL in the signature being verified.
Defaults to yes.
@item show-uid-validity
@opindex verify-options:show-uid-validity
Display the calculated validity of the user IDs on the key that issued
the signature. Defaults to yes.
@item show-unusable-uids
@opindex verify-options:show-unusable-uids
Show revoked and expired user IDs during signature verification.
Defaults to no.
@item show-primary-uid-only
@opindex verify-options:show-primary-uid-only
Show only the primary user ID during signature verification. That is
all the AKA lines as well as photo Ids are not shown with the signature
verification status.
@item pka-lookups
@opindex verify-options:pka-lookups
Enable PKA lookups to verify sender addresses. Note that PKA is based
on DNS, and so enabling this option may disclose information on when
and what signatures are verified or to whom data is encrypted. This
is similar to the "web bug" described for the @option{--auto-key-retrieve}
option.
@item pka-trust-increase
@opindex verify-options:pka-trust-increase
Raise the trust in a signature to full if the signature passes PKA
validation. This option is only meaningful if pka-lookups is set.
@end table
@item --enable-large-rsa
@itemx --disable-large-rsa
@opindex enable-large-rsa
@opindex disable-large-rsa
With --generate-key and --batch, enable the creation of RSA secret keys as
large as 8192 bit. Note: 8192 bit is more than is generally
recommended. These large keys don't significantly improve security,
but they are more expensive to use, and their signatures and
certifications are larger. This option is only available if the
binary was build with large-secmem support.
@item --enable-dsa2
@itemx --disable-dsa2
@opindex enable-dsa2
@opindex disable-dsa2
Enable hash truncation for all DSA keys even for old DSA Keys up to
1024 bit. This is also the default with @option{--openpgp}. Note
that older versions of GnuPG also required this flag to allow the
generation of DSA larger than 1024 bit.
@item --photo-viewer @var{string}
@opindex photo-viewer
This is the command line that should be run to view a photo ID. "%i"
will be expanded to a filename containing the photo. "%I" does the
same, except the file will not be deleted once the viewer exits.
Other flags are "%k" for the key ID, "%K" for the long key ID, "%f"
for the key fingerprint, "%t" for the extension of the image type
(e.g. "jpg"), "%T" for the MIME type of the image (e.g. "image/jpeg"),
"%v" for the single-character calculated validity of the image being
viewed (e.g. "f"), "%V" for the calculated validity as a string (e.g.
"full"), "%U" for a base32 encoded hash of the user ID,
and "%%" for an actual percent sign. If neither %i or %I are present,
then the photo will be supplied to the viewer on standard input.
-The default viewer is "xloadimage -fork -quiet -title 'KeyID 0x%k'
-STDIN". Note that if your image viewer program is not secure, then
-executing it from GnuPG does not make it secure.
+On Unix the default viewer is
+@code{xloadimage -fork -quiet -title 'KeyID 0x%k' STDIN}
+with a fallback to
+@code{display -title 'KeyID 0x%k' %i}
+and finally to
+@code{xdg-open %i}.
+On Windows
+@code{!ShellExecute 400 %i} is used; here the command is a meta
+command to use that API call followed by a wait time in milliseconds
+which is used to give the viewer time to read the temporary image file
+before gpg deletes it again. Note that if your image viewer program
+is not secure, then executing it from gpg does not make it secure.
@item --exec-path @var{string}
@opindex exec-path
@efindex PATH
-Sets a list of directories to search for photo viewers and keyserver
-helpers. If not provided, keyserver helpers use the compiled-in
-default directory, and photo viewers use the @code{PATH} environment
-variable.
-Note, that on W32 system this value is ignored when searching for
-keyserver helpers.
+Sets a list of directories to search for photo viewers If not provided
+photo viewers use the @code{PATH} environment variable.
@item --keyring @var{file}
@opindex keyring
Add @var{file} to the current list of keyrings. If @var{file} begins
with a tilde and a slash, these are replaced by the $HOME directory. If
the filename does not contain a slash, it is assumed to be in the GnuPG
home directory ("~/.gnupg" if @option{--homedir} or $GNUPGHOME is not
used).
Note that this adds a keyring to the current list. If the intent is to
use the specified keyring alone, use @option{--keyring} along with
@option{--no-default-keyring}.
If the option @option{--no-keyring} has been used no keyrings will
be used at all.
@item --secret-keyring @var{file}
@opindex secret-keyring
This is an obsolete option and ignored. All secret keys are stored in
the @file{private-keys-v1.d} directory below the GnuPG home directory.
@item --primary-keyring @var{file}
@opindex primary-keyring
Designate @var{file} as the primary public keyring. This means that
newly imported keys (via @option{--import} or keyserver
@option{--recv-from}) will go to this keyring.
@item --trustdb-name @var{file}
@opindex trustdb-name
Use @var{file} instead of the default trustdb. If @var{file} begins
with a tilde and a slash, these are replaced by the $HOME directory. If
the filename does not contain a slash, it is assumed to be in the GnuPG
home directory (@file{~/.gnupg} if @option{--homedir} or $GNUPGHOME is
not used).
@include opt-homedir.texi
@item --display-charset @var{name}
@opindex display-charset
Set the name of the native character set. This is used to convert
some informational strings like user IDs to the proper UTF-8 encoding.
Note that this has nothing to do with the character set of data to be
encrypted or signed; GnuPG does not recode user-supplied data. If
this option is not used, the default character set is determined from
the current locale. A verbosity level of 3 shows the chosen set.
Valid values for @var{name} are:
@table @asis
@item iso-8859-1
@opindex display-charset:iso-8859-1
This is the Latin 1 set.
@item iso-8859-2
@opindex display-charset:iso-8859-2
The Latin 2 set.
@item iso-8859-15
@opindex display-charset:iso-8859-15
This is currently an alias for
the Latin 1 set.
@item koi8-r
@opindex display-charset:koi8-r
The usual Russian set (RFC-1489).
@item utf-8
@opindex display-charset:utf-8
Bypass all translations and assume
that the OS uses native UTF-8 encoding.
@end table
@item --utf8-strings
@itemx --no-utf8-strings
@opindex utf8-strings
Assume that command line arguments are given as UTF-8 strings. The
default (@option{--no-utf8-strings}) is to assume that arguments are
encoded in the character set as specified by
@option{--display-charset}. These options affect all following
arguments. Both options may be used multiple times.
@anchor{gpg-option --options}
@item --options @var{file}
@opindex options
Read options from @var{file} and do not try to read them from the
default options file in the homedir (see @option{--homedir}). This
option is ignored if used in an options file.
@item --no-options
@opindex no-options
Shortcut for @option{--options /dev/null}. This option is detected
before an attempt to open an option file. Using this option will also
prevent the creation of a @file{~/.gnupg} homedir.
@item -z @var{n}
@itemx --compress-level @var{n}
@itemx --bzip2-compress-level @var{n}
@opindex compress-level
@opindex bzip2-compress-level
Set compression level to @var{n} for the ZIP and ZLIB compression
algorithms. The default is to use the default compression level of zlib
(normally 6). @option{--bzip2-compress-level} sets the compression level
for the BZIP2 compression algorithm (defaulting to 6 as well). This is a
different option from @option{--compress-level} since BZIP2 uses a
significant amount of memory for each additional compression level.
@option{-z} sets both. A value of 0 for @var{n} disables compression.
@item --bzip2-decompress-lowmem
@opindex bzip2-decompress-lowmem
Use a different decompression method for BZIP2 compressed files. This
alternate method uses a bit more than half the memory, but also runs
at half the speed. This is useful under extreme low memory
circumstances when the file was originally compressed at a high
@option{--bzip2-compress-level}.
@item --mangle-dos-filenames
@itemx --no-mangle-dos-filenames
@opindex mangle-dos-filenames
@opindex no-mangle-dos-filenames
Older version of Windows cannot handle filenames with more than one
dot. @option{--mangle-dos-filenames} causes GnuPG to replace (rather
than add to) the extension of an output filename to avoid this
problem. This option is off by default and has no effect on non-Windows
platforms.
@item --ask-cert-level
@itemx --no-ask-cert-level
@opindex ask-cert-level
When making a key signature, prompt for a certification level. If this
option is not specified, the certification level used is set via
@option{--default-cert-level}. See @option{--default-cert-level} for
information on the specific levels and how they are
used. @option{--no-ask-cert-level} disables this option. This option
defaults to no.
@item --default-cert-level @var{n}
@opindex default-cert-level
The default to use for the check level when signing a key.
0 means you make no particular claim as to how carefully you verified
the key.
1 means you believe the key is owned by the person who claims to own
it but you could not, or did not verify the key at all. This is
useful for a "persona" verification, where you sign the key of a
pseudonymous user.
2 means you did casual verification of the key. For example, this
could mean that you verified the key fingerprint and checked the
user ID on the key against a photo ID.
3 means you did extensive verification of the key. For example, this
could mean that you verified the key fingerprint with the owner of the
key in person, and that you checked, by means of a hard to forge
document with a photo ID (such as a passport) that the name of the key
owner matches the name in the user ID on the key, and finally that you
verified (by exchange of email) that the email address on the key
belongs to the key owner.
Note that the examples given above for levels 2 and 3 are just that:
examples. In the end, it is up to you to decide just what "casual"
and "extensive" mean to you.
This option defaults to 0 (no particular claim).
@item --min-cert-level
@opindex min-cert-level
When building the trust database, treat any signatures with a
certification level below this as invalid. Defaults to 2, which
disregards level 1 signatures. Note that level 0 "no particular
claim" signatures are always accepted.
@item --trusted-key @var{long key ID}
@opindex trusted-key
Assume that the specified key (which must be given
as a full 8 byte key ID) is as trustworthy as one of
your own secret keys. This option is useful if you
don't want to keep your secret keys (or one of them)
online but still want to be able to check the validity of a given
recipient's or signator's key.
@item --trust-model @{pgp|classic|tofu|tofu+pgp|direct|always|auto@}
@opindex trust-model
Set what trust model GnuPG should follow. The models are:
@table @asis
@item pgp
@opindex trust-model:pgp
This is the Web of Trust combined with trust signatures as used in PGP
5.x and later. This is the default trust model when creating a new
trust database.
@item classic
@opindex trust-model:classic
This is the standard Web of Trust as introduced by PGP 2.
@item tofu
@opindex trust-model:tofu
@anchor{trust-model-tofu}
TOFU stands for Trust On First Use. In this trust model, the first
time a key is seen, it is memorized. If later another key with a
user id with the same email address is seen, both keys are marked as
suspect. In that case, the next time either is used, a warning is
displayed describing the conflict, why it might have occurred
(either the user generated a new key and failed to cross sign the
old and new keys, the key is forgery, or a man-in-the-middle attack
is being attempted), and the user is prompted to manually confirm
the validity of the key in question.
Because a potential attacker is able to control the email address
and thereby circumvent the conflict detection algorithm by using an
email address that is similar in appearance to a trusted email
address, whenever a message is verified, statistics about the number
of messages signed with the key are shown. In this way, a user can
easily identify attacks using fake keys for regular correspondents.
When compared with the Web of Trust, TOFU offers significantly
weaker security guarantees. In particular, TOFU only helps ensure
consistency (that is, that the binding between a key and email
address doesn't change). A major advantage of TOFU is that it
requires little maintenance to use correctly. To use the web of
trust properly, you need to actively sign keys and mark users as
trusted introducers. This is a time-consuming process and anecdotal
evidence suggests that even security-conscious users rarely take the
time to do this thoroughly and instead rely on an ad-hoc TOFU
process.
In the TOFU model, policies are associated with bindings between
keys and email addresses (which are extracted from user ids and
normalized). There are five policies, which can be set manually
using the @option{--tofu-policy} option. The default policy can be
set using the @option{--tofu-default-policy} option.
The TOFU policies are: @code{auto}, @code{good}, @code{unknown},
@code{bad} and @code{ask}. The @code{auto} policy is used by
default (unless overridden by @option{--tofu-default-policy}) and
marks a binding as marginally trusted. The @code{good},
@code{unknown} and @code{bad} policies mark a binding as fully
trusted, as having unknown trust or as having trust never,
respectively. The @code{unknown} policy is useful for just using
TOFU to detect conflicts, but to never assign positive trust to a
binding. The final policy, @code{ask} prompts the user to indicate
the binding's trust. If batch mode is enabled (or input is
inappropriate in the context), then the user is not prompted and the
@code{undefined} trust level is returned.
@item tofu+pgp
@opindex trust-model:tofu+pgp
This trust model combines TOFU with the Web of Trust. This is done
by computing the trust level for each model and then taking the
maximum trust level where the trust levels are ordered as follows:
@code{unknown < undefined < marginal < fully < ultimate < expired <
never}.
By setting @option{--tofu-default-policy=unknown}, this model can be
used to implement the web of trust with TOFU's conflict detection
algorithm, but without its assignment of positive trust values,
which some security-conscious users don't like.
@item direct
@opindex trust-model:direct
Key validity is set directly by the user and not calculated via the
Web of Trust. This model is solely based on the key and does
not distinguish user IDs. Note that when changing to another trust
model the trust values assigned to a key are transformed into
ownertrust values, which also indicate how you trust the owner of
the key to sign other keys.
@item always
@opindex trust-model:always
Skip key validation and assume that used keys are always fully
valid. You generally won't use this unless you are using some
external validation scheme. This option also suppresses the
"[uncertain]" tag printed with signature checks when there is no
evidence that the user ID is bound to the key. Note that this
trust model still does not allow the use of expired, revoked, or
disabled keys.
@item auto
@opindex trust-model:auto
Select the trust model depending on whatever the internal trust
database says. This is the default model if such a database already
exists. Note that a tofu trust model is not considered here and
must be enabled explicitly.
@end table
@item --auto-key-locate @var{mechanisms}
@itemx --no-auto-key-locate
@opindex auto-key-locate
GnuPG can automatically locate and retrieve keys as needed using this
option. This happens when encrypting to an email address (in the
"user@@example.com" form), and there are no "user@@example.com" keys
on the local keyring. This option takes any number of the mechanisms
listed below, in the order they are to be tried. Instead of listing
the mechanisms as comma delimited arguments, the option may also be
given several times to add more mechanism. The option
@option{--no-auto-key-locate} or the mechanism "clear" resets the
list. The default is "local,wkd".
@table @asis
@item cert
Locate a key using DNS CERT, as specified in RFC-4398.
@item pka
Locate a key using DNS PKA.
@item dane
Locate a key using DANE, as specified
in draft-ietf-dane-openpgpkey-05.txt.
@item wkd
Locate a key using the Web Key Directory protocol.
@item ldap
Using DNS Service Discovery, check the domain in question for any LDAP
keyservers to use. If this fails, attempt to locate the key using the
PGP Universal method of checking @samp{ldap://keys.(thedomain)}.
@item keyserver
- Locate a key using whatever keyserver is defined using the
- @option{--keyserver} option.
+ Locate a key using a keyserver.
@item keyserver-URL
- In addition, a keyserver URL as used in the @option{--keyserver} option
- may be used here to query that particular keyserver.
+ In addition, a keyserver URL as used in the @command{dirmngr}
+ configuration may be used here to query that particular keyserver.
@item local
Locate the key using the local keyrings. This mechanism allows the user to
select the order a local key lookup is done. Thus using
@samp{--auto-key-locate local} is identical to
@option{--no-auto-key-locate}.
@item nodefault
This flag disables the standard local key lookup, done before any of the
mechanisms defined by the @option{--auto-key-locate} are tried. The
position of this mechanism in the list does not matter. It is not
required if @code{local} is also used.
@item clear
Clear all defined mechanisms. This is useful to override
mechanisms given in a config file. Note that a @code{nodefault} in
@var{mechanisms} will also be cleared unless it is given after the
@code{clear}.
@end table
@item --auto-key-retrieve
@itemx --no-auto-key-retrieve
@opindex auto-key-retrieve
@opindex no-auto-key-retrieve
These options enable or disable the automatic retrieving of keys from
a keyserver when verifying signatures made by keys that are not on the
local keyring. The default is @option{--no-auto-key-retrieve}.
-If the method "wkd" is included in the list of methods given to
-@option{auto-key-locate}, the signer's user ID is part of the
-signature, and the option @option{--disable-signer-uid} is not used,
-the "wkd" method may also be used to retrieve a key.
+The order of methods tried to lookup the key is:
+
+1. If a preferred keyserver is specified in the signature and the
+option @option{honor-keyserver-url} is active (which is not the
+default), that keyserver is tried. Note that the creator of the
+signature uses the option @option{--sig-keyserver-url} to specify the
+preferred keyserver for data signatures.
+
+2. If the signature has the Signer's UID set (e.g. using
+@option{--sender} while creating the signature) a Web Key Directory
+(WKD) lookup is done. This is the default configuration but can be
+disabled by removing WKD from the auto-key-locate list or by using the
+option @option{--disable-signer-uid}.
+
+3. If the option @option{honor-pka-record} is active, the legacy PKA
+method is used.
+
+4. If any keyserver is configured and the Issuer Fingerprint is part
+of the signature (since GnuPG 2.1.16), the configured keyservers are
+tried.
Note that this option makes a "web bug" like behavior possible.
Keyserver or Web Key Directory operators can see which keys you
request, so by sending you a message signed by a brand new key (which
you naturally will not have on your local keyring), the operator can
tell both your IP address and the time when you verified the
signature.
@item --keyid-format @{none|short|0xshort|long|0xlong@}
@opindex keyid-format
Select how to display key IDs. "none" does not show the key ID at all
but shows the fingerprint in a separate line. "short" is the
traditional 8-character key ID. "long" is the more accurate (but less
convenient) 16-character key ID. Add an "0x" to either to include an
"0x" at the beginning of the key ID, as in 0x99242560. Note that this
option is ignored if the option @option{--with-colons} is used.
@item --keyserver @var{name}
@opindex keyserver
This option is deprecated - please use the @option{--keyserver} in
@file{dirmngr.conf} instead.
Use @var{name} as your keyserver. This is the server that
@option{--receive-keys}, @option{--send-keys}, and @option{--search-keys}
will communicate with to receive keys from, send keys to, and search for
keys on. The format of the @var{name} is a URI:
`scheme:[//]keyservername[:port]' The scheme is the type of keyserver:
"hkp" for the HTTP (or compatible) keyservers, "ldap" for the LDAP
keyservers, or "mailto" for the Graff email keyserver. Note that your
particular installation of GnuPG may have other keyserver types
available as well. Keyserver schemes are case-insensitive. After the
keyserver name, optional keyserver configuration options may be
provided. These are the same as the global @option{--keyserver-options}
from below, but apply only to this particular keyserver.
Most keyservers synchronize with each other, so there is generally no
need to send keys to more than one server. The keyserver
@code{hkp://keys.gnupg.net} uses round robin DNS to give a different
keyserver each time you use it.
@item --keyserver-options @{@var{name}=@var{value}@}
@opindex keyserver-options
This is a space or comma delimited string that gives options for the
keyserver. Options can be prefixed with a `no-' to give the opposite
meaning. Valid import-options or export-options may be used here as
well to apply to importing (@option{--recv-key}) or exporting
(@option{--send-key}) a key from a keyserver. While not all options
are available for all keyserver types, some common options are:
@table @asis
@item include-revoked
When searching for a key with @option{--search-keys}, include keys that
are marked on the keyserver as revoked. Note that not all keyservers
differentiate between revoked and unrevoked keys, and for such
keyservers this option is meaningless. Note also that most keyservers do
not have cryptographic verification of key revocations, and so turning
this option off may result in skipping keys that are incorrectly marked
as revoked.
@item include-disabled
When searching for a key with @option{--search-keys}, include keys that
are marked on the keyserver as disabled. Note that this option is not
used with HKP keyservers.
@item auto-key-retrieve
This is an obsolete alias for the option @option{auto-key-retrieve}.
Please do not use it; it will be removed in future versions..
@item honor-keyserver-url
When using @option{--refresh-keys}, if the key in question has a preferred
keyserver URL, then use that preferred keyserver to refresh the key
from. In addition, if auto-key-retrieve is set, and the signature
being verified has a preferred keyserver URL, then use that preferred
keyserver to fetch the key from. Note that this option introduces a
"web bug": The creator of the key can see when the keys is
refreshed. Thus this option is not enabled by default.
@item honor-pka-record
If @option{--auto-key-retrieve} is used, and the signature being
verified has a PKA record, then use the PKA information to fetch
the key. Defaults to "yes".
@item include-subkeys
When receiving a key, include subkeys as potential targets. Note that
this option is not used with HKP keyservers, as they do not support
retrieving keys by subkey id.
@item timeout
@itemx http-proxy=@var{value}
@itemx verbose
@itemx debug
@itemx check-cert
@item ca-cert-file
These options have no more function since GnuPG 2.1. Use the
@code{dirmngr} configuration options instead.
@end table
+The default list of options is: "self-sigs-only, import-clean,
+repair-keys, repair-pks-subkey-bug, export-attributes,
+honor-pka-record".
+
+
@item --completes-needed @var{n}
@opindex compliant-needed
Number of completely trusted users to introduce a new
key signer (defaults to 1).
@item --marginals-needed @var{n}
@opindex marginals-needed
Number of marginally trusted users to introduce a new
key signer (defaults to 3)
@item --tofu-default-policy @{auto|good|unknown|bad|ask@}
@opindex tofu-default-policy
The default TOFU policy (defaults to @code{auto}). For more
information about the meaning of this option, @pxref{trust-model-tofu}.
@item --max-cert-depth @var{n}
@opindex max-cert-depth
Maximum depth of a certification chain (default is 5).
@item --no-sig-cache
@opindex no-sig-cache
Do not cache the verification status of key signatures.
Caching gives a much better performance in key listings. However, if
you suspect that your public keyring is not safe against write
modifications, you can use this option to disable the caching. It
probably does not make sense to disable it because all kind of damage
can be done if someone else has write access to your public keyring.
@item --auto-check-trustdb
@itemx --no-auto-check-trustdb
@opindex auto-check-trustdb
If GnuPG feels that its information about the Web of Trust has to be
updated, it automatically runs the @option{--check-trustdb} command
internally. This may be a time consuming
process. @option{--no-auto-check-trustdb} disables this option.
@item --use-agent
@itemx --no-use-agent
@opindex use-agent
This is dummy option. @command{@gpgname} always requires the agent.
@item --gpg-agent-info
@opindex gpg-agent-info
This is dummy option. It has no effect when used with @command{@gpgname}.
@item --agent-program @var{file}
@opindex agent-program
Specify an agent program to be used for secret key operations. The
default value is determined by running @command{gpgconf} with the
option @option{--list-dirs}. Note that the pipe symbol (@code{|}) is
used for a regression test suite hack and may thus not be used in the
file name.
@item --dirmngr-program @var{file}
@opindex dirmngr-program
Specify a dirmngr program to be used for keyserver access. The
default value is @file{@value{BINDIR}/dirmngr}.
@item --disable-dirmngr
Entirely disable the use of the Dirmngr.
@item --no-autostart
@opindex no-autostart
Do not start the gpg-agent or the dirmngr if it has not yet been
started and its service is required. This option is mostly useful on
machines where the connection to gpg-agent has been redirected to
another machines. If dirmngr is required on the remote machine, it
may be started manually using @command{gpgconf --launch dirmngr}.
@item --lock-once
@opindex lock-once
Lock the databases the first time a lock is requested
and do not release the lock until the process
terminates.
@item --lock-multiple
@opindex lock-multiple
Release the locks every time a lock is no longer
needed. Use this to override a previous @option{--lock-once}
from a config file.
@item --lock-never
@opindex lock-never
Disable locking entirely. This option should be used only in very
special environments, where it can be assured that only one process
is accessing those files. A bootable floppy with a stand-alone
encryption system will probably use this. Improper usage of this
option may lead to data and key corruption.
@item --exit-on-status-write-error
@opindex exit-on-status-write-error
This option will cause write errors on the status FD to immediately
terminate the process. That should in fact be the default but it never
worked this way and thus we need an option to enable this, so that the
change won't break applications which close their end of a status fd
connected pipe too early. Using this option along with
@option{--enable-progress-filter} may be used to cleanly cancel long
running gpg operations.
@item --limit-card-insert-tries @var{n}
@opindex limit-card-insert-tries
With @var{n} greater than 0 the number of prompts asking to insert a
smartcard gets limited to N-1. Thus with a value of 1 gpg won't at
all ask to insert a card if none has been inserted at startup. This
option is useful in the configuration file in case an application does
not know about the smartcard support and waits ad infinitum for an
inserted card.
@item --no-random-seed-file
@opindex no-random-seed-file
GnuPG uses a file to store its internal random pool over invocations.
This makes random generation faster; however sometimes write operations
are not desired. This option can be used to achieve that with the cost of
slower random generation.
@item --no-greeting
@opindex no-greeting
Suppress the initial copyright message.
@item --no-secmem-warning
@opindex no-secmem-warning
Suppress the warning about "using insecure memory".
@item --no-permission-warning
@opindex permission-warning
Suppress the warning about unsafe file and home directory (@option{--homedir})
permissions. Note that the permission checks that GnuPG performs are
not intended to be authoritative, but rather they simply warn about
certain common permission problems. Do not assume that the lack of a
warning means that your system is secure.
Note that the warning for unsafe @option{--homedir} permissions cannot be
suppressed in the gpg.conf file, as this would allow an attacker to
place an unsafe gpg.conf file in place, and use this file to suppress
warnings about itself. The @option{--homedir} permissions warning may only be
suppressed on the command line.
@item --require-secmem
@itemx --no-require-secmem
@opindex require-secmem
Refuse to run if GnuPG cannot get secure memory. Defaults to no
(i.e. run, but give a warning).
@item --require-cross-certification
@itemx --no-require-cross-certification
@opindex require-cross-certification
When verifying a signature made from a subkey, ensure that the cross
certification "back signature" on the subkey is present and valid. This
protects against a subtle attack against subkeys that can sign.
Defaults to @option{--require-cross-certification} for
@command{@gpgname}.
@item --expert
@itemx --no-expert
@opindex expert
Allow the user to do certain nonsensical or "silly" things like
signing an expired or revoked key, or certain potentially incompatible
things like generating unusual key types. This also disables certain
warning messages about potentially incompatible actions. As the name
implies, this option is for experts only. If you don't fully
understand the implications of what it allows you to do, leave this
off. @option{--no-expert} disables this option.
@end table
@c *******************************************
@c ******** KEY RELATED OPTIONS ************
@c *******************************************
@node GPG Key related Options
@subsection Key related options
@table @gnupgtabopt
@item --recipient @var{name}
@itemx -r
@opindex recipient
Encrypt for user id @var{name}. If this option or
@option{--hidden-recipient} is not specified, GnuPG asks for the user-id
unless @option{--default-recipient} is given.
@item --hidden-recipient @var{name}
@itemx -R
@opindex hidden-recipient
Encrypt for user ID @var{name}, but hide the key ID of this user's
key. This option helps to hide the receiver of the message and is a
limited countermeasure against traffic analysis. If this option or
@option{--recipient} is not specified, GnuPG asks for the user ID unless
@option{--default-recipient} is given.
@item --recipient-file @var{file}
@itemx -f
@opindex recipient-file
This option is similar to @option{--recipient} except that it
encrypts to a key stored in the given file. @var{file} must be the
name of a file containing exactly one key. @command{@gpgname} assumes that
the key in this file is fully valid.
@item --hidden-recipient-file @var{file}
@itemx -F
@opindex hidden-recipient-file
This option is similar to @option{--hidden-recipient} except that it
encrypts to a key stored in the given file. @var{file} must be the
name of a file containing exactly one key. @command{@gpgname} assumes that
the key in this file is fully valid.
@item --encrypt-to @var{name}
@opindex encrypt-to
Same as @option{--recipient} but this one is intended for use in the
options file and may be used with your own user-id as an
"encrypt-to-self". These keys are only used when there are other
recipients given either by use of @option{--recipient} or by the asked
user id. No trust checking is performed for these user ids and even
disabled keys can be used.
@item --hidden-encrypt-to @var{name}
@opindex hidden-encrypt-to
Same as @option{--hidden-recipient} but this one is intended for use in the
options file and may be used with your own user-id as a hidden
"encrypt-to-self". These keys are only used when there are other
recipients given either by use of @option{--recipient} or by the asked user id.
No trust checking is performed for these user ids and even disabled
keys can be used.
@item --no-encrypt-to
@opindex no-encrypt-to
Disable the use of all @option{--encrypt-to} and
@option{--hidden-encrypt-to} keys.
@item --group @{@var{name}=@var{value}@}
@opindex group
Sets up a named group, which is similar to aliases in email programs.
Any time the group name is a recipient (@option{-r} or
@option{--recipient}), it will be expanded to the values
specified. Multiple groups with the same name are automatically merged
into a single group.
The values are @code{key IDs} or fingerprints, but any key description
is accepted. Note that a value with spaces in it will be treated as
two different values. Note also there is only one level of expansion
--- you cannot make an group that points to another group. When used
from the command line, it may be necessary to quote the argument to
this option to prevent the shell from treating it as multiple
arguments.
@item --ungroup @var{name}
@opindex ungroup
Remove a given entry from the @option{--group} list.
@item --no-groups
@opindex no-groups
Remove all entries from the @option{--group} list.
@item --local-user @var{name}
@itemx -u
@opindex local-user
Use @var{name} as the key to sign with. Note that this option overrides
@option{--default-key}.
@item --sender @var{mbox}
@opindex sender
This option has two purposes. @var{mbox} must either be a complete
user id with a proper mail address or just a mail address. When
creating a signature this option tells gpg the user id of a key used
to make a signature if the key was not directly specified by a user
id. When verifying a signature the @var{mbox} is used to restrict the
information printed by the TOFU code to matching user ids.
@item --try-secret-key @var{name}
@opindex try-secret-key
For hidden recipients GPG needs to know the keys to use for trial
decryption. The key set with @option{--default-key} is always tried
first, but this is often not sufficient. This option allows setting more
keys to be used for trial decryption. Although any valid user-id
specification may be used for @var{name} it makes sense to use at least
the long keyid to avoid ambiguities. Note that gpg-agent might pop up a
pinentry for a lot keys to do the trial decryption. If you want to stop
all further trial decryption you may use close-window button instead of
the cancel button.
@item --try-all-secrets
@opindex try-all-secrets
Don't look at the key ID as stored in the message but try all secret
keys in turn to find the right decryption key. This option forces the
behaviour as used by anonymous recipients (created by using
@option{--throw-keyids} or @option{--hidden-recipient}) and might come
handy in case where an encrypted message contains a bogus key ID.
@item --skip-hidden-recipients
@itemx --no-skip-hidden-recipients
@opindex skip-hidden-recipients
@opindex no-skip-hidden-recipients
During decryption skip all anonymous recipients. This option helps in
the case that people use the hidden recipients feature to hide their
own encrypt-to key from others. If one has many secret keys this
may lead to a major annoyance because all keys are tried in turn to
decrypt something which was not really intended for it. The drawback
of this option is that it is currently not possible to decrypt a
message which includes real anonymous recipients.
@end table
@c *******************************************
@c ******** INPUT AND OUTPUT ***************
@c *******************************************
@node GPG Input and Output
@subsection Input and Output
@table @gnupgtabopt
@item --armor
@itemx -a
@opindex armor
Create ASCII armored output. The default is to create the binary
OpenPGP format.
@item --no-armor
@opindex no-armor
Assume the input data is not in ASCII armored format.
@item --output @var{file}
@itemx -o @var{file}
@opindex output
Write output to @var{file}. To write to stdout use @code{-} as the
filename.
@item --max-output @var{n}
@opindex max-output
This option sets a limit on the number of bytes that will be generated
when processing a file. Since OpenPGP supports various levels of
compression, it is possible that the plaintext of a given message may be
significantly larger than the original OpenPGP message. While GnuPG
works properly with such messages, there is often a desire to set a
maximum file size that will be generated before processing is forced to
stop by the OS limits. Defaults to 0, which means "no limit".
@item --chunk-size @var{n}
@opindex chunk-size
The AEAD encryption mode encrypts the data in chunks so that a
receiving side can check for transmission errors or tampering at the
end of each chunk and does not need to delay this until all data has
been received. The used chunk size is 2^@var{n} byte. The lowest
allowed value for @var{n} is 6 (64 byte) and the largest is the
default of 27 which creates chunks not larger than 128 MiB.
@item --input-size-hint @var{n}
@opindex input-size-hint
This option can be used to tell GPG the size of the input data in
bytes. @var{n} must be a positive base-10 number. This option is
only useful if the input is not taken from a file. GPG may use this
hint to optimize its buffer allocation strategy. It is also used by
the @option{--status-fd} line ``PROGRESS'' to provide a value for
``total'' if that is not available by other means.
@item --key-origin @var{string}[,@var{url}]
@opindex key-origin
gpg can track the origin of a key. Certain origins are implicitly
known (e.g. keyserver, web key directory) and set. For a standard
import the origin of the keys imported can be set with this option.
To list the possible values use "help" for @var{string}. Some origins
can store an optional @var{url} argument. That URL can appended to
@var{string} after a comma.
@item --import-options @var{parameters}
@opindex import-options
This is a space or comma delimited string that gives options for
importing keys. Options can be prepended with a `no-' to give the
opposite meaning. The options are:
@table @asis
@item import-local-sigs
Allow importing key signatures marked as "local". This is not
generally useful unless a shared keyring scheme is being used.
Defaults to no.
@item keep-ownertrust
Normally possible still existing ownertrust values of a key are
cleared if a key is imported. This is in general desirable so that
a formerly deleted key does not automatically gain an ownertrust
values merely due to import. On the other hand it is sometimes
necessary to re-import a trusted set of keys again but keeping
already assigned ownertrust values. This can be achieved by using
this option.
@item repair-pks-subkey-bug
During import, attempt to repair the damage caused by the PKS keyserver
bug (pre version 0.9.6) that mangles keys with multiple subkeys. Note
that this cannot completely repair the damaged key as some crucial data
is removed by the keyserver, but it does at least give you back one
subkey. Defaults to no for regular @option{--import} and to yes for
keyserver @option{--receive-keys}.
@item import-show
@itemx show-only
Show a listing of the key as imported right before it is stored.
This can be combined with the option @option{--dry-run} to only look
at keys; the option @option{show-only} is a shortcut for this
combination. The command @option{--show-keys} is another shortcut
for this. Note that suffixes like '#' for "sec" and "sbb" lines
may or may not be printed.
@item import-export
Run the entire import code but instead of storing the key to the
local keyring write it to the output. The export options
@option{export-pka} and @option{export-dane} affect the output. This
option can be used to remove all invalid parts from a key without the
need to store it.
@item merge-only
During import, allow key updates to existing keys, but do not allow
any new keys to be imported. Defaults to no.
@item import-clean
After import, compact (remove all signatures except the
self-signature) any user IDs from the new key that are not usable.
Then, remove any signatures from the new key that are not usable.
This includes signatures that were issued by keys that are not present
on the keyring. This option is the same as running the @option{--edit-key}
command "clean" after import. Defaults to no.
@item import-drop-uids
Do not import any user ids or their binding signatures. This option
can be used to update only the subkeys or other non-user id related
information.
- @item repair-keys. After import, fix various problems with the
+ @item self-sigs-only
+ Accept only self-signatures while importing a key. All other
+ key-signatures are skipped at an early import stage. This option
+ can be used with @code{keyserver-options} to mitigate attempts to
+ flood a key with bogus signatures from a keyserver. The drawback is
+ that all other valid key-signatures, as required by the Web of Trust
+ are also not imported.
+
+ @item repair-keys
+ After import, fix various problems with the
keys. For example, this reorders signatures, and strips duplicate
signatures. Defaults to yes.
@item import-minimal
Import the smallest key possible. This removes all signatures except
the most recent self-signature on each user ID. This option is the
same as running the @option{--edit-key} command "minimize" after import.
Defaults to no.
@item restore
@itemx import-restore
Import in key restore mode. This imports all data which is usually
skipped during import; including all GnuPG specific data. All other
contradicting options are overridden.
@end table
@item --import-filter @{@var{name}=@var{expr}@}
@itemx --export-filter @{@var{name}=@var{expr}@}
@opindex import-filter
@opindex export-filter
These options define an import/export filter which are applied to the
imported/exported keyblock right before it will be stored/written.
@var{name} defines the type of filter to use, @var{expr} the
expression to evaluate. The option can be used several times which
then appends more expression to the same @var{name}.
@noindent
The available filter types are:
@table @asis
@item keep-uid
This filter will keep a user id packet and its dependent packets in
the keyblock if the expression evaluates to true.
@item drop-subkey
This filter drops the selected subkeys.
Currently only implemented for --export-filter.
@item drop-sig
This filter drops the selected key signatures on user ids.
Self-signatures are not considered.
Currently only implemented for --import-filter.
@end table
For the syntax of the expression see the chapter "FILTER EXPRESSIONS".
The property names for the expressions depend on the actual filter
type and are indicated in the following table.
The available properties are:
@table @asis
@item uid
A string with the user id. (keep-uid)
@item mbox
The addr-spec part of a user id with mailbox or the empty string.
(keep-uid)
@item key_algo
A number with the public key algorithm of a key or subkey packet.
(drop-subkey)
@item key_created
@itemx key_created_d
The first is the timestamp a public key or subkey packet was
created. The second is the same but given as an ISO string,
e.g. "2016-08-17". (drop-subkey)
@item primary
Boolean indicating whether the user id is the primary one. (keep-uid)
@item expired
Boolean indicating whether a user id (keep-uid), a key (drop-subkey), or a
signature (drop-sig) expired.
@item revoked
Boolean indicating whether a user id (keep-uid) or a key (drop-subkey) has
been revoked.
@item disabled
Boolean indicating whether a primary key is disabled. (not used)
@item secret
Boolean indicating whether a key or subkey is a secret one.
(drop-subkey)
@item usage
A string indicating the usage flags for the subkey, from the
sequence ``ecsa?''. For example, a subkey capable of just signing
and authentication would be an exact match for ``sa''. (drop-subkey)
@item sig_created
@itemx sig_created_d
The first is the timestamp a signature packet was created. The
second is the same but given as an ISO date string,
e.g. "2016-08-17". (drop-sig)
@item sig_algo
A number with the public key algorithm of a signature packet. (drop-sig)
@item sig_digest_algo
A number with the digest algorithm of a signature packet. (drop-sig)
@end table
@item --export-options @var{parameters}
@opindex export-options
This is a space or comma delimited string that gives options for
exporting keys. Options can be prepended with a `no-' to give the
opposite meaning. The options are:
@table @asis
@item export-local-sigs
Allow exporting key signatures marked as "local". This is not
generally useful unless a shared keyring scheme is being used.
Defaults to no.
@item export-attributes
Include attribute user IDs (photo IDs) while exporting. Not
including attribute user IDs is useful to export keys that are going
to be used by an OpenPGP program that does not accept attribute user
IDs. Defaults to yes.
@item export-sensitive-revkeys
Include designated revoker information that was marked as
"sensitive". Defaults to no.
@c Since GnuPG 2.1 gpg-agent manages the secret key and thus the
@c export-reset-subkey-passwd hack is not anymore justified. Such use
@c cases may be implemented using a specialized secret key export
@c tool.
@c @item export-reset-subkey-passwd
@c When using the @option{--export-secret-subkeys} command, this option resets
@c the passphrases for all exported subkeys to empty. This is useful
@c when the exported subkey is to be used on an unattended machine where
@c a passphrase doesn't necessarily make sense. Defaults to no.
@item backup
@itemx export-backup
Export for use as a backup. The exported data includes all data
which is needed to restore the key or keys later with GnuPG. The
format is basically the OpenPGP format but enhanced with GnuPG
specific data. All other contradicting options are overridden.
@item export-clean
Compact (remove all signatures from) user IDs on the key being
exported if the user IDs are not usable. Also, do not export any
signatures that are not usable. This includes signatures that were
issued by keys that are not present on the keyring. This option is
the same as running the @option{--edit-key} command "clean" before export
except that the local copy of the key is not modified. Defaults to
no.
@item export-minimal
Export the smallest key possible. This removes all signatures except the
most recent self-signature on each user ID. This option is the same as
running the @option{--edit-key} command "minimize" before export except
that the local copy of the key is not modified. Defaults to no.
@item export-drop-uids
Do no export any user id or attribute packets or their associates
signatures. Note that due to missing user ids the resulting output is
not strictly RFC-4880 compliant.
@item export-pka
Instead of outputting the key material output PKA records suitable
to put into DNS zone files. An ORIGIN line is printed before each
record to allow diverting the records to the corresponding zone file.
@item export-dane
Instead of outputting the key material output OpenPGP DANE records
suitable to put into DNS zone files. An ORIGIN line is printed before
each record to allow diverting the records to the corresponding zone
file.
@end table
@item --with-colons
@opindex with-colons
Print key listings delimited by colons. Note that the output will be
encoded in UTF-8 regardless of any @option{--display-charset} setting. This
format is useful when GnuPG is called from scripts and other programs
as it is easily machine parsed. The details of this format are
documented in the file @file{doc/DETAILS}, which is included in the GnuPG
source distribution.
@item --fixed-list-mode
@opindex fixed-list-mode
Do not merge primary user ID and primary key in @option{--with-colon}
listing mode and print all timestamps as seconds since 1970-01-01.
Since GnuPG 2.0.10, this mode is always used and thus this option is
obsolete; it does not harm to use it though.
@item --legacy-list-mode
@opindex legacy-list-mode
Revert to the pre-2.1 public key list mode. This only affects the
human readable output and not the machine interface
(i.e. @code{--with-colons}). Note that the legacy format does not
convey suitable information for elliptic curves.
@item --with-fingerprint
@opindex with-fingerprint
Same as the command @option{--fingerprint} but changes only the format
of the output and may be used together with another command.
@item --with-subkey-fingerprint
@opindex with-subkey-fingerprint
If a fingerprint is printed for the primary key, this option forces
printing of the fingerprint for all subkeys. This could also be
achieved by using the @option{--with-fingerprint} twice but by using
this option along with keyid-format "none" a compact fingerprint is
printed.
@item --with-icao-spelling
@opindex with-icao-spelling
Print the ICAO spelling of the fingerprint in addition to the hex digits.
@item --with-keygrip
@opindex with-keygrip
Include the keygrip in the key listings. In @code{--with-colons} mode
this is implicitly enable for secret keys.
@item --with-key-origin
@opindex with-key-origin
Include the locally held information on the origin and last update of
a key in a key listing. In @code{--with-colons} mode this is always
printed. This data is currently experimental and shall not be
considered part of the stable API.
@item --with-wkd-hash
@opindex with-wkd-hash
Print a Web Key Directory identifier along with each user ID in key
listings. This is an experimental feature and semantics may change.
@item --with-secret
@opindex with-secret
Include info about the presence of a secret key in public key listings
done with @code{--with-colons}.
@end table
@c *******************************************
@c ******** OPENPGP OPTIONS ****************
@c *******************************************
@node OpenPGP Options
@subsection OpenPGP protocol specific options
@table @gnupgtabopt
@item -t, --textmode
@itemx --no-textmode
@opindex textmode
Treat input files as text and store them in the OpenPGP canonical text
form with standard "CRLF" line endings. This also sets the necessary
flags to inform the recipient that the encrypted or signed data is text
and may need its line endings converted back to whatever the local
system uses. This option is useful when communicating between two
platforms that have different line ending conventions (UNIX-like to Mac,
Mac to Windows, etc). @option{--no-textmode} disables this option, and
is the default.
@item --force-v3-sigs
@itemx --no-force-v3-sigs
@item --force-v4-certs
@itemx --no-force-v4-certs
These options are obsolete and have no effect since GnuPG 2.1.
@item --force-aead
@opindex force-aead
Force the use of AEAD encryption over MDC encryption. AEAD is a
modern and faster way to do authenticated encryption than the old MDC
method. See also options @option{--aead-algo} and
@option{--chunk-size}.
As of now this option requires the use of option @option{--rfc4880bis}
to declare that a not yet standardized feature is used.
@item --force-mdc
@itemx --disable-mdc
@opindex force-mdc
@opindex disable-mdc
These options are obsolete and have no effect since GnuPG 2.2.8. The
MDC is always used unless the keys indicate that an AEAD algorithm can
be used in which case AEAD is used. But note: If the creation of a
legacy non-MDC message is exceptionally required, the option
@option{--rfc2440} allows for this.
@item --disable-signer-uid
@opindex disable-signer-uid
-By default the user ID of the signing key is embedded in the data
-signature. As of now this is only done if the signing key has been
-specified with @option{local-user} using a mail address. This
-information can be helpful for verifier to locate the key; see
-option @option{--auto-key-retrieve}.
+By default the user ID of the signing key is embedded in the data signature.
+As of now this is only done if the signing key has been specified with
+@option{local-user} using a mail address, or with @option{sender}. This
+information can be helpful for verifier to locate the key; see option
+@option{--auto-key-retrieve}.
@item --personal-cipher-preferences @var{string}
@opindex personal-cipher-preferences
Set the list of personal cipher preferences to @var{string}. Use
@command{@gpgname --version} to get a list of available algorithms,
and use @code{none} to set no preference at all. This allows the user
to safely override the algorithm chosen by the recipient key
preferences, as GPG will only select an algorithm that is usable by
all recipients. The most highly ranked cipher in this list is also
used for the @option{--symmetric} encryption command.
@item --personal-aead-preferences @var{string}
@opindex personal-aead-preferences
Set the list of personal AEAD preferences to @var{string}. Use
@command{@gpgname --version} to get a list of available algorithms,
and use @code{none} to set no preference at all. This allows the user
to safely override the algorithm chosen by the recipient key
preferences, as GPG will only select an algorithm that is usable by
all recipients. The most highly ranked cipher in this list is also
used for the @option{--symmetric} encryption command.
@item --personal-digest-preferences @var{string}
@opindex personal-digest-preferences
Set the list of personal digest preferences to @var{string}. Use
@command{@gpgname --version} to get a list of available algorithms,
and use @code{none} to set no preference at all. This allows the user
to safely override the algorithm chosen by the recipient key
preferences, as GPG will only select an algorithm that is usable by
all recipients. The most highly ranked digest algorithm in this list
is also used when signing without encryption
(e.g. @option{--clear-sign} or @option{--sign}).
@item --personal-compress-preferences @var{string}
@opindex personal-compress-preferences
Set the list of personal compression preferences to @var{string}.
Use @command{@gpgname --version} to get a list of available
algorithms, and use @code{none} to set no preference at all. This
allows the user to safely override the algorithm chosen by the
recipient key preferences, as GPG will only select an algorithm that
is usable by all recipients. The most highly ranked compression
algorithm in this list is also used when there are no recipient keys
to consider (e.g. @option{--symmetric}).
@item --s2k-cipher-algo @var{name}
@opindex s2k-cipher-algo
Use @var{name} as the cipher algorithm for symmetric encryption with
a passphrase if @option{--personal-cipher-preferences} and
@option{--cipher-algo} are not given. The default is @value{GPGSYMENCALGO}.
@item --s2k-digest-algo @var{name}
@opindex s2k-digest-algo
Use @var{name} as the digest algorithm used to mangle the passphrases
for symmetric encryption. The default is SHA-1.
@item --s2k-mode @var{n}
@opindex s2k-mode
Selects how passphrases for symmetric encryption are mangled. If
@var{n} is 0 a plain passphrase (which is in general not recommended)
will be used, a 1 adds a salt (which should not be used) to the
passphrase and a 3 (the default) iterates the whole process a number
of times (see @option{--s2k-count}).
@item --s2k-count @var{n}
@opindex s2k-count
Specify how many times the passphrases mangling for symmetric
encryption is repeated. This value may range between 1024 and
65011712 inclusive. The default is inquired from gpg-agent. Note
that not all values in the 1024-65011712 range are legal and if an
illegal value is selected, GnuPG will round up to the nearest legal
value. This option is only meaningful if @option{--s2k-mode} is set
to the default of 3.
@end table
@c ***************************
@c ******* Compliance ********
@c ***************************
@node Compliance Options
@subsection Compliance options
These options control what GnuPG is compliant to. Only one of these
options may be active at a time. Note that the default setting of
this is nearly always the correct one. See the INTEROPERABILITY WITH
OTHER OPENPGP PROGRAMS section below before using one of these
options.
@table @gnupgtabopt
@item --gnupg
@opindex gnupg
Use standard GnuPG behavior. This is essentially OpenPGP behavior
(see @option{--openpgp}), but with some additional workarounds for common
compatibility problems in different versions of PGP. This is the
default option, so it is not generally needed, but it may be useful to
override a different compliance option in the gpg.conf file.
@item --openpgp
@opindex openpgp
Reset all packet, cipher and digest options to strict OpenPGP
behavior. Use this option to reset all previous options like
@option{--s2k-*}, @option{--cipher-algo}, @option{--digest-algo} and
@option{--compress-algo} to OpenPGP compliant values. All PGP
workarounds are disabled.
@item --rfc4880
@opindex rfc4880
Reset all packet, cipher and digest options to strict RFC-4880
behavior. Note that this is currently the same thing as
@option{--openpgp}.
@item --rfc4880bis
@opindex rfc4880bis
Enable experimental features from proposed updates to RFC-4880. This
option can be used in addition to the other compliance options.
Warning: The behavior may change with any GnuPG release and created
keys or data may not be usable with future GnuPG versions.
@item --rfc2440
@opindex rfc2440
Reset all packet, cipher and digest options to strict RFC-2440
behavior. Note that by using this option encryption packets are
created in a legacy mode without MDC protection. This is dangerous
and should thus only be used for experiments. See also option
@option{--ignore-mdc-error}.
@item --pgp6
@opindex pgp6
This option is obsolete; it is handled as an alias for @option{--pgp7}
@item --pgp7
@opindex pgp7
Set up all options to be as PGP 7 compliant as possible. This allowed
the ciphers IDEA, 3DES, CAST5,AES128, AES192, AES256, and TWOFISH.,
the hashes MD5, SHA1 and RIPEMD160, and the compression algorithms
none and ZIP. This option implies @option{--escape-from-lines} and
disables @option{--throw-keyids},
@item --pgp8
@opindex pgp8
Set up all options to be as PGP 8 compliant as possible. PGP 8 is a lot
closer to the OpenPGP standard than previous versions of PGP, so all
this does is disable @option{--throw-keyids} and set
@option{--escape-from-lines}. All algorithms are allowed except for the
SHA224, SHA384, and SHA512 digests.
@item --compliance @var{string}
@opindex compliance
This option can be used instead of one of the options above. Valid
values for @var{string} are the above option names (without the double
dash) and possibly others as shown when using "help" for @var{value}.
@end table
@c *******************************************
@c ******** ESOTERIC OPTIONS ***************
@c *******************************************
@node GPG Esoteric Options
@subsection Doing things one usually doesn't want to do
@table @gnupgtabopt
@item -n
@itemx --dry-run
@opindex dry-run
Don't make any changes (this is not completely implemented).
@item --list-only
@opindex list-only
Changes the behaviour of some commands. This is like @option{--dry-run} but
different in some cases. The semantic of this option may be extended in
the future. Currently it only skips the actual decryption pass and
therefore enables a fast listing of the encryption keys.
@item -i
@itemx --interactive
@opindex interactive
Prompt before overwriting any files.
@item --debug-level @var{level}
@opindex debug-level
Select the debug level for investigating problems. @var{level} may be
a numeric value or by a keyword:
@table @code
@item none
No debugging at all. A value of less than 1 may be used instead of
the keyword.
@item basic
Some basic debug messages. A value between 1 and 2 may be used
instead of the keyword.
@item advanced
More verbose debug messages. A value between 3 and 5 may be used
instead of the keyword.
@item expert
Even more detailed messages. A value between 6 and 8 may be used
instead of the keyword.
@item guru
All of the debug messages you can get. A value greater than 8 may be
used instead of the keyword. The creation of hash tracing files is
only enabled if the keyword is used.
@end table
How these messages are mapped to the actual debugging flags is not
specified and may change with newer releases of this program. They are
however carefully selected to best aid in debugging.
@item --debug @var{flags}
@opindex debug
Set debugging flags. All flags are or-ed and @var{flags} may be given
in C syntax (e.g. 0x0042) or as a comma separated list of flag names.
To get a list of all supported flags the single word "help" can be
used.
@item --debug-all
@opindex debug-all
Set all useful debugging flags.
@item --debug-iolbf
@opindex debug-iolbf
Set stdout into line buffered mode. This option is only honored when
given on the command line.
@item --debug-set-iobuf-size @var{n}
@opindex debug-iolbf
Change the buffer size of the IOBUFs to @var{n} kilobyte. Using 0
prints the current size. Note well: This is a maintainer only option
and may thus be changed or removed at any time without notice.
@item --debug-allow-large-chunks
@opindex debug-allow-large-chunks
To facilitate in-memory decryption on the receiving site, the largest
recommended chunk size is 128 MiB (@code{--chunk-size 27}). This
option allows to specify a limit of up to 4 EiB (@code{--chunk-size
62}) for experiments.
@item --faked-system-time @var{epoch}
@opindex faked-system-time
This option is only useful for testing; it sets the system time back or
forth to @var{epoch} which is the number of seconds elapsed since the year
1970. Alternatively @var{epoch} may be given as a full ISO time string
(e.g. "20070924T154812").
If you suffix @var{epoch} with an exclamation mark (!), the system time
will appear to be frozen at the specified time.
@item --enable-progress-filter
@opindex enable-progress-filter
Enable certain PROGRESS status outputs. This option allows frontends
to display a progress indicator while gpg is processing larger files.
There is a slight performance overhead using it.
@item --status-fd @var{n}
@opindex status-fd
Write special status strings to the file descriptor @var{n}.
See the file DETAILS in the documentation for a listing of them.
@item --status-file @var{file}
@opindex status-file
Same as @option{--status-fd}, except the status data is written to file
@var{file}.
@item --logger-fd @var{n}
@opindex logger-fd
Write log output to file descriptor @var{n} and not to STDERR.
@item --log-file @var{file}
@itemx --logger-file @var{file}
@opindex log-file
Same as @option{--logger-fd}, except the logger data is written to
file @var{file}. Use @file{socket://} to log to s socket.
@item --attribute-fd @var{n}
@opindex attribute-fd
Write attribute subpackets to the file descriptor @var{n}. This is most
useful for use with @option{--status-fd}, since the status messages are
needed to separate out the various subpackets from the stream delivered
to the file descriptor.
@item --attribute-file @var{file}
@opindex attribute-file
Same as @option{--attribute-fd}, except the attribute data is written to
file @var{file}.
@item --comment @var{string}
@itemx --no-comments
@opindex comment
Use @var{string} as a comment string in cleartext signatures and ASCII
armored messages or keys (see @option{--armor}). The default behavior is
not to use a comment string. @option{--comment} may be repeated multiple
times to get multiple comment strings. @option{--no-comments} removes
all comments. It is a good idea to keep the length of a single comment
below 60 characters to avoid problems with mail programs wrapping such
lines. Note that comment lines, like all other header lines, are not
protected by the signature.
@item --emit-version
@itemx --no-emit-version
@opindex emit-version
Force inclusion of the version string in ASCII armored output. If
given once only the name of the program and the major number is
emitted, given twice the minor is also emitted, given thrice
the micro is added, and given four times an operating system identification
is also emitted. @option{--no-emit-version} (default) disables the version
line.
@item --sig-notation @{@var{name}=@var{value}@}
@itemx --cert-notation @{@var{name}=@var{value}@}
@itemx -N, --set-notation @{@var{name}=@var{value}@}
@opindex sig-notation
@opindex cert-notation
@opindex set-notation
Put the name value pair into the signature as notation data.
@var{name} must consist only of printable characters or spaces, and
must contain a '@@' character in the form keyname@@domain.example.com
(substituting the appropriate keyname and domain name, of course). This
is to help prevent pollution of the IETF reserved notation
namespace. The @option{--expert} flag overrides the '@@'
check. @var{value} may be any printable string; it will be encoded in
UTF-8, so you should check that your @option{--display-charset} is set
correctly. If you prefix @var{name} with an exclamation mark (!), the
notation data will be flagged as critical
(rfc4880:5.2.3.16). @option{--sig-notation} sets a notation for data
signatures. @option{--cert-notation} sets a notation for key signatures
(certifications). @option{--set-notation} sets both.
There are special codes that may be used in notation names. "%k" will
be expanded into the key ID of the key being signed, "%K" into the
long key ID of the key being signed, "%f" into the fingerprint of the
key being signed, "%s" into the key ID of the key making the
signature, "%S" into the long key ID of the key making the signature,
"%g" into the fingerprint of the key making the signature (which might
be a subkey), "%p" into the fingerprint of the primary key of the key
making the signature, "%c" into the signature count from the OpenPGP
smartcard, and "%%" results in a single "%". %k, %K, and %f are only
meaningful when making a key signature (certification), and %c is only
meaningful when using the OpenPGP smartcard.
@item --known-notation @var{name}
@opindex known-notation
Adds @var{name} to a list of known critical signature notations. The
effect of this is that gpg will not mark a signature with a critical
signature notation of that name as bad. Note that gpg already knows
by default about a few critical signatures notation names.
@item --sig-policy-url @var{string}
@itemx --cert-policy-url @var{string}
@itemx --set-policy-url @var{string}
@opindex sig-policy-url
@opindex cert-policy-url
@opindex set-policy-url
Use @var{string} as a Policy URL for signatures (rfc4880:5.2.3.20). If
you prefix it with an exclamation mark (!), the policy URL packet will
be flagged as critical. @option{--sig-policy-url} sets a policy url for
data signatures. @option{--cert-policy-url} sets a policy url for key
signatures (certifications). @option{--set-policy-url} sets both.
The same %-expandos used for notation data are available here as well.
@item --sig-keyserver-url @var{string}
@opindex sig-keyserver-url
Use @var{string} as a preferred keyserver URL for data signatures. If
you prefix it with an exclamation mark (!), the keyserver URL packet
will be flagged as critical.
The same %-expandos used for notation data are available here as well.
@item --set-filename @var{string}
@opindex set-filename
Use @var{string} as the filename which is stored inside messages.
This overrides the default, which is to use the actual filename of the
file being encrypted. Using the empty string for @var{string}
effectively removes the filename from the output.
@item --for-your-eyes-only
@itemx --no-for-your-eyes-only
@opindex for-your-eyes-only
Set the `for your eyes only' flag in the message. This causes GnuPG to
refuse to save the file unless the @option{--output} option is given,
and PGP to use a "secure viewer" with a claimed Tempest-resistant font
to display the message. This option overrides @option{--set-filename}.
@option{--no-for-your-eyes-only} disables this option.
@item --use-embedded-filename
@itemx --no-use-embedded-filename
@opindex use-embedded-filename
Try to create a file with a name as embedded in the data. This can be
-a dangerous option as it enables overwriting files. Defaults to no.
+a dangerous option as it enables overwriting files. Defaults to no.
+Note that the option @option{--output} overrides this option.
@item --cipher-algo @var{name}
@opindex cipher-algo
Use @var{name} as cipher algorithm. Running the program with the
command @option{--version} yields a list of supported algorithms. If
this is not used the cipher algorithm is selected from the preferences
stored with the key. In general, you do not want to use this option as
it allows you to violate the OpenPGP standard. The option
@option{--personal-cipher-preferences} is the safe way to accomplish the
same thing.
@item --aead-algo @var{name}
@opindex aead-algo
Specify that the AEAD algorithm @var{name} is to be used. This is
useful for symmetric encryption where no key preference are available
to select the AEAD algorithm. Running @command{@gpgname} with option
@option{--version} shows the available AEAD algorithms. In general,
you do not want to use this option as it allows you to violate the
OpenPGP standard. The option @option{--personal-aead-preferences} is
the safe way to accomplish the same thing.
@item --digest-algo @var{name}
@opindex digest-algo
Use @var{name} as the message digest algorithm. Running the program
with the command @option{--version} yields a list of supported
algorithms. In general, you do not want to use this option as it
allows you to violate the OpenPGP standard. The option
@option{--personal-digest-preferences} is the safe way to accomplish
the same thing.
@item --compress-algo @var{name}
@opindex compress-algo
Use compression algorithm @var{name}. "zlib" is RFC-1950 ZLIB
compression. "zip" is RFC-1951 ZIP compression which is used by PGP.
"bzip2" is a more modern compression scheme that can compress some
things better than zip or zlib, but at the cost of more memory used
during compression and decompression. "uncompressed" or "none"
disables compression. If this option is not used, the default
behavior is to examine the recipient key preferences to see which
algorithms the recipient supports. If all else fails, ZIP is used for
maximum compatibility.
ZLIB may give better compression results than ZIP, as the compression
window size is not limited to 8k. BZIP2 may give even better
compression results than that, but will use a significantly larger
amount of memory while compressing and decompressing. This may be
significant in low memory situations. Note, however, that PGP (all
versions) only supports ZIP compression. Using any algorithm other
than ZIP or "none" will make the message unreadable with PGP. In
general, you do not want to use this option as it allows you to
violate the OpenPGP standard. The option
@option{--personal-compress-preferences} is the safe way to accomplish
the same thing.
@item --cert-digest-algo @var{name}
@opindex cert-digest-algo
Use @var{name} as the message digest algorithm used when signing a
key. Running the program with the command @option{--version} yields a
-list of supported algorithms. Be aware that if you choose an algorithm
-that GnuPG supports but other OpenPGP implementations do not, then some
-users will not be able to use the key signatures you make, or quite
-possibly your entire key.
+list of supported algorithms. Be aware that if you choose an
+algorithm that GnuPG supports but other OpenPGP implementations do
+not, then some users will not be able to use the key signatures you
+make, or quite possibly your entire key. Note also that a public key
+algorithm must be compatible with the specified digest algorithm; thus
+selecting an arbitrary digest algorithm may result in error messages
+from lower crypto layers or lead to security flaws.
+
@item --disable-cipher-algo @var{name}
@opindex disable-cipher-algo
Never allow the use of @var{name} as cipher algorithm.
The given name will not be checked so that a later loaded algorithm
will still get disabled.
@item --disable-pubkey-algo @var{name}
@opindex disable-pubkey-algo
Never allow the use of @var{name} as public key algorithm.
The given name will not be checked so that a later loaded algorithm
will still get disabled.
@item --throw-keyids
@itemx --no-throw-keyids
@opindex throw-keyids
Do not put the recipient key IDs into encrypted messages. This helps to
hide the receivers of the message and is a limited countermeasure
against traffic analysis.@footnote{Using a little social engineering
anyone who is able to decrypt the message can check whether one of the
other recipients is the one he suspects.} On the receiving side, it may
slow down the decryption process because all available secret keys must
be tried. @option{--no-throw-keyids} disables this option. This option
is essentially the same as using @option{--hidden-recipient} for all
recipients.
@item --not-dash-escaped
@opindex not-dash-escaped
This option changes the behavior of cleartext signatures
so that they can be used for patch files. You should not
send such an armored file via email because all spaces
and line endings are hashed too. You can not use this
option for data which has 5 dashes at the beginning of a
line, patch files don't have this. A special armor header
line tells GnuPG about this cleartext signature option.
@item --escape-from-lines
@itemx --no-escape-from-lines
@opindex escape-from-lines
Because some mailers change lines starting with "From " to ">From " it
is good to handle such lines in a special way when creating cleartext
signatures to prevent the mail system from breaking the signature. Note
that all other PGP versions do it this way too. Enabled by
default. @option{--no-escape-from-lines} disables this option.
@item --passphrase-repeat @var{n}
@opindex passphrase-repeat
Specify how many times @command{@gpgname} will request a new
passphrase be repeated. This is useful for helping memorize a
passphrase. Defaults to 1 repetition.
@item --passphrase-fd @var{n}
@opindex passphrase-fd
Read the passphrase from file descriptor @var{n}. Only the first line
will be read from file descriptor @var{n}. If you use 0 for @var{n},
the passphrase will be read from STDIN. This can only be used if only
one passphrase is supplied.
Note that since Version 2.0 this passphrase is only used if the
option @option{--batch} has also been given. Since Version 2.1
the @option{--pinentry-mode} also needs to be set to @code{loopback}.
@item --passphrase-file @var{file}
@opindex passphrase-file
Read the passphrase from file @var{file}. Only the first line will
be read from file @var{file}. This can only be used if only one
passphrase is supplied. Obviously, a passphrase stored in a file is
of questionable security if other users can read this file. Don't use
this option if you can avoid it.
Note that since Version 2.0 this passphrase is only used if the
option @option{--batch} has also been given. Since Version 2.1
the @option{--pinentry-mode} also needs to be set to @code{loopback}.
@item --passphrase @var{string}
@opindex passphrase
Use @var{string} as the passphrase. This can only be used if only one
passphrase is supplied. Obviously, this is of very questionable
security on a multi-user system. Don't use this option if you can
avoid it.
Note that since Version 2.0 this passphrase is only used if the
option @option{--batch} has also been given. Since Version 2.1
the @option{--pinentry-mode} also needs to be set to @code{loopback}.
@item --pinentry-mode @var{mode}
@opindex pinentry-mode
Set the pinentry mode to @var{mode}. Allowed values for @var{mode}
are:
@table @asis
@item default
Use the default of the agent, which is @code{ask}.
@item ask
Force the use of the Pinentry.
@item cancel
Emulate use of Pinentry's cancel button.
@item error
Return a Pinentry error (``No Pinentry'').
@item loopback
Redirect Pinentry queries to the caller. Note that in contrast to
Pinentry the user is not prompted again if he enters a bad password.
@end table
@item --no-symkey-cache
@opindex no-symkey-cache
Disable the passphrase cache used for symmetrical en- and decryption.
This cache is based on the message specific salt value
(cf. @option{--s2k-mode}).
@item --request-origin @var{origin}
@opindex request-origin
Tell gpg to assume that the operation ultimately originated at
@var{origin}. Depending on the origin certain restrictions are applied
and the Pinentry may include an extra note on the origin. Supported
values for @var{origin} are: @code{local} which is the default,
@code{remote} to indicate a remote origin or @code{browser} for an
operation requested by a web browser.
@item --command-fd @var{n}
@opindex command-fd
This is a replacement for the deprecated shared-memory IPC mode.
If this option is enabled, user input on questions is not expected
from the TTY but from the given file descriptor. It should be used
together with @option{--status-fd}. See the file doc/DETAILS in the source
distribution for details on how to use it.
@item --command-file @var{file}
@opindex command-file
Same as @option{--command-fd}, except the commands are read out of file
@var{file}
@item --allow-non-selfsigned-uid
@itemx --no-allow-non-selfsigned-uid
@opindex allow-non-selfsigned-uid
Allow the import and use of keys with user IDs which are not
self-signed. This is not recommended, as a non self-signed user ID is
trivial to forge. @option{--no-allow-non-selfsigned-uid} disables.
@item --allow-freeform-uid
@opindex allow-freeform-uid
Disable all checks on the form of the user ID while generating a new
one. This option should only be used in very special environments as
it does not ensure the de-facto standard format of user IDs.
@item --ignore-time-conflict
@opindex ignore-time-conflict
GnuPG normally checks that the timestamps associated with keys and
signatures have plausible values. However, sometimes a signature
seems to be older than the key due to clock problems. This option
makes these checks just a warning. See also @option{--ignore-valid-from} for
timestamp issues on subkeys.
@item --ignore-valid-from
@opindex ignore-valid-from
GnuPG normally does not select and use subkeys created in the future.
This option allows the use of such keys and thus exhibits the
pre-1.0.7 behaviour. You should not use this option unless there
is some clock problem. See also @option{--ignore-time-conflict} for timestamp
issues with signatures.
@item --ignore-crc-error
@opindex ignore-crc-error
The ASCII armor used by OpenPGP is protected by a CRC checksum against
transmission errors. Occasionally the CRC gets mangled somewhere on
the transmission channel but the actual content (which is protected by
the OpenPGP protocol anyway) is still okay. This option allows GnuPG
to ignore CRC errors.
@item --ignore-mdc-error
@opindex ignore-mdc-error
This option changes a MDC integrity protection failure into a warning.
It is required to decrypt old messages which did not use an MDC. It
may also be useful if a message is partially garbled, but it is
necessary to get as much data as possible out of that garbled message.
Be aware that a missing or failed MDC can be an indication of an
attack. Use with great caution; see also option @option{--rfc2440}.
@item --allow-weak-digest-algos
@opindex allow-weak-digest-algos
Signatures made with known-weak digest algorithms are normally
rejected with an ``invalid digest algorithm'' message. This option
allows the verification of signatures made with such weak algorithms.
MD5 is the only digest algorithm considered weak by default. See also
@option{--weak-digest} to reject other digest algorithms.
@item --weak-digest @var{name}
@opindex weak-digest
Treat the specified digest algorithm as weak. Signatures made over
weak digests algorithms are normally rejected. This option can be
supplied multiple times if multiple algorithms should be considered
weak. See also @option{--allow-weak-digest-algos} to disable
rejection of weak digests. MD5 is always considered weak, and does
not need to be listed explicitly.
@item --no-default-keyring
@opindex no-default-keyring
Do not add the default keyrings to the list of keyrings. Note that
GnuPG will not operate without any keyrings, so if you use this option
and do not provide alternate keyrings via @option{--keyring} or
@option{--secret-keyring}, then GnuPG will still use the default public or
secret keyrings.
@item --no-keyring
@opindex no-keyring
-Do not add use any keyrings even if specified as options.
+Do not use any keyring at all. This overrides the default and all
+options which specify keyrings.
@item --skip-verify
@opindex skip-verify
Skip the signature verification step. This may be
used to make the decryption faster if the signature
verification is not needed.
@item --with-key-data
@opindex with-key-data
Print key listings delimited by colons (like @option{--with-colons}) and
print the public key data.
@item --list-signatures
@opindex list-signatures
@itemx --list-sigs
@opindex list-sigs
Same as @option{--list-keys}, but the signatures are listed too. This
command has the same effect as using @option{--list-keys} with
@option{--with-sig-list}. Note that in contrast to
@option{--check-signatures} the key signatures are not verified. This
command can be used to create a list of signing keys missing in the
local keyring; for example:
@example
gpg --list-sigs --with-colons USERID | \
awk -F: '$1=="sig" && $2=="?" @{if($13)@{print $13@}else@{print $5@}@}'
@end example
@item --fast-list-mode
@opindex fast-list-mode
Changes the output of the list commands to work faster; this is achieved
by leaving some parts empty. Some applications don't need the user ID
and the trust information given in the listings. By using this options
they can get a faster listing. The exact behaviour of this option may
change in future versions. If you are missing some information, don't
use this option.
@item --no-literal
@opindex no-literal
This is not for normal use. Use the source to see for what it might be useful.
@item --set-filesize
@opindex set-filesize
This is not for normal use. Use the source to see for what it might be useful.
@item --show-session-key
@opindex show-session-key
Display the session key used for one message. See
@option{--override-session-key} for the counterpart of this option.
We think that Key Escrow is a Bad Thing; however the user should have
the freedom to decide whether to go to prison or to reveal the content
of one specific message without compromising all messages ever
encrypted for one secret key.
You can also use this option if you receive an encrypted message which
is abusive or offensive, to prove to the administrators of the
messaging system that the ciphertext transmitted corresponds to an
inappropriate plaintext so they can take action against the offending
user.
@item --override-session-key @var{string}
@itemx --override-session-key-fd @var{fd}
@opindex override-session-key
Don't use the public key but the session key @var{string} respective
the session key taken from the first line read from file descriptor
@var{fd}. The format of this string is the same as the one printed by
@option{--show-session-key}. This option is normally not used but
comes handy in case someone forces you to reveal the content of an
encrypted message; using this option you can do this without handing
out the secret key. Note that using @option{--override-session-key}
may reveal the session key to all local users via the global process
table. Often it is useful to combine this option with
@option{--no-keyring}.
@item --ask-sig-expire
@itemx --no-ask-sig-expire
@opindex ask-sig-expire
When making a data signature, prompt for an expiration time. If this
option is not specified, the expiration time set via
@option{--default-sig-expire} is used. @option{--no-ask-sig-expire}
disables this option.
@item --default-sig-expire
@opindex default-sig-expire
The default expiration time to use for signature expiration. Valid
values are "0" for no expiration, a number followed by the letter d
(for days), w (for weeks), m (for months), or y (for years) (for
example "2m" for two months, or "5y" for five years), or an absolute
date in the form YYYY-MM-DD. Defaults to "0".
@item --ask-cert-expire
@itemx --no-ask-cert-expire
@opindex ask-cert-expire
When making a key signature, prompt for an expiration time. If this
option is not specified, the expiration time set via
@option{--default-cert-expire} is used. @option{--no-ask-cert-expire}
disables this option.
@item --default-cert-expire
@opindex default-cert-expire
The default expiration time to use for key signature expiration.
Valid values are "0" for no expiration, a number followed by the
letter d (for days), w (for weeks), m (for months), or y (for years)
(for example "2m" for two months, or "5y" for five years), or an
absolute date in the form YYYY-MM-DD. Defaults to "0".
@item --default-new-key-algo @var{string}
@opindex default-new-key-algo @var{string}
This option can be used to change the default algorithms for key
generation. The @var{string} is similar to the arguments required for
the command @option{--quick-add-key} but slightly different. For
example the current default of @code{"rsa2048/cert,sign+rsa2048/encr"}
(or @code{"rsa3072"}) can be changed to the value of what we currently
call future default, which is @code{"ed25519/cert,sign+cv25519/encr"}.
You need to consult the source code to learn the details. Note that
the advanced key generation commands can always be used to specify a
key algorithm directly.
@item --allow-secret-key-import
@opindex allow-secret-key-import
This is an obsolete option and is not used anywhere.
@item --allow-multiple-messages
@item --no-allow-multiple-messages
These are obsolete options; they have no more effect since GnuPG 2.2.8.
@item --enable-special-filenames
@opindex enable-special-filenames
This option enables a mode in which filenames of the form
@file{-&n}, where n is a non-negative decimal number,
refer to the file descriptor n and not to a file with that name.
@item --no-expensive-trust-checks
@opindex no-expensive-trust-checks
Experimental use only.
@item --preserve-permissions
@opindex preserve-permissions
Don't change the permissions of a secret keyring back to user
read/write only. Use this option only if you really know what you are doing.
@item --default-preference-list @var{string}
@opindex default-preference-list
Set the list of default preferences to @var{string}. This preference
list is used for new keys and becomes the default for "setpref" in the
edit menu.
@item --default-keyserver-url @var{name}
@opindex default-keyserver-url
Set the default keyserver URL to @var{name}. This keyserver will be
used as the keyserver URL when writing a new self-signature on a key,
which includes key generation and changing preferences.
@item --list-config
@opindex list-config
Display various internal configuration parameters of GnuPG. This option
is intended for external programs that call GnuPG to perform tasks, and
is thus not generally useful. See the file @file{doc/DETAILS} in the
source distribution for the details of which configuration items may be
listed. @option{--list-config} is only usable with
@option{--with-colons} set.
@item --list-gcrypt-config
@opindex list-gcrypt-config
Display various internal configuration parameters of Libgcrypt.
@item --gpgconf-list
@opindex gpgconf-list
This command is similar to @option{--list-config} but in general only
internally used by the @command{gpgconf} tool.
@item --gpgconf-test
@opindex gpgconf-test
This is more or less dummy action. However it parses the configuration
file and returns with failure if the configuration file would prevent
@command{@gpgname} from startup. Thus it may be used to run a syntax check
on the configuration file.
@end table
@c *******************************
@c ******* Deprecated ************
@c *******************************
@node Deprecated Options
@subsection Deprecated options
@table @gnupgtabopt
@item --show-photos
@itemx --no-show-photos
@opindex show-photos
Causes @option{--list-keys}, @option{--list-signatures},
@option{--list-public-keys}, @option{--list-secret-keys}, and verifying
a signature to also display the photo ID attached to the key, if
any. See also @option{--photo-viewer}. These options are deprecated. Use
@option{--list-options [no-]show-photos} and/or @option{--verify-options
[no-]show-photos} instead.
@item --show-keyring
@opindex show-keyring
Display the keyring name at the head of key listings to show which
keyring a given key resides on. This option is deprecated: use
@option{--list-options [no-]show-keyring} instead.
@item --always-trust
@opindex always-trust
Identical to @option{--trust-model always}. This option is deprecated.
@item --show-notation
@itemx --no-show-notation
@opindex show-notation
Show signature notations in the @option{--list-signatures} or @option{--check-signatures} listings
as well as when verifying a signature with a notation in it. These
options are deprecated. Use @option{--list-options [no-]show-notation}
and/or @option{--verify-options [no-]show-notation} instead.
@item --show-policy-url
@itemx --no-show-policy-url
@opindex show-policy-url
Show policy URLs in the @option{--list-signatures} or @option{--check-signatures}
listings as well as when verifying a signature with a policy URL in
it. These options are deprecated. Use @option{--list-options
[no-]show-policy-url} and/or @option{--verify-options
[no-]show-policy-url} instead.
@end table
@c *******************************************
@c *************** ****************
@c *************** FILES ****************
@c *************** ****************
@c *******************************************
@mansect files
@node GPG Configuration
@section Configuration files
There are a few configuration files to control certain aspects of
@command{@gpgname}'s operation. Unless noted, they are expected in the
current home directory (@pxref{option --homedir}).
@table @file
@item gpg.conf
@efindex gpg.conf
This is the standard configuration file read by @command{@gpgname} on
startup. It may contain any valid long option; the leading two dashes
may not be entered and the option may not be abbreviated. This default
name may be changed on the command line (@pxref{gpg-option --options}).
You should backup this file.
@end table
Note that on larger installations, it is useful to put predefined files
into the directory @file{@value{SYSCONFSKELDIR}} so that
newly created users start up with a working configuration.
For existing users a small
helper script is provided to create these files (@pxref{addgnupghome}).
For internal purposes @command{@gpgname} creates and maintains a few other
files; They all live in the current home directory (@pxref{option
--homedir}). Only the @command{@gpgname} program may modify these files.
@table @file
@item ~/.gnupg
@efindex ~/.gnupg
This is the default home directory which is used if neither the
environment variable @code{GNUPGHOME} nor the option
@option{--homedir} is given.
@item ~/.gnupg/pubring.gpg
@efindex pubring.gpg
The public keyring. You should backup this file.
@item ~/.gnupg/pubring.gpg.lock
The lock file for the public keyring.
@item ~/.gnupg/pubring.kbx
@efindex pubring.kbx
The public keyring using a different format. This file is shared
with @command{gpgsm}. You should backup this file.
@item ~/.gnupg/pubring.kbx.lock
The lock file for @file{pubring.kbx}.
@item ~/.gnupg/secring.gpg
@efindex secring.gpg
A secret keyring as used by GnuPG versions before 2.1. It is not
used by GnuPG 2.1 and later.
@item ~/.gnupg/secring.gpg.lock
The lock file for the secret keyring.
@item ~/.gnupg/.gpg-v21-migrated
@efindex .gpg-v21-migrated
File indicating that a migration to GnuPG 2.1 has been done.
@item ~/.gnupg/trustdb.gpg
@efindex trustdb.gpg
The trust database. There is no need to backup this file; it is better
to backup the ownertrust values (@pxref{option --export-ownertrust}).
@item ~/.gnupg/trustdb.gpg.lock
The lock file for the trust database.
@item ~/.gnupg/random_seed
@efindex random_seed
A file used to preserve the state of the internal random pool.
@item ~/.gnupg/openpgp-revocs.d/
@efindex openpgp-revocs.d
This is the directory where gpg stores pre-generated revocation
certificates. The file name corresponds to the OpenPGP fingerprint of
the respective key. It is suggested to backup those certificates and
if the primary private key is not stored on the disk to move them to
an external storage device. Anyone who can access theses files is
able to revoke the corresponding key. You may want to print them out.
You should backup all files in this directory and take care to keep
this backup closed away.
@end table
Operation is further controlled by a few environment variables:
@table @asis
@item HOME
@efindex HOME
Used to locate the default home directory.
@item GNUPGHOME
@efindex GNUPGHOME
If set directory used instead of "~/.gnupg".
@item GPG_AGENT_INFO
This variable is obsolete; it was used by GnuPG versions before 2.1.
@item PINENTRY_USER_DATA
@efindex PINENTRY_USER_DATA
This value is passed via gpg-agent to pinentry. It is useful to convey
extra information to a custom pinentry.
@item COLUMNS
@itemx LINES
@efindex COLUMNS
@efindex LINES
Used to size some displays to the full size of the screen.
@item LANGUAGE
@efindex LANGUAGE
Apart from its use by GNU, it is used in the W32 version to override the
language selection done through the Registry. If used and set to a
valid and available language name (@var{langid}), the file with the
translation is loaded from
@code{@var{gpgdir}/gnupg.nls/@var{langid}.mo}. Here @var{gpgdir} is the
directory out of which the gpg binary has been loaded. If it can't be
loaded the Registry is tried and as last resort the native Windows
locale system is used.
@end table
When calling the gpg-agent component @command{@gpgname} sends a set of
environment variables to gpg-agent. The names of these variables can
be listed using the command:
@example
gpg-connect-agent 'getinfo std_env_names' /bye | awk '$1=="D" @{print $2@}'
@end example
@c *******************************************
@c *************** ****************
@c *************** EXAMPLES ****************
@c *************** ****************
@c *******************************************
@mansect examples
@node GPG Examples
@section Examples
@table @asis
@item gpg -se -r @code{Bob} @code{file}
sign and encrypt for user Bob
@item gpg --clear-sign @code{file}
make a cleartext signature
@item gpg -sb @code{file}
make a detached signature
@item gpg -u 0x12345678 -sb @code{file}
make a detached signature with the key 0x12345678
@item gpg --list-keys @code{user_ID}
show keys
@item gpg --fingerprint @code{user_ID}
show fingerprint
@item gpg --verify @code{pgpfile}
@itemx gpg --verify @code{sigfile} [@code{datafile}]
Verify the signature of the file but do not output the data unless
requested. The second form is used for detached signatures, where
@code{sigfile} is the detached signature (either ASCII armored or
binary) and @code{datafile} are the signed data; if this is not given, the name of the
file holding the signed data is constructed by cutting off the
extension (".asc" or ".sig") of @code{sigfile} or by asking the user
for the filename. If the option @option{--output} is also used the
signed data is written to the file specified by that option; use
@code{-} to write the signed data to stdout.
@end table
@c *******************************************
@c *************** ****************
@c *************** USER ID ****************
@c *************** ****************
@c *******************************************
@mansect how to specify a user id
@ifset isman
@include specify-user-id.texi
@end ifset
@mansect filter expressions
@chapheading FILTER EXPRESSIONS
The options @option{--import-filter} and @option{--export-filter} use
expressions with this syntax (square brackets indicate an optional
part and curly braces a repetition, white space between the elements
are allowed):
@c man:.RS
@example
[lc] @{[@{flag@}] PROPNAME op VALUE [lc]@}
@end example
@c man:.RE
The name of a property (@var{PROPNAME}) may only consist of letters,
digits and underscores. The description for the filter type
describes which properties are defined. If an undefined property is
used it evaluates to the empty string. Unless otherwise noted, the
@var{VALUE} must always be given and may not be the empty string. No
quoting is defined for the value, thus the value may not contain the
strings @code{&&} or @code{||}, which are used as logical connection
operators. The flag @code{--} can be used to remove this restriction.
Numerical values are computed as long int; standard C notation
applies. @var{lc} is the logical connection operator; either
@code{&&} for a conjunction or @code{||} for a disjunction. A
conjunction is assumed at the begin of an expression. Conjunctions
have higher precedence than disjunctions. If @var{VALUE} starts with
one of the characters used in any @var{op} a space after the
@var{op} is required.
@noindent
The supported operators (@var{op}) are:
@table @asis
@item =~
Substring must match.
@item !~
Substring must not match.
@item =
The full string must match.
@item <>
The full string must not match.
@item ==
The numerical value must match.
@item !=
The numerical value must not match.
@item <=
The numerical value of the field must be LE than the value.
@item <
The numerical value of the field must be LT than the value.
@item >
The numerical value of the field must be GT than the value.
@item >=
The numerical value of the field must be GE than the value.
@item -le
The string value of the field must be less or equal than the value.
@item -lt
The string value of the field must be less than the value.
@item -gt
The string value of the field must be greater than the value.
@item -ge
The string value of the field must be greater or equal than the value.
@item -n
True if value is not empty (no value allowed).
@item -z
True if value is empty (no value allowed).
@item -t
Alias for "PROPNAME != 0" (no value allowed).
@item -f
Alias for "PROPNAME == 0" (no value allowed).
@end table
@noindent
Values for @var{flag} must be space separated. The supported flags
are:
@table @asis
@item --
@var{VALUE} spans to the end of the expression.
@item -c
The string match in this part is done case-sensitive.
@end table
The filter options concatenate several specifications for a filter of
the same type. For example the four options in this example:
@c man:.RS
@example
--import-option keep-uid="uid =~ Alfa"
--import-option keep-uid="&& uid !~ Test"
--import-option keep-uid="|| uid =~ Alpha"
--import-option keep-uid="uid !~ Test"
@end example
@c man:.RE
@noindent
which is equivalent to
@c man:.RS
@example
--import-option \
keep-uid="uid =~ Alfa" && uid !~ Test" || uid =~ Alpha" && "uid !~ Test"
@end example
@c man:.RE
imports only the user ids of a key containing the strings "Alfa"
or "Alpha" but not the string "test".
@mansect trust values
@ifset isman
@include trust-values.texi
@end ifset
@mansect return value
@chapheading RETURN VALUE
The program returns 0 if everything was fine, 1 if at least
a signature was bad, and other error codes for fatal errors.
@mansect warnings
@chapheading WARNINGS
Use a *good* password for your user account and a *good* passphrase
to protect your secret key. This passphrase is the weakest part of the
whole system. Programs to do dictionary attacks on your secret keyring
are very easy to write and so you should protect your "~/.gnupg/"
directory very well.
Keep in mind that, if this program is used over a network (telnet), it
is *very* easy to spy out your passphrase!
If you are going to verify detached signatures, make sure that the
program knows about it; either give both filenames on the command line
or use @samp{-} to specify STDIN.
For scripted or other unattended use of @command{gpg} make sure to use
the machine-parseable interface and not the default interface which is
intended for direct use by humans. The machine-parseable interface
provides a stable and well documented API independent of the locale or
future changes of @command{gpg}. To enable this interface use the
options @option{--with-colons} and @option{--status-fd}. For certain
operations the option @option{--command-fd} may come handy too. See
this man page and the file @file{DETAILS} for the specification of the
interface. Note that the GnuPG ``info'' pages as well as the PDF
version of the GnuPG manual features a chapter on unattended use of
GnuPG. As an alternative the library @command{GPGME} can be used as a
high-level abstraction on top of that interface.
@mansect interoperability
@chapheading INTEROPERABILITY WITH OTHER OPENPGP PROGRAMS
GnuPG tries to be a very flexible implementation of the OpenPGP
standard. In particular, GnuPG implements many of the optional parts
of the standard, such as the SHA-512 hash, and the ZLIB and BZIP2
compression algorithms. It is important to be aware that not all
OpenPGP programs implement these optional algorithms and that by
forcing their use via the @option{--cipher-algo},
@option{--digest-algo}, @option{--cert-digest-algo}, or
@option{--compress-algo} options in GnuPG, it is possible to create a
perfectly valid OpenPGP message, but one that cannot be read by the
intended recipient.
There are dozens of variations of OpenPGP programs available, and each
supports a slightly different subset of these optional algorithms.
For example, until recently, no (unhacked) version of PGP supported
the BLOWFISH cipher algorithm. A message using BLOWFISH simply could
not be read by a PGP user. By default, GnuPG uses the standard
OpenPGP preferences system that will always do the right thing and
create messages that are usable by all recipients, regardless of which
OpenPGP program they use. Only override this safe default if you
really know what you are doing.
If you absolutely must override the safe default, or if the preferences
on a given key are invalid for some reason, you are far better off using
the @option{--pgp6}, @option{--pgp7}, or @option{--pgp8} options. These
options are safe as they do not force any particular algorithms in
violation of OpenPGP, but rather reduce the available algorithms to a
"PGP-safe" list.
@mansect bugs
@chapheading BUGS
On older systems this program should be installed as setuid(root). This
is necessary to lock memory pages. Locking memory pages prevents the
operating system from writing memory pages (which may contain
passphrases or other sensitive material) to disk. If you get no
warning message about insecure memory your operating system supports
locking without being root. The program drops root privileges as soon
as locked memory is allocated.
Note also that some systems (especially laptops) have the ability to
``suspend to disk'' (also known as ``safe sleep'' or ``hibernate'').
This writes all memory to disk before going into a low power or even
powered off mode. Unless measures are taken in the operating system
to protect the saved memory, passphrases or other sensitive material
may be recoverable from it later.
Before you report a bug you should first search the mailing list
archives for similar problems and second check whether such a bug has
already been reported to our bug tracker at @url{https://bugs.gnupg.org}.
@c *******************************************
@c *************** **************
@c *************** UNATTENDED **************
@c *************** **************
@c *******************************************
@manpause
@node Unattended Usage of GPG
@section Unattended Usage
@command{@gpgname} is often used as a backend engine by other software. To help
with this a machine interface has been defined to have an unambiguous
way to do this. The options @option{--status-fd} and @option{--batch}
are almost always required for this.
@menu
* Programmatic use of GnuPG:: Programmatic use of GnuPG
* Ephemeral home directories:: Ephemeral home directories
* The quick key manipulation interface:: The quick key manipulation interface
* Unattended GPG key generation:: Unattended key generation
@end menu
@node Programmatic use of GnuPG
@subsection Programmatic use of GnuPG
Please consider using GPGME instead of calling @command{@gpgname}
directly. GPGME offers a stable, backend-independent interface for
many cryptographic operations. It supports OpenPGP and S/MIME, and
also allows interaction with various GnuPG components.
GPGME provides a C-API, and comes with bindings for C++, Qt, and
Python. Bindings for other languages are available.
@node Ephemeral home directories
@subsection Ephemeral home directories
Sometimes you want to contain effects of some operation, for example
you want to import a key to inspect it, but you do not want this key
to be added to your keyring. In earlier versions of GnuPG, it was
possible to specify alternate keyring files for both public and secret
keys. In modern GnuPG versions, however, we changed how secret keys
are stored in order to better protect secret key material, and it was
not possible to preserve this interface.
The preferred way to do this is to use ephemeral home directories.
This technique works across all versions of GnuPG.
Create a temporary directory, create (or copy) a configuration that
meets your needs, make @command{@gpgname} use this directory either
using the environment variable @var{GNUPGHOME}, or the option
@option{--homedir}. GPGME supports this too on a per-context basis,
by modifying the engine info of contexts. Now execute whatever
operation you like, import and export key material as necessary. Once
finished, you can delete the directory. All GnuPG backend services
that were started will detect this and shut down.
@node The quick key manipulation interface
@subsection The quick key manipulation interface
Recent versions of GnuPG have an interface to manipulate keys without
using the interactive command @option{--edit-key}. This interface was
added mainly for the benefit of GPGME (please consider using GPGME,
see the manual subsection ``Programmatic use of GnuPG''). This
interface is described in the subsection ``How to manage your keys''.
@node Unattended GPG key generation
@subsection Unattended key generation
The command @option{--generate-key} may be used along with the option
@option{--batch} for unattended key generation. This is the most
flexible way of generating keys, but it is also the most complex one.
Consider using the quick key manipulation interface described in the
previous subsection ``The quick key manipulation interface''.
The parameters for the key are either read from stdin or given as a
file on the command line. The format of the parameter file is as
follows:
@itemize @bullet
@item Text only, line length is limited to about 1000 characters.
@item UTF-8 encoding must be used to specify non-ASCII characters.
@item Empty lines are ignored.
@item Leading and trailing white space is ignored.
@item A hash sign as the first non white space character indicates
a comment line.
@item Control statements are indicated by a leading percent sign, the
arguments are separated by white space from the keyword.
@item Parameters are specified by a keyword, followed by a colon. Arguments
are separated by white space.
@item
The first parameter must be @samp{Key-Type}; control statements may be
placed anywhere.
@item
The order of the parameters does not matter except for @samp{Key-Type}
which must be the first parameter. The parameters are only used for
the generated keyblock (primary and subkeys); parameters from previous
sets are not used. Some syntactically checks may be performed.
@item
Key generation takes place when either the end of the parameter file
is reached, the next @samp{Key-Type} parameter is encountered or at the
control statement @samp{%commit} is encountered.
@end itemize
@noindent
Control statements:
@table @asis
@item %echo @var{text}
Print @var{text} as diagnostic.
@item %dry-run
Suppress actual key generation (useful for syntax checking).
@item %commit
Perform the key generation. Note that an implicit commit is done at
the next @asis{Key-Type} parameter.
@item %pubring @var{filename}
Do not write the key to the default or commandline given keyring but
to @var{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).
See the previous subsection ``Ephemeral home directories'' for a more
robust way to contain side-effects.
@item %secring @var{filename}
This option is a no-op for GnuPG 2.1 and later.
See the previous subsection ``Ephemeral home directories''.
@item %ask-passphrase
@itemx %no-ask-passphrase
This option is a no-op for GnuPG 2.1 and later.
@item %no-protection
Using this option allows the creation of keys without any passphrase
protection. This option is mainly intended for regression tests.
@item %transient-key
If given the keys are created using a faster and a somewhat less
secure random number generator. This option may be used for keys
which are only used for a short time and do not require full
cryptographic strength. It takes only effect if used together with
the control statement @samp{%no-protection}.
@end table
@noindent
General Parameters:
@table @asis
@item Key-Type: @var{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. @var{algo} may either be an OpenPGP algorithm number or a
string with the algorithm name. The special value @samp{default} may
be used for @var{algo} to create the default key type; in this case a
@samp{Key-Usage} shall not be given and @samp{default} also be used
for @samp{Subkey-Type}.
@item Key-Length: @var{nbits}
The requested length of the generated key in bits. The default is
returned by running the command @samp{@gpgname --gpgconf-list}.
@item Key-Grip: @var{hexstring}
This is optional and used to generate a CSR or certificate for an
already existing key. Key-Length will be ignored when given.
@item Key-Usage: @var{usage-list}
Space or comma delimited list of key usages. Allowed values are
@samp{encrypt}, @samp{sign}, and @samp{auth}. This is used to
generate the key flags. Please make sure that the algorithm is
capable of this usage. Note that OpenPGP requires that all primary
keys are capable of certification, so no matter what usage is given
here, the @samp{cert} flag will be on. If no @samp{Key-Usage} is
specified and the @samp{Key-Type} is not @samp{default}, all allowed
usages for that particular algorithm are used; if it is not given but
@samp{default} is used the usage will be @samp{sign}.
@item Subkey-Type: @var{algo}
This generates a secondary key (subkey). Currently only one subkey
can be handled. See also @samp{Key-Type} above.
@item Subkey-Length: @var{nbits}
Length of the secondary key (subkey) in bits. The default is returned
by running the command @samp{@gpgname --gpgconf-list}.
@item Subkey-Usage: @var{usage-list}
Key usage lists for a subkey; similar to @samp{Key-Usage}.
@item Passphrase: @var{string}
If you want to specify a passphrase for the secret key, enter it here.
Default is to use the Pinentry dialog to ask for a passphrase.
@item Name-Real: @var{name}
@itemx Name-Comment: @var{comment}
@itemx Name-Email: @var{email}
The three parts of a user name. Remember to use UTF-8 encoding here.
If you don't give any of them, no user ID is created.
@item Expire-Date: @var{iso-date}|(@var{number}[d|w|m|y])
Set the expiration date for the key (and the subkey). It may either
be entered in ISO date format (e.g. "20000815T145012") or as number of
days, weeks, month or years after the creation date. The special
notation "seconds=N" is also allowed to specify a number of seconds
since creation. Without a letter days are assumed. Note that there
is no check done on the overflow of the type used by OpenPGP for
timestamps. Thus you better make sure that the given value make
sense. Although OpenPGP works with time intervals, GnuPG uses an
absolute value internally and thus the last year we can represent is
2105.
@item Creation-Date: @var{iso-date}
Set the creation date of the key as stored in the key information and
which is also part of the fingerprint calculation. Either a date like
"1986-04-26" or a full timestamp like "19860426T042640" may be used.
The time is considered to be UTC. The special notation "seconds=N"
may be used to directly specify a the number of seconds since Epoch
(Unix time). If it is not given the current time is used.
@item Preferences: @var{string}
Set the cipher, hash, and compression preference values for this key.
This expects the same type of string as the sub-command @samp{setpref}
in the @option{--edit-key} menu.
@item Revoker: @var{algo}:@var{fpr} [sensitive]
Add a designated revoker to the generated key. Algo is the public key
algorithm of the designated revoker (i.e. RSA=1, DSA=17, etc.)
@var{fpr} is the fingerprint of the designated revoker. The optional
@samp{sensitive} flag marks the designated revoker as sensitive
information. Only v4 keys may be designated revokers.
@item Keyserver: @var{string}
This is an optional parameter that specifies the preferred keyserver
URL for the key.
@item Handle: @var{string}
This is an optional parameter only used with the status lines
KEY_CREATED and KEY_NOT_CREATED. @var{string} may be up to 100
characters and should not contain spaces. It is useful for batch key
generation to associate a key parameter block with a status line.
@end table
@noindent
Here is an example on how to create a key in an ephemeral home directory:
@smallexample
$ export GNUPGHOME="$(mktemp -d)"
$ cat >foo <<EOF
%echo Generating a basic OpenPGP key
Key-Type: DSA
Key-Length: 1024
Subkey-Type: ELG-E
Subkey-Length: 1024
Name-Real: Joe Tester
Name-Comment: with stupid passphrase
Name-Email: joe@@foo.bar
Expire-Date: 0
Passphrase: abc
# Do a commit here, so that we can later print "done" :-)
%commit
%echo done
EOF
$ @gpgname --batch --generate-key foo
[...]
$ @gpgname --list-secret-keys
/tmp/tmp.0NQxB74PEf/pubring.kbx
-------------------------------
sec dsa1024 2016-12-16 [SCA]
768E895903FC1C44045C8CB95EEBDB71E9E849D0
uid [ultimate] Joe Tester (with stupid passphrase) <joe@@foo.bar>
ssb elg1024 2016-12-16 [E]
@end smallexample
@noindent
If you want to create a key with the default algorithms you would use
these parameters:
@smallexample
%echo Generating a default key
Key-Type: default
Subkey-Type: default
Name-Real: Joe Tester
Name-Comment: with stupid passphrase
Name-Email: joe@@foo.bar
Expire-Date: 0
Passphrase: abc
# Do a commit here, so that we can later print "done" :-)
%commit
%echo done
@end smallexample
@mansect see also
@ifset isman
@command{gpgv}(1),
@command{gpgsm}(1),
@command{gpg-agent}(1)
@end ifset
@include see-also-note.texi
diff --git a/doc/gpgsm.texi b/doc/gpgsm.texi
index 1736ff111..75ccdc3ba 100644
--- a/doc/gpgsm.texi
+++ b/doc/gpgsm.texi
@@ -1,1629 +1,1630 @@
@c Copyright (C) 2002 Free Software Foundation, Inc.
@c This is part of the GnuPG manual.
@c For copying conditions, see the file gnupg.texi.
@include defs.inc
@node Invoking GPGSM
@chapter Invoking GPGSM
@cindex GPGSM command options
@cindex command options
@cindex options, GPGSM command
@manpage gpgsm.1
@ifset manverb
.B gpgsm
\- CMS encryption and signing tool
@end ifset
@mansect synopsis
@ifset manverb
.B gpgsm
.RB [ \-\-homedir
.IR dir ]
.RB [ \-\-options
.IR file ]
.RI [ options ]
.I command
.RI [ args ]
@end ifset
@mansect description
@command{gpgsm} is a tool similar to @command{gpg} to provide digital
encryption and signing services on X.509 certificates and the CMS
protocol. It is mainly used as a backend for S/MIME mail processing.
@command{gpgsm} includes a full featured certificate management and
complies with all rules defined for the German Sphinx project.
@manpause
@xref{Option Index}, for an index to @command{GPGSM}'s commands and options.
@mancont
@menu
* GPGSM Commands:: List of all commands.
* GPGSM Options:: List of all options.
* GPGSM Configuration:: Configuration files.
* GPGSM Examples:: Some usage examples.
Developer information:
* Unattended Usage:: Using @command{gpgsm} from other programs.
* GPGSM Protocol:: The protocol the server mode uses.
@end menu
@c *******************************************
@c *************** ****************
@c *************** COMMANDS ****************
@c *************** ****************
@c *******************************************
@mansect commands
@node GPGSM Commands
@section Commands
Commands are not distinguished from options except for the fact that
only one command is allowed.
@menu
* General GPGSM Commands:: Commands not specific to the functionality.
* Operational GPGSM Commands:: Commands to select the type of operation.
* Certificate Management:: How to manage certificates.
@end menu
@c *******************************************
@c ********** GENERAL COMMANDS *************
@c *******************************************
@node General GPGSM Commands
@subsection Commands not specific to the function
@table @gnupgtabopt
@item --version
@opindex version
Print the program version and licensing information. Note that you
cannot abbreviate this command.
@item --help, -h
@opindex help
Print a usage message summarizing the most useful command-line options.
Note that you cannot abbreviate this command.
@item --warranty
@opindex warranty
Print warranty information. Note that you cannot abbreviate this
command.
@item --dump-options
@opindex dump-options
Print a list of all available options and commands. Note that you cannot
abbreviate this command.
@end table
@c *******************************************
@c ******** OPERATIONAL COMMANDS ***********
@c *******************************************
@node Operational GPGSM Commands
@subsection Commands to select the type of operation
@table @gnupgtabopt
@item --encrypt
@opindex encrypt
Perform an encryption. The keys the data is encrypted to must be set
using the option @option{--recipient}.
@item --decrypt
@opindex decrypt
Perform a decryption; the type of input is automatically determined. It
may either be in binary form or PEM encoded; automatic determination of
base-64 encoding is not done.
@item --sign
@opindex sign
Create a digital signature. The key used is either the fist one found
in the keybox or those set with the @option{--local-user} option.
@item --verify
@opindex verify
Check a signature file for validity. Depending on the arguments a
detached signature may also be checked.
@item --server
@opindex server
Run in server mode and wait for commands on the @code{stdin}.
@item --call-dirmngr @var{command} [@var{args}]
@opindex call-dirmngr
Behave as a Dirmngr client issuing the request @var{command} with the
optional list of @var{args}. The output of the Dirmngr is printed
stdout. Please note that file names given as arguments should have an
absolute file name (i.e. commencing with @code{/}) because they are
passed verbatim to the Dirmngr and the working directory of the
Dirmngr might not be the same as the one of this client. Currently it
is not possible to pass data via stdin to the Dirmngr. @var{command}
should not contain spaces.
This is command is required for certain maintaining tasks of the dirmngr
where a dirmngr must be able to call back to @command{gpgsm}. See the Dirmngr
manual for details.
@item --call-protect-tool @var{arguments}
@opindex call-protect-tool
Certain maintenance operations are done by an external program call
@command{gpg-protect-tool}; this is usually not installed in a directory
listed in the PATH variable. This command provides a simple wrapper to
access this tool. @var{arguments} are passed verbatim to this command;
use @samp{--help} to get a list of supported operations.
@end table
@c *******************************************
@c ******* CERTIFICATE MANAGEMENT **********
@c *******************************************
@node Certificate Management
@subsection How to manage the certificates and keys
@table @gnupgtabopt
@item --generate-key
@opindex generate-key
@itemx --gen-key
@opindex gen-key
This command allows the creation of a certificate signing request or a
self-signed certificate. It is commonly used along with the
@option{--output} option to save the created CSR or certificate into a
file. If used with the @option{--batch} a parameter file is used to
create the CSR or certificate and it is further possible to create
non-self-signed certificates.
@item --list-keys
@itemx -k
@opindex list-keys
List all available certificates stored in the local key database.
Note that the displayed data might be reformatted for better human
readability and illegal characters are replaced by safe substitutes.
@item --list-secret-keys
@itemx -K
@opindex list-secret-keys
List all available certificates for which a corresponding a secret key
is available.
@item --list-external-keys @var{pattern}
@opindex list-keys
List certificates matching @var{pattern} using an external server. This
utilizes the @code{dirmngr} service.
@item --list-chain
@opindex list-chain
Same as @option{--list-keys} but also prints all keys making up the chain.
@item --dump-cert
@itemx --dump-keys
@opindex dump-cert
@opindex dump-keys
List all available certificates stored in the local key database using a
format useful mainly for debugging.
@item --dump-chain
@opindex dump-chain
Same as @option{--dump-keys} but also prints all keys making up the chain.
@item --dump-secret-keys
@opindex dump-secret-keys
List all available certificates for which a corresponding a secret key
is available using a format useful mainly for debugging.
@item --dump-external-keys @var{pattern}
@opindex dump-external-keys
List certificates matching @var{pattern} using an external server.
This utilizes the @code{dirmngr} service. It uses a format useful
mainly for debugging.
@item --keydb-clear-some-cert-flags
@opindex keydb-clear-some-cert-flags
This is a debugging aid to reset certain flags in the key database
which are used to cache certain certificate stati. It is especially
useful if a bad CRL or a weird running OCSP responder did accidentally
revoke certificate. There is no security issue with this command
because @command{gpgsm} always make sure that the validity of a certificate is
checked right before it is used.
@item --delete-keys @var{pattern}
@opindex delete-keys
Delete the keys matching @var{pattern}. Note that there is no command
to delete the secret part of the key directly. In case you need to do
this, you should run the command @code{gpgsm --dump-secret-keys KEYID}
before you delete the key, copy the string of hex-digits in the
``keygrip'' line and delete the file consisting of these hex-digits
and the suffix @code{.key} from the @file{private-keys-v1.d} directory
below our GnuPG home directory (usually @file{~/.gnupg}).
@item --export [@var{pattern}]
@opindex export
Export all certificates stored in the Keybox or those specified by the
optional @var{pattern}. Those pattern consist of a list of user ids
(@pxref{how-to-specify-a-user-id}). When used along with the
@option{--armor} option a few informational lines are prepended before
each block. There is one limitation: As there is no commonly agreed
upon way to pack more than one certificate into an ASN.1 structure,
the binary export (i.e. without using @option{armor}) works only for
the export of one certificate. Thus it is required to specify a
@var{pattern} which yields exactly one certificate. Ephemeral
certificate are only exported if all @var{pattern} are given as
fingerprints or keygrips.
@item --export-secret-key-p12 @var{key-id}
@opindex export-secret-key-p12
Export the private key and the certificate identified by @var{key-id}
using the PKCS#12 format. When used with the @code{--armor} option a few
informational lines are prepended to the output. Note, that the PKCS#12
format is not very secure and proper transport security should be used
to convey the exported key. (@xref{option --p12-charset}.)
@item --export-secret-key-p8 @var{key-id}
@itemx --export-secret-key-raw @var{key-id}
@opindex export-secret-key-p8
@opindex export-secret-key-raw
Export the private key of the certificate identified by @var{key-id}
with any encryption stripped. The @code{...-raw} command exports in
PKCS#1 format; the @code{...-p8} command exports in PKCS#8 format.
When used with the @code{--armor} option a few informational lines are
prepended to the output. These commands are useful to prepare a key
for use on a TLS server.
@item --import [@var{files}]
@opindex import
Import the certificates from the PEM or binary encoded files as well as
from signed-only messages. This command may also be used to import a
secret key from a PKCS#12 file.
@item --learn-card
@opindex learn-card
Read information about the private keys from the smartcard and import
the certificates from there. This command utilizes the @command{gpg-agent}
and in turn the @command{scdaemon}.
@item --change-passphrase @var{user_id}
@opindex change-passphrase
@itemx --passwd @var{user_id}
@opindex passwd
Change the passphrase of the private key belonging to the certificate
specified as @var{user_id}. Note, that changing the passphrase/PIN of a
smartcard is not yet supported.
@end table
@c *******************************************
@c *************** ****************
@c *************** OPTIONS ****************
@c *************** ****************
@c *******************************************
@mansect options
@node GPGSM Options
@section Option Summary
@command{GPGSM} features a bunch of options to control the exact behaviour
and to change the default configuration.
@menu
* Configuration Options:: How to change the configuration.
* Certificate Options:: Certificate related options.
* Input and Output:: Input and Output.
* CMS Options:: How to change how the CMS is created.
* Esoteric Options:: Doing things one usually do not want to do.
@end menu
@c *******************************************
@c ******** CONFIGURATION OPTIONS **********
@c *******************************************
@node Configuration Options
@subsection How to change the configuration
These options are used to change the configuration and are usually found
in the option file.
@table @gnupgtabopt
@anchor{gpgsm-option --options}
@item --options @var{file}
@opindex options
Reads configuration from @var{file} instead of from the default
per-user configuration file. The default configuration file is named
@file{gpgsm.conf} and expected in the @file{.gnupg} directory directly
below the home directory of the user.
@include opt-homedir.texi
@item -v
@item --verbose
@opindex v
@opindex verbose
Outputs additional information while running.
You can increase the verbosity by giving several
verbose commands to @command{gpgsm}, such as @samp{-vv}.
@item --policy-file @var{filename}
@opindex policy-file
-Change the default name of the policy file to @var{filename}.
+Change the default name of the policy file to @var{filename}. The
+default name is @file{policies.txt}.
@item --agent-program @var{file}
@opindex agent-program
Specify an agent program to be used for secret key operations. The
default value is determined by running the command @command{gpgconf}.
Note that the pipe symbol (@code{|}) is used for a regression test
suite hack and may thus not be used in the file name.
@item --dirmngr-program @var{file}
@opindex dirmngr-program
Specify a dirmngr program to be used for @acronym{CRL} checks. The
default value is @file{@value{BINDIR}/dirmngr}.
@item --prefer-system-dirmngr
@opindex prefer-system-dirmngr
This option is obsolete and ignored.
@item --disable-dirmngr
Entirely disable the use of the Dirmngr.
@item --no-autostart
@opindex no-autostart
Do not start the gpg-agent or the dirmngr if it has not yet been
started and its service is required. This option is mostly useful on
machines where the connection to gpg-agent has been redirected to
another machines. If dirmngr is required on the remote machine, it
may be started manually using @command{gpgconf --launch dirmngr}.
@item --no-secmem-warning
@opindex no-secmem-warning
Do not print a warning when the so called "secure memory" cannot be used.
@item --log-file @var{file}
@opindex log-file
When running in server mode, append all logging output to @var{file}.
Use @file{socket://} to log to socket.
@end table
@c *******************************************
@c ******** CERTIFICATE OPTIONS ************
@c *******************************************
@node Certificate Options
@subsection Certificate related options
@table @gnupgtabopt
@item --enable-policy-checks
@itemx --disable-policy-checks
@opindex enable-policy-checks
@opindex disable-policy-checks
By default policy checks are enabled. These options may be used to
change it.
@item --enable-crl-checks
@itemx --disable-crl-checks
@opindex enable-crl-checks
@opindex disable-crl-checks
By default the @acronym{CRL} checks are enabled and the DirMngr is used
to check for revoked certificates. The disable option is most useful
with an off-line network connection to suppress this check.
@item --enable-trusted-cert-crl-check
@itemx --disable-trusted-cert-crl-check
@opindex enable-trusted-cert-crl-check
@opindex disable-trusted-cert-crl-check
By default the @acronym{CRL} for trusted root certificates are checked
like for any other certificates. This allows a CA to revoke its own
certificates voluntary without the need of putting all ever issued
certificates into a CRL. The disable option may be used to switch this
extra check off. Due to the caching done by the Dirmngr, there will not be
any noticeable performance gain. Note, that this also disables possible
OCSP checks for trusted root certificates. A more specific way of
disabling this check is by adding the ``relax'' keyword to the root CA
line of the @file{trustlist.txt}
@item --force-crl-refresh
@opindex force-crl-refresh
Tell the dirmngr to reload the CRL for each request. For better
performance, the dirmngr will actually optimize this by suppressing
the loading for short time intervals (e.g. 30 minutes). This option
is useful to make sure that a fresh CRL is available for certificates
hold in the keybox. The suggested way of doing this is by using it
along with the option @option{--with-validation} for a key listing
command. This option should not be used in a configuration file.
@item --enable-ocsp
@itemx --disable-ocsp
@opindex enable-ocsp
@opindex disable-ocsp
By default @acronym{OCSP} checks are disabled. The enable option may
be used to enable OCSP checks via Dirmngr. If @acronym{CRL} checks
are also enabled, CRLs will be used as a fallback if for some reason an
OCSP request will not succeed. Note, that you have to allow OCSP
requests in Dirmngr's configuration too (option
@option{--allow-ocsp}) and configure Dirmngr properly. If you do not do
so you will get the error code @samp{Not supported}.
@item --auto-issuer-key-retrieve
@opindex auto-issuer-key-retrieve
If a required certificate is missing while validating the chain of
certificates, try to load that certificate from an external location.
This usually means that Dirmngr is employed to search for the
certificate. Note that this option makes a "web bug" like behavior
possible. LDAP server operators can see which keys you request, so by
sending you a message signed by a brand new key (which you naturally
will not have on your local keybox), the operator can tell both your IP
address and the time when you verified the signature.
@anchor{gpgsm-option --validation-model}
@item --validation-model @var{name}
@opindex validation-model
This option changes the default validation model. The only possible
values are "shell" (which is the default), "chain" which forces the
use of the chain model and "steed" for a new simplified model. The
chain model is also used if an option in the @file{trustlist.txt} or
an attribute of the certificate requests it. However the standard
model (shell) is in that case always tried first.
@item --ignore-cert-extension @var{oid}
@opindex ignore-cert-extension
Add @var{oid} to the list of ignored certificate extensions. The
@var{oid} is expected to be in dotted decimal form, like
@code{2.5.29.3}. This option may be used more than once. Critical
flagged certificate extensions matching one of the OIDs in the list
are treated as if they are actually handled and thus the certificate
will not be rejected due to an unknown critical extension. Use this
option with care because extensions are usually flagged as critical
for a reason.
@end table
@c *******************************************
@c *********** INPUT AND OUTPUT ************
@c *******************************************
@node Input and Output
@subsection Input and Output
@table @gnupgtabopt
@item --armor
@itemx -a
@opindex armor
Create PEM encoded output. Default is binary output.
@item --base64
@opindex base64
Create Base-64 encoded output; i.e. PEM without the header lines.
@item --assume-armor
@opindex assume-armor
Assume the input data is PEM encoded. Default is to autodetect the
encoding but this is may fail.
@item --assume-base64
@opindex assume-base64
Assume the input data is plain base-64 encoded.
@item --assume-binary
@opindex assume-binary
Assume the input data is binary encoded.
@anchor{option --p12-charset}
@item --p12-charset @var{name}
@opindex p12-charset
@command{gpgsm} uses the UTF-8 encoding when encoding passphrases for
PKCS#12 files. This option may be used to force the passphrase to be
encoded in the specified encoding @var{name}. This is useful if the
application used to import the key uses a different encoding and thus
will not be able to import a file generated by @command{gpgsm}. Commonly
used values for @var{name} are @code{Latin1} and @code{CP850}. Note
that @command{gpgsm} itself automagically imports any file with a
passphrase encoded to the most commonly used encodings.
@item --default-key @var{user_id}
@opindex default-key
Use @var{user_id} as the standard key for signing. This key is used if
no other key has been defined as a signing key. Note, that the first
@option{--local-users} option also sets this key if it has not yet been
set; however @option{--default-key} always overrides this.
@item --local-user @var{user_id}
@item -u @var{user_id}
@opindex local-user
Set the user(s) to be used for signing. The default is the first
secret key found in the database.
@item --recipient @var{name}
@itemx -r
@opindex recipient
Encrypt to the user id @var{name}. There are several ways a user id
may be given (@pxref{how-to-specify-a-user-id}).
@item --output @var{file}
@itemx -o @var{file}
@opindex output
Write output to @var{file}. The default is to write it to stdout.
@anchor{gpgsm-option --with-key-data}
@item --with-key-data
@opindex with-key-data
Displays extra information with the @code{--list-keys} commands. Especially
a line tagged @code{grp} is printed which tells you the keygrip of a
key. This string is for example used as the file name of the
secret key. Implies @code{--with-colons}.
@anchor{gpgsm-option --with-validation}
@item --with-validation
@opindex with-validation
When doing a key listing, do a full validation check for each key and
print the result. This is usually a slow operation because it
requires a CRL lookup and other operations.
When used along with @option{--import}, a validation of the certificate to
import is done and only imported if it succeeds the test. Note that
this does not affect an already available certificate in the DB.
This option is therefore useful to simply verify a certificate.
@item --with-md5-fingerprint
For standard key listings, also print the MD5 fingerprint of the
certificate.
@item --with-keygrip
Include the keygrip in standard key listings. Note that the keygrip is
always listed in @option{--with-colons} mode.
@item --with-secret
@opindex with-secret
Include info about the presence of a secret key in public key listings
done with @code{--with-colons}.
@end table
@c *******************************************
@c ************* CMS OPTIONS ***************
@c *******************************************
@node CMS Options
@subsection How to change how the CMS is created
@table @gnupgtabopt
@item --include-certs @var{n}
@opindex include-certs
Using @var{n} of -2 includes all certificate except for the root cert,
-1 includes all certs, 0 does not include any certs, 1 includes only the
signers cert and all other positive values include up to @var{n}
certificates starting with the signer cert. The default is -2.
@item --cipher-algo @var{oid}
@opindex cipher-algo
Use the cipher algorithm with the ASN.1 object identifier @var{oid} for
encryption. For convenience the strings @code{3DES}, @code{AES} and
@code{AES256} may be used instead of their OIDs. The default is
@code{AES} (2.16.840.1.101.3.4.1.2).
@item --digest-algo @code{name}
Use @code{name} as the message digest algorithm. Usually this
algorithm is deduced from the respective signing certificate. This
option forces the use of the given algorithm and may lead to severe
interoperability problems.
@end table
@c *******************************************
@c ******** ESOTERIC OPTIONS ***************
@c *******************************************
@node Esoteric Options
@subsection Doing things one usually do not want to do
@table @gnupgtabopt
@item --extra-digest-algo @var{name}
@opindex extra-digest-algo
Sometimes signatures are broken in that they announce a different digest
algorithm than actually used. @command{gpgsm} uses a one-pass data
processing model and thus needs to rely on the announced digest
algorithms to properly hash the data. As a workaround this option may
be used to tell @command{gpgsm} to also hash the data using the algorithm
@var{name}; this slows processing down a little bit but allows verification of
such broken signatures. If @command{gpgsm} prints an error like
``digest algo 8 has not been enabled'' you may want to try this option,
with @samp{SHA256} for @var{name}.
@item --faked-system-time @var{epoch}
@opindex faked-system-time
This option is only useful for testing; it sets the system time back or
forth to @var{epoch} which is the number of seconds elapsed since the year
1970. Alternatively @var{epoch} may be given as a full ISO time string
(e.g. "20070924T154812").
@item --with-ephemeral-keys
@opindex with-ephemeral-keys
Include ephemeral flagged keys in the output of key listings. Note
that they are included anyway if the key specification for a listing
is given as fingerprint or keygrip.
@item --debug-level @var{level}
@opindex debug-level
Select the debug level for investigating problems. @var{level} may be
a numeric value or by a keyword:
@table @code
@item none
No debugging at all. A value of less than 1 may be used instead of
the keyword.
@item basic
Some basic debug messages. A value between 1 and 2 may be used
instead of the keyword.
@item advanced
More verbose debug messages. A value between 3 and 5 may be used
instead of the keyword.
@item expert
Even more detailed messages. A value between 6 and 8 may be used
instead of the keyword.
@item guru
All of the debug messages you can get. A value greater than 8 may be
used instead of the keyword. The creation of hash tracing files is
only enabled if the keyword is used.
@end table
How these messages are mapped to the actual debugging flags is not
specified and may change with newer releases of this program. They are
however carefully selected to best aid in debugging.
@item --debug @var{flags}
@opindex debug
This option is only useful for debugging and the behaviour may change
at any time without notice; using @code{--debug-levels} is the
preferred method to select the debug verbosity. FLAGS are bit encoded
and may be given in usual C-Syntax. The currently defined bits are:
@table @code
@item 0 (1)
X.509 or OpenPGP protocol related data
@item 1 (2)
values of big number integers
@item 2 (4)
low level crypto operations
@item 5 (32)
memory allocation
@item 6 (64)
caching
@item 7 (128)
show memory statistics
@item 9 (512)
write hashed data to files named @code{dbgmd-000*}
@item 10 (1024)
trace Assuan protocol
@end table
Note, that all flags set using this option may get overridden by
@code{--debug-level}.
@item --debug-all
@opindex debug-all
Same as @code{--debug=0xffffffff}
@item --debug-allow-core-dump
@opindex debug-allow-core-dump
Usually @command{gpgsm} tries to avoid dumping core by well written code and by
disabling core dumps for security reasons. However, bugs are pretty
durable beasts and to squash them it is sometimes useful to have a core
dump. This option enables core dumps unless the Bad Thing happened
before the option parsing.
@item --debug-no-chain-validation
@opindex debug-no-chain-validation
This is actually not a debugging option but only useful as such. It
lets @command{gpgsm} bypass all certificate chain validation checks.
@item --debug-ignore-expiration
@opindex debug-ignore-expiration
This is actually not a debugging option but only useful as such. It
lets @command{gpgsm} ignore all notAfter dates, this is used by the regression
tests.
@item --passphrase-fd @code{n}
@opindex passphrase-fd
Read the passphrase from file descriptor @code{n}. Only the first line
will be read from file descriptor @code{n}. If you use 0 for @code{n},
the passphrase will be read from STDIN. This can only be used if only
one passphrase is supplied.
Note that this passphrase is only used if the option @option{--batch}
has also been given.
@item --pinentry-mode @code{mode}
@opindex pinentry-mode
Set the pinentry mode to @code{mode}. Allowed values for @code{mode}
are:
@table @asis
@item default
Use the default of the agent, which is @code{ask}.
@item ask
Force the use of the Pinentry.
@item cancel
Emulate use of Pinentry's cancel button.
@item error
Return a Pinentry error (``No Pinentry'').
@item loopback
Redirect Pinentry queries to the caller. Note that in contrast to
Pinentry the user is not prompted again if he enters a bad password.
@end table
@item --request-origin @var{origin}
@opindex request-origin
Tell gpgsm to assume that the operation ultimately originated at
@var{origin}. Depending on the origin certain restrictions are applied
and the Pinentry may include an extra note on the origin. Supported
values for @var{origin} are: @code{local} which is the default,
@code{remote} to indicate a remote origin or @code{browser} for an
operation requested by a web browser.
@item --no-common-certs-import
@opindex no-common-certs-import
Suppress the import of common certificates on keybox creation.
@end table
All the long options may also be given in the configuration file after
stripping off the two leading dashes.
@c *******************************************
@c *************** ****************
@c *************** USER ID ****************
@c *************** ****************
@c *******************************************
@mansect how to specify a user id
@ifset isman
@include specify-user-id.texi
@end ifset
@c *******************************************
@c *************** ****************
@c *************** FILES ****************
@c *************** ****************
@c *******************************************
@mansect files
@node GPGSM Configuration
@section Configuration files
There are a few configuration files to control certain aspects of
@command{gpgsm}'s operation. Unless noted, they are expected in the
current home directory (@pxref{option --homedir}).
@table @file
@item gpgsm.conf
@efindex gpgsm.conf
This is the standard configuration file read by @command{gpgsm} on
startup. It may contain any valid long option; the leading two dashes
may not be entered and the option may not be abbreviated. This default
name may be changed on the command line (@pxref{gpgsm-option --options}).
You should backup this file.
@item policies.txt
@efindex policies.txt
This is a list of allowed CA policies. This file should list the
object identifiers of the policies line by line. Empty lines and
lines starting with a hash mark are ignored. Policies missing in this
file and not marked as critical in the certificate will print only a
warning; certificates with policies marked as critical and not listed
in this file will fail the signature verification. You should backup
this file.
For example, to allow only the policy 2.289.9.9, the file should look
like this:
@c man:.RS
@example
# Allowed policies
2.289.9.9
@end example
@c man:.RE
@item qualified.txt
@efindex qualified.txt
This is the list of root certificates used for qualified certificates.
They are defined as certificates capable of creating legally binding
signatures in the same way as handwritten signatures are. Comments
start with a hash mark and empty lines are ignored. Lines do have a
length limit but this is not a serious limitation as the format of the
entries is fixed and checked by @command{gpgsm}: A non-comment line starts with
optional whitespace, followed by exactly 40 hex characters, white space
and a lowercased 2 letter country code. Additional data delimited with
by a white space is current ignored but might late be used for other
purposes.
Note that even if a certificate is listed in this file, this does not
mean that the certificate is trusted; in general the certificates listed
in this file need to be listed also in @file{trustlist.txt}. This is a global
file an installed in the sysconf directory (e.g.
@file{@value{SYSCONFDIR}/qualified.txt}).
Every time @command{gpgsm} uses a certificate for signing or verification
this file will be consulted to check whether the certificate under
question has ultimately been issued by one of these CAs. If this is the
case the user will be informed that the verified signature represents a
legally binding (``qualified'') signature. When creating a signature
using such a certificate an extra prompt will be issued to let the user
confirm that such a legally binding signature shall really be created.
Because this software has not yet been approved for use with such
certificates, appropriate notices will be shown to indicate this fact.
@item help.txt
@efindex help.txt
This is plain text file with a few help entries used with
@command{pinentry} as well as a large list of help items for
@command{gpg} and @command{gpgsm}. The standard file has English help
texts; to install localized versions use filenames like @file{help.LL.txt}
with LL denoting the locale. GnuPG comes with a set of predefined help
files in the data directory (e.g. @file{@value{DATADIR}/gnupg/help.de.txt})
and allows overriding of any help item by help files stored in the
system configuration directory (e.g. @file{@value{SYSCONFDIR}/help.de.txt}).
For a reference of the help file's syntax, please see the installed
@file{help.txt} file.
@item com-certs.pem
@efindex com-certs.pem
This file is a collection of common certificates used to populated a
newly created @file{pubring.kbx}. An administrator may replace this
file with a custom one. The format is a concatenation of PEM encoded
X.509 certificates. This global file is installed in the data directory
(e.g. @file{@value{DATADIR}/com-certs.pem}).
@end table
@c man:.RE
Note that on larger installations, it is useful to put predefined files
into the directory @file{/etc/skel/.gnupg/} so that newly created users
start up with a working configuration. For existing users a small
helper script is provided to create these files (@pxref{addgnupghome}).
For internal purposes @command{gpgsm} creates and maintains a few other files;
they all live in the current home directory (@pxref{option
--homedir}). Only @command{gpgsm} may modify these files.
@table @file
@item pubring.kbx
@efindex pubring.kbx
This a database file storing the certificates as well as meta
information. For debugging purposes the tool @command{kbxutil} may be
used to show the internal structure of this file. You should backup
this file.
@item random_seed
@efindex random_seed
This content of this file is used to maintain the internal state of the
random number generator across invocations. The same file is used by
other programs of this software too.
@item S.gpg-agent
@efindex S.gpg-agent
If this file exists
@command{gpgsm} will first try to connect to this socket for
accessing @command{gpg-agent} before starting a new @command{gpg-agent}
instance. Under Windows this socket (which in reality be a plain file
describing a regular TCP listening port) is the standard way of
connecting the @command{gpg-agent}.
@end table
@c *******************************************
@c *************** ****************
@c *************** EXAMPLES ****************
@c *************** ****************
@c *******************************************
@mansect examples
@node GPGSM Examples
@section Examples
@example
$ gpgsm -er goo@@bar.net <plaintext >ciphertext
@end example
@c *******************************************
@c *************** **************
@c *************** UNATTENDED **************
@c *************** **************
@c *******************************************
@manpause
@node Unattended Usage
@section Unattended Usage
@command{gpgsm} is often used as a backend engine by other software. To help
with this a machine interface has been defined to have an unambiguous
way to do this. This is most likely used with the @code{--server} command
but may also be used in the standard operation mode by using the
@code{--status-fd} option.
@menu
* Automated signature checking:: Automated signature checking.
* CSR and certificate creation:: CSR and certificate creation.
@end menu
@node Automated signature checking
@subsection Automated signature checking
It is very important to understand the semantics used with signature
verification. Checking a signature is not as simple as it may sound and
so the operation is a bit complicated. In most cases it is required
to look at several status lines. Here is a table of all cases a signed
message may have:
@table @asis
@item The signature is valid
This does mean that the signature has been successfully verified, the
certificates are all sane. However there are two subcases with
important information: One of the certificates may have expired or a
signature of a message itself as expired. It is a sound practise to
consider such a signature still as valid but additional information
should be displayed. Depending on the subcase @command{gpgsm} will issue
these status codes:
@table @asis
@item signature valid and nothing did expire
@code{GOODSIG}, @code{VALIDSIG}, @code{TRUST_FULLY}
@item signature valid but at least one certificate has expired
@code{EXPKEYSIG}, @code{VALIDSIG}, @code{TRUST_FULLY}
@item signature valid but expired
@code{EXPSIG}, @code{VALIDSIG}, @code{TRUST_FULLY}
Note, that this case is currently not implemented.
@end table
@item The signature is invalid
This means that the signature verification failed (this is an indication
of a transfer error, a program error or tampering with the message).
@command{gpgsm} issues one of these status codes sequences:
@table @code
@item @code{BADSIG}
@item @code{GOODSIG}, @code{VALIDSIG} @code{TRUST_NEVER}
@end table
@item Error verifying a signature
For some reason the signature could not be verified, i.e. it cannot be
decided whether the signature is valid or invalid. A common reason for
this is a missing certificate.
@end table
@node CSR and certificate creation
@subsection CSR and certificate creation
The command @option{--generate-key} may be used along with the option
@option{--batch} to either create a certificate signing request (CSR)
or an X.509 certificate. This is controlled by a parameter file; the
format of this file is as follows:
@itemize @bullet
@item Text only, line length is limited to about 1000 characters.
@item UTF-8 encoding must be used to specify non-ASCII characters.
@item Empty lines are ignored.
@item Leading and trailing while space is ignored.
@item A hash sign as the first non white space character indicates
a comment line.
@item Control statements are indicated by a leading percent sign, the
arguments are separated by white space from the keyword.
@item Parameters are specified by a keyword, followed by a colon. Arguments
are separated by white space.
@item The first parameter must be @samp{Key-Type}, control statements
may be placed anywhere.
@item
The order of the parameters does not matter except for @samp{Key-Type}
which must be the first parameter. The parameters are only used for
the generated CSR/certificate; parameters from previous sets are not
used. Some syntactically checks may be performed.
@item
Key generation takes place when either the end of the parameter file
is reached, the next @samp{Key-Type} parameter is encountered or at the
control statement @samp{%commit} is encountered.
@end itemize
@noindent
Control statements:
@table @asis
@item %echo @var{text}
Print @var{text} as diagnostic.
@item %dry-run
Suppress actual key generation (useful for syntax checking).
@item %commit
Perform the key generation. Note that an implicit commit is done at
the next @asis{Key-Type} parameter.
@c %certfile <filename>
@c [Not yet implemented!]
@c Do not write the certificate to the keyDB but to <filename>.
@c This must be given before the first
@c commit to take place, duplicate specification of the same filename
@c is ignored, the last filename before a commit is used.
@c The filename is used until a new filename is used (at commit points)
@c and all keys are written to that file. If a new filename is given,
@c this file is created (and overwrites an existing one).
@c Both control statements must be given.
@end table
@noindent
General Parameters:
@table @asis
@item Key-Type: @var{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. The only supported value for @var{algo} is @samp{rsa}.
@item Key-Length: @var{nbits}
The requested length of a generated key in bits. Defaults to 3072.
@item Key-Grip: @var{hexstring}
This is optional and used to generate a CSR or certificate for an
already existing key. Key-Length will be ignored when given.
@item Key-Usage: @var{usage-list}
Space or comma delimited list of key usage, allowed values are
@samp{encrypt}, @samp{sign} and @samp{cert}. 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.
@item Name-DN: @var{subject-name}
This is the Distinguished Name (DN) of the subject in RFC-2253 format.
@item Name-Email: @var{string}
This is an email address for the altSubjectName. This parameter is
optional but may occur several times to add several email addresses to
a certificate.
@item Name-DNS: @var{string}
The is an DNS name for the altSubjectName. This parameter is optional
but may occur several times to add several DNS names to a certificate.
@item Name-URI: @var{string}
This is an URI for the altSubjectName. This parameter is optional but
may occur several times to add several URIs to a certificate.
@end table
@noindent
Additional parameters used to create a certificate (in contrast to a
certificate signing request):
@table @asis
@item Serial: @var{sn}
If this parameter is given an X.509 certificate will be generated.
@var{sn} is expected to be a hex string representing an unsigned
integer of arbitrary length. The special value @samp{random} can be
used to create a 64 bit random serial number.
@item Issuer-DN: @var{issuer-name}
This is the DN name of the issuer in RFC-2253 format. If it is not set
it will default to the subject DN and a special GnuPG extension will
be included in the certificate to mark it as a standalone certificate.
@item Creation-Date: @var{iso-date}
@itemx Not-Before: @var{iso-date}
Set the notBefore date of the certificate. Either a date like
@samp{1986-04-26} or @samp{1986-04-26 12:00} or a standard ISO
timestamp like @samp{19860426T042640} may be used. The time is
considered to be UTC. If it is not given the current date is used.
@item Expire-Date: @var{iso-date}
@itemx Not-After: @var{iso-date}
Set the notAfter date of the certificate. Either a date like
@samp{2063-04-05} or @samp{2063-04-05 17:00} or a standard ISO
timestamp like @samp{20630405T170000} may be used. The time is
considered to be UTC. If it is not given a default value in the not
too far future is used.
@item Signing-Key: @var{keygrip}
This gives the keygrip of the key used to sign the certificate. If it
is not given a self-signed certificate will be created. For
compatibility with future versions, it is suggested to prefix the
keygrip with a @samp{&}.
@item Hash-Algo: @var{hash-algo}
Use @var{hash-algo} for this CSR or certificate. The supported hash
algorithms are: @samp{sha1}, @samp{sha256}, @samp{sha384} and
@samp{sha512}; they may also be specified with uppercase letters. The
default is @samp{sha256}.
@end table
@c *******************************************
@c *************** *****************
@c *************** ASSSUAN *****************
@c *************** *****************
@c *******************************************
@node GPGSM Protocol
@section The Protocol the Server Mode Uses
Description of the protocol used to access @command{GPGSM}.
@command{GPGSM} does implement the Assuan protocol and in addition
provides a regular command line interface which exhibits a full client
to this protocol (but uses internal linking). To start
@command{gpgsm} as a server the command line the option
@code{--server} must be used. Additional options are provided to
select the communication method (i.e. the name of the socket).
We assume that the connection has already been established; see the
Assuan manual for details.
@menu
* GPGSM ENCRYPT:: Encrypting a message.
* GPGSM DECRYPT:: Decrypting a message.
* GPGSM SIGN:: Signing a message.
* GPGSM VERIFY:: Verifying a message.
* GPGSM GENKEY:: Generating a key.
* GPGSM LISTKEYS:: List available keys.
* GPGSM EXPORT:: Export certificates.
* GPGSM IMPORT:: Import certificates.
* GPGSM DELETE:: Delete certificates.
* GPGSM GETAUDITLOG:: Retrieve an audit log.
* GPGSM GETINFO:: Information about the process
* GPGSM OPTION:: Session options.
@end menu
@node GPGSM ENCRYPT
@subsection Encrypting a Message
Before encryption can be done the recipient must be set using the
command:
@example
RECIPIENT @var{userID}
@end example
Set the recipient for the encryption. @var{userID} should be the
internal representation of the key; the server may accept any other way
of specification. 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 cannot 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
@code{RECIPIENT} commands are cumulative until a @code{RESET} or an
successful @code{ENCRYPT} command.
@example
INPUT FD[=@var{n}] [--armor|--base64|--binary]
@end example
Set the file descriptor for the message to be encrypted to @var{n}.
Obviously the pipe must be open at that point, the server establishes
its own end. If the server returns an error the client should consider
this session failed. If @var{n} is not given, this commands uses the
last file descriptor passed to the application.
@xref{fun-assuan_sendfd, ,the assuan_sendfd function,assuan,the Libassuan
manual}, on how to do descriptor passing.
The @code{--armor} option may be used to advice the server that the
input data is in @acronym{PEM} format, @code{--base64} advices that a
raw base-64 encoding is used, @code{--binary} advices of raw binary
input (@acronym{BER}). If none of these options is used, the server
tries to figure out the used encoding, but this may not always be
correct.
@example
OUTPUT FD[=@var{n}] [--armor|--base64]
@end example
Set the file descriptor to be used for the output (i.e. the encrypted
message). Obviously the pipe must be open at that point, the server
establishes its own end. If the server returns an error the client
should consider this session failed.
The option @option{--armor} encodes the output in @acronym{PEM} format, the
@option{--base64} option applies just a base-64 encoding. No option
creates binary output (@acronym{BER}).
The actual encryption is done using the command
@example
ENCRYPT
@end example
It takes the plaintext from the @code{INPUT} command, writes to the
ciphertext to the file descriptor set with the @code{OUTPUT} command,
take the recipients from 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. @command{GPGSM} does ensure that there
will not 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.
@node GPGSM DECRYPT
@subsection Decrypting a message
Input and output FDs are set the same way as in encryption, but
@code{INPUT} refers to the ciphertext and @code{OUTPUT} to the plaintext. There
is no need to set recipients. @command{GPGSM} automatically strips any
@acronym{S/MIME} headers from the input, so it is valid to pass an
entire MIME part to the INPUT pipe.
The decryption is done by using the command
@example
DECRYPT
@end example
It performs the decrypt operation after doing some check on the internal
state (e.g. that all 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 takes care of this by
requesting this from the user.
@node GPGSM SIGN
@subsection Signing a Message
Signing is usually done with these commands:
@example
INPUT FD[=@var{n}] [--armor|--base64|--binary]
@end example
This tells @command{GPGSM} to read the data to sign from file descriptor @var{n}.
@example
OUTPUT FD[=@var{m}] [--armor|--base64]
@end example
Write the output to file descriptor @var{m}. If a detached signature is
requested, only the signature is written.
@example
SIGN [--detached]
@end example
Sign the data set with the @code{INPUT} command and write it to the sink set by
@code{OUTPUT}. With @code{--detached}, a detached signature is created
(surprise).
The key used for signing is the default one or the one specified in
the configuration file. To get finer control over the keys, it is
possible to use the command
@example
SIGNER @var{userID}
@end example
to set the signer's key. @var{userID} should be the
internal representation of the key; the server may accept any other way
of specification. 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 key cannot be used, the signature will then not be created using
this key. If the policy is not to sign at all if not all
keys are valid, the client has to take care of this. All
@code{SIGNER} commands are cumulative until a @code{RESET} is done.
Note that a @code{SIGN} does not reset this list of signers which is in
contrast to the @code{RECIPIENT} command.
@node GPGSM VERIFY
@subsection Verifying a Message
To verify a message the command:
@example
VERIFY
@end example
is used. It 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.
@node GPGSM GENKEY
@subsection Generating a Key
This is used to generate a new keypair, store the secret part in the
@acronym{PSE} and the public key in the key database. We will probably
add optional commands to allow the client to select whether a hardware
token is used to store the key. Configuration options to
@command{GPGSM} can be used to restrict the use of this command.
@example
GENKEY
@end example
@command{GPGSM} checks whether this command is allowed and then does an
INQUIRY to get the key parameters, the client should then send the
key parameters in the native format:
@example
S: INQUIRE KEY_PARAM native
C: D foo:fgfgfg
C: D bar
C: END
@end example
Please note that the server may send Status info lines while reading the
data lines from the client. After this the key generation takes place
and the server eventually does send an ERR or OK response. Status lines
may be issued as a progress indicator.
@node GPGSM LISTKEYS
@subsection List available keys
@anchor{gpgsm-cmd listkeys}
To list the keys in the internal database or using an external key
provider, the command:
@example
LISTKEYS @var{pattern}
@end example
is used. To allow multiple patterns (which are ORed during the search)
quoting is required: Spaces are to be translated into "+" or into "%20";
in turn this requires that the usual escape quoting rules are done.
@example
LISTSECRETKEYS @var{pattern}
@end example
Lists only the keys where a secret key is available.
The list commands are affected by the option
@example
OPTION list-mode=@var{mode}
@end example
where mode may be:
@table @code
@item 0
Use default (which is usually the same as 1).
@item 1
List only the internal keys.
@item 2
List only the external keys.
@item 3
List internal and external keys.
@end table
Note that options are valid for the entire session.
@node GPGSM EXPORT
@subsection Export certificates
To export certificate from the internal key database the command:
@example
EXPORT [--data [--armor] [--base64]] [--] @var{pattern}
@end example
is used. To allow multiple patterns (which are ORed) quoting is
required: Spaces are to be translated into "+" or into "%20"; in turn
this requires that the usual escape quoting rules are done.
If the @option{--data} option has not been given, the format of the
output depends on what was set with the @code{OUTPUT} command. When using
@acronym{PEM} encoding a few informational lines are prepended.
If the @option{--data} has been given, a target set via @code{OUTPUT} is
ignored and the data is returned inline using standard
@code{D}-lines. This avoids the need for an extra file descriptor. In
this case the options @option{--armor} and @option{--base64} may be used
in the same way as with the @code{OUTPUT} command.
@node GPGSM IMPORT
@subsection Import certificates
To import certificates into the internal key database, the command
@example
IMPORT [--re-import]
@end example
is used. The data is expected on the file descriptor set with the
@code{INPUT} command. Certain checks are performed on the
certificate. Note that the code will also handle PKCS#12 files and
import private keys; a helper program is used for that.
With the option @option{--re-import} the input data is expected to a be
a linefeed separated list of fingerprints. The command will re-import
the corresponding certificates; that is they are made permanent by
removing their ephemeral flag.
@node GPGSM DELETE
@subsection Delete certificates
To delete a certificate the command
@example
DELKEYS @var{pattern}
@end example
is used. To allow multiple patterns (which are ORed) quoting is
required: Spaces are to be translated into "+" or into "%20"; in turn
this requires that the usual escape quoting rules are done.
The certificates must be specified unambiguously otherwise an error is
returned.
@node GPGSM GETAUDITLOG
@subsection Retrieve an audit log
@anchor{gpgsm-cmd getauditlog}
This command is used to retrieve an audit log.
@example
GETAUDITLOG [--data] [--html]
@end example
If @option{--data} is used, the audit log is send using D-lines
instead of being sent to the file descriptor given by an @code{OUTPUT}
command. If @option{--html} is used, the output is formatted as an
XHTML block. This is designed to be incorporated into a HTML
document.
@node GPGSM GETINFO
@subsection Return information about the process
This is a multipurpose function to return a variety of information.
@example
GETINFO @var{what}
@end example
The value of @var{what} specifies the kind of information returned:
@table @code
@item version
Return the version of the program.
@item pid
Return the process id of the process.
@item agent-check
Return OK if the agent is running.
@item cmd_has_option @var{cmd} @var{opt}
Return OK if the command @var{cmd} implements the option @var{opt}.
The leading two dashes usually used with @var{opt} shall not be given.
@item offline
Return OK if the connection is in offline mode. This may be either
due to a @code{OPTION offline=1} or due to @command{gpgsm} being
started with option @option{--disable-dirmngr}.
@end table
@node GPGSM OPTION
@subsection Session options
The standard Assuan option handler supports these options.
@example
OPTION @var{name}[=@var{value}]
@end example
These @var{name}s are recognized:
@table @code
@item putenv
Change the session's environment to be passed via gpg-agent to
Pinentry. @var{value} is a string of the form
@code{<KEY>[=[<STRING>]]}. If only @code{<KEY>} is given the
environment variable @code{<KEY>} is removed from the session
environment, if @code{<KEY>=} is given that environment variable is
set to the empty string, and if @code{<STRING>} is given it is set to
that string.
@item display
@efindex DISPLAY
Set the session environment variable @code{DISPLAY} is set to @var{value}.
@item ttyname
@efindex GPG_TTY
Set the session environment variable @code{GPG_TTY} is set to @var{value}.
@item ttytype
@efindex TERM
Set the session environment variable @code{TERM} is set to @var{value}.
@item lc-ctype
@efindex LC_CTYPE
Set the session environment variable @code{LC_CTYPE} is set to @var{value}.
@item lc-messages
@efindex LC_MESSAGES
Set the session environment variable @code{LC_MESSAGES} is set to @var{value}.
@item xauthority
@efindex XAUTHORITY
Set the session environment variable @code{XAUTHORITY} is set to @var{value}.
@item pinentry-user-data
@efindex PINENTRY_USER_DATA
Set the session environment variable @code{PINENTRY_USER_DATA} is set
to @var{value}.
@item include-certs
This option overrides the command line option
@option{--include-certs}. A @var{value} of -2 includes all
certificates except for the root certificate, -1 includes all
certificates, 0 does not include any certificates, 1 includes only the
signers certificate and all other positive values include up to
@var{value} certificates starting with the signer cert.
@item list-mode
@xref{gpgsm-cmd listkeys}.
@item list-to-output
If @var{value} is true the output of the list commands
(@pxref{gpgsm-cmd listkeys}) is written to the file descriptor set
with the last @code{OUTPUT} command. If @var{value} is false the output is
written via data lines; this is the default.
@item with-validation
If @var{value} is true for each listed certificate the validation
status is printed. This may result in the download of a CRL or the
user being asked about the trustworthiness of a root certificate. The
default is given by a command line option (@pxref{gpgsm-option
--with-validation}).
@item with-secret
If @var{value} is true certificates with a corresponding private key
are marked by the list commands.
@item validation-model
This option overrides the command line option
@option{validation-model} for the session.
(@xref{gpgsm-option --validation-model}.)
@item with-key-data
This option globally enables the command line option
@option{--with-key-data}. (@xref{gpgsm-option --with-key-data}.)
@item enable-audit-log
If @var{value} is true data to write an audit log is gathered.
(@xref{gpgsm-cmd getauditlog}.)
@item allow-pinentry-notify
If this option is used notifications about the launch of a Pinentry
are passed back to the client.
@item with-ephemeral-keys
If @var{value} is true ephemeral certificates are included in the
output of the list commands.
@item no-encrypt-to
If this option is used all keys set by the command line option
@option{--encrypt-to} are ignored.
@item offline
If @var{value} is true or @var{value} is not given all network access
is disabled for this session. This is the same as the command line
option @option{--disable-dirmngr}.
@end table
@mansect see also
@ifset isman
@command{gpg2}(1),
@command{gpg-agent}(1)
@end ifset
@include see-also-note.texi
diff --git a/doc/scdaemon.texi b/doc/scdaemon.texi
index 81af28105..21c3fd826 100644
--- a/doc/scdaemon.texi
+++ b/doc/scdaemon.texi
@@ -1,770 +1,776 @@
@c Copyright (C) 2002 Free Software Foundation, Inc.
@c This is part of the GnuPG manual.
@c For copying conditions, see the file gnupg.texi.
@include defs.inc
@node Invoking SCDAEMON
@chapter Invoking the SCDAEMON
@cindex SCDAEMON command options
@cindex command options
@cindex options, SCDAEMON command
@manpage scdaemon.1
@ifset manverb
.B scdaemon
\- Smartcard daemon for the GnuPG system
@end ifset
@mansect synopsis
@ifset manverb
.B scdaemon
.RB [ \-\-homedir
.IR dir ]
.RB [ \-\-options
.IR file ]
.RI [ options ]
.B \-\-server
.br
.B scdaemon
.RB [ \-\-homedir
.IR dir ]
.RB [ \-\-options
.IR file ]
.RI [ options ]
.B \-\-daemon
.RI [ command_line ]
@end ifset
@mansect description
The @command{scdaemon} is a daemon to manage smartcards. It is usually
invoked by @command{gpg-agent} and in general not used directly.
@manpause
@xref{Option Index}, for an index to @command{scdaemon}'s commands and
options.
@mancont
@menu
* Scdaemon Commands:: List of all commands.
* Scdaemon Options:: List of all options.
* Card applications:: Description of card applications.
* Scdaemon Configuration:: Configuration files.
* Scdaemon Examples:: Some usage examples.
* Scdaemon Protocol:: The protocol the daemon uses.
@end menu
@mansect commands
@node Scdaemon Commands
@section Commands
Commands are not distinguished from options except for the fact that
only one command is allowed.
@table @gnupgtabopt
@item --version
@opindex version
Print the program version and licensing information. Note that you cannot
abbreviate this command.
@item --help, -h
@opindex help
Print a usage message summarizing the most useful command-line options.
Note that you cannot abbreviate this command.
@item --dump-options
@opindex dump-options
Print a list of all available options and commands. Note that you cannot
abbreviate this command.
@item --server
@opindex server
Run in server mode and wait for commands on the @code{stdin}. The
default mode is to create a socket and listen for commands there.
@item --multi-server
@opindex multi-server
Run in server mode and wait for commands on the @code{stdin} as well as
on an additional Unix Domain socket. The server command @code{GETINFO}
may be used to get the name of that extra socket.
@item --daemon
@opindex daemon
Run the program in the background. This option is required to prevent
it from being accidentally running in the background.
@end table
@mansect options
@node Scdaemon Options
@section Option Summary
@table @gnupgtabopt
@item --options @var{file}
@opindex options
Reads configuration from @var{file} instead of from the default
per-user configuration file. The default configuration file is named
@file{scdaemon.conf} and expected in the @file{.gnupg} directory directly
below the home directory of the user.
@include opt-homedir.texi
@item -v
@item --verbose
@opindex v
@opindex verbose
Outputs additional information while running.
You can increase the verbosity by giving several
verbose commands to @command{gpgsm}, such as @samp{-vv}.
@item --debug-level @var{level}
@opindex debug-level
Select the debug level for investigating problems. @var{level} may be
a numeric value or a keyword:
@table @code
@item none
No debugging at all. A value of less than 1 may be used instead of
the keyword.
@item basic
Some basic debug messages. A value between 1 and 2 may be used
instead of the keyword.
@item advanced
More verbose debug messages. A value between 3 and 5 may be used
instead of the keyword.
@item expert
Even more detailed messages. A value between 6 and 8 may be used
instead of the keyword.
@item guru
All of the debug messages you can get. A value greater than 8 may be
used instead of the keyword. The creation of hash tracing files is
only enabled if the keyword is used.
@end table
How these messages are mapped to the actual debugging flags is not
specified and may change with newer releases of this program. They are
however carefully selected to best aid in debugging.
@quotation Note
All debugging options are subject to change and thus should not be used
by any application program. As the name says, they are only used as
helpers to debug problems.
@end quotation
@item --debug @var{flags}
@opindex debug
This option is only useful for debugging and the behavior may change at
any time without notice. FLAGS are bit encoded and may be given in
usual C-Syntax. The currently defined bits are:
@table @code
@item 0 (1)
command I/O
@item 1 (2)
values of big number integers
@item 2 (4)
low level crypto operations
@item 5 (32)
memory allocation
@item 6 (64)
caching
@item 7 (128)
show memory statistics
@item 9 (512)
write hashed data to files named @code{dbgmd-000*}
@item 10 (1024)
trace Assuan protocol.
See also option @option{--debug-assuan-log-cats}.
@item 11 (2048)
trace APDU I/O to the card. This may reveal sensitive data.
@item 12 (4096)
trace some card reader related function calls.
@end table
@item --debug-all
@opindex debug-all
Same as @code{--debug=0xffffffff}
@item --debug-wait @var{n}
@opindex debug-wait
When running in server mode, wait @var{n} seconds before entering the
actual processing loop and print the pid. This gives time to attach a
debugger.
@item --debug-ccid-driver
@opindex debug-wait
Enable debug output from the included CCID driver for smartcards.
Using this option twice will also enable some tracing of the T=1
protocol. Note that this option may reveal sensitive data.
@item --debug-disable-ticker
@opindex debug-disable-ticker
This option disables all ticker functions like checking for card
insertions.
@item --debug-allow-core-dump
@opindex debug-allow-core-dump
For security reasons we won't create a core dump when the process
aborts. For debugging purposes it is sometimes better to allow core
dump. This option enables it and also changes the working directory to
@file{/tmp} when running in @option{--server} mode.
@item --debug-log-tid
@opindex debug-log-tid
This option appends a thread ID to the PID in the log output.
@item --debug-assuan-log-cats @var{cats}
@opindex debug-assuan-log-cats
@efindex ASSUAN_DEBUG
Changes the active Libassuan logging categories to @var{cats}. The
value for @var{cats} is an unsigned integer given in usual C-Syntax.
A value of 0 switches to a default category. If this option is not
used the categories are taken from the environment variable
@code{ASSUAN_DEBUG}. Note that this option has only an effect if the
Assuan debug flag has also been with the option @option{--debug}. For
a list of categories see the Libassuan manual.
@item --no-detach
@opindex no-detach
Don't detach the process from the console. This is mainly useful for
debugging.
@item --listen-backlog @var{n}
@opindex listen-backlog
Set the size of the queue for pending connections. The default is 64.
This option has an effect only if @option{--multi-server} is also
used.
@item --log-file @var{file}
@opindex log-file
Append all logging output to @var{file}. This is very helpful in
seeing what the agent actually does. Use @file{socket://} to log to
socket.
@item --pcsc-driver @var{library}
@opindex pcsc-driver
Use @var{library} to access the smartcard reader. The current default
is @file{libpcsclite.so}. Instead of using this option you might also
want to install a symbolic link to the default file name
(e.g. from @file{libpcsclite.so.1}).
@item --ctapi-driver @var{library}
@opindex ctapi-driver
Use @var{library} to access the smartcard reader. The current default
is @file{libtowitoko.so}. Note that the use of this interface is
deprecated; it may be removed in future releases.
@item --disable-ccid
@opindex disable-ccid
Disable the integrated support for CCID compliant readers. This
allows falling back to one of the other drivers even if the internal
CCID driver can handle the reader. Note, that CCID support is only
available if libusb was available at build time.
@item --reader-port @var{number_or_string}
@opindex reader-port
This option may be used to specify the port of the card terminal. A
value of 0 refers to the first serial device; add 32768 to access USB
devices. The default is 32768 (first USB device). PC/SC or CCID
readers might need a string here; run the program in verbose mode to get
a list of available readers. The default is then the first reader
found.
To get a list of available CCID readers you may use this command:
@cartouche
@smallexample
echo scd getinfo reader_list \
| gpg-connect-agent --decode | awk '/^D/ @{print $2@}'
@end smallexample
@end cartouche
@item --card-timeout @var{n}
@opindex card-timeout
-If @var{n} is not 0 and no client is actively using the card, the card
-will be powered down after @var{n} seconds. Powering down the card
-avoids a potential risk of damaging a card when used with certain
-cheap readers. This also allows applications that are not aware of
-Scdaemon to access the card. The disadvantage of using a card timeout
-is that accessing the card takes longer and that the user needs to
-enter the PIN again after the next power up.
-
-Note that with the current version of Scdaemon the card is powered
-down immediately at the next timer tick for any value of @var{n} other
-than 0.
+This option is deprecated. In GnuPG 2.0, it used to be used for
+DISCONNECT command to control timing issue. Since DISCONNECT command
+works synchronously, it has no effect.
@item --enable-pinpad-varlen
@opindex enable-pinpad-varlen
Please specify this option when the card reader supports variable
length input for pinpad (default is no). For known readers (listed in
ccid-driver.c and apdu.c), this option is not needed. Note that if
your card reader doesn't supports variable length input but you want
to use it, you need to specify your pinpad request on your card.
@item --disable-pinpad
@opindex disable-pinpad
Even if a card reader features a pinpad, do not try to use it.
@item --deny-admin
@opindex deny-admin
@opindex allow-admin
This option disables the use of admin class commands for card
applications where this is supported. Currently we support it for the
OpenPGP card. This option is useful to inhibit accidental access to
admin class command which could ultimately lock the card through wrong
PIN numbers. Note that GnuPG versions older than 2.0.11 featured an
@option{--allow-admin} option which was required to use such admin
commands. This option has no more effect today because the default is
now to allow admin commands.
@item --disable-application @var{name}
@opindex disable-application
This option disables the use of the card application named
@var{name}. This is mainly useful for debugging or if a application
with lower priority should be used by default.
+@item --application-priority @var{namelist}
+@opindex application-priority
+This option allows to change the order in which applications of a card
+a tried if no specific application was requested. @var{namelist} is a
+space or comma delimited list of application names. Unknown names are
+simply skipped. Applications not mentioned in the list are put in the
+former order at the end of the new priority list.
+
+To get the list of current active applications, use
+@cartouche
+@smallexample
+ gpg-connect-agent 'scd getinfo app_list' /bye
+@end smallexample
+@end cartouche
+
@end table
All the long options may also be given in the configuration file after
stripping off the two leading dashes.
@mansect card applications
@node Card applications
@section Description of card applications
@command{scdaemon} supports the card applications as described below.
@menu
* OpenPGP Card:: The OpenPGP card application
* NKS Card:: The Telesec NetKey card application
* DINSIG Card:: The DINSIG card application
* PKCS#15 Card:: The PKCS#15 card application
* Geldkarte Card:: The Geldkarte application
* SmartCard-HSM:: The SmartCard-HSM application
* Undefined Card:: The Undefined stub application
@end menu
@node OpenPGP Card
@subsection The OpenPGP card application ``openpgp''
This application is currently only used by @command{gpg} but may in
future also be useful with @command{gpgsm}. Version 1 and version 2 of
the card is supported.
@noindent
The specifications for these cards are available at@*
@uref{http://g10code.com/docs/openpgp-card-1.0.pdf} and@*
@uref{http://g10code.com/docs/openpgp-card-2.0.pdf}.
@node NKS Card
@subsection The Telesec NetKey card ``nks''
This is the main application of the Telesec cards as available in
Germany. It is a superset of the German DINSIG card. The card is
used by @command{gpgsm}.
@node DINSIG Card
@subsection The DINSIG card application ``dinsig''
This is an application as described in the German draft standard
@emph{DIN V 66291-1}. It is intended to be used by cards supporting
the German signature law and its bylaws (SigG and SigV).
@node PKCS#15 Card
@subsection The PKCS#15 card application ``p15''
This is common framework for smart card applications. It is used by
@command{gpgsm}.
@node Geldkarte Card
@subsection The Geldkarte card application ``geldkarte''
This is a simple application to display information of a German
Geldkarte. The Geldkarte is a small amount debit card application which
comes with almost all German banking cards.
@node SmartCard-HSM
@subsection The SmartCard-HSM card application ``sc-hsm''
This application adds read-only support for keys and certificates
stored on a @uref{http://www.smartcard-hsm.com, SmartCard-HSM}.
To generate keys and store certificates you may use
@uref{https://github.com/OpenSC/OpenSC/wiki/SmartCardHSM, OpenSC} or
the tools from @uref{http://www.openscdp.org, OpenSCDP}.
The SmartCard-HSM cards requires a card reader that supports Extended
Length APDUs.
@node Undefined Card
@subsection The Undefined card application ``undefined''
This is a stub application to allow the use of the APDU command even
if no supported application is found on the card. This application is
not used automatically but must be explicitly requested using the
SERIALNO command.
@c *******************************************
@c *************** ****************
@c *************** FILES ****************
@c *************** ****************
@c *******************************************
@mansect files
@node Scdaemon Configuration
@section Configuration files
There are a few configuration files to control certain aspects of
@command{scdaemons}'s operation. Unless noted, they are expected in the
current home directory (@pxref{option --homedir}).
@table @file
@item scdaemon.conf
@cindex scdaemon.conf
This is the standard configuration file read by @command{scdaemon} on
startup. It may contain any valid long option; the leading two dashes
may not be entered and the option may not be abbreviated. This default
name may be changed on the command line (@pxref{option --options}).
@item scd-event
@cindex scd-event
If this file is present and executable, it will be called on every card
reader's status change. An example of this script is provided with the
distribution
@item reader_@var{n}.status
This file is created by @command{scdaemon} to let other applications now
about reader status changes. Its use is now deprecated in favor of
@file{scd-event}.
@end table
@c
@c Examples
@c
@mansect examples
@node Scdaemon Examples
@section Examples
@c man begin EXAMPLES
@example
$ scdaemon --server -v
@end example
@c man end
@c
@c Assuan Protocol
@c
@manpause
@node Scdaemon Protocol
@section Scdaemon's Assuan Protocol
The SC-Daemon should be started by the system to provide access to
external tokens. Using Smartcards on a multi-user system does not
make much sense except for system services, but in this case no
regular user accounts are hosted on the machine.
A client connects to the SC-Daemon by connecting to the socket named
@file{@value{LOCALRUNDIR}/scdaemon/socket}, configuration information
is read from @var{@value{SYSCONFDIR}/scdaemon.conf}
Each connection acts as one session, SC-Daemon takes care of
synchronizing access to a token between sessions.
@menu
* Scdaemon SERIALNO:: Return the serial number.
* Scdaemon LEARN:: Read all useful information from the card.
* Scdaemon READCERT:: Return a certificate.
* Scdaemon READKEY:: Return a public key.
* Scdaemon PKSIGN:: Signing data with a Smartcard.
* Scdaemon PKDECRYPT:: Decrypting data with a Smartcard.
* Scdaemon GETATTR:: Read an attribute's value.
* Scdaemon SETATTR:: Update an attribute's value.
* Scdaemon WRITEKEY:: Write a key to a card.
* Scdaemon GENKEY:: Generate a new key on-card.
* Scdaemon RANDOM:: Return random bytes generated on-card.
* Scdaemon PASSWD:: Change PINs.
* Scdaemon CHECKPIN:: Perform a VERIFY operation.
* Scdaemon RESTART:: Restart connection
* Scdaemon APDU:: Send a verbatim APDU to the card
@end menu
@node Scdaemon SERIALNO
@subsection Return the serial number
This command should be used to check for the presence of a card. It is
special in that it can be used to reset the card. Most other commands
will return an error when a card change has been detected and the use of
this function is therefore required.
Background: We want to keep the client clear of handling card changes
between operations; i.e. the client can assume that all operations are
done on the same card unless he call this function.
@example
SERIALNO
@end example
Return the serial number of the card using a status response like:
@example
S SERIALNO D27600000000000000000000
@end example
The serial number is the hex encoded value identified by
the @code{0x5A} tag in the GDO file (FIX=0x2F02).
@node Scdaemon LEARN
@subsection Read all useful information from the card
@example
LEARN [--force]
@end example
Learn all useful information of the currently inserted card. When
used without the @option{--force} option, the command might do an INQUIRE
like this:
@example
INQUIRE KNOWNCARDP <hexstring_with_serialNumber>
@end example
The client should just send an @code{END} if the processing should go on
or a @code{CANCEL} to force the function to terminate with a cancel
error message. The response of this command is a list of status lines
formatted as this:
@example
S KEYPAIRINFO @var{hexstring_with_keygrip} @var{hexstring_with_id}
@end example
If there is no certificate yet stored on the card a single "X" is
returned in @var{hexstring_with_keygrip}.
@node Scdaemon READCERT
@subsection Return a certificate
@example
READCERT @var{hexified_certid}|@var{keyid}
@end example
This function is used to read a certificate identified by
@var{hexified_certid} from the card. With OpenPGP cards the keyid
@code{OpenPGP.3} may be used to read the certificate of version 2 cards.
@node Scdaemon READKEY
@subsection Return a public key
@example
READKEY @var{hexified_certid}
@end example
Return the public key for the given cert or key ID as an standard
S-Expression.
@node Scdaemon PKSIGN
@subsection Signing data with a Smartcard
To sign some data the caller should use the command
@example
SETDATA @var{hexstring}
@end example
to tell @command{scdaemon} about the data to be signed. The data must be given in
hex notation. The actual signing is done using the command
@example
PKSIGN @var{keyid}
@end example
where @var{keyid} is the hexified ID of the key to be used. The key id
may have been retrieved using the command @code{LEARN}. If another
hash algorithm than SHA-1 is used, that algorithm may be given like:
@example
PKSIGN --hash=@var{algoname} @var{keyid}
@end example
With @var{algoname} are one of @code{sha1}, @code{rmd160} or @code{md5}.
@node Scdaemon PKDECRYPT
@subsection Decrypting data with a Smartcard
To decrypt some data the caller should use the command
@example
SETDATA @var{hexstring}
@end example
to tell @command{scdaemon} about the data to be decrypted. The data
must be given in hex notation. The actual decryption is then done
using the command
@example
PKDECRYPT @var{keyid}
@end example
where @var{keyid} is the hexified ID of the key to be used.
If the card is aware of the apdding format a status line with padding
information is send before the plaintext data. The key for this
status line is @code{PADDING} with the only defined value being 0 and
meaning padding has been removed.
@node Scdaemon GETATTR
@subsection Read an attribute's value
TO BE WRITTEN.
@node Scdaemon SETATTR
@subsection Update an attribute's value
TO BE WRITTEN.
@node Scdaemon WRITEKEY
@subsection Write a key to a card
@example
WRITEKEY [--force] @var{keyid}
@end example
This command is used to store a secret key on a smartcard. The
allowed keyids depend on the currently selected smartcard
application. The actual keydata is requested using the inquiry
@code{KEYDATA} and need to be provided without any protection. With
@option{--force} set an existing key under this @var{keyid} will get
overwritten. The key data is expected to be the usual canonical encoded
S-expression.
A PIN will be requested in most cases. This however depends on the
actual card application.
@node Scdaemon GENKEY
@subsection Generate a new key on-card
TO BE WRITTEN.
@node Scdaemon RANDOM
@subsection Return random bytes generated on-card
TO BE WRITTEN.
@node Scdaemon PASSWD
@subsection Change PINs
@example
PASSWD [--reset] [--nullpin] @var{chvno}
@end example
Change the PIN or reset the retry counter of the card holder
verification vector number @var{chvno}. The option @option{--nullpin}
is used to initialize the PIN of TCOS cards (6 byte NullPIN only).
@node Scdaemon CHECKPIN
@subsection Perform a VERIFY operation
@example
CHECKPIN @var{idstr}
@end example
Perform a VERIFY operation without doing anything else. This may be
used to initialize a the PIN cache earlier to long lasting
operations. Its use is highly application dependent:
@table @strong
@item OpenPGP
Perform a simple verify operation for CHV1 and CHV2, so that further
operations won't ask for CHV2 and it is possible to do a cheap check on
the PIN: If there is something wrong with the PIN entry system, only the
regular CHV will get blocked and not the dangerous CHV3. @var{idstr} is
the usual card's serial number in hex notation; an optional fingerprint
part will get ignored.
There is however a special mode if @var{idstr} is suffixed with the
literal string @code{[CHV3]}: In this case the Admin PIN is checked if
and only if the retry counter is still at 3.
@end table
@node Scdaemon RESTART
@subsection Perform a RESTART operation
@example
RESTART
@end example
Restart the current connection; this is a kind of warm reset. It
deletes the context used by this connection but does not actually
reset the card.
This is used by gpg-agent to reuse a primary pipe connection and
may be used by clients to backup from a conflict in the serial
command; i.e. to select another application.
@node Scdaemon APDU
@subsection Send a verbatim APDU to the card
@example
APDU [--atr] [--more] [--exlen[=@var{n}]] [@var{hexstring}]
@end example
Send an APDU to the current reader. This command bypasses the high
level functions and sends the data directly to the card.
@var{hexstring} is expected to be a proper APDU. If @var{hexstring} is
not given no commands are send to the card; However the command will
implicitly check whether the card is ready for use.
Using the option @code{--atr} returns the ATR of the card as a status
message before any data like this:
@example
S CARD-ATR 3BFA1300FF813180450031C173C00100009000B1
@end example
Using the option @code{--more} handles the card status word MORE_DATA
(61xx) and concatenate all responses to one block.
Using the option @code{--exlen} the returned APDU may use extended
length up to N bytes. If N is not given a default value is used
(currently 4096).
@mansect see also
@ifset isman
@command{gpg-agent}(1),
@command{gpgsm}(1),
@command{gpg2}(1)
@end ifset
@include see-also-note.texi
-
diff --git a/doc/tools.texi b/doc/tools.texi
index 119f698d6..460030038 100644
--- a/doc/tools.texi
+++ b/doc/tools.texi
@@ -1,2109 +1,2160 @@
@c Copyright (C) 2004, 2008 Free Software Foundation, Inc.
@c This is part of the GnuPG manual.
@c For copying conditions, see the file GnuPG.texi.
@include defs.inc
@node Helper Tools
@chapter Helper Tools
GnuPG comes with a couple of smaller tools:
@menu
* watchgnupg:: Read logs from a socket.
* gpgv:: Verify OpenPGP signatures.
* addgnupghome:: Create .gnupg home directories.
* gpgconf:: Modify .gnupg home directories.
* applygnupgdefaults:: Run gpgconf for all users.
* gpg-preset-passphrase:: Put a passphrase into the cache.
* gpg-connect-agent:: Communicate with a running agent.
* dirmngr-client:: How to use the Dirmngr client tool.
* gpgparsemail:: Parse a mail message into an annotated format
* symcryptrun:: Call a simple symmetric encryption tool.
* gpgtar:: Encrypt or sign files into an archive.
+* gpg-check-pattern:: Check a passphrase on stdin against the patternfile.
@end menu
@c
@c WATCHGNUPG
@c
@manpage watchgnupg.1
@node watchgnupg
@section Read logs from a socket
@ifset manverb
.B watchgnupg
\- Read and print logs from a socket
@end ifset
@mansect synopsis
@ifset manverb
.B watchgnupg
.RB [ \-\-force ]
.RB [ \-\-verbose ]
.I socketname
@end ifset
@mansect description
Most of the main utilities are able to write their log files to a Unix
Domain socket if configured that way. @command{watchgnupg} is a simple
listener for such a socket. It ameliorates the output with a time stamp
and makes sure that long lines are not interspersed with log output from
other utilities. This tool is not available for Windows.
@noindent
@command{watchgnupg} is commonly invoked as
@example
watchgnupg --force $(gpgconf --list-dirs socketdir)/S.log
@end example
@manpause
@noindent
This starts it on the current terminal for listening on the standard
logging socket (which is either @file{~/.gnupg/S.log} or
@file{/var/run/user/UID/gnupg/S.log}).
@mansect options
@noindent
@command{watchgnupg} understands these options:
@table @gnupgtabopt
@item --force
@opindex force
Delete an already existing socket file.
@anchor{option watchgnupg --tcp}
@item --tcp @var{n}
Instead of reading from a local socket, listen for connects on TCP port
@var{n}.
@item --time-only
@opindex time-only
Do not print the date part of the timestamp.
@item --verbose
@opindex verbose
Enable extra informational output.
@item --version
@opindex version
Print version of the program and exit.
@item --help
@opindex help
Display a brief help page and exit.
@end table
@noindent
@mansect examples
@chapheading Examples
@example
$ watchgnupg --force --time-only $(gpgconf --list-dirs socketdir)/S.log
@end example
This waits for connections on the local socket
(e.g. @file{/home/foo/.gnupg/S.log}) and shows all log entries. To
make this work the option @option{log-file} needs to be used with all
modules which logs are to be shown. The suggested entry for the
configuration files is:
@example
log-file socket://
@end example
If the default socket as given above and returned by "echo $(gpgconf
--list-dirs socketdir)/S.log" is not desired an arbitrary socket name
can be specified, for example @file{socket:///home/foo/bar/mysocket}.
For debugging purposes it is also possible to do remote logging. Take
care if you use this feature because the information is send in the
clear over the network. Use this syntax in the conf files:
@example
log-file tcp://192.168.1.1:4711
@end example
You may use any port and not just 4711 as shown above; only IP
addresses are supported (v4 and v6) and no host names. You need to
start @command{watchgnupg} with the @option{tcp} option. Note that
under Windows the registry entry
@var{HKCU\Software\GNU\GnuPG:DefaultLogFile} can be used to change the
default log output from @code{stderr} to whatever is given by that
entry. However the only useful entry is a TCP name for remote
debugging.
@mansect see also
@ifset isman
@command{gpg}(1),
@command{gpgsm}(1),
@command{gpg-agent}(1),
@command{scdaemon}(1)
@end ifset
@include see-also-note.texi
@c
@c GPGV
@c
@include gpgv.texi
@c
@c ADDGNUPGHOME
@c
@manpage addgnupghome.8
@node addgnupghome
@section Create .gnupg home directories
@ifset manverb
.B addgnupghome
\- Create .gnupg home directories
@end ifset
@mansect synopsis
@ifset manverb
.B addgnupghome
.I account_1
.IR account_2 ... account_n
@end ifset
@mansect description
If GnuPG is installed on a system with existing user accounts, it is
sometimes required to populate the GnuPG home directory with existing
files. Especially a @file{trustlist.txt} and a keybox with some
initial certificates are often desired. This script helps to do this
by copying all files from @file{/etc/skel/.gnupg} to the home
directories of the accounts given on the command line. It takes care
not to overwrite existing GnuPG home directories.
@noindent
@command{addgnupghome} is invoked by root as:
@example
addgnupghome account1 account2 ... accountn
@end example
@c
@c GPGCONF
@c
@manpage gpgconf.1
@node gpgconf
@section Modify .gnupg home directories
@ifset manverb
.B gpgconf
\- Modify .gnupg home directories
@end ifset
@mansect synopsis
@ifset manverb
.B gpgconf
.RI [ options ]
.B \-\-list-components
.br
.B gpgconf
.RI [ options ]
.B \-\-list-options
.I component
.br
.B gpgconf
.RI [ options ]
.B \-\-change-options
.I component
@end ifset
@mansect description
The @command{gpgconf} is a utility to automatically and reasonable
safely query and modify configuration files in the @file{.gnupg} home
directory. It is designed not to be invoked manually by the user, but
automatically by graphical user interfaces (GUI).@footnote{Please note
that currently no locking is done, so concurrent access should be
avoided. There are some precautions to avoid corruption with
concurrent usage, but results may be inconsistent and some changes may
get lost. The stateless design makes it difficult to provide more
guarantees.}
@command{gpgconf} provides access to the configuration of one or more
components of the GnuPG system. These components correspond more or
less to the programs that exist in the GnuPG framework, like GPG,
GPGSM, DirMngr, etc. But this is not a strict one-to-one
relationship. Not all configuration options are available through
@command{gpgconf}. @command{gpgconf} provides a generic and abstract
method to access the most important configuration options that can
feasibly be controlled via such a mechanism.
@command{gpgconf} can be used to gather and change the options
available in each component, and can also provide their default
values. @command{gpgconf} will give detailed type information that
can be used to restrict the user's input without making an attempt to
commit the changes.
@command{gpgconf} provides the backend of a configuration editor. The
configuration editor would usually be a graphical user interface
program that displays the current options, their default
values, and allows the user to make changes to the options. These
changes can then be made active with @command{gpgconf} again. Such a
program that uses @command{gpgconf} in this way will be called GUI
throughout this section.
@menu
* Invoking gpgconf:: List of all commands and options.
* Format conventions:: Formatting conventions relevant for all commands.
* Listing components:: List all gpgconf components.
* Checking programs:: Check all programs known to gpgconf.
* Listing options:: List all options of a component.
* Changing options:: Changing options of a component.
* Listing global options:: List all global options.
* Querying versions:: Get and compare software versions.
* Files used by gpgconf:: What files are used by gpgconf.
@end menu
@manpause
@node Invoking gpgconf
@subsection Invoking gpgconf
@mansect commands
One of the following commands must be given:
@table @gnupgtabopt
@item --list-components
List all components. This is the default command used if none is
specified.
@item --check-programs
List all available backend programs and test whether they are runnable.
@item --list-options @var{component}
List all options of the component @var{component}.
@item --change-options @var{component}
Change the options of the component @var{component}.
@item --check-options @var{component}
Check the options for the component @var{component}.
@item --apply-profile @var{file}
Apply the configuration settings listed in @var{file} to the
configuration files. If @var{file} has no suffix and no slashes the
command first tries to read a file with the suffix @code{.prf} from
the data directory (@code{gpgconf --list-dirs datadir}) before it
reads the file verbatim. A profile is divided into sections using the
bracketed component name. Each section then lists the option which
shall go into the respective configuration file.
@item --apply-defaults
Update all configuration files with values taken from the global
configuration file (usually @file{/etc/gnupg/gpgconf.conf}).
@item --list-dirs [@var{names}]
Lists the directories used by @command{gpgconf}. One directory is
listed per line, and each line consists of a colon-separated list where
the first field names the directory type (for example @code{sysconfdir})
and the second field contains the percent-escaped directory. Although
they are not directories, the socket file names used by
@command{gpg-agent} and @command{dirmngr} are printed as well. Note
that the socket file names and the @code{homedir} lines are the default
names and they may be overridden by command line switches. If
@var{names} are given only the directories or file names specified by
the list names are printed without any escaping.
@item --list-config [@var{filename}]
List the global configuration file in a colon separated format. If
@var{filename} is given, check that file instead.
@item --check-config [@var{filename}]
Run a syntax check on the global configuration file. If @var{filename}
is given, check that file instead.
@item --query-swdb @var{package_name} [@var{version_string}]
Returns the current version for @var{package_name} and if
@var{version_string} is given also an indicator on whether an update
is available. The actual file with the software version is
automatically downloaded and checked by @command{dirmngr}.
@command{dirmngr} uses a thresholds to avoid download the file too
often and it does this by default only if it can be done via Tor. To
force an update of that file this command can be used:
@example
gpg-connect-agent --dirmngr 'loadswdb --force' /bye
@end example
@item --reload [@var{component}]
@opindex reload
Reload all or the given component. This is basically the same as
sending a SIGHUP to the component. Components which don't support
reloading are ignored. Without @var{component} or by using "all" for
@var{component} all components which are daemons are reloaded.
@item --launch [@var{component}]
@opindex launch
If the @var{component} is not already running, start it.
@command{component} must be a daemon. This is in general not required
because the system starts these daemons as needed. However, external
software making direct use of @command{gpg-agent} or @command{dirmngr}
may use this command to ensure that they are started. Using "all" for
@var{component} launches all components which are daemons.
@item --kill [@var{component}]
@opindex kill
-Kill the given component. Components which support killing are
-@command{gpg-agent} and @command{scdaemon}. Components which don't
-support reloading are ignored. Using "all" for @var{component} kills
-all components running as daemons. Note that as of now reload and
-kill have the same effect for @command{scdaemon}.
+Kill the given component that runs as a daemon, including
+@command{gpg-agent}, @command{dirmngr}, and @command{scdaemon}. A
+@command{component} which does not run as a daemon will be ignored.
+Using "all" for @var{component} kills all components running as
+daemons. Note that as of now reload and kill have the same effect for
+@command{scdaemon}.
@item --create-socketdir
@opindex create-socketdir
Create a directory for sockets below /run/user or /var/run/user. This
is command is only required if a non default home directory is used
and the /run based sockets shall be used. For the default home
directory GnUPG creates a directory on the fly.
@item --remove-socketdir
@opindex remove-socketdir
Remove a directory created with command @option{--create-socketdir}.
@end table
@mansect options
The following options may be used:
@table @gnupgtabopt
@item -o @var{file}
@itemx --output @var{file}
Write output to @var{file}. Default is to write to stdout.
@item -v
@itemx --verbose
Outputs additional information while running. Specifically, this
extends numerical field values by human-readable descriptions.
@item -q
@itemx --quiet
@opindex quiet
Try to be as quiet as possible.
+@include opt-homedir.texi
+
@item -n
@itemx --dry-run
Do not actually change anything. This is currently only implemented
for @code{--change-options} and can be used for testing purposes.
@item -r
@itemx --runtime
Only used together with @code{--change-options}. If one of the
modified options can be changed in a running daemon process, signal
the running daemon to ask it to reparse its configuration file after
changing.
This means that the changes will take effect at run-time, as far as
this is possible. Otherwise, they will take effect at the next start
of the respective backend programs.
@item --status-fd @var{n}
@opindex status-fd
Write special status strings to the file descriptor @var{n}. This
program returns the status messages SUCCESS or FAILURE which are
helpful when the caller uses a double fork approach and can't easily
get the return code of the process.
@manpause
@end table
@node Format conventions
@subsection Format conventions
Some lines in the output of @command{gpgconf} contain a list of
colon-separated fields. The following conventions apply:
@itemize @bullet
@item
The GUI program is required to strip off trailing newline and/or
carriage return characters from the output.
@item
@command{gpgconf} will never leave out fields. If a certain version
provides a certain field, this field will always be present in all
@command{gpgconf} versions from that time on.
@item
Future versions of @command{gpgconf} might append fields to the list.
New fields will always be separated from the previously last field by
a colon separator. The GUI should be prepared to parse the last field
it knows about up until a colon or end of line.
@item
Not all fields are defined under all conditions. You are required to
ignore the content of undefined fields.
@end itemize
There are several standard types for the content of a field:
@table @asis
@item verbatim
Some fields contain strings that are not escaped in any way. Such
fields are described to be used @emph{verbatim}. These fields will
never contain a colon character (for obvious reasons). No de-escaping
or other formatting is required to use the field content. This is for
easy parsing of the output, when it is known that the content can
never contain any special characters.
@item percent-escaped
Some fields contain strings that are described to be
@emph{percent-escaped}. Such strings need to be de-escaped before
their content can be presented to the user. A percent-escaped string
is de-escaped by replacing all occurrences of @code{%XY} by the byte
that has the hexadecimal value @code{XY}. @code{X} and @code{Y} are
from the set @code{0-9a-f}.
@item localized
Some fields contain strings that are described to be @emph{localized}.
Such strings are translated to the active language and formatted in
the active character set.
@item @w{unsigned number}
Some fields contain an @emph{unsigned number}. This number will
always fit into a 32-bit unsigned integer variable. The number may be
followed by a space, followed by a human readable description of that
value (if the verbose option is used). You should ignore everything
in the field that follows the number.
@item @w{signed number}
Some fields contain a @emph{signed number}. This number will always
fit into a 32-bit signed integer variable. The number may be followed
by a space, followed by a human readable description of that value (if
the verbose option is used). You should ignore everything in the
field that follows the number.
@item @w{boolean value}
Some fields contain a @emph{boolean value}. This is a number with
either the value 0 or 1. The number may be followed by a space,
followed by a human readable description of that value (if the verbose
option is used). You should ignore everything in the field that follows
the number; checking just the first character is sufficient in this
case.
@item option
Some fields contain an @emph{option} argument. The format of an
option argument depends on the type of the option and on some flags:
@table @asis
@item no argument
The simplest case is that the option does not take an argument at all
(@var{type} @code{0}). Then the option argument is an unsigned number
that specifies how often the option occurs. If the @code{list} flag
is not set, then the only valid number is @code{1}. Options that do
not take an argument never have the @code{default} or @code{optional
arg} flag set.
@item number
If the option takes a number argument (@var{alt-type} is @code{2} or
@code{3}), and it can only occur once (@code{list} flag is not set),
then the option argument is either empty (only allowed if the argument
is optional), or it is a number. A number is a string that begins
with an optional minus character, followed by one or more digits. The
number must fit into an integer variable (unsigned or signed,
depending on @var{alt-type}).
@item number list
If the option takes a number argument and it can occur more than once,
then the option argument is either empty, or it is a comma-separated
list of numbers as described above.
@item string
If the option takes a string argument (@var{alt-type} is 1), and it
can only occur once (@code{list} flag is not set) then the option
argument is either empty (only allowed if the argument is optional),
or it starts with a double quote character (@code{"}) followed by a
percent-escaped string that is the argument value. Note that there is
only a leading double quote character, no trailing one. The double
quote character is only needed to be able to differentiate between no
value and the empty string as value.
@item string list
If the option takes a string argument and it can occur more than once,
then the option argument is either empty, or it is a comma-separated
list of string arguments as described above.
@end table
@end table
The active language and character set are currently determined from
the locale environment of the @command{gpgconf} program.
@c FIXME: Document the active language and active character set. Allow
@c to change it via the command line?
@mansect usage
@node Listing components
@subsection Listing components
The command @code{--list-components} will list all components that can
be configured with @command{gpgconf}. Usually, one component will
correspond to one GnuPG-related program and contain the options of
that program's configuration file that can be modified using
@command{gpgconf}. However, this is not necessarily the case. A
component might also be a group of selected options from several
programs, or contain entirely virtual options that have a special
effect rather than changing exactly one option in one configuration
file.
A component is a set of configuration options that semantically belong
together. Furthermore, several changes to a component can be made in
an atomic way with a single operation. The GUI could for example
provide a menu with one entry for each component, or a window with one
tabulator sheet per component.
The command @code{--list-components} lists all available
components, one per line. The format of each line is:
@code{@var{name}:@var{description}:@var{pgmname}:}
@table @var
@item name
This field contains a name tag of the component. The name tag is used
to specify the component in all communication with @command{gpgconf}.
The name tag is to be used @emph{verbatim}. It is thus not in any
escaped format.
@item description
The @emph{string} in this field contains a human-readable description
of the component. It can be displayed to the user of the GUI for
informational purposes. It is @emph{percent-escaped} and
@emph{localized}.
@item pgmname
The @emph{string} in this field contains the absolute name of the
program's file. It can be used to unambiguously invoke that program.
It is @emph{percent-escaped}.
@end table
Example:
@example
$ gpgconf --list-components
gpg:GPG for OpenPGP:/usr/local/bin/gpg2:
gpg-agent:GPG Agent:/usr/local/bin/gpg-agent:
scdaemon:Smartcard Daemon:/usr/local/bin/scdaemon:
gpgsm:GPG for S/MIME:/usr/local/bin/gpgsm:
dirmngr:Directory Manager:/usr/local/bin/dirmngr:
@end example
@node Checking programs
@subsection Checking programs
The command @code{--check-programs} is similar to
@code{--list-components} but works on backend programs and not on
components. It runs each program to test whether it is installed and
runnable. This also includes a syntax check of all config file options
of the program.
The command @code{--check-programs} lists all available
programs, one per line. The format of each line is:
@code{@var{name}:@var{description}:@var{pgmname}:@var{avail}:@var{okay}:@var{cfgfile}:@var{line}:@var{error}:}
@table @var
@item name
This field contains a name tag of the program which is identical to the
name of the component. The name tag is to be used @emph{verbatim}. It
is thus not in any escaped format. This field may be empty to indicate
a continuation of error descriptions for the last name. The description
and pgmname fields are then also empty.
@item description
The @emph{string} in this field contains a human-readable description
of the component. It can be displayed to the user of the GUI for
informational purposes. It is @emph{percent-escaped} and
@emph{localized}.
@item pgmname
The @emph{string} in this field contains the absolute name of the
program's file. It can be used to unambiguously invoke that program.
It is @emph{percent-escaped}.
@item avail
The @emph{boolean value} in this field indicates whether the program is
installed and runnable.
@item okay
The @emph{boolean value} in this field indicates whether the program's
config file is syntactically okay.
@item cfgfile
If an error occurred in the configuration file (as indicated by a false
value in the field @code{okay}), this field has the name of the failing
configuration file. It is @emph{percent-escaped}.
@item line
If an error occurred in the configuration file, this field has the line
number of the failing statement in the configuration file.
It is an @emph{unsigned number}.
@item error
If an error occurred in the configuration file, this field has the error
text of the failing statement in the configuration file. It is
@emph{percent-escaped} and @emph{localized}.
@end table
@noindent
In the following example the @command{dirmngr} is not runnable and the
configuration file of @command{scdaemon} is not okay.
@example
$ gpgconf --check-programs
gpg:GPG for OpenPGP:/usr/local/bin/gpg2:1:1:
gpg-agent:GPG Agent:/usr/local/bin/gpg-agent:1:1:
scdaemon:Smartcard Daemon:/usr/local/bin/scdaemon:1:0:
gpgsm:GPG for S/MIME:/usr/local/bin/gpgsm:1:1:
dirmngr:Directory Manager:/usr/local/bin/dirmngr:0:0:
@end example
@noindent
The command @w{@code{--check-options @var{component}}} will verify the
configuration file in the same manner as @code{--check-programs}, but
only for the component @var{component}.
@node Listing options
@subsection Listing options
Every component contains one or more options. Options may be gathered
into option groups to allow the GUI to give visual hints to the user
about which options are related.
The command @code{@w{--list-options @var{component}}} lists
all options (and the groups they belong to) in the component
@var{component}, one per line. @var{component} must be the string in
the field @var{name} in the output of the @code{--list-components}
command.
There is one line for each option and each group. First come all
options that are not in any group. Then comes a line describing a
group. Then come all options that belong into each group. Then comes
the next group and so on. There does not need to be any group (and in
this case the output will stop after the last non-grouped option).
The format of each line is:
@code{@var{name}:@var{flags}:@var{level}:@var{description}:@var{type}:@var{alt-type}:@var{argname}:@var{default}:@var{argdef}:@var{value}}
@table @var
@item name
This field contains a name tag for the group or option. The name tag
is used to specify the group or option in all communication with
@command{gpgconf}. The name tag is to be used @emph{verbatim}. It is
thus not in any escaped format.
@item flags
The flags field contains an @emph{unsigned number}. Its value is the
OR-wise combination of the following flag values:
@table @code
@item group (1)
If this flag is set, this is a line describing a group and not an
option.
@end table
The following flag values are only defined for options (that is, if
the @code{group} flag is not used).
@table @code
@item optional arg (2)
If this flag is set, the argument is optional. This is never set for
@var{type} @code{0} (none) options.
@item list (4)
If this flag is set, the option can be given multiple times.
@item runtime (8)
If this flag is set, the option can be changed at runtime.
@item default (16)
If this flag is set, a default value is available.
@item default desc (32)
If this flag is set, a (runtime) default is available. This and the
@code{default} flag are mutually exclusive.
@item no arg desc (64)
If this flag is set, and the @code{optional arg} flag is set, then the
option has a special meaning if no argument is given.
@item no change (128)
If this flag is set, @command{gpgconf} ignores requests to change the
value. GUI frontends should grey out this option. Note, that manual
changes of the configuration files are still possible.
@end table
@item level
This field is defined for options and for groups. It contains an
@emph{unsigned number} that specifies the expert level under which
this group or option should be displayed. The following expert levels
are defined for options (they have analogous meaning for groups):
@table @code
@item basic (0)
This option should always be offered to the user.
@item advanced (1)
This option may be offered to advanced users.
@item expert (2)
This option should only be offered to expert users.
@item invisible (3)
This option should normally never be displayed, not even to expert
users.
@item internal (4)
This option is for internal use only. Ignore it.
@end table
The level of a group will always be the lowest level of all options it
contains.
@item description
This field is defined for options and groups. The @emph{string} in
this field contains a human-readable description of the option or
group. It can be displayed to the user of the GUI for informational
purposes. It is @emph{percent-escaped} and @emph{localized}.
@item type
This field is only defined for options. It contains an @emph{unsigned
number} that specifies the type of the option's argument, if any. The
following types are defined:
Basic types:
@table @code
@item none (0)
No argument allowed.
@item string (1)
An @emph{unformatted string}.
@item int32 (2)
A @emph{signed number}.
@item uint32 (3)
An @emph{unsigned number}.
@end table
Complex types:
@table @code
@item pathname (32)
A @emph{string} that describes the pathname of a file. The file does
not necessarily need to exist.
@item ldap server (33)
A @emph{string} that describes an LDAP server in the format:
@code{@var{hostname}:@var{port}:@var{username}:@var{password}:@var{base_dn}}
@item key fingerprint (34)
A @emph{string} with a 40 digit fingerprint specifying a certificate.
@item pub key (35)
A @emph{string} that describes a certificate by user ID, key ID or
fingerprint.
@item sec key (36)
A @emph{string} that describes a certificate with a key by user ID,
key ID or fingerprint.
@item alias list (37)
A @emph{string} that describes an alias list, like the one used with
gpg's group option. The list consists of a key, an equal sign and space
separated values.
@end table
More types will be added in the future. Please see the @var{alt-type}
field for information on how to cope with unknown types.
@item alt-type
This field is identical to @var{type}, except that only the types
@code{0} to @code{31} are allowed. The GUI is expected to present the
user the option in the format specified by @var{type}. But if the
argument type @var{type} is not supported by the GUI, it can still
display the option in the more generic basic type @var{alt-type}. The
GUI must support all the defined basic types to be able to display all
options. More basic types may be added in future versions. If the
GUI encounters a basic type it doesn't support, it should report an
error and abort the operation.
@item argname
This field is only defined for options with an argument type
@var{type} that is not @code{0}. In this case it may contain a
@emph{percent-escaped} and @emph{localized string} that gives a short
name for the argument. The field may also be empty, though, in which
case a short name is not known.
@item default
This field is defined only for options for which the @code{default} or
@code{default desc} flag is set. If the @code{default} flag is set,
its format is that of an @emph{option argument} (@pxref{Format
conventions}, for details). If the default value is empty, then no
default is known. Otherwise, the value specifies the default value
for this option. If the @code{default desc} flag is set, the field is
either empty or contains a description of the effect if the option is
not given.
@item argdef
This field is defined only for options for which the @code{optional
arg} flag is set. If the @code{no arg desc} flag is not set, its
format is that of an @emph{option argument} (@pxref{Format
conventions}, for details). If the default value is empty, then no
default is known. Otherwise, the value specifies the default argument
for this option. If the @code{no arg desc} flag is set, the field is
either empty or contains a description of the effect of this option if
no argument is given.
@item value
This field is defined only for options. Its format is that of an
@emph{option argument}. If it is empty, then the option is not
explicitly set in the current configuration, and the default applies
(if any). Otherwise, it contains the current value of the option.
Note that this field is also meaningful if the option itself does not
take a real argument (in this case, it contains the number of times
the option appears).
@end table
@node Changing options
@subsection Changing options
The command @w{@code{--change-options @var{component}}} will attempt
to change the options of the component @var{component} to the
specified values. @var{component} must be the string in the field
@var{name} in the output of the @code{--list-components} command. You
have to provide the options that shall be changed in the following
format on standard input:
@code{@var{name}:@var{flags}:@var{new-value}}
@table @var
@item name
This is the name of the option to change. @var{name} must be the
string in the field @var{name} in the output of the
@code{--list-options} command.
@item flags
The flags field contains an @emph{unsigned number}. Its value is the
OR-wise combination of the following flag values:
@table @code
@item default (16)
If this flag is set, the option is deleted and the default value is
used instead (if applicable).
@end table
@item new-value
The new value for the option. This field is only defined if the
@code{default} flag is not set. The format is that of an @emph{option
argument}. If it is empty (or the field is omitted), the default
argument is used (only allowed if the argument is optional for this
option). Otherwise, the option will be set to the specified value.
@end table
@noindent
The output of the command is the same as that of
@code{--check-options} for the modified configuration file.
Examples:
To set the force option, which is of basic type @code{none (0)}:
@example
$ echo 'force:0:1' | gpgconf --change-options dirmngr
@end example
To delete the force option:
@example
$ echo 'force:16:' | gpgconf --change-options dirmngr
@end example
The @code{--runtime} option can influence when the changes take
effect.
@node Listing global options
@subsection Listing global options
Sometimes it is useful for applications to look at the global options
file @file{gpgconf.conf}.
The colon separated listing format is record oriented and uses the first
field to identify the record type:
@table @code
@item k
This describes a key record to start the definition of a new ruleset for
a user/group. The format of a key record is:
@code{k:@var{user}:@var{group}:}
@table @var
@item user
This is the user field of the key. It is percent escaped. See the
definition of the gpgconf.conf format for details.
@item group
This is the group field of the key. It is percent escaped.
@end table
@item r
This describes a rule record. All rule records up to the next key record
make up a rule set for that key. The format of a rule record is:
@code{r:::@var{component}:@var{option}:@var{flag}:@var{value}:}
@table @var
@item component
This is the component part of a rule. It is a plain string.
@item option
This is the option part of a rule. It is a plain string.
@item flag
This is the flags part of a rule. There may be only one flag per rule
but by using the same component and option, several flags may be
assigned to an option. It is a plain string.
@item value
This is the optional value for the option. It is a percent escaped
string with a single quotation mark to indicate a string. The quotation
mark is only required to distinguish between no value specified and an
empty string.
@end table
@end table
@noindent
Unknown record types should be ignored. Note that there is intentionally
no feature to change the global option file through @command{gpgconf}.
@node Querying versions
@subsection Get and compare software versions.
The GnuPG Project operates a server to query the current versions of
software packages related to GnuPG. @command{gpgconf} can be used to
access this online database. To allow for offline operations, this
feature works by having @command{dirmngr} download a file from
@code{https://versions.gnupg.org}, checking the signature of that file
and storing the file in the GnuPG home directory. If
@command{gpgconf} is used and @command{dirmngr} is running, it may ask
@command{dirmngr} to refresh that file before itself uses the file.
The command @option{--query-swdb} returns information for the given
package in a colon delimited format:
@table @var
@item name
This is the name of the package as requested. Note that "gnupg" is a
special name which is replaced by the actual package implementing this
version of GnuPG. For this name it is also not required to specify a
version because @command{gpgconf} takes its own version in this case.
@item iversion
The currently installed version or an empty string. The value is
taken from the command line argument but may be provided by gpg
if not given.
@item status
The status of the software package according to this table:
@table @code
@item -
No information available. This is either because no current version
has been specified or due to an error.
@item ?
The given name is not known in the online database.
@item u
An update of the software is available.
@item c
The installed version of the software is current.
@item n
The installed version is already newer than the released version.
@end table
@item urgency
If the value (the empty string should be considered as zero) is
greater than zero an important update is available.
@item error
This returns an @command{gpg-error} error code to distinguish between
various failure modes.
@item filedate
This gives the date of the file with the version numbers in standard
ISO format (@code{yyyymmddThhmmss}). The date has been extracted by
@command{dirmngr} from the signature of the file.
@item verified
This gives the date in ISO format the file was downloaded. This value
can be used to evaluate the freshness of the information.
@item version
This returns the version string for the requested software from the
file.
@item reldate
This returns the release date in ISO format.
@item size
This returns the size of the package as decimal number of bytes.
@item hash
This returns a hexified SHA-2 hash of the package.
@end table
@noindent
More fields may be added in future to the output.
@mansect files
@node Files used by gpgconf
@subsection Files used by gpgconf
@table @file
@item /etc/gnupg/gpgconf.conf
@cindex gpgconf.conf
If this file exists, it is processed as a global configuration file.
A commented example can be found in the @file{examples} directory of
the distribution.
@item @var{GNUPGHOME}/swdb.lst
@cindex swdb.lst
A file with current software versions. @command{dirmngr} creates
this file on demand from an online resource.
@end table
@mansect see also
@ifset isman
@command{gpg}(1),
@command{gpgsm}(1),
@command{gpg-agent}(1),
@command{scdaemon}(1),
@command{dirmngr}(1)
@end ifset
@include see-also-note.texi
@c
@c APPLYGNUPGDEFAULTS
@c
@manpage applygnupgdefaults.8
@node applygnupgdefaults
@section Run gpgconf for all users
@ifset manverb
.B applygnupgdefaults
\- Run gpgconf --apply-defaults for all users.
@end ifset
@mansect synopsis
@ifset manverb
.B applygnupgdefaults
@end ifset
@mansect description
This script is a wrapper around @command{gpgconf} to run it with the
command @code{--apply-defaults} for all real users with an existing
GnuPG home directory. Admins might want to use this script to update he
GnuPG configuration files for all users after
@file{/etc/gnupg/gpgconf.conf} has been changed. This allows enforcing
certain policies for all users. Note, that this is not a bulletproof way to
force a user to use certain options. A user may always directly edit
the configuration files and bypass gpgconf.
@noindent
@command{applygnupgdefaults} is invoked by root as:
@example
applygnupgdefaults
@end example
@c
@c GPG-PRESET-PASSPHRASE
@c
@node gpg-preset-passphrase
@section Put a passphrase into the cache
@manpage gpg-preset-passphrase.1
@ifset manverb
.B gpg-preset-passphrase
\- Put a passphrase into gpg-agent's cache
@end ifset
@mansect synopsis
@ifset manverb
.B gpg-preset-passphrase
.RI [ options ]
.RI [ command ]
.I cache-id
@end ifset
@mansect description
The @command{gpg-preset-passphrase} is a utility to seed the internal
cache of a running @command{gpg-agent} with passphrases. It is mainly
useful for unattended machines, where the usual @command{pinentry} tool
may not be used and the passphrases for the to be used keys are given at
machine startup.
This program works with GnuPG 2 and later. GnuPG 1.x is not supported.
Passphrases set with this utility don't expire unless the
@option{--forget} option is used to explicitly clear them from the
cache --- or @command{gpg-agent} is either restarted or reloaded (by
sending a SIGHUP to it). Note that the maximum cache time as set with
@option{--max-cache-ttl} is still honored. It is necessary to allow
this passphrase presetting by starting @command{gpg-agent} with the
@option{--allow-preset-passphrase}.
@menu
* Invoking gpg-preset-passphrase:: List of all commands and options.
@end menu
@manpause
@node Invoking gpg-preset-passphrase
@subsection List of all commands and options
@mancont
@noindent
@command{gpg-preset-passphrase} is invoked this way:
@example
gpg-preset-passphrase [options] [command] @var{cacheid}
@end example
@var{cacheid} is either a 40 character keygrip of hexadecimal
characters identifying the key for which the passphrase should be set
or cleared. The keygrip is listed along with the key when running the
command: @code{gpgsm --with-keygrip --list-secret-keys}.
Alternatively an arbitrary string may be used to identify a
passphrase; it is suggested that such a string is prefixed with the
name of the application (e.g @code{foo:12346}). Scripts should always
use the option @option{--with-colons}, which provides the keygrip in a
"grp" line (cf. @file{doc/DETAILS})/
@noindent
One of the following command options must be given:
@table @gnupgtabopt
@item --preset
@opindex preset
Preset a passphrase. This is what you usually will
use. @command{gpg-preset-passphrase} will then read the passphrase from
@code{stdin}.
@item --forget
@opindex forget
Flush the passphrase for the given cache ID from the cache.
@end table
@noindent
The following additional options may be used:
@table @gnupgtabopt
@item -v
@itemx --verbose
@opindex verbose
Output additional information while running.
@item -P @var{string}
@itemx --passphrase @var{string}
@opindex passphrase
Instead of reading the passphrase from @code{stdin}, use the supplied
@var{string} as passphrase. Note that this makes the passphrase visible
for other users.
@end table
@mansect see also
@ifset isman
@command{gpg}(1),
@command{gpgsm}(1),
@command{gpg-agent}(1),
@command{scdaemon}(1)
@end ifset
@include see-also-note.texi
@c
@c GPG-CONNECT-AGENT
@c
@node gpg-connect-agent
@section Communicate with a running agent
@manpage gpg-connect-agent.1
@ifset manverb
.B gpg-connect-agent
\- Communicate with a running agent
@end ifset
@mansect synopsis
@ifset manverb
.B gpg-connect-agent
.RI [ options ] [commands]
@end ifset
@mansect description
The @command{gpg-connect-agent} is a utility to communicate with a
running @command{gpg-agent}. It is useful to check out the commands
@command{gpg-agent} provides using the Assuan interface. It might
also be useful for scripting simple applications. Input is expected
at stdin and output gets printed to stdout.
It is very similar to running @command{gpg-agent} in server mode; but
here we connect to a running instance.
@menu
* Invoking gpg-connect-agent:: List of all options.
* Controlling gpg-connect-agent:: Control commands.
@end menu
@manpause
@node Invoking gpg-connect-agent
@subsection List of all options
@noindent
@command{gpg-connect-agent} is invoked this way:
@example
gpg-connect-agent [options] [commands]
@end example
@mancont
@noindent
The following options may be used:
@table @gnupgtabopt
@item -v
@itemx --verbose
@opindex verbose
Output additional information while running.
@item -q
@item --quiet
@opindex q
@opindex quiet
Try to be as quiet as possible.
@include opt-homedir.texi
@item --agent-program @var{file}
@opindex agent-program
Specify the agent program to be started if none is running. The
default value is determined by running @command{gpgconf} with the
option @option{--list-dirs}. Note that the pipe symbol (@code{|}) is
used for a regression test suite hack and may thus not be used in the
file name.
@item --dirmngr-program @var{file}
@opindex dirmngr-program
Specify the directory manager (keyserver client) program to be started
if none is running. This has only an effect if used together with the
option @option{--dirmngr}.
@item --dirmngr
@opindex dirmngr
Connect to a running directory manager (keyserver client) instead of
to the gpg-agent. If a dirmngr is not running, start it.
@item -S
@itemx --raw-socket @var{name}
@opindex raw-socket
Connect to socket @var{name} assuming this is an Assuan style server.
Do not run any special initializations or environment checks. This may
be used to directly connect to any Assuan style socket server.
@item -E
@itemx --exec
@opindex exec
Take the rest of the command line as a program and it's arguments and
execute it as an Assuan server. Here is how you would run @command{gpgsm}:
@smallexample
gpg-connect-agent --exec gpgsm --server
@end smallexample
Note that you may not use options on the command line in this case.
@item --no-ext-connect
@opindex no-ext-connect
When using @option{-S} or @option{--exec}, @command{gpg-connect-agent}
connects to the Assuan server in extended mode to allow descriptor
passing. This option makes it use the old mode.
@item --no-autostart
@opindex no-autostart
Do not start the gpg-agent or the dirmngr if it has not yet been
started.
@item -r @var{file}
@itemx --run @var{file}
@opindex run
Run the commands from @var{file} at startup and then continue with the
regular input method. Note, that commands given on the command line are
executed after this file.
@item -s
@itemx --subst
@opindex subst
Run the command @code{/subst} at startup.
@item --hex
@opindex hex
Print data lines in a hex format and the ASCII representation of
non-control characters.
@item --decode
@opindex decode
Decode data lines. That is to remove percent escapes but make sure that
a new line always starts with a D and a space.
@end table
@mansect control commands
@node Controlling gpg-connect-agent
@subsection Control commands
While reading Assuan commands, gpg-agent also allows a few special
commands to control its operation. These control commands all start
with a slash (@code{/}).
@table @code
@item /echo @var{args}
Just print @var{args}.
@item /let @var{name} @var{value}
Set the variable @var{name} to @var{value}. Variables are only
substituted on the input if the @command{/subst} has been used.
Variables are referenced by prefixing the name with a dollar sign and
optionally include the name in curly braces. The rules for a valid name
are identically to those of the standard bourne shell. This is not yet
enforced but may be in the future. When used with curly braces no
leading or trailing white space is allowed.
If a variable is not found, it is searched in the environment and if
found copied to the table of variables.
Variable functions are available: The name of the function must be
followed by at least one space and the at least one argument. The
following functions are available:
@table @code
@item get
Return a value described by the argument. Available arguments are:
@table @code
@item cwd
The current working directory.
@item homedir
The gnupg homedir.
@item sysconfdir
GnuPG's system configuration directory.
@item bindir
GnuPG's binary directory.
@item libdir
GnuPG's library directory.
@item libexecdir
GnuPG's library directory for executable files.
@item datadir
GnuPG's data directory.
@item serverpid
The PID of the current server. Command @command{/serverpid} must
have been given to return a useful value.
@end table
@item unescape @var{args}
Remove C-style escapes from @var{args}. Note that @code{\0} and
@code{\x00} terminate the returned string implicitly. The string to be
converted are the entire arguments right behind the delimiting space of
the function name.
@item unpercent @var{args}
@itemx unpercent+ @var{args}
Remove percent style escaping from @var{args}. Note that @code{%00}
terminates the string implicitly. The string to be converted are the
entire arguments right behind the delimiting space of the function
name. @code{unpercent+} also maps plus signs to a spaces.
@item percent @var{args}
@itemx percent+ @var{args}
Escape the @var{args} using percent style escaping. Tabs, formfeeds,
linefeeds, carriage returns and colons are escaped. @code{percent+} also
maps spaces to plus signs.
@item errcode @var{arg}
@itemx errsource @var{arg}
@itemx errstring @var{arg}
Assume @var{arg} is an integer and evaluate it using @code{strtol}. Return
the gpg-error error code, error source or a formatted string with the
error code and error source.
@item +
@itemx -
@itemx *
@itemx /
@itemx %
Evaluate all arguments as long integers using @code{strtol} and apply
this operator. A division by zero yields an empty string.
@item !
@itemx |
@itemx &
Evaluate all arguments as long integers using @code{strtol} and apply
the logical operators NOT, OR or AND. The NOT operator works on the
last argument only.
@end table
@item /definq @var{name} @var{var}
Use content of the variable @var{var} for inquiries with @var{name}.
@var{name} may be an asterisk (@code{*}) to match any inquiry.
@item /definqfile @var{name} @var{file}
Use content of @var{file} for inquiries with @var{name}.
@var{name} may be an asterisk (@code{*}) to match any inquiry.
@item /definqprog @var{name} @var{prog}
Run @var{prog} for inquiries matching @var{name} and pass the
entire line to it as command line arguments.
@item /datafile @var{name}
Write all data lines from the server to the file @var{name}. The file
is opened for writing and created if it does not exists. An existing
file is first truncated to 0. The data written to the file fully
decoded. Using a single dash for @var{name} writes to stdout. The
file is kept open until a new file is set using this command or this
command is used without an argument.
@item /showdef
Print all definitions
@item /cleardef
Delete all definitions
@item /sendfd @var{file} @var{mode}
Open @var{file} in @var{mode} (which needs to be a valid @code{fopen}
mode string) and send the file descriptor to the server. This is
usually followed by a command like @code{INPUT FD} to set the
input source for other commands.
@item /recvfd
Not yet implemented.
@item /open @var{var} @var{file} [@var{mode}]
Open @var{file} and assign the file descriptor to @var{var}. Warning:
This command is experimental and might change in future versions.
@item /close @var{fd}
Close the file descriptor @var{fd}. Warning: This command is
experimental and might change in future versions.
@item /showopen
Show a list of open files.
@item /serverpid
Send the Assuan command @command{GETINFO pid} to the server and store
the returned PID for internal purposes.
@item /sleep
Sleep for a second.
@item /hex
@itemx /nohex
Same as the command line option @option{--hex}.
@item /decode
@itemx /nodecode
Same as the command line option @option{--decode}.
@item /subst
@itemx /nosubst
Enable and disable variable substitution. It defaults to disabled
unless the command line option @option{--subst} has been used.
If /subst as been enabled once, leading whitespace is removed from
input lines which makes scripts easier to read.
@item /while @var{condition}
@itemx /end
These commands provide a way for executing loops. All lines between
the @code{while} and the corresponding @code{end} are executed as long
as the evaluation of @var{condition} yields a non-zero value or is the
string @code{true} or @code{yes}. The evaluation is done by passing
@var{condition} to the @code{strtol} function. Example:
@smallexample
/subst
/let i 3
/while $i
/echo loop counter is $i
/let i $@{- $i 1@}
/end
@end smallexample
@item /if @var{condition}
@itemx /end
These commands provide a way for conditional execution. All lines between
the @code{if} and the corresponding @code{end} are executed only if
the evaluation of @var{condition} yields a non-zero value or is the
string @code{true} or @code{yes}. The evaluation is done by passing
@var{condition} to the @code{strtol} function.
@item /run @var{file}
Run commands from @var{file}.
@item /bye
Terminate the connection and the program.
@item /help
Print a list of available control commands.
@end table
@ifset isman
@mansect see also
@command{gpg-agent}(1),
@command{scdaemon}(1)
@include see-also-note.texi
@end ifset
@c
@c DIRMNGR-CLIENT
@c
@node dirmngr-client
@section The Dirmngr Client Tool
@manpage dirmngr-client.1
@ifset manverb
.B dirmngr-client
\- Tool to access the Dirmngr services
@end ifset
@mansect synopsis
@ifset manverb
.B dirmngr-client
.RI [ options ]
.RI [ certfile | pattern ]
@end ifset
@mansect description
The @command{dirmngr-client} is a simple tool to contact a running
dirmngr and test whether a certificate has been revoked --- either by
being listed in the corresponding CRL or by running the OCSP protocol.
If no dirmngr is running, a new instances will be started but this is
in general not a good idea due to the huge performance overhead.
@noindent
The usual way to run this tool is either:
@example
dirmngr-client @var{acert}
@end example
@noindent
or
@example
dirmngr-client <@var{acert}
@end example
Where @var{acert} is one DER encoded (binary) X.509 certificates to be
tested.
@ifclear isman
The return value of this command is
@end ifclear
@mansect return value
@ifset isman
@command{dirmngr-client} returns these values:
@end ifset
@table @code
@item 0
The certificate under question is valid; i.e. there is a valid CRL
available and it is not listed there or the OCSP request returned that
that certificate is valid.
@item 1
The certificate has been revoked
@item 2 (and other values)
There was a problem checking the revocation state of the certificate.
A message to stderr has given more detailed information. Most likely
this is due to a missing or expired CRL or due to a network problem.
@end table
@mansect options
@noindent
@command{dirmngr-client} may be called with the following options:
@table @gnupgtabopt
@item --version
@opindex version
Print the program version and licensing information. Note that you cannot
abbreviate this command.
@item --help, -h
@opindex help
Print a usage message summarizing the most useful command-line options.
Note that you cannot abbreviate this command.
@item --quiet, -q
@opindex quiet
Make the output extra brief by suppressing any informational messages.
@item -v
@item --verbose
@opindex v
@opindex verbose
Outputs additional information while running.
You can increase the verbosity by giving several
verbose commands to @sc{dirmngr}, such as @samp{-vv}.
@item --pem
@opindex pem
Assume that the given certificate is in PEM (armored) format.
@item --ocsp
@opindex ocsp
Do the check using the OCSP protocol and ignore any CRLs.
@item --force-default-responder
@opindex force-default-responder
When checking using the OCSP protocol, force the use of the default OCSP
responder. That is not to use the Reponder as given by the certificate.
@item --ping
@opindex ping
Check whether the dirmngr daemon is up and running.
@item --cache-cert
@opindex cache-cert
Put the given certificate into the cache of a running dirmngr. This is
mainly useful for debugging.
@item --validate
@opindex validate
Validate the given certificate using dirmngr's internal validation code.
This is mainly useful for debugging.
@item --load-crl
@opindex load-crl
This command expects a list of filenames with DER encoded CRL files.
With the option @option{--url} URLs are expected in place of filenames
and they are loaded directly from the given location. All CRLs will be
validated and then loaded into dirmngr's cache.
@item --lookup
@opindex lookup
Take the remaining arguments and run a lookup command on each of them.
The results are Base-64 encoded outputs (without header lines). This
may be used to retrieve certificates from a server. However the output
format is not very well suited if more than one certificate is returned.
@item --url
@itemx -u
@opindex url
Modify the @command{lookup} and @command{load-crl} commands to take an URL.
@item --local
@itemx -l
@opindex url
Let the @command{lookup} command only search the local cache.
@item --squid-mode
@opindex squid-mode
Run @sc{dirmngr-client} in a mode suitable as a helper program for
Squid's @option{external_acl_type} option.
@end table
@ifset isman
@mansect see also
@command{dirmngr}(8),
@command{gpgsm}(1)
@include see-also-note.texi
@end ifset
@c
@c GPGPARSEMAIL
@c
@node gpgparsemail
@section Parse a mail message into an annotated format
@manpage gpgparsemail.1
@ifset manverb
.B gpgparsemail
\- Parse a mail message into an annotated format
@end ifset
@mansect synopsis
@ifset manverb
.B gpgparsemail
.RI [ options ]
.RI [ file ]
@end ifset
@mansect description
The @command{gpgparsemail} is a utility currently only useful for
debugging. Run it with @code{--help} for usage information.
@c
@c SYMCRYPTRUN
@c
@node symcryptrun
@section Call a simple symmetric encryption tool
@manpage symcryptrun.1
@ifset manverb
.B symcryptrun
\- Call a simple symmetric encryption tool
@end ifset
@mansect synopsis
@ifset manverb
.B symcryptrun
.B \-\-class
.I class
.B \-\-program
.I program
.B \-\-keyfile
.I keyfile
.RB [ --decrypt | --encrypt ]
.RI [ inputfile ]
@end ifset
@mansect description
Sometimes simple encryption tools are already in use for a long time
and there might be a desire to integrate them into the GnuPG
framework. The protocols and encryption methods might be non-standard
or not even properly documented, so that a full-fledged encryption
tool with an interface like @command{gpg} is not doable.
@command{symcryptrun} provides a solution: It operates by calling the
external encryption/decryption module and provides a passphrase for a
key using the standard @command{pinentry} based mechanism through
@command{gpg-agent}.
Note, that @command{symcryptrun} is only available if GnuPG has been
configured with @samp{--enable-symcryptrun} at build time.
@menu
* Invoking symcryptrun:: List of all commands and options.
@end menu
@manpause
@node Invoking symcryptrun
@subsection List of all commands and options
@noindent
@command{symcryptrun} is invoked this way:
@example
symcryptrun --class CLASS --program PROGRAM --keyfile KEYFILE
[--decrypt | --encrypt] [inputfile]
@end example
@mancont
For encryption, the plain text must be provided on STDIN or as the
argument @var{inputfile}, and the ciphertext will be output to STDOUT.
For decryption vice versa.
@var{CLASS} describes the calling conventions of the external tool.
Currently it must be given as @samp{confucius}. @var{PROGRAM} is
the full filename of that external tool.
For the class @samp{confucius} the option @option{--keyfile} is
required; @var{keyfile} is the name of a file containing the secret key,
which may be protected by a passphrase. For detailed calling
conventions, see the source code.
@noindent
Note, that @command{gpg-agent} must be running before starting
@command{symcryptrun}.
@noindent
The following additional options may be used:
@table @gnupgtabopt
@item -v
@itemx --verbose
@opindex verbose
Output additional information while running.
@item -q
@item --quiet
@opindex q
@opindex quiet
Try to be as quiet as possible.
@include opt-homedir.texi
@item --log-file @var{file}
@opindex log-file
Append all logging output to @var{file}. Use @file{socket://} to log
to socket. Default is to write logging information to STDERR.
@end table
@noindent
The possible exit status codes of @command{symcryptrun} are:
@table @code
@item 0
Success.
@item 1
Some error occurred.
@item 2
No valid passphrase was provided.
@item 3
The operation was canceled by the user.
@end table
@mansect see also
@ifset isman
@command{gpg}(1),
@command{gpgsm}(1),
@command{gpg-agent}(1),
@end ifset
@include see-also-note.texi
@c
@c GPGTAR
@c
@manpage gpgtar.1
@node gpgtar
@section Encrypt or sign files into an archive
@ifset manverb
.B gpgtar
\- Encrypt or sign files into an archive
@end ifset
@mansect synopsis
@ifset manverb
.B gpgtar
.RI [ options ]
.I filename1
.I [ filename2, ... ]
.I directory1
.I [ directory2, ... ]
@end ifset
@mansect description
@command{gpgtar} encrypts or signs files into an archive. It is an
gpg-ized tar using the same format as used by PGP's PGP Zip.
@manpause
@noindent
@command{gpgtar} is invoked this way:
@example
gpgtar [options] @var{filename1} [@var{filename2}, ...] @var{directory} [@var{directory2}, ...]
@end example
@mansect options
@noindent
@command{gpgtar} understands these options:
@table @gnupgtabopt
@item --create
@opindex create
Put given files and directories into a vanilla ``ustar'' archive.
@item --extract
@opindex extract
Extract all files from a vanilla ``ustar'' archive.
@item --encrypt
@itemx -e
@opindex encrypt
Encrypt given files and directories into an archive. This option may
be combined with option @option{--symmetric} for an archive that may
be decrypted via a secret key or a passphrase.
@item --decrypt
@itemx -d
@opindex decrypt
Extract all files from an encrypted archive.
@item --sign
@itemx -s
Make a signed archive from the given files and directories. This can
be combined with option @option{--encrypt} to create a signed and then
encrypted archive.
@item --list-archive
@itemx -t
@opindex list-archive
List the contents of the specified archive.
@item --symmetric
@itemx -c
Encrypt with a symmetric cipher using a passphrase. The default
symmetric cipher used is @value{GPGSYMENCALGO}, but may be chosen with the
@option{--cipher-algo} option to @command{gpg}.
@item --recipient @var{user}
@itemx -r @var{user}
@opindex recipient
Encrypt for user id @var{user}. For details see @command{gpg}.
@item --local-user @var{user}
@itemx -u @var{user}
@opindex local-user
Use @var{user} as the key to sign with. For details see @command{gpg}.
@item --output @var{file}
@itemx -o @var{file}
@opindex output
Write the archive to the specified file @var{file}.
@item --verbose
@itemx -v
@opindex verbose
Enable extra informational output.
@item --quiet
@itemx -q
@opindex quiet
Try to be as quiet as possible.
@item --skip-crypto
@opindex skip-crypto
Skip all crypto operations and create or extract vanilla ``ustar''
archives.
@item --dry-run
@opindex dry-run
Do not actually output the extracted files.
@item --directory @var{dir}
@itemx -C @var{dir}
@opindex directory
Extract the files into the directory @var{dir}. The default is to
take the directory name from the input filename. If no input filename
is known a directory named @file{GPGARCH} is used. For tarball
creation, switch to directory @var{dir} before performing any
operations.
@item --files-from @var{file}
@itemx -T @var{file}
Take the file names to work from the file @var{file}; one file per
line.
@item --null
@opindex null
Modify option @option{--files-from} to use a binary nul instead of a
linefeed to separate file names.
@item --openpgp
@opindex openpgp
This option has no effect because OpenPGP encryption and signing is
the default.
@item --cms
@opindex cms
This option is reserved and shall not be used. It will eventually be
used to encrypt or sign using the CMS protocol; but that is not yet
implemented.
@item --set-filename @var{file}
@opindex set-filename
Use the last component of @var{file} as the output directory. The
default is to take the directory name from the input filename. If no
input filename is known a directory named @file{GPGARCH} is used.
This option is deprecated in favor of option @option{--directory}.
@item --gpg @var{gpgcmd}
@opindex gpg
Use the specified command @var{gpgcmd} instead of @command{gpg}.
@item --gpg-args @var{args}
@opindex gpg-args
Pass the specified extra options to @command{gpg}.
@item --tar-args @var{args}
@opindex tar-args
Assume @var{args} are standard options of the command @command{tar}
and parse them. The only supported tar options are "--directory",
"--files-from", and "--null" This is an obsolete options because those
supported tar options can also be given directly.
@item --version
@opindex version
Print version of the program and exit.
@item --help
@opindex help
Display a brief help page and exit.
@end table
@mansect diagnostics
@noindent
The program returns 0 if everything was fine, 1 otherwise.
@mansect examples
@ifclear isman
@noindent
Some examples:
@end ifclear
@noindent
Encrypt the contents of directory @file{mydocs} for user Bob to file
@file{test1}:
@example
gpgtar --encrypt --output test1 -r Bob mydocs
@end example
@noindent
List the contents of archive @file{test1}:
@example
gpgtar --list-archive test1
@end example
@mansect see also
@ifset isman
@command{gpg}(1),
@command{tar}(1),
@end ifset
@include see-also-note.texi
+
+@c
+@c GPG-CHECK-PATTERN
+@c
+@manpage gpg-check-pattern.1
+@node gpg-check-pattern
+@section Check a passphrase on stdin against the patternfile
+@ifset manverb
+.B gpg-check-pattern
+\- Check a passphrase on stdin against the patternfile
+@end ifset
+
+@mansect synopsis
+@ifset manverb
+.B gpg\-check\-pattern
+.RI [ options ]
+.I patternfile
+@end ifset
+
+@mansect description
+@command{gpg-check-pattern} checks a passphrase given on stdin against
+a specified pattern file.
+
+@mansect options
+@noindent
+
+@table @gnupgtabopt
+
+@item --verbose
+@opindex verbose
+Enable extra informational output.
+
+@item --check
+@opindex check
+Run only a syntax check on the patternfile.
+
+@item --null
+@opindex null
+Input is expected to be null delimited.
+
+@end table
+
+@mansect see also
+@ifset isman
+@command{gpg}(1),
+@end ifset
+@include see-also-note.texi
diff --git a/doc/wks.texi b/doc/wks.texi
index f132b3186..9f1fff2a8 100644
--- a/doc/wks.texi
+++ b/doc/wks.texi
@@ -1,439 +1,447 @@
@c wks.texi - man pages for the Web Key Service tools.
@c Copyright (C) 2017 g10 Code GmbH
@c Copyright (C) 2017 Bundesamt für Sicherheit in der Informationstechnik
@c This is part of the GnuPG manual.
@c For copying conditions, see the file GnuPG.texi.
@include defs.inc
@node Web Key Service
@chapter Web Key Service
GnuPG comes with tools used to maintain and access a Web Key
Directory.
@menu
* gpg-wks-client:: Send requests via WKS
* gpg-wks-server:: Server to provide the WKS.
@end menu
@c
@c GPG-WKS-CLIENT
@c
@manpage gpg-wks-client.1
@node gpg-wks-client
@section Send requests via WKS
@ifset manverb
.B gpg-wks-client
\- Client for the Web Key Service
@end ifset
@mansect synopsis
@ifset manverb
.B gpg-wks-client
.RI [ options ]
.B \-\-supported
.I user-id
.br
.B gpg-wks-client
.RI [ options ]
.B \-\-check
.I user-id
.br
.B gpg-wks-client
.RI [ options ]
.B \-\-create
.I fingerprint
.I user-id
.br
.B gpg-wks-client
.RI [ options ]
.B \-\-receive
.br
.B gpg-wks-client
.RI [ options ]
.B \-\-read
@end ifset
@mansect description
The @command{gpg-wks-client} is used to send requests to a Web Key
Service provider. This is usuallay done to upload a key into a Web
Key Directory.
With the @option{--supported} command the caller can test whether a
site supports the Web Key Service. The argument is an arbitrary
address in the to be tested domain. For example
@file{foo@@example.net}. The command returns success if the Web Key
Service is supported. The operation is silent; to get diagnostic
output use the option @option{--verbose}. See option
@option{--with-colons} for a variant of this command.
With the @option{--check} command the caller can test whether a key
exists for a supplied mail address. The command returns success if a
key is available.
The @option{--create} command is used to send a request for
publication in the Web Key Directory. The arguments are the
fingerprint of the key and the user id to publish. The output from
the command is a properly formatted mail with all standard headers.
This mail can be fed to @command{sendmail(8)} or any other tool to
actually send that mail. If @command{sendmail(8)} is installed the
option @option{--send} can be used to directly send the created
request. If the provider request a 'mailbox-only' user id and no such
user id is found, @command{gpg-wks-client} will try an additional user
id.
The @option{--receive} and @option{--read} commands are used to
process confirmation mails as send from the service provider. The
former expects an encrypted MIME messages, the latter an already
decrypted MIME message. The result of these commands are another mail
which can be send in the same way as the mail created with
@option{--create}.
The command @option{--install-key} manually installs a key into a
local directory (see option @option{-C}) reflecting the structure of a
WKD. The arguments are a file with the keyblock and the user-id to
install. If the first argument resembles a fingerprint the key is
taken from the current keyring; to force the use of a file, prefix the
first argument with "./". If no arguments are given the parameters
are read from stdin; the expected format are lines with the
fingerprint and the mailbox separated by a space. The command
@option{--remove-key} removes a key from that directory, its only
argument is a user-id.
+The command @option{--print-wkd-hash} prints the WKD user-id identifiers
+and the corresponding mailboxes from the user-ids given on the command
+line or via stdin (one user-id per line).
+
+The command @option{--print-wkd-url} prints the URLs used to fetch the
+key for the given user-ids from WKD. The meanwhile preferred format
+with sub-domains is used here.
+
@command{gpg-wks-client} is not commonly invoked directly and thus it
is not installed in the bin directory. Here is an example how it can
be invoked manually to check for a Web Key Directory entry for
@file{foo@@example.org}:
@example
$(gpgconf --list-dirs libexecdir)/gpg-wks-client --check foo@@example.net
@end example
@mansect options
@noindent
@command{gpg-wks-client} understands these options:
@table @gnupgtabopt
@item --send
@opindex send
Directly send created mails using the @command{sendmail} command.
Requires installation of that command.
@item --with-colons
@opindex with-colons
This option has currently only an effect on the @option{--supported}
command. If it is used all arguments on the command line are taken
as domain names and tested for WKD support. The output format is one
line per domain with colon delimited fields. The currently specified
fields are (future versions may specify additional fields):
@table @asis
@item 1 - domain
This is the domain name. Although quoting is not required for valid
domain names this field is specified to be quoted in standard C
manner.
@item 2 - WKD
If the value is true the domain supports the Web Key Directory.
@item 3 - WKS
If the value is true the domain supports the Web Key Service
protocol to upload keys to the directory.
@item 4 - error-code
This may contain an gpg-error code to describe certain
failures. Use @samp{gpg-error CODE} to explain the code.
@item 5 - protocol-version
The minimum protocol version supported by the server.
@item 6 - auth-submit
The auth-submit flag from the policy file of the server.
@item 7 - mailbox-only
The mailbox-only flag from the policy file of the server.
@end table
@item --output @var{file}
@itemx -o
@opindex output
Write the created mail to @var{file} instead of stdout. Note that the
value @code{-} for @var{file} is the same as writing to stdout.
@item --status-fd @var{n}
@opindex status-fd
Write special status strings to the file descriptor @var{n}.
This program returns only the status messages SUCCESS or FAILURE which
are helpful when the caller uses a double fork approach and can't
easily get the return code of the process.
@item -C @var{dir}
@itemx --directory @var{dir}
@opindex directory
Use @var{dir} as top level directory for the commands
@option{--install-key} and @option{--remove-key}. The default is
@file{openpgpkey}.
@item --verbose
@opindex verbose
Enable extra informational output.
@item --quiet
@opindex quiet
Disable almost all informational output.
@item --version
@opindex version
Print version of the program and exit.
@item --help
@opindex help
Display a brief help page and exit.
@end table
@mansect see also
@ifset isman
@command{gpg-wks-server}(1)
@end ifset
@c
@c GPG-WKS-SERVER
@c
@manpage gpg-wks-server.1
@node gpg-wks-server
@section Provide the Web Key Service
@ifset manverb
.B gpg-wks-server
\- Server providing the Web Key Service
@end ifset
@mansect synopsis
@ifset manverb
.B gpg-wks-server
.RI [ options ]
.B \-\-receive
.br
.B gpg-wks-server
.RI [ options ]
.B \-\-cron
.br
.B gpg-wks-server
.RI [ options ]
.B \-\-list-domains
.br
.B gpg-wks-server
.RI [ options ]
.B \-\-check-key
.I user-id
.br
.B gpg-wks-server
.RI [ options ]
.B \-\-install-key
.I file
.I user-id
.br
.B gpg-wks-server
.RI [ options ]
.B \-\-remove-key
.I user-id
.br
.B gpg-wks-server
.RI [ options ]
.B \-\-revoke-key
.I user-id
@end ifset
@mansect description
The @command{gpg-wks-server} is a server site implementation of the
Web Key Service. It receives requests for publication, sends
confirmation requests, receives confirmations, and published the key.
It also has features to ease the setup and maintenance of a Web Key
Directory.
When used with the command @option{--receive} a single Web Key Service
mail is processed. Commonly this command is used with the option
@option{--send} to directly send the crerated mails back. See below
for an installation example.
The command @option{--cron} is used for regular cleanup tasks. For
example non-confirmed requested should be removed after their expire
time. It is best to run this command once a day from a cronjob.
The command @option{--list-domains} prints all configured domains.
Further it creates missing directories for the configuration and
prints warnings pertaining to problems in the configuration.
The command @option{--check-key} (or just @option{--check}) checks
whether a key with the given user-id is installed. The process returns
success in this case; to also print a diagnostic use the option
@option{-v}. If the key is not installed a diagnostic is printed and
the process returns failure; to suppress the diagnostic, use option
@option{-q}. More than one user-id can be given; see also option
@option{with-file}.
The command @option{--install-key} manually installs a key into the
WKD. The arguments are a file with the keyblock and the user-id to
install. If the first argument resembles a fingerprint the key is
taken from the current keyring; to force the use of a file, prefix the
first argument with "./". If no arguments are given the parameters
are read from stdin; the expected format are lines with the
fingerprint and the mailbox separated by a space.
The command @option{--remove-key} uninstalls a key from the WKD. The
process returns success in this case; to also print a diagnostic, use
option @option{-v}. If the key is not installed a diagnostic is
printed and the process returns failure; to suppress the diagnostic,
use option @option{-q}.
The command @option{--revoke-key} is not yet functional.
@mansect options
@noindent
@command{gpg-wks-server} understands these options:
@table @gnupgtabopt
@item -C @var{dir}
@itemx --directory @var{dir}
@opindex directory
Use @var{dir} as top level directory for domains. The default is
@file{/var/lib/gnupg/wks}.
@item --from @var{mailaddr}
@opindex from
Use @var{mailaddr} as the default sender address.
@item --header @var{name}=@var{value}
@opindex header
Add the mail header "@var{name}: @var{value}" to all outgoing mails.
@item --send
@opindex send
Directly send created mails using the @command{sendmail} command.
Requires installation of that command.
@item -o @var{file}
@itemx --output @var{file}
@opindex output
Write the created mail also to @var{file}. Note that the value
@code{-} for @var{file} would write it to stdout.
@item --with-dir
@opindex with-dir
When used with the command @option{--list-domains} print for each
installed domain the domain name and its directory name.
@item --with-file
@opindex with-file
When used with the command @option{--check-key} print for each user-id,
the address, 'i' for installed key or 'n' for not installed key, and
the filename.
@item --verbose
@opindex verbose
Enable extra informational output.
@item --quiet
@opindex quiet
Disable almost all informational output.
@item --version
@opindex version
Print version of the program and exit.
@item --help
@opindex help
Display a brief help page and exit.
@end table
@noindent
@mansect examples
@chapheading Examples
The Web Key Service requires a working directory to store keys
pending for publication. As root create a working directory:
@example
# mkdir /var/lib/gnupg/wks
# chown webkey:webkey /var/lib/gnupg/wks
# chmod 2750 /var/lib/gnupg/wks
@end example
Then under your webkey account create directories for all your
domains. Here we do it for "example.net":
@example
$ mkdir /var/lib/gnupg/wks/example.net
@end example
Finally run
@example
$ gpg-wks-server --list-domains
@end example
to create the required sub-directories with the permissions set
correctly. For each domain a submission address needs to be
configured. All service mails are directed to that address. It can
be the same address for all configured domains, for example:
@example
$ cd /var/lib/gnupg/wks/example.net
$ echo key-submission@@example.net >submission-address
@end example
The protocol requires that the key to be published is send with an
encrypted mail to the service. Thus you need to create a key for
the submission address:
@example
$ gpg --batch --passphrase '' --quick-gen-key key-submission@@example.net
$ gpg -K key-submission@@example.net
@end example
The output of the last command looks similar to this:
@example
sec rsa3072 2016-08-30 [SC]
C0FCF8642D830C53246211400346653590B3795B
uid [ultimate] key-submission@@example.net
bxzcxpxk8h87z1k7bzk86xn5aj47intu@@example.net
ssb rsa3072 2016-08-30 [E]
@end example
Take the fingerprint from that output and manually publish the key:
@example
$ gpg-wks-server --install-key C0FCF8642D830C53246211400346653590B3795B \
> key-submission@@example.net
@end example
Finally that submission address needs to be redirected to a script
running @command{gpg-wks-server}. The @command{procmail} command can
be used for this: Redirect the submission address to the user "webkey"
and put this into webkey's @file{.procmailrc}:
@example
:0
* !^From: webkey@@example.net
* !^X-WKS-Loop: webkey.example.net
|gpg-wks-server -v --receive \
--header X-WKS-Loop=webkey.example.net \
--from webkey@@example.net --send
@end example
@mansect see also
@ifset isman
@command{gpg-wks-client}(1)
@end ifset
diff --git a/doc/yat2m.c b/doc/yat2m.c
index be0ef17fd..2d6f54ea2 100644
--- a/doc/yat2m.c
+++ b/doc/yat2m.c
@@ -1,1646 +1,1647 @@
/* yat2m.c - Yet Another Texi 2 Man converter
* Copyright (C) 2005, 2013, 2015, 2016, 2017 g10 Code GmbH
* Copyright (C) 2006, 2008, 2011 Free Software Foundation, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
/**********************************************
* Note: The canonical source of this tool **
* is part of libgpg-error and it **
* installs yat2m on the build system. **
**********************************************/
/*
This is a simple texinfo to man page converter. It needs some
special markup in th e texinfo and tries best to get a create man
page. It has been designed for the GnuPG man pages and thus only
a few texinfo commands are supported.
To use this you need to add the following macros into your texinfo
source:
@macro manpage {a}
@end macro
@macro mansect {a}
@end macro
@macro manpause
@end macro
@macro mancont
@end macro
They are used by yat2m to select parts of the Texinfo which should
go into the man page. These macros need to be used without leading
left space. Processing starts after a "manpage" macro has been
seen. "mansect" identifies the section and yat2m make sure to
emit the sections in the proper order. Note that @mansect skips
the next input line if that line begins with @section, @subsection or
@chapheading.
To insert verbatim troff markup, the following texinfo code may be
used:
@ifset manverb
.B whateever you want
@end ifset
alternatively a special comment may be used:
@c man:.B whatever you want
This is useful in case you need just one line. If you want to
include parts only in the man page but keep the texinfo
translation you may use:
@ifset isman
stuff to be rendered only on man pages
@end ifset
or to exclude stuff from man pages:
@ifclear isman
stuff not to be rendered on man pages
@end ifclear
the keyword @section is ignored, however @subsection gets rendered
as ".SS". @menu is completely skipped. Several man pages may be
extracted from one file, either using the --store or the --select
option.
If you want to indent tables in the source use this style:
@table foo
@item
@item
@table
@item
@end
@end
Don't change the indentation within a table and keep the same
number of white space at the start of the line. yat2m simply
detects the number of white spaces in front of an @item and remove
this number of spaces from all following lines until a new @item
is found or there are less spaces than for the last @item.
Note that @* does only work correctly if used at the end of an
input line.
*/
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <string.h>
#include <errno.h>
#include <stdarg.h>
#include <assert.h>
#include <ctype.h>
#include <time.h>
#if __GNUC__
# define MY_GCC_VERSION (__GNUC__ * 10000 \
+ __GNUC_MINOR__ * 100 \
+ __GNUC_PATCHLEVEL__)
#else
# define MY_GCC_VERSION 0
#endif
#if MY_GCC_VERSION >= 20500
# define ATTR_PRINTF(f, a) __attribute__ ((format(printf,f,a)))
# define ATTR_NR_PRINTF(f, a) __attribute__ ((noreturn, format(printf,f,a)))
#else
# define ATTR_PRINTF(f, a)
# define ATTR_NR_PRINTF(f, a)
#endif
#if MY_GCC_VERSION >= 30200
# define ATTR_MALLOC __attribute__ ((__malloc__))
#else
# define ATTR_MALLOC
#endif
#define PGM "yat2m"
#define VERSION "1.0"
/* The maximum length of a line including the linefeed and one extra
character. */
#define LINESIZE 1024
/* Number of allowed condition nestings. */
#define MAX_CONDITION_NESTING 10
/* Option flags. */
static int verbose;
static int quiet;
static int debug;
static const char *opt_source;
static const char *opt_release;
static const char *opt_date;
static const char *opt_select;
static const char *opt_include;
static int opt_store;
/* Flag to keep track whether any error occurred. */
static int any_error;
/* Object to keep macro definitions. */
struct macro_s
{
struct macro_s *next;
char *value; /* Malloced value. */
char name[1];
};
typedef struct macro_s *macro_t;
/* List of all defined macros. */
static macro_t macrolist;
/* List of variables set by @set. */
static macro_t variablelist;
/* List of global macro names. The value part is not used. */
static macro_t predefinedmacrolist;
/* Object to keep track of @isset and @ifclear. */
struct condition_s
{
int manverb; /* "manverb" needs special treatment. */
int isset; /* This is an @isset condition. */
char name[1]; /* Name of the condition macro. */
};
typedef struct condition_s *condition_t;
/* The stack used to evaluate conditions. And the current states. */
static condition_t condition_stack[MAX_CONDITION_NESTING];
static int condition_stack_idx;
static int cond_is_active; /* State of ifset/ifclear */
static int cond_in_verbatim; /* State of "manverb". */
/* Object to store one line of content. */
struct line_buffer_s
{
struct line_buffer_s *next;
int verbatim; /* True if LINE contains verbatim data. The default
is Texinfo source. */
char *line;
};
typedef struct line_buffer_s *line_buffer_t;
/* Object to collect the data of a section. */
struct section_buffer_s
{
char *name; /* Malloced name of the section. This may be
NULL to indicate this slot is not used. */
line_buffer_t lines; /* Linked list with the lines of the section. */
line_buffer_t *lines_tail; /* Helper for faster appending to the
linked list. */
line_buffer_t last_line; /* Points to the last line appended. */
};
typedef struct section_buffer_s *section_buffer_t;
/* Variable to keep info about the current page together. */
static struct
{
/* Filename of the current page or NULL if no page is active. Malloced. */
char *name;
/* Number of allocated elements in SECTIONS below. */
size_t n_sections;
/* Array with the data of the sections. */
section_buffer_t sections;
} thepage;
/* The list of standard section names. COMMANDS and ASSUAN are GnuPG
specific. */
static const char * const standard_sections[] =
{ "NAME", "SYNOPSIS", "DESCRIPTION",
"RETURN VALUE", "EXIT STATUS", "ERROR HANDLING", "ERRORS",
"COMMANDS", "OPTIONS", "USAGE", "EXAMPLES", "FILES",
"ENVIRONMENT", "DIAGNOSTICS", "SECURITY", "CONFORMING TO",
"ASSUAN", "NOTES", "BUGS", "AUTHOR", "SEE ALSO", NULL };
/*-- Local prototypes. --*/
static void proc_texi_buffer (FILE *fp, const char *line, size_t len,
int *table_level, int *eol_action);
static void die (const char *format, ...) ATTR_NR_PRINTF(1,2);
static void err (const char *format, ...) ATTR_PRINTF(1,2);
static void inf (const char *format, ...) ATTR_PRINTF(1,2);
static void *xmalloc (size_t n) ATTR_MALLOC;
static void *xcalloc (size_t n, size_t m) ATTR_MALLOC;
/*-- Functions --*/
/* Print diagnostic message and exit with failure. */
static void
die (const char *format, ...)
{
va_list arg_ptr;
fflush (stdout);
fprintf (stderr, "%s: ", PGM);
va_start (arg_ptr, format);
vfprintf (stderr, format, arg_ptr);
va_end (arg_ptr);
putc ('\n', stderr);
exit (1);
}
/* Print diagnostic message. */
static void
err (const char *format, ...)
{
va_list arg_ptr;
fflush (stdout);
if (strncmp (format, "%s:%d:", 6))
fprintf (stderr, "%s: ", PGM);
va_start (arg_ptr, format);
vfprintf (stderr, format, arg_ptr);
va_end (arg_ptr);
putc ('\n', stderr);
any_error = 1;
}
/* Print diagnostic message. */
static void
inf (const char *format, ...)
{
va_list arg_ptr;
fflush (stdout);
fprintf (stderr, "%s: ", PGM);
va_start (arg_ptr, format);
vfprintf (stderr, format, arg_ptr);
va_end (arg_ptr);
putc ('\n', stderr);
}
static void *
xmalloc (size_t n)
{
void *p = malloc (n);
if (!p)
die ("out of core: %s", strerror (errno));
return p;
}
static void *
xcalloc (size_t n, size_t m)
{
void *p = calloc (n, m);
if (!p)
die ("out of core: %s", strerror (errno));
return p;
}
static void *
xrealloc (void *old, size_t n)
{
void *p = realloc (old, n);
if (!p)
die ("out of core: %s", strerror (errno));
return p;
}
static char *
xstrdup (const char *string)
{
void *p = malloc (strlen (string)+1);
if (!p)
die ("out of core: %s", strerror (errno));
strcpy (p, string);
return p;
}
/* Uppercase the ascii characters in STRING. */
static char *
ascii_strupr (char *string)
{
char *p;
for (p = string; *p; p++)
if (!(*p & 0x80))
*p = toupper (*p);
return string;
}
/* Return the current date as an ISO string. */
const char *
isodatestring (void)
{
static char buffer[11+5];
struct tm *tp;
time_t atime;
if (opt_date && *opt_date)
atime = strtoul (opt_date, NULL, 10);
else
atime = time (NULL);
if (atime < 0)
strcpy (buffer, "????" "-??" "-??");
else
{
tp = gmtime (&atime);
sprintf (buffer,"%04d-%02d-%02d",
1900+tp->tm_year, tp->tm_mon+1, tp->tm_mday );
}
return buffer;
}
/* Add NAME to the list of predefined macros which are global for all
files. */
static void
add_predefined_macro (const char *name)
{
macro_t m;
for (m=predefinedmacrolist; m; m = m->next)
if (!strcmp (m->name, name))
break;
if (!m)
{
m = xcalloc (1, sizeof *m + strlen (name));
strcpy (m->name, name);
m->next = predefinedmacrolist;
predefinedmacrolist = m;
}
}
/* Create or update a macro with name MACRONAME and set its values TO
MACROVALUE. Note that ownership of the macro value is transferred
to this function. */
static void
set_macro (const char *macroname, char *macrovalue)
{
macro_t m;
for (m=macrolist; m; m = m->next)
if (!strcmp (m->name, macroname))
break;
if (m)
free (m->value);
else
{
m = xcalloc (1, sizeof *m + strlen (macroname));
strcpy (m->name, macroname);
m->next = macrolist;
macrolist = m;
}
m->value = macrovalue;
macrovalue = NULL;
}
/* Create or update a variable with name and value given in NAMEANDVALUE. */
static void
set_variable (char *nameandvalue)
{
macro_t m;
const char *value;
char *p;
for (p = nameandvalue; *p && *p != ' ' && *p != '\t'; p++)
;
if (!*p)
value = "";
else
{
*p++ = 0;
while (*p == ' ' || *p == '\t')
p++;
value = p;
}
for (m=variablelist; m; m = m->next)
if (!strcmp (m->name, nameandvalue))
break;
if (m)
free (m->value);
else
{
m = xcalloc (1, sizeof *m + strlen (nameandvalue));
strcpy (m->name, nameandvalue);
m->next = variablelist;
variablelist = m;
}
m->value = xstrdup (value);
}
/* Return true if the macro or variable NAME is set, i.e. not the
empty string and not evaluating to 0. */
static int
macro_set_p (const char *name)
{
macro_t m;
for (m = macrolist; m ; m = m->next)
if (!strcmp (m->name, name))
break;
if (!m)
for (m = variablelist; m ; m = m->next)
if (!strcmp (m->name, name))
break;
if (!m || !m->value || !*m->value)
return 0;
if ((*m->value & 0x80) || !isdigit (*m->value))
return 1; /* Not a digit but some other string. */
return !!atoi (m->value);
}
/* Evaluate the current conditions. */
static void
evaluate_conditions (const char *fname, int lnr)
{
int i;
/* for (i=0; i < condition_stack_idx; i++) */
/* inf ("%s:%d: stack[%d] %s %s %c", */
/* fname, lnr, i, condition_stack[i]->isset? "set":"clr", */
/* condition_stack[i]->name, */
/* (macro_set_p (condition_stack[i]->name) */
/* ^ !condition_stack[i]->isset)? 't':'f'); */
cond_is_active = 1;
cond_in_verbatim = 0;
if (condition_stack_idx)
{
for (i=0; i < condition_stack_idx; i++)
{
if (condition_stack[i]->manverb)
cond_in_verbatim = (macro_set_p (condition_stack[i]->name)
^ !condition_stack[i]->isset);
else if (!(macro_set_p (condition_stack[i]->name)
^ !condition_stack[i]->isset))
{
cond_is_active = 0;
break;
}
}
}
/* inf ("%s:%d: active=%d verbatim=%d", */
/* fname, lnr, cond_is_active, cond_in_verbatim); */
}
/* Push a condition with condition macro NAME onto the stack. If
ISSET is true, a @isset condition is pushed. */
static void
push_condition (const char *name, int isset, const char *fname, int lnr)
{
condition_t cond;
int manverb = 0;
if (condition_stack_idx >= MAX_CONDITION_NESTING)
{
err ("%s:%d: condition nested too deep", fname, lnr);
return;
}
if (!strcmp (name, "manverb"))
{
if (!isset)
{
err ("%s:%d: using \"@ifclear manverb\" is not allowed", fname, lnr);
return;
}
manverb = 1;
}
cond = xcalloc (1, sizeof *cond + strlen (name));
cond->manverb = manverb;
cond->isset = isset;
strcpy (cond->name, name);
condition_stack[condition_stack_idx++] = cond;
evaluate_conditions (fname, lnr);
}
/* Remove the last condition from the stack. ISSET is used for error
reporting. */
static void
pop_condition (int isset, const char *fname, int lnr)
{
if (!condition_stack_idx)
{
err ("%s:%d: unbalanced \"@end %s\"",
fname, lnr, isset?"isset":"isclear");
return;
}
condition_stack_idx--;
free (condition_stack[condition_stack_idx]);
condition_stack[condition_stack_idx] = NULL;
evaluate_conditions (fname, lnr);
}
/* Return a section buffer for the section NAME. Allocate a new buffer
if this is a new section. Keep track of the sections in THEPAGE.
This function may reallocate the section array in THEPAGE. */
static section_buffer_t
get_section_buffer (const char *name)
{
int i;
section_buffer_t sect;
/* If there is no section we put everything into the required NAME
section. Given that this is the first one listed it is likely
that error are easily visible. */
if (!name)
name = "NAME";
for (i=0; i < thepage.n_sections; i++)
{
sect = thepage.sections + i;
if (sect->name && !strcmp (name, sect->name))
return sect;
}
for (i=0; i < thepage.n_sections; i++)
if (!thepage.sections[i].name)
break;
if (thepage.n_sections && i < thepage.n_sections)
sect = thepage.sections + i;
else
{
/* We need to allocate or reallocate the section array. */
size_t old_n = thepage.n_sections;
size_t new_n = 20;
if (!old_n)
thepage.sections = xcalloc (new_n, sizeof *thepage.sections);
else
{
thepage.sections = xrealloc (thepage.sections,
((old_n + new_n)
* sizeof *thepage.sections));
memset (thepage.sections + old_n, 0,
new_n * sizeof *thepage.sections);
}
thepage.n_sections += new_n;
/* Setup the tail pointers. */
for (i=old_n; i < thepage.n_sections; i++)
{
sect = thepage.sections + i;
sect->lines_tail = &sect->lines;
}
sect = thepage.sections + old_n;
}
/* Store the name. */
assert (!sect->name);
sect->name = xstrdup (name);
return sect;
}
/* Add the content of LINE to the section named SECTNAME. */
static void
add_content (const char *sectname, char *line, int verbatim)
{
section_buffer_t sect;
line_buffer_t lb;
sect = get_section_buffer (sectname);
if (sect->last_line && !sect->last_line->verbatim == !verbatim)
{
/* Lets append that line to the last one. We do this to keep
all lines of the same kind (i.e.verbatim or not) together in
one large buffer. */
size_t n1, n;
lb = sect->last_line;
n1 = strlen (lb->line);
n = n1 + 1 + strlen (line) + 1;
lb->line = xrealloc (lb->line, n);
strcpy (lb->line+n1, "\n");
strcpy (lb->line+n1+1, line);
}
else
{
lb = xcalloc (1, sizeof *lb);
lb->verbatim = verbatim;
lb->line = xstrdup (line);
sect->last_line = lb;
*sect->lines_tail = lb;
sect->lines_tail = &lb->next;
}
}
/* Prepare for a new man page using the filename NAME. */
static void
start_page (char *name)
{
if (verbose)
inf ("starting page '%s'", name);
assert (!thepage.name);
thepage.name = xstrdup (name);
thepage.n_sections = 0;
}
/* Write the .TH entry of the current page. Return -1 if there is a
problem with the page. */
static int
write_th (FILE *fp)
{
char *name, *p;
fputs (".\\\" Created from Texinfo source by yat2m " VERSION "\n", fp);
name = ascii_strupr (xstrdup (thepage.name));
p = strrchr (name, '.');
if (!p || !p[1])
{
err ("no section name in man page '%s'", thepage.name);
free (name);
return -1;
}
*p++ = 0;
fprintf (fp, ".TH %s %s %s \"%s\" \"%s\"\n",
name, p, isodatestring (), opt_release, opt_source);
free (name);
return 0;
}
/* Process the texinfo command COMMAND (without the leading @) and
write output if needed to FP. REST is the remainder of the line
which should either point to an opening brace or to a white space.
The function returns the number of characters already processed
from REST. LEN is the usable length of REST. TABLE_LEVEL is used to
control the indentation of tables. */
static size_t
proc_texi_cmd (FILE *fp, const char *command, const char *rest, size_t len,
int *table_level, int *eol_action)
{
static struct {
const char *name; /* Name of the command. */
int what; /* What to do with this command. */
const char *lead_in; /* String to print with a opening brace. */
const char *lead_out;/* String to print with the closing brace. */
} cmdtbl[] = {
{ "command", 0, "\\fB", "\\fR" },
{ "code", 0, "\\fB", "\\fR" },
{ "url", 0, "\\fB", "\\fR" },
{ "sc", 0, "\\fB", "\\fR" },
{ "var", 0, "\\fI", "\\fR" },
- { "samp", 0, "\\(aq", "\\(aq" },
+ { "samp", 0, "\\(oq", "\\(cq" },
+ { "kbd", 0, "\\(oq", "\\(cq" },
{ "file", 0, "\\(oq\\fI","\\fR\\(cq" },
{ "env", 0, "\\(oq\\fI","\\fR\\(cq" },
{ "acronym", 0 },
{ "dfn", 0 },
{ "option", 0, "\\fB", "\\fR" },
{ "example", 1, ".RS 2\n.nf\n" },
{ "smallexample", 1, ".RS 2\n.nf\n" },
{ "asis", 7 },
{ "anchor", 7 },
{ "cartouche", 1 },
{ "ref", 0, "[", "]" },
{ "xref", 0, "See: [", "]" },
{ "pxref", 0, "see: [", "]" },
{ "uref", 0, "(\\fB", "\\fR)" },
{ "footnote",0, " ([", "])" },
{ "emph", 0, "\\fI", "\\fR" },
{ "w", 1 },
{ "c", 5 },
{ "efindex", 1 },
{ "opindex", 1 },
{ "cpindex", 1 },
{ "cindex", 1 },
{ "noindent", 0 },
{ "section", 1 },
{ "chapter", 1 },
{ "subsection", 6, "\n.SS " },
{ "chapheading", 0},
{ "item", 2, ".TP\n.B " },
{ "itemx", 2, ".TQ\n.B " },
{ "table", 3 },
{ "itemize", 3 },
{ "bullet", 0, "* " },
{ "*", 0, "\n.br"},
{ "/", 0 },
{ "end", 4 },
{ "quotation",1, ".RS\n\\fB" },
{ "value", 8 },
{ NULL }
};
size_t n;
int i;
const char *s;
const char *lead_out = NULL;
int ignore_args = 0;
for (i=0; cmdtbl[i].name && strcmp (cmdtbl[i].name, command); i++)
;
if (cmdtbl[i].name)
{
s = cmdtbl[i].lead_in;
if (s)
fputs (s, fp);
lead_out = cmdtbl[i].lead_out;
switch (cmdtbl[i].what)
{
case 1: /* Throw away the entire line. */
s = memchr (rest, '\n', len);
return s? (s-rest)+1 : len;
case 2: /* Handle @item. */
break;
case 3: /* Handle table. */
if (++(*table_level) > 1)
fputs (".RS\n", fp);
/* Now throw away the entire line. */
s = memchr (rest, '\n', len);
return s? (s-rest)+1 : len;
break;
case 4: /* Handle end. */
for (s=rest, n=len; n && (*s == ' ' || *s == '\t'); s++, n--)
;
if (n >= 5 && !memcmp (s, "table", 5)
&& (!n || s[5] == ' ' || s[5] == '\t' || s[5] == '\n'))
{
if ((*table_level)-- > 1)
fputs (".RE\n", fp);
else
fputs (".P\n", fp);
}
else if (n >= 7 && !memcmp (s, "example", 7)
&& (!n || s[7] == ' ' || s[7] == '\t' || s[7] == '\n'))
{
fputs (".fi\n.RE\n", fp);
}
else if (n >= 12 && !memcmp (s, "smallexample", 12)
&& (!n || s[12] == ' ' || s[12] == '\t' || s[12] == '\n'))
{
fputs (".fi\n.RE\n", fp);
}
else if (n >= 9 && !memcmp (s, "quotation", 9)
&& (!n || s[9] == ' ' || s[9] == '\t' || s[9] == '\n'))
{
fputs ("\\fR\n.RE\n", fp);
}
/* Now throw away the entire line. */
s = memchr (rest, '\n', len);
return s? (s-rest)+1 : len;
case 5: /* Handle special comments. */
for (s=rest, n=len; n && (*s == ' ' || *s == '\t'); s++, n--)
;
if (n >= 4 && !memcmp (s, "man:", 4))
{
for (s+=4, n-=4; n && *s != '\n'; n--, s++)
putc (*s, fp);
putc ('\n', fp);
}
/* Now throw away the entire line. */
s = memchr (rest, '\n', len);
return s? (s-rest)+1 : len;
case 6:
*eol_action = 1;
break;
case 7:
ignore_args = 1;
break;
case 8:
ignore_args = 1;
if (*rest != '{')
{
err ("opening brace for command '%s' missing", command);
return len;
}
else
{
/* Find closing brace. */
for (s=rest+1, n=1; *s && n < len; s++, n++)
if (*s == '}')
break;
if (*s != '}')
{
err ("closing brace for command '%s' not found", command);
return len;
}
else
{
size_t len = s - (rest + 1);
macro_t m;
for (m = variablelist; m; m = m->next)
if (strlen (m->name) == len
&&!strncmp (m->name, rest+1, len))
break;
if (m)
fputs (m->value, fp);
else
inf ("texinfo variable '%.*s' is not set",
(int)len, rest+1);
}
}
break;
default:
break;
}
}
else /* macro */
{
macro_t m;
for (m = macrolist; m ; m = m->next)
if (!strcmp (m->name, command))
break;
if (m)
{
proc_texi_buffer (fp, m->value, strlen (m->value),
table_level, eol_action);
ignore_args = 1; /* Parameterized macros are not yet supported. */
}
else
inf ("texinfo command '%s' not supported (%.*s)", command,
(int)((s = memchr (rest, '\n', len)), (s? (s-rest) : len)), rest);
}
if (*rest == '{')
{
/* Find matching closing brace. */
for (s=rest+1, n=1, i=1; i && *s && n < len; s++, n++)
if (*s == '{')
i++;
else if (*s == '}')
i--;
if (i)
{
err ("closing brace for command '%s' not found", command);
return len;
}
if (n > 2 && !ignore_args)
proc_texi_buffer (fp, rest+1, n-2, table_level, eol_action);
}
else
n = 0;
if (lead_out)
fputs (lead_out, fp);
return n;
}
/* Process the string LINE with LEN bytes of Texinfo content. */
static void
proc_texi_buffer (FILE *fp, const char *line, size_t len,
int *table_level, int *eol_action)
{
const char *s;
char cmdbuf[256];
int cmdidx = 0;
int in_cmd = 0;
size_t n;
for (s=line; *s && len; s++, len--)
{
if (in_cmd)
{
if (in_cmd == 1)
{
switch (*s)
{
case '@': case '{': case '}':
putc (*s, fp); in_cmd = 0;
break;
case ':': /* Not ending a sentence flag. */
in_cmd = 0;
break;
case '.': case '!': case '?': /* Ending a sentence. */
putc (*s, fp); in_cmd = 0;
break;
case ' ': case '\t': case '\n': /* Non collapsing spaces. */
putc (*s, fp); in_cmd = 0;
break;
default:
cmdidx = 0;
cmdbuf[cmdidx++] = *s;
in_cmd++;
break;
}
}
else if (*s == '{' || *s == ' ' || *s == '\t' || *s == '\n')
{
cmdbuf[cmdidx] = 0;
n = proc_texi_cmd (fp, cmdbuf, s, len, table_level, eol_action);
assert (n <= len);
s += n; len -= n;
s--; len++;
in_cmd = 0;
}
else if (cmdidx < sizeof cmdbuf -1)
cmdbuf[cmdidx++] = *s;
else
{
err ("texinfo command too long - ignored");
in_cmd = 0;
}
}
else if (*s == '@')
in_cmd = 1;
else if (*s == '\n')
{
switch (*eol_action)
{
case 1: /* Create a dummy paragraph. */
fputs ("\n\\ \n", fp);
break;
default:
putc (*s, fp);
}
*eol_action = 0;
}
else if (*s == '\\')
fputs ("\\\\", fp);
else
putc (*s, fp);
}
if (in_cmd > 1)
{
cmdbuf[cmdidx] = 0;
n = proc_texi_cmd (fp, cmdbuf, s, len, table_level, eol_action);
assert (n <= len);
s += n; len -= n;
s--; len++;
/* in_cmd = 0; -- doc only */
}
}
/* Do something with the Texinfo line LINE. */
static void
parse_texi_line (FILE *fp, const char *line, int *table_level)
{
int eol_action = 0;
/* A quick test whether there are any texinfo commands. */
if (!strchr (line, '@'))
{
fputs (line, fp);
putc ('\n', fp);
return;
}
proc_texi_buffer (fp, line, strlen (line), table_level, &eol_action);
putc ('\n', fp);
}
/* Write all the lines LINES to FP. */
static void
write_content (FILE *fp, line_buffer_t lines)
{
line_buffer_t line;
int table_level = 0;
for (line = lines; line; line = line->next)
{
if (line->verbatim)
{
fputs (line->line, fp);
putc ('\n', fp);
}
else
{
/* fputs ("TEXI---", fp); */
/* fputs (line->line, fp); */
/* fputs ("---\n", fp); */
parse_texi_line (fp, line->line, &table_level);
}
}
}
static int
is_standard_section (const char *name)
{
int i;
const char *s;
for (i=0; (s=standard_sections[i]); i++)
if (!strcmp (s, name))
return 1;
return 0;
}
/* Finish a page; that is sort the data and write it out to the file. */
static void
finish_page (void)
{
FILE *fp;
section_buffer_t sect = NULL;
int idx;
const char *s;
int i;
if (!thepage.name)
return; /* No page active. */
if (verbose)
inf ("finishing page '%s'", thepage.name);
if (opt_select)
{
if (!strcmp (opt_select, thepage.name))
{
inf ("selected '%s'", thepage.name );
fp = stdout;
}
else
{
fp = fopen ( "/dev/null", "w" );
if (!fp)
die ("failed to open /dev/null: %s\n", strerror (errno));
}
}
else if (opt_store)
{
inf ("writing '%s'", thepage.name );
fp = fopen ( thepage.name, "w" );
if (!fp)
die ("failed to create '%s': %s\n", thepage.name, strerror (errno));
}
else
fp = stdout;
if (write_th (fp))
goto leave;
for (idx=0; (s=standard_sections[idx]); idx++)
{
for (i=0; i < thepage.n_sections; i++)
{
sect = thepage.sections + i;
if (sect->name && !strcmp (s, sect->name))
break;
}
if (i == thepage.n_sections)
sect = NULL;
if (sect)
{
fprintf (fp, ".SH %s\n", sect->name);
write_content (fp, sect->lines);
/* Now continue with all non standard sections directly
following this one. */
for (i++; i < thepage.n_sections; i++)
{
sect = thepage.sections + i;
if (sect->name && is_standard_section (sect->name))
break;
if (sect->name)
{
fprintf (fp, ".SH %s\n", sect->name);
write_content (fp, sect->lines);
}
}
}
}
leave:
if (fp != stdout)
fclose (fp);
free (thepage.name);
thepage.name = NULL;
/* FIXME: Cleanup the content. */
}
/* Parse one Texinfo file and create manpages according to the
embedded instructions. */
static void
parse_file (const char *fname, FILE *fp, char **section_name, int in_pause)
{
char *line;
int lnr = 0;
/* Fixme: The following state variables don't carry over to include
files. */
int skip_to_end = 0; /* Used to skip over menu entries. */
int skip_sect_line = 0; /* Skip after @mansect. */
int item_indent = 0; /* How far is the current @item indented. */
/* Helper to define a macro. */
char *macroname = NULL;
char *macrovalue = NULL;
size_t macrovaluesize = 0;
size_t macrovalueused = 0;
line = xmalloc (LINESIZE);
while (fgets (line, LINESIZE, fp))
{
size_t n = strlen (line);
int got_line = 0;
char *p, *pend;
lnr++;
if (!n || line[n-1] != '\n')
{
err ("%s:%d: trailing linefeed missing, line too long or "
"embedded Nul character", fname, lnr);
break;
}
line[--n] = 0;
/* Kludge to allow indentation of tables. */
for (p=line; *p == ' ' || *p == '\t'; p++)
;
if (*p)
{
if (*p == '@' && !strncmp (p+1, "item", 4))
item_indent = p - line; /* Set a new indent level. */
else if (p - line < item_indent)
item_indent = 0; /* Switch off indentation. */
if (item_indent)
{
memmove (line, line+item_indent, n - item_indent + 1);
n -= item_indent;
}
}
if (*line == '@')
{
for (p=line+1, n=1; *p && *p != ' ' && *p != '\t'; p++)
n++;
while (*p == ' ' || *p == '\t')
p++;
}
else
p = line;
/* Take action on macro. */
if (macroname)
{
if (n == 4 && !memcmp (line, "@end", 4)
&& (line[4]==' '||line[4]=='\t'||!line[4])
&& !strncmp (p, "macro", 5)
&& (p[5]==' '||p[5]=='\t'||!p[5]))
{
if (macrovalueused)
macrovalue[--macrovalueused] = 0; /* Kill the last LF. */
macrovalue[macrovalueused] = 0; /* Terminate macro. */
macrovalue = xrealloc (macrovalue, macrovalueused+1);
set_macro (macroname, macrovalue);
macrovalue = NULL;
free (macroname);
macroname = NULL;
}
else
{
if (macrovalueused + strlen (line) + 2 >= macrovaluesize)
{
macrovaluesize += strlen (line) + 256;
macrovalue = xrealloc (macrovalue, macrovaluesize);
}
strcpy (macrovalue+macrovalueused, line);
macrovalueused += strlen (line);
macrovalue[macrovalueused++] = '\n';
}
continue;
}
if (n >= 5 && !memcmp (line, "@node", 5)
&& (line[5]==' '||line[5]=='\t'||!line[5]))
{
/* Completey ignore @node lines. */
continue;
}
if (skip_sect_line)
{
skip_sect_line = 0;
if (!strncmp (line, "@section", 8)
|| !strncmp (line, "@subsection", 11)
|| !strncmp (line, "@chapheading", 12))
continue;
}
/* We only parse lines we need and ignore the rest. There are a
few macros used to control this as well as one @ifset
command. Parts we know about are saved away into containers
separate for each section. */
/* First process ifset/ifclear commands. */
if (*line == '@')
{
if (n == 6 && !memcmp (line, "@ifset", 6)
&& (line[6]==' '||line[6]=='\t'))
{
for (p=line+7; *p == ' ' || *p == '\t'; p++)
;
if (!*p)
{
err ("%s:%d: name missing after \"@ifset\"", fname, lnr);
continue;
}
for (pend=p; *pend && *pend != ' ' && *pend != '\t'; pend++)
;
*pend = 0; /* Ignore rest of the line. */
push_condition (p, 1, fname, lnr);
continue;
}
else if (n == 8 && !memcmp (line, "@ifclear", 8)
&& (line[8]==' '||line[8]=='\t'))
{
for (p=line+9; *p == ' ' || *p == '\t'; p++)
;
if (!*p)
{
err ("%s:%d: name missing after \"@ifsclear\"", fname, lnr);
continue;
}
for (pend=p; *pend && *pend != ' ' && *pend != '\t'; pend++)
;
*pend = 0; /* Ignore rest of the line. */
push_condition (p, 0, fname, lnr);
continue;
}
else if (n == 4 && !memcmp (line, "@end", 4)
&& (line[4]==' '||line[4]=='\t')
&& !strncmp (p, "ifset", 5)
&& (p[5]==' '||p[5]=='\t'||!p[5]))
{
pop_condition (1, fname, lnr);
continue;
}
else if (n == 4 && !memcmp (line, "@end", 4)
&& (line[4]==' '||line[4]=='\t')
&& !strncmp (p, "ifclear", 7)
&& (p[7]==' '||p[7]=='\t'||!p[7]))
{
pop_condition (0, fname, lnr);
continue;
}
}
/* Take action on ifset/ifclear. */
if (!cond_is_active)
continue;
/* Process commands. */
if (*line == '@')
{
if (skip_to_end
&& n == 4 && !memcmp (line, "@end", 4)
&& (line[4]==' '||line[4]=='\t'||!line[4]))
{
skip_to_end = 0;
}
else if (cond_in_verbatim)
{
got_line = 1;
}
else if (n == 6 && !memcmp (line, "@macro", 6))
{
macroname = xstrdup (p);
macrovalue = xmalloc ((macrovaluesize = 1024));
macrovalueused = 0;
}
else if (n == 4 && !memcmp (line, "@set", 4))
{
set_variable (p);
}
else if (n == 8 && !memcmp (line, "@manpage", 8))
{
free (*section_name);
*section_name = NULL;
finish_page ();
start_page (p);
in_pause = 0;
}
else if (n == 8 && !memcmp (line, "@mansect", 8))
{
if (!thepage.name)
err ("%s:%d: section outside of a man page", fname, lnr);
else
{
free (*section_name);
*section_name = ascii_strupr (xstrdup (p));
in_pause = 0;
skip_sect_line = 1;
}
}
else if (n == 9 && !memcmp (line, "@manpause", 9))
{
if (!*section_name)
err ("%s:%d: pausing outside of a man section", fname, lnr);
else if (in_pause)
err ("%s:%d: already pausing", fname, lnr);
else
in_pause = 1;
}
else if (n == 8 && !memcmp (line, "@mancont", 8))
{
if (!*section_name)
err ("%s:%d: continue outside of a man section", fname, lnr);
else if (!in_pause)
err ("%s:%d: continue while not pausing", fname, lnr);
else
in_pause = 0;
}
else if (n == 5 && !memcmp (line, "@menu", 5)
&& (line[5]==' '||line[5]=='\t'||!line[5]))
{
skip_to_end = 1;
}
else if (n == 8 && !memcmp (line, "@include", 8)
&& (line[8]==' '||line[8]=='\t'||!line[8]))
{
char *incname = xstrdup (p);
FILE *incfp = fopen (incname, "r");
if (!incfp && opt_include && *opt_include && *p != '/')
{
free (incname);
incname = xmalloc (strlen (opt_include) + 1
+ strlen (p) + 1);
strcpy (incname, opt_include);
if ( incname[strlen (incname)-1] != '/' )
strcat (incname, "/");
strcat (incname, p);
incfp = fopen (incname, "r");
}
if (!incfp)
err ("can't open include file '%s': %s",
incname, strerror (errno));
else
{
parse_file (incname, incfp, section_name, in_pause);
fclose (incfp);
}
free (incname);
}
else if (n == 4 && !memcmp (line, "@bye", 4)
&& (line[4]==' '||line[4]=='\t'||!line[4]))
{
break;
}
else if (!skip_to_end)
got_line = 1;
}
else if (!skip_to_end)
got_line = 1;
if (got_line && cond_in_verbatim)
add_content (*section_name, line, 1);
else if (got_line && thepage.name && *section_name && !in_pause)
add_content (*section_name, line, 0);
}
if (ferror (fp))
err ("%s:%d: read error: %s", fname, lnr, strerror (errno));
free (macroname);
free (macrovalue);
free (line);
}
static void
top_parse_file (const char *fname, FILE *fp)
{
char *section_name = NULL; /* Name of the current section or NULL
if not in a section. */
macro_t m;
while (macrolist)
{
macro_t next = macrolist->next;
free (macrolist->value);
free (macrolist);
macrolist = next;
}
while (variablelist)
{
macro_t next = variablelist->next;
free (variablelist->value);
free (variablelist);
variablelist = next;
}
for (m=predefinedmacrolist; m; m = m->next)
set_macro (m->name, xstrdup ("1"));
cond_is_active = 1;
cond_in_verbatim = 0;
parse_file (fname, fp, &section_name, 0);
free (section_name);
finish_page ();
}
int
main (int argc, char **argv)
{
int last_argc = -1;
const char *s;
opt_source = "GNU";
opt_release = "";
/* Define default macros. The trick is that these macros are not
defined when using the actual texinfo renderer. */
add_predefined_macro ("isman");
add_predefined_macro ("manverb");
/* Option parsing. */
if (argc)
{
argc--; argv++;
}
while (argc && last_argc != argc )
{
last_argc = argc;
if (!strcmp (*argv, "--"))
{
argc--; argv++;
break;
}
else if (!strcmp (*argv, "--help"))
{
puts (
"Usage: " PGM " [OPTION] [FILE]\n"
"Extract man pages from a Texinfo source.\n\n"
" --source NAME use NAME as source field\n"
" --release STRING use STRING as the release field\n"
" --date EPOCH use EPOCH as publication date\n"
" --store write output using @manpage name\n"
" --select NAME only output pages with @manpage NAME\n"
" --verbose enable extra informational output\n"
" --debug enable additional debug output\n"
" --help display this help and exit\n"
" -I DIR also search in include DIR\n"
" -D gpgone the only usable define\n\n"
"With no FILE, or when FILE is -, read standard input.\n\n"
"Report bugs to <bugs@g10code.com>.");
exit (0);
}
else if (!strcmp (*argv, "--version"))
{
puts (PGM " " VERSION "\n"
"Copyright (C) 2005 g10 Code GmbH\n"
"This program comes with ABSOLUTELY NO WARRANTY.\n"
"This is free software, and you are welcome to redistribute it\n"
"under certain conditions. See the file COPYING for details.");
exit (0);
}
else if (!strcmp (*argv, "--verbose"))
{
verbose = 1;
argc--; argv++;
}
else if (!strcmp (*argv, "--quiet"))
{
quiet = 1;
argc--; argv++;
}
else if (!strcmp (*argv, "--debug"))
{
verbose = debug = 1;
argc--; argv++;
}
else if (!strcmp (*argv, "--source"))
{
argc--; argv++;
if (argc)
{
opt_source = *argv;
argc--; argv++;
}
}
else if (!strcmp (*argv, "--release"))
{
argc--; argv++;
if (argc)
{
opt_release = *argv;
argc--; argv++;
}
}
else if (!strcmp (*argv, "--date"))
{
argc--; argv++;
if (argc)
{
opt_date = *argv;
argc--; argv++;
}
}
else if (!strcmp (*argv, "--store"))
{
opt_store = 1;
argc--; argv++;
}
else if (!strcmp (*argv, "--select"))
{
argc--; argv++;
if (argc)
{
opt_select = strrchr (*argv, '/');
if (opt_select)
opt_select++;
else
opt_select = *argv;
argc--; argv++;
}
}
else if (!strcmp (*argv, "-I"))
{
argc--; argv++;
if (argc)
{
opt_include = *argv;
argc--; argv++;
}
}
else if (!strcmp (*argv, "-D"))
{
argc--; argv++;
if (argc)
{
add_predefined_macro (*argv);
argc--; argv++;
}
}
}
if (argc > 1)
die ("usage: " PGM " [OPTION] [FILE] (try --help for more information)\n");
/* Take care of supplied timestamp for reproducible builds. See
* https://reproducible-builds.org/specs/source-date-epoch/ */
if (!opt_date && (s = getenv ("SOURCE_DATE_EPOCH")) && *s)
opt_date = s;
/* Start processing. */
if (argc && strcmp (*argv, "-"))
{
FILE *fp = fopen (*argv, "rb");
if (!fp)
die ("%s:0: can't open file: %s", *argv, strerror (errno));
top_parse_file (*argv, fp);
fclose (fp);
}
else
top_parse_file ("-", stdin);
return !!any_error;
}
/*
Local Variables:
compile-command: "gcc -Wall -g -Wall -o yat2m yat2m.c"
End:
*/
diff --git a/g10/Makefile.am b/g10/Makefile.am
index 3b4464364..884b4749b 100644
--- a/g10/Makefile.am
+++ b/g10/Makefile.am
@@ -1,259 +1,260 @@
# Copyright (C) 1998, 1999, 2000, 2001, 2002,
# 2003, 2006, 2010 Free Software Foundation, Inc.
#
# This file is part of GnuPG.
#
# GnuPG is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# GnuPG is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, see <https://www.gnu.org/licenses/>.
## Process this file with automake to produce Makefile.in
EXTRA_DIST = distsigkey.gpg \
ChangeLog-2011 gpg-w32info.rc \
gpg.w32-manifest.in test.c t-keydb-keyring.kbx \
t-keydb-get-keyblock.gpg t-stutter-data.asc \
all-tests.scm
AM_CPPFLAGS =
include $(top_srcdir)/am/cmacros.am
AM_CFLAGS = $(SQLITE3_CFLAGS) $(LIBGCRYPT_CFLAGS) \
$(LIBASSUAN_CFLAGS) $(GPG_ERROR_CFLAGS)
needed_libs = ../kbx/libkeybox.a $(libcommon)
# Because there are no program specific transform macros we need to
# work around that to allow installing gpg as gpg2.
gpg2_hack_list = gpg gpgv
if USE_GPG2_HACK
gpg2_hack_uninst = gpg2 gpgv2
use_gpg2_hack = yes
else
gpg2_hack_uninst = $(gpg2_hack_list)
use_gpg2_hack = no
endif
# NB: We use noinst_ for gpg and gpgv so that we can install them with
# the install-hook target under the name gpg2/gpgv2.
noinst_PROGRAMS = gpg
if !HAVE_W32CE_SYSTEM
noinst_PROGRAMS += gpgv
endif
if MAINTAINER_MODE
noinst_PROGRAMS += gpgcompose
endif
noinst_PROGRAMS += $(module_tests)
TESTS = $(module_tests)
TESTS_ENVIRONMENT = \
abs_top_srcdir=$(abs_top_srcdir)
if ENABLE_BZIP2_SUPPORT
bzip2_source = compress-bz2.c
else
bzip2_source =
endif
if ENABLE_CARD_SUPPORT
card_source = card-util.c
else
card_source =
endif
if NO_TRUST_MODELS
trust_source =
else
trust_source = trustdb.c trustdb.h tdbdump.c tdbio.c tdbio.h
endif
if USE_TOFU
tofu_source = tofu.h tofu.c gpgsql.c gpgsql.h
else
tofu_source =
endif
if HAVE_W32_SYSTEM
resource_objs += gpg-w32info.o
gpg-w32info.o : gpg.w32-manifest
endif
common_source = \
gpg.h \
dek.h \
build-packet.c \
compress.c \
$(bzip2_source) \
filter.h \
free-packet.c \
getkey.c \
keydb.c keydb.h \
keyring.c keyring.h \
seskey.c \
kbnode.c \
main.h \
mainproc.c \
armor.c \
mdfilter.c \
textfilter.c \
progress.c \
misc.c \
rmd160.c rmd160.h \
options.h \
openfile.c \
keyid.c \
packet.h \
parse-packet.c \
cpr.c \
plaintext.c \
sig-check.c \
keylist.c \
pkglue.c pkglue.h \
+ objcache.c objcache.h \
ecdh.c
gpg_sources = server.c \
$(common_source) \
pkclist.c \
skclist.c \
pubkey-enc.c \
passphrase.c \
decrypt.c \
decrypt-data.c \
cipher-cfb.c \
cipher-aead.c \
encrypt.c \
sign.c \
verify.c \
revoke.c \
dearmor.c \
import.c \
export.c \
migrate.c \
delkey.c \
keygen.c \
helptext.c \
keyserver.c \
keyserver-internal.h \
call-dirmngr.c call-dirmngr.h \
photoid.c photoid.h \
call-agent.c call-agent.h \
trust.c $(trust_source) $(tofu_source) \
$(card_source) \
exec.c exec.h \
key-clean.c key-clean.h \
key-check.c key-check.h
gpg_SOURCES = gpg.c \
keyedit.c keyedit.h \
$(gpg_sources)
gpgcompose_SOURCES = gpgcompose.c $(gpg_sources)
gpgv_SOURCES = gpgv.c \
$(common_source) \
verify.c
#gpgd_SOURCES = gpgd.c \
# ks-proto.h \
# ks-proto.c \
# ks-db.c \
# ks-db.h \
# $(common_source)
LDADD = $(needed_libs) ../common/libgpgrl.a \
$(ZLIBS) $(LIBINTL) $(CAPLIBS) $(NETLIBS)
gpg_LDADD = $(LDADD) $(SQLITE3_LIBS) $(LIBGCRYPT_LIBS) $(LIBREADLINE) \
$(LIBASSUAN_LIBS) $(GPG_ERROR_LIBS) \
$(LIBICONV) $(resource_objs) $(extra_sys_libs)
gpg_LDFLAGS = $(extra_bin_ldflags)
gpgv_LDADD = $(LDADD) $(LIBGCRYPT_LIBS) \
$(GPG_ERROR_LIBS) \
$(LIBICONV) $(resource_objs) $(extra_sys_libs)
gpgv_LDFLAGS = $(extra_bin_ldflags)
gpgcompose_LDADD = $(LDADD) $(SQLITE3_LIBS) $(LIBGCRYPT_LIBS) $(LIBREADLINE) \
$(LIBASSUAN_LIBS) $(GPG_ERROR_LIBS) \
$(LIBICONV) $(resource_objs) $(extra_sys_libs)
gpgcompose_LDFLAGS = $(extra_bin_ldflags)
t_common_ldadd =
module_tests = t-rmd160 t-keydb t-keydb-get-keyblock t-stutter
t_rmd160_SOURCES = t-rmd160.c rmd160.c
t_rmd160_LDADD = $(t_common_ldadd)
t_keydb_SOURCES = t-keydb.c test-stubs.c $(common_source)
t_keydb_LDADD = $(LDADD) $(LIBGCRYPT_LIBS) $(GPG_ERROR_LIBS) \
$(LIBICONV) $(t_common_ldadd)
t_keydb_get_keyblock_SOURCES = t-keydb-get-keyblock.c test-stubs.c \
$(common_source)
t_keydb_get_keyblock_LDADD = $(LDADD) $(LIBGCRYPT_LIBS) $(GPG_ERROR_LIBS) \
$(LIBICONV) $(t_common_ldadd)
t_stutter_SOURCES = t-stutter.c test-stubs.c \
$(common_source)
t_stutter_LDADD = $(LDADD) $(LIBGCRYPT_LIBS) $(GPG_ERROR_LIBS) \
$(LIBICONV) $(t_common_ldadd)
$(PROGRAMS): $(needed_libs) ../common/libgpgrl.a
# NB: To install gpg and gpgv we use this -hook. This code has to
# duplicate most of the automake generated install-binPROGRAMS target
# so that directories are created and the transform feature works.
install-exec-hook:
@echo "running install-exec-hook"; \
echo " $(MKDIR_P) '$(DESTDIR)$(bindir)'"; \
$(MKDIR_P) "$(DESTDIR)$(bindir)"; \
for p in $(gpg2_hack_list); do \
echo "$$p$(EXEEXT) $$p$(EXEEXT)"; done | \
sed 's/$(EXEEXT)$$//' | \
while read p p1; do if test -f $$p \
; then echo "$$p"; echo "$$p"; else :; fi; \
done | \
sed -e 'p;s,.*/,,;n;h' \
-e 's|.*|.|' \
-e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \
sed 'N;N;N;s,\n, ,g' | \
$(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \
{ d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \
if ($$2 == $$4) files[d] = files[d] " " $$1; \
else { print "f", $$3 "/" $$4, $$1; } } \
END { for (d in files) print "f", d, files[d] }' | \
while read type dir files; do \
for f in $$files; do \
if test $(use_gpg2_hack) = yes ; \
then f2=`echo "$${f}" | sed 's/$(EXEEXT)$$//'`2$(EXEEXT); \
else f2="$${f}" ;\
fi ; \
echo "$(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) \
$${f} '$(DESTDIR)$(bindir)/$${f2}'"; \
$(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) \
$${f} "$(DESTDIR)$(bindir)/$${f2}"; \
done; \
done
install-data-local:
$(mkinstalldirs) $(DESTDIR)$(pkgdatadir)
$(INSTALL_DATA) $(srcdir)/distsigkey.gpg \
$(DESTDIR)$(pkgdatadir)/distsigkey.gpg
# NB: For uninstalling gpg and gpgv we use -local because there is
# no need for a specific order the targets need to be run.
uninstall-local:
-@rm $(DESTDIR)$(pkgdatadir)/distsigkey.gpg
-@files=`for p in $(gpg2_hack_uninst); do echo "$$p"; done | \
sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \
-e 's/$$/$(EXEEXT)/' \
`; \
echo " ( cd '$(DESTDIR)$(bindir)' && rm -f" $$files ")"; \
cd "$(DESTDIR)$(bindir)" && rm -f $$files
diff --git a/g10/armor.c b/g10/armor.c
index 972766503..eb2d28bca 100644
--- a/g10/armor.c
+++ b/g10/armor.c
@@ -1,1561 +1,1561 @@
/* armor.c - Armor filter
* Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006,
* 2007 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#include "gpg.h"
#include "../common/status.h"
#include "../common/iobuf.h"
#include "../common/util.h"
#include "filter.h"
#include "packet.h"
#include "options.h"
#include "main.h"
#include "../common/i18n.h"
#define MAX_LINELEN 20000
static const byte bintoasc[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789+/";
static u32 asctobin[4][256]; /* runtime initialized */
static int is_initialized;
typedef enum {
fhdrHASArmor = 0,
fhdrNOArmor,
fhdrINIT,
fhdrINITCont,
fhdrINITSkip,
fhdrCHECKBegin,
fhdrWAITHeader,
fhdrWAITClearsig,
fhdrSKIPHeader,
fhdrCLEARSIG,
fhdrREADClearsig,
fhdrNullClearsig,
fhdrEMPTYClearsig,
fhdrCHECKClearsig,
fhdrCHECKClearsig2,
fhdrCHECKDashEscaped,
fhdrCHECKDashEscaped2,
fhdrCHECKDashEscaped3,
fhdrREADClearsigNext,
fhdrENDClearsig,
fhdrENDClearsigHelp,
fhdrTESTSpaces,
fhdrCLEARSIGSimple,
fhdrCLEARSIGSimpleNext,
fhdrTEXT,
fhdrTEXTSimple,
fhdrERROR,
fhdrERRORShow,
fhdrEOF
} fhdr_state_t;
/* if we encounter this armor string with this index, go
* into a mode which fakes packets and wait for the next armor */
#define BEGIN_SIGNATURE 2
#define BEGIN_SIGNED_MSG_IDX 3
static char *head_strings[] = {
"BEGIN PGP MESSAGE",
"BEGIN PGP PUBLIC KEY BLOCK",
"BEGIN PGP SIGNATURE",
"BEGIN PGP SIGNED MESSAGE",
"BEGIN PGP ARMORED FILE", /* gnupg extension */
"BEGIN PGP PRIVATE KEY BLOCK",
"BEGIN PGP SECRET KEY BLOCK", /* only used by pgp2 */
NULL
};
static char *tail_strings[] = {
"END PGP MESSAGE",
"END PGP PUBLIC KEY BLOCK",
"END PGP SIGNATURE",
"END dummy",
"END PGP ARMORED FILE",
"END PGP PRIVATE KEY BLOCK",
"END PGP SECRET KEY BLOCK",
NULL
};
static int armor_filter ( void *opaque, int control,
iobuf_t chain, byte *buf, size_t *ret_len);
/* Create a new context for armor filters. */
armor_filter_context_t *
new_armor_context (void)
{
armor_filter_context_t *afx;
gpg_error_t err;
afx = xcalloc (1, sizeof *afx);
if (afx)
{
err = gcry_md_open (&afx->crc_md, GCRY_MD_CRC24_RFC2440, 0);
if (err != 0)
{
log_error ("gcry_md_open failed for GCRY_MD_CRC24_RFC2440: %s",
gpg_strerror (err));
xfree (afx);
return NULL;
}
afx->refcount = 1;
}
return afx;
}
/* Release an armor filter context. Passing NULL is explicitly
allowed and a no-op. */
void
release_armor_context (armor_filter_context_t *afx)
{
if (!afx)
return;
log_assert (afx->refcount);
if ( --afx->refcount )
return;
gcry_md_close (afx->crc_md);
xfree (afx);
}
/* Push the armor filter onto the iobuf stream IOBUF. */
int
push_armor_filter (armor_filter_context_t *afx, iobuf_t iobuf)
{
int rc;
afx->refcount++;
rc = iobuf_push_filter (iobuf, armor_filter, afx);
if (rc)
afx->refcount--;
return rc;
}
static void
initialize(void)
{
u32 i;
const byte *s;
/* Build the helptable for radix64 to bin conversion. Value 0xffffffff is
used to detect invalid characters. */
memset (asctobin, 0xff, sizeof(asctobin));
for(s=bintoasc,i=0; *s; s++,i++ )
{
asctobin[0][*s] = i << (0 * 6);
asctobin[1][*s] = i << (1 * 6);
asctobin[2][*s] = i << (2 * 6);
asctobin[3][*s] = i << (3 * 6);
}
is_initialized=1;
}
static inline u32
get_afx_crc (armor_filter_context_t *afx)
{
const byte *crc_buf;
u32 crc;
crc_buf = gcry_md_read (afx->crc_md, GCRY_MD_CRC24_RFC2440);
crc = crc_buf[0];
crc <<= 8;
crc |= crc_buf[1];
crc <<= 8;
crc |= crc_buf[2];
return crc;
}
/*
* Check whether this is an armored file. See also
* parse-packet.c for details on this code.
*
* Note that the buffer BUF needs to be at least 2 bytes long. If in
* doubt that the second byte to 0.
*
* Returns: True if it seems to be armored
*/
static int
is_armored (const byte *buf)
{
int ctb, pkttype;
int indeterminate_length_allowed;
ctb = *buf;
if( !(ctb & 0x80) )
/* The most significant bit of the CTB must be set. Since it is
cleared, this is not a binary OpenPGP message. Assume it is
armored. */
return 1;
pkttype = ctb & 0x40 ? (ctb & 0x3f) : ((ctb>>2)&0xf);
switch( pkttype ) {
case PKT_PUBKEY_ENC:
case PKT_SIGNATURE:
case PKT_SYMKEY_ENC:
case PKT_ONEPASS_SIG:
case PKT_SECRET_KEY:
case PKT_PUBLIC_KEY:
case PKT_SECRET_SUBKEY:
case PKT_MARKER:
case PKT_RING_TRUST:
case PKT_USER_ID:
case PKT_PUBLIC_SUBKEY:
case PKT_ATTRIBUTE:
case PKT_MDC:
indeterminate_length_allowed = 0;
break;
case PKT_COMPRESSED:
case PKT_ENCRYPTED:
case PKT_ENCRYPTED_MDC:
case PKT_PLAINTEXT:
case PKT_OLD_COMMENT:
case PKT_COMMENT:
case PKT_GPG_CONTROL:
indeterminate_length_allowed = 1;
break;
default:
/* Invalid packet type. */
return 1;
}
if (! indeterminate_length_allowed)
/* It is only legal to use an indeterminate length with a few
packet types. If a packet uses an indeterminate length, but
that is not allowed, then the data is not valid binary
OpenPGP data. */
{
int new_format;
int indeterminate_length;
new_format = !! (ctb & (1 << 6));
if (new_format)
indeterminate_length = (buf[1] >= 224 && buf[1] < 255);
else
indeterminate_length = (ctb & 3) == 3;
if (indeterminate_length)
return 1;
}
/* The first CTB seems legit. It is probably not armored
data. */
return 0;
}
/****************
* Try to check whether the iobuf is armored
* Returns true if this may be the case; the caller should use the
* filter to do further processing.
*/
int
use_armor_filter( IOBUF a )
{
byte buf[2];
int n;
/* fixme: there might be a problem with iobuf_peek */
n = iobuf_peek (a, buf, 2);
if( n == -1 )
return 0; /* EOF, doesn't matter whether armored or not */
if( !n )
return 1; /* can't check it: try armored */
if (n != 2)
return 0; /* short buffer */
return is_armored(buf);
}
static void
invalid_armor(void)
{
write_status(STATUS_BADARMOR);
g10_exit(1); /* stop here */
}
/****************
* check whether the armor header is valid on a signed message.
* this is for security reasons: the header lines are not included in the
* hash and by using some creative formatting rules, Mallory could fake
* any text at the beginning of a document; assuming it is read with
* a simple viewer. We only allow the Hash Header.
*/
static int
parse_hash_header( const char *line )
{
const char *s, *s2;
unsigned found = 0;
if( strlen(line) < 6 || strlen(line) > 60 )
return 0; /* too short or too long */
if( memcmp( line, "Hash:", 5 ) )
return 0; /* invalid header */
for(s=line+5;;s=s2) {
for(; *s && (*s==' ' || *s == '\t'); s++ )
;
if( !*s )
break;
for(s2=s+1; *s2 && *s2!=' ' && *s2 != '\t' && *s2 != ','; s2++ )
;
if( !strncmp( s, "RIPEMD160", s2-s ) )
found |= 1;
else if( !strncmp( s, "SHA1", s2-s ) )
found |= 2;
else if( !strncmp( s, "SHA224", s2-s ) )
found |= 8;
else if( !strncmp( s, "SHA256", s2-s ) )
found |= 16;
else if( !strncmp( s, "SHA384", s2-s ) )
found |= 32;
else if( !strncmp( s, "SHA512", s2-s ) )
found |= 64;
else
return 0;
for(; *s2 && (*s2==' ' || *s2 == '\t'); s2++ )
;
if( *s2 && *s2 != ',' )
return 0;
if( *s2 )
s2++;
}
return found;
}
/* Returns true if this is a valid armor tag as per RFC-2440bis-21. */
static int
is_armor_tag(const char *line)
{
if(strncmp(line,"Version",7)==0
|| strncmp(line,"Comment",7)==0
|| strncmp(line,"MessageID",9)==0
|| strncmp(line,"Hash",4)==0
|| strncmp(line,"Charset",7)==0)
return 1;
return 0;
}
/****************
* Check whether this is a armor line.
* returns: -1 if it is not a armor header or the index number of the
* armor header.
*/
static int
is_armor_header( byte *line, unsigned len )
{
const char *s;
byte *save_p, *p;
int save_c;
int i;
if( len < 15 )
return -1; /* too short */
if( memcmp( line, "-----", 5 ) )
return -1; /* no */
p = strstr( line+5, "-----");
if( !p )
return -1;
save_p = p;
p += 5;
/* Some Windows environments seem to add whitespace to the end of
the line, so we strip it here. This becomes strict if
--rfc2440 is set since 2440 reads "The header lines, therefore,
MUST start at the beginning of a line, and MUST NOT have text
following them on the same line." It is unclear whether "text"
refers to all text or just non-whitespace text. 4880 clarified
this was only non-whitespace text. */
if(RFC2440)
{
if( *p == '\r' )
p++;
if( *p == '\n' )
p++;
}
else
while(*p==' ' || *p=='\r' || *p=='\n' || *p=='\t')
p++;
if( *p )
return -1; /* garbage after dashes */
save_c = *save_p; *save_p = 0;
p = line+5;
for(i=0; (s=head_strings[i]); i++ )
if( !strcmp(s, p) )
break;
*save_p = save_c;
if( !s )
return -1; /* unknown armor line */
if( opt.verbose > 1 )
log_info(_("armor: %s\n"), head_strings[i]);
return i;
}
/****************
* Parse a header lines
* Return 0: Empty line (end of header lines)
* -1: invalid header line
* >0: Good header line
*/
static int
parse_header_line( armor_filter_context_t *afx, byte *line, unsigned int len )
{
byte *p;
int hashes=0;
unsigned int len2;
len2 = length_sans_trailing_ws ( line, len );
if( !len2 ) {
afx->buffer_pos = len2; /* (it is not the fine way to do it here) */
return 0; /* WS only: same as empty line */
}
/*
This is fussy. The spec says that a header line is delimited
with a colon-space pair. This means that a line such as
"Comment: " (with nothing else) is actually legal as an empty
string comment. However, email and cut-and-paste being what it
is, that trailing space may go away. Therefore, we accept empty
headers delimited with only a colon. --rfc2440, as always,
makes this strict and enforces the colon-space pair. -dms
*/
p = strchr( line, ':');
if( !p || (RFC2440 && p[1]!=' ')
|| (!RFC2440 && p[1]!=' ' && p[1]!='\n' && p[1]!='\r'))
{
log_error (_("invalid armor header: "));
es_write_sanitized (log_get_stream (), line, len, NULL, NULL);
log_printf ("\n");
return -1;
}
/* Chop off the whitespace we detected before */
len=len2;
line[len2]='\0';
if( opt.verbose ) {
log_info(_("armor header: "));
es_write_sanitized (log_get_stream (), line, len, NULL, NULL);
log_printf ("\n");
}
if( afx->in_cleartext )
{
if( (hashes=parse_hash_header( line )) )
afx->hashes |= hashes;
else if( strlen(line) > 15 && !memcmp( line, "NotDashEscaped:", 15 ) )
afx->not_dash_escaped = 1;
else
{
log_error(_("invalid clearsig header\n"));
return -1;
}
}
else if(!is_armor_tag(line))
{
/* Section 6.2: "Unknown keys should be reported to the user,
but OpenPGP should continue to process the message." Note
that in a clearsigned message this applies to the signature
part (i.e. "BEGIN PGP SIGNATURE") and not the signed data
("BEGIN PGP SIGNED MESSAGE"). The only key allowed in the
signed data section is "Hash". */
log_info(_("unknown armor header: "));
es_write_sanitized (log_get_stream (), line, len, NULL, NULL);
log_printf ("\n");
}
return 1;
}
/* figure out whether the data is armored or not */
static int
check_input( armor_filter_context_t *afx, IOBUF a )
{
int rc = 0;
int i;
byte *line;
unsigned len;
unsigned maxlen;
int hdr_line = -1;
/* read the first line to see whether this is armored data */
maxlen = MAX_LINELEN;
len = afx->buffer_len = iobuf_read_line( a, &afx->buffer,
&afx->buffer_size, &maxlen );
line = afx->buffer;
if( !maxlen ) {
/* line has been truncated: assume not armored */
afx->inp_checked = 1;
afx->inp_bypass = 1;
return 0;
}
if( !len ) {
return -1; /* eof */
}
/* (the line is always a C string but maybe longer) */
if( *line == '\n' || ( len && (*line == '\r' && line[1]=='\n') ) )
;
else if (len >= 2 && !is_armored (line)) {
afx->inp_checked = 1;
afx->inp_bypass = 1;
return 0;
}
/* find the armor header */
while(len) {
i = is_armor_header( line, len );
if( i >= 0 && !(afx->only_keyblocks && i != 1 && i != 5 && i != 6 )) {
hdr_line = i;
if( hdr_line == BEGIN_SIGNED_MSG_IDX ) {
if( afx->in_cleartext ) {
log_error(_("nested clear text signatures\n"));
rc = gpg_error (GPG_ERR_INV_ARMOR);
}
afx->in_cleartext = 1;
}
break;
}
/* read the next line (skip all truncated lines) */
do {
maxlen = MAX_LINELEN;
afx->buffer_len = iobuf_read_line( a, &afx->buffer,
&afx->buffer_size, &maxlen );
line = afx->buffer;
len = afx->buffer_len;
} while( !maxlen );
}
/* Parse the header lines. */
while(len) {
/* Read the next line (skip all truncated lines). */
do {
maxlen = MAX_LINELEN;
afx->buffer_len = iobuf_read_line( a, &afx->buffer,
&afx->buffer_size, &maxlen );
line = afx->buffer;
len = afx->buffer_len;
} while( !maxlen );
i = parse_header_line( afx, line, len );
if( i <= 0 ) {
if (i && RFC2440)
rc = GPG_ERR_INV_ARMOR;
break;
}
}
if( rc )
invalid_armor();
else if( afx->in_cleartext )
afx->faked = 1;
else {
afx->inp_checked = 1;
gcry_md_reset (afx->crc_md);
afx->idx = 0;
afx->radbuf[0] = 0;
}
return rc;
}
#define PARTIAL_CHUNK 512
#define PARTIAL_POW 9
/****************
* Fake a literal data packet and wait for the next armor line
* fixme: empty line handling and null length clear text signature are
* not implemented/checked.
*/
static int
fake_packet( armor_filter_context_t *afx, IOBUF a,
size_t *retn, byte *buf, size_t size )
{
int rc = 0;
size_t len = 0;
int lastline = 0;
unsigned maxlen, n;
byte *p;
byte tempbuf[PARTIAL_CHUNK];
size_t tempbuf_len=0;
while( !rc && size-len>=(PARTIAL_CHUNK+1)) {
/* copy what we have in the line buffer */
if( afx->faked == 1 )
afx->faked++; /* skip the first (empty) line */
else
{
/* It's full, so write this partial chunk */
if(tempbuf_len==PARTIAL_CHUNK)
{
buf[len++]=0xE0+PARTIAL_POW;
memcpy(&buf[len],tempbuf,PARTIAL_CHUNK);
len+=PARTIAL_CHUNK;
tempbuf_len=0;
continue;
}
while( tempbuf_len < PARTIAL_CHUNK
&& afx->buffer_pos < afx->buffer_len )
tempbuf[tempbuf_len++] = afx->buffer[afx->buffer_pos++];
if( tempbuf_len==PARTIAL_CHUNK )
continue;
}
/* read the next line */
maxlen = MAX_LINELEN;
afx->buffer_pos = 0;
afx->buffer_len = iobuf_read_line( a, &afx->buffer,
&afx->buffer_size, &maxlen );
if( !afx->buffer_len ) {
rc = -1; /* eof (should not happen) */
continue;
}
if( !maxlen )
afx->truncated++;
p = afx->buffer;
n = afx->buffer_len;
/* Armor header or dash-escaped line? */
if(p[0]=='-')
{
/* 2440bis-10: When reversing dash-escaping, an
implementation MUST strip the string "- " if it occurs
at the beginning of a line, and SHOULD warn on "-" and
any character other than a space at the beginning of a
line. */
if(p[1]==' ' && !afx->not_dash_escaped)
{
/* It's a dash-escaped line, so skip over the
escape. */
afx->buffer_pos = 2;
}
else if(p[1]=='-' && p[2]=='-' && p[3]=='-' && p[4]=='-')
{
/* Five dashes in a row mean it's probably armor
header. */
int type = is_armor_header( p, n );
if( afx->not_dash_escaped && type != BEGIN_SIGNATURE )
; /* this is okay */
else
{
if( type != BEGIN_SIGNATURE )
{
log_info(_("unexpected armor: "));
es_write_sanitized (log_get_stream (), p, n,
NULL, NULL);
log_printf ("\n");
}
lastline = 1;
rc = -1;
}
}
else if(!afx->not_dash_escaped)
{
/* Bad dash-escaping. */
log_info (_("invalid dash escaped line: "));
es_write_sanitized (log_get_stream (), p, n, NULL, NULL);
log_printf ("\n");
}
}
/* Now handle the end-of-line canonicalization */
if( !afx->not_dash_escaped )
{
int crlf = n > 1 && p[n-2] == '\r' && p[n-1]=='\n';
afx->buffer_len=
trim_trailing_chars( &p[afx->buffer_pos], n-afx->buffer_pos,
" \t\r\n");
afx->buffer_len+=afx->buffer_pos;
/* the buffer is always allocated with enough space to append
* the removed [CR], LF and a Nul
* The reason for this complicated procedure is to keep at least
* the original type of lineending - handling of the removed
* trailing spaces seems to be impossible in our method
* of faking a packet; either we have to use a temporary file
* or calculate the hash here in this module and somehow find
* a way to send the hash down the processing line (well, a special
* faked packet could do the job).
*/
if( crlf )
afx->buffer[afx->buffer_len++] = '\r';
afx->buffer[afx->buffer_len++] = '\n';
afx->buffer[afx->buffer_len] = '\0';
}
}
if( lastline ) { /* write last (ending) length header */
if(tempbuf_len<192)
buf[len++]=tempbuf_len;
else
{
buf[len++]=((tempbuf_len-192)/256) + 192;
buf[len++]=(tempbuf_len-192) % 256;
}
memcpy(&buf[len],tempbuf,tempbuf_len);
len+=tempbuf_len;
rc = 0;
afx->faked = 0;
afx->in_cleartext = 0;
/* and now read the header lines */
afx->buffer_pos = 0;
for(;;) {
int i;
/* read the next line (skip all truncated lines) */
do {
maxlen = MAX_LINELEN;
afx->buffer_len = iobuf_read_line( a, &afx->buffer,
&afx->buffer_size, &maxlen );
} while( !maxlen );
p = afx->buffer;
n = afx->buffer_len;
if( !n ) {
rc = -1;
break; /* eof */
}
i = parse_header_line( afx, p , n );
if( i <= 0 ) {
if( i )
invalid_armor();
break;
}
}
afx->inp_checked = 1;
gcry_md_reset (afx->crc_md);
afx->idx = 0;
afx->radbuf[0] = 0;
}
*retn = len;
return rc;
}
static int
invalid_crc(void)
{
if ( opt.ignore_crc_error )
return 0;
log_inc_errorcount();
return gpg_error (GPG_ERR_INV_ARMOR);
}
static int
radix64_read( armor_filter_context_t *afx, IOBUF a, size_t *retn,
byte *buf, size_t size )
{
byte val;
int c;
u32 binc;
int checkcrc=0;
int rc = 0;
size_t n = 0;
int idx, onlypad=0;
int skip_fast = 0;
idx = afx->idx;
val = afx->radbuf[0];
for( n=0; n < size; ) {
if( afx->buffer_pos < afx->buffer_len )
c = afx->buffer[afx->buffer_pos++];
else { /* read the next line */
unsigned maxlen = MAX_LINELEN;
afx->buffer_pos = 0;
afx->buffer_len = iobuf_read_line( a, &afx->buffer,
&afx->buffer_size, &maxlen );
if( !maxlen )
afx->truncated++;
if( !afx->buffer_len )
break; /* eof */
continue;
}
again:
binc = asctobin[0][c];
if( binc != 0xffffffffUL )
{
if( idx == 0 && skip_fast == 0
&& afx->buffer_pos + (16 - 1) < afx->buffer_len
&& n + 12 < size)
{
/* Fast path for radix64 to binary conversion. */
u32 b0,b1,b2,b3;
/* Speculatively load 15 more input bytes. */
b0 = binc << (3 * 6);
b0 |= asctobin[2][afx->buffer[afx->buffer_pos + 0]];
b0 |= asctobin[1][afx->buffer[afx->buffer_pos + 1]];
b0 |= asctobin[0][afx->buffer[afx->buffer_pos + 2]];
b1 = asctobin[3][afx->buffer[afx->buffer_pos + 3]];
b1 |= asctobin[2][afx->buffer[afx->buffer_pos + 4]];
b1 |= asctobin[1][afx->buffer[afx->buffer_pos + 5]];
b1 |= asctobin[0][afx->buffer[afx->buffer_pos + 6]];
b2 = asctobin[3][afx->buffer[afx->buffer_pos + 7]];
b2 |= asctobin[2][afx->buffer[afx->buffer_pos + 8]];
b2 |= asctobin[1][afx->buffer[afx->buffer_pos + 9]];
b2 |= asctobin[0][afx->buffer[afx->buffer_pos + 10]];
b3 = asctobin[3][afx->buffer[afx->buffer_pos + 11]];
b3 |= asctobin[2][afx->buffer[afx->buffer_pos + 12]];
b3 |= asctobin[1][afx->buffer[afx->buffer_pos + 13]];
b3 |= asctobin[0][afx->buffer[afx->buffer_pos + 14]];
/* Check if any of the input bytes were invalid. */
if( (b0 | b1 | b2 | b3) != 0xffffffffUL )
{
/* All 16 bytes are valid. */
buf[n + 0] = b0 >> (2 * 8);
buf[n + 1] = b0 >> (1 * 8);
buf[n + 2] = b0 >> (0 * 8);
buf[n + 3] = b1 >> (2 * 8);
buf[n + 4] = b1 >> (1 * 8);
buf[n + 5] = b1 >> (0 * 8);
buf[n + 6] = b2 >> (2 * 8);
buf[n + 7] = b2 >> (1 * 8);
buf[n + 8] = b2 >> (0 * 8);
buf[n + 9] = b3 >> (2 * 8);
buf[n + 10] = b3 >> (1 * 8);
buf[n + 11] = b3 >> (0 * 8);
afx->buffer_pos += 16 - 1;
n += 12;
continue;
}
else if( b0 == 0xffffffffUL )
{
/* byte[1..3] have invalid character(s). Switch to slow
path. */
skip_fast = 1;
}
else if( b1 == 0xffffffffUL )
{
/* byte[4..7] have invalid character(s), first 4 bytes are
valid. */
buf[n + 0] = b0 >> (2 * 8);
buf[n + 1] = b0 >> (1 * 8);
buf[n + 2] = b0 >> (0 * 8);
afx->buffer_pos += 4 - 1;
n += 3;
skip_fast = 1;
continue;
}
else if( b2 == 0xffffffffUL )
{
/* byte[8..11] have invalid character(s), first 8 bytes are
valid. */
buf[n + 0] = b0 >> (2 * 8);
buf[n + 1] = b0 >> (1 * 8);
buf[n + 2] = b0 >> (0 * 8);
buf[n + 3] = b1 >> (2 * 8);
buf[n + 4] = b1 >> (1 * 8);
buf[n + 5] = b1 >> (0 * 8);
afx->buffer_pos += 8 - 1;
n += 6;
skip_fast = 1;
continue;
}
else /*if( b3 == 0xffffffffUL )*/
{
/* byte[12..15] have invalid character(s), first 12 bytes
are valid. */
buf[n + 0] = b0 >> (2 * 8);
buf[n + 1] = b0 >> (1 * 8);
buf[n + 2] = b0 >> (0 * 8);
buf[n + 3] = b1 >> (2 * 8);
buf[n + 4] = b1 >> (1 * 8);
buf[n + 5] = b1 >> (0 * 8);
buf[n + 6] = b2 >> (2 * 8);
buf[n + 7] = b2 >> (1 * 8);
buf[n + 8] = b2 >> (0 * 8);
afx->buffer_pos += 12 - 1;
n += 9;
skip_fast = 1;
continue;
}
}
switch(idx)
{
case 0: val = binc << 2; break;
case 1: val |= (binc>>4)&3; buf[n++]=val;val=(binc<<4)&0xf0;break;
case 2: val |= (binc>>2)&15; buf[n++]=val;val=(binc<<6)&0xc0;break;
case 3: val |= binc&0x3f; buf[n++] = val; break;
}
idx = (idx+1) % 4;
continue;
}
skip_fast = 0;
if( c == '\n' || c == ' ' || c == '\r' || c == '\t' )
continue;
else if( c == '=' ) { /* pad character: stop */
/* some mailers leave quoted-printable encoded characters
* so we try to workaround this */
if( afx->buffer_pos+2 < afx->buffer_len ) {
int cc1, cc2, cc3;
cc1 = afx->buffer[afx->buffer_pos];
cc2 = afx->buffer[afx->buffer_pos+1];
cc3 = afx->buffer[afx->buffer_pos+2];
if( isxdigit(cc1) && isxdigit(cc2)
&& strchr( "=\n\r\t ", cc3 )) {
/* well it seems to be the case - adjust */
c = isdigit(cc1)? (cc1 - '0'): (ascii_toupper(cc1)-'A'+10);
c <<= 4;
c |= isdigit(cc2)? (cc2 - '0'): (ascii_toupper(cc2)-'A'+10);
afx->buffer_pos += 2;
afx->qp_detected = 1;
goto again;
}
}
/* Occasionally a bug MTA will leave the = escaped as
=3D. If the 4 characters following that are valid
Radix64 characters and they are following by a new
line, assume that this is the case and skip the
3D. */
if (afx->buffer_pos + 6 < afx->buffer_len
&& afx->buffer[afx->buffer_pos + 0] == '3'
&& afx->buffer[afx->buffer_pos + 1] == 'D'
&& asctobin[0][afx->buffer[afx->buffer_pos + 2]] != 0xffffffffUL
&& asctobin[0][afx->buffer[afx->buffer_pos + 3]] != 0xffffffffUL
&& asctobin[0][afx->buffer[afx->buffer_pos + 4]] != 0xffffffffUL
&& asctobin[0][afx->buffer[afx->buffer_pos + 5]] != 0xffffffffUL
&& afx->buffer[afx->buffer_pos + 6] == '\n')
{
afx->buffer_pos += 2;
afx->qp_detected = 1;
}
if (!n)
onlypad = 1;
if( idx == 1 )
buf[n++] = val;
checkcrc++;
break;
}
else {
log_error(_("invalid radix64 character %02X skipped\n"), c);
continue;
}
}
afx->idx = idx;
afx->radbuf[0] = val;
if( n )
gcry_md_write (afx->crc_md, buf, n);
if( checkcrc ) {
gcry_md_final (afx->crc_md);
afx->any_data = 1;
afx->inp_checked=0;
afx->faked = 0;
for(;;) { /* skip lf and pad characters */
if( afx->buffer_pos < afx->buffer_len )
c = afx->buffer[afx->buffer_pos++];
else { /* read the next line */
unsigned maxlen = MAX_LINELEN;
afx->buffer_pos = 0;
afx->buffer_len = iobuf_read_line( a, &afx->buffer,
&afx->buffer_size, &maxlen );
if( !maxlen )
afx->truncated++;
if( !afx->buffer_len )
break; /* eof */
continue;
}
if( c == '\n' || c == ' ' || c == '\r'
|| c == '\t' || c == '=' )
continue;
break;
}
if( !afx->buffer_len )
log_error(_("premature eof (no CRC)\n"));
else {
u32 mycrc = 0;
idx = 0;
do {
if( (binc = asctobin[0][c]) == 0xffffffffUL )
break;
switch(idx) {
case 0: val = binc << 2; break;
case 1: val |= (binc>>4)&3; mycrc |= val << 16;val=(binc<<4)&0xf0;break;
case 2: val |= (binc>>2)&15; mycrc |= val << 8;val=(binc<<6)&0xc0;break;
case 3: val |= binc&0x3f; mycrc |= val; break;
}
for(;;) {
if( afx->buffer_pos < afx->buffer_len )
c = afx->buffer[afx->buffer_pos++];
else { /* read the next line */
unsigned maxlen = MAX_LINELEN;
afx->buffer_pos = 0;
afx->buffer_len = iobuf_read_line( a, &afx->buffer,
&afx->buffer_size,
&maxlen );
if( !maxlen )
afx->truncated++;
if( !afx->buffer_len )
break; /* eof */
continue;
}
break;
}
if( !afx->buffer_len )
break; /* eof */
} while( ++idx < 4 );
if( !afx->buffer_len ) {
log_info(_("premature eof (in CRC)\n"));
rc = invalid_crc();
}
else if( idx == 0 ) {
/* No CRC at all is legal ("MAY") */
rc=0;
}
else if( idx != 4 ) {
log_info(_("malformed CRC\n"));
rc = invalid_crc();
}
else if( mycrc != get_afx_crc (afx) ) {
log_info (_("CRC error; %06lX - %06lX\n"),
(ulong)get_afx_crc (afx), (ulong)mycrc);
rc = invalid_crc();
}
else {
rc = 0;
/* FIXME: Here we should emit another control packet,
* so that we know in mainproc that we are processing
* a clearsign message */
#if 0
for(rc=0;!rc;) {
rc = 0 /*check_trailer( &fhdr, c )*/;
if( !rc ) {
if( (c=iobuf_get(a)) == -1 )
rc = 2;
}
}
if( rc == -1 )
rc = 0;
else if( rc == 2 ) {
log_error(_("premature eof (in trailer)\n"));
rc = GPG_ERR_INVALID_ARMOR;
}
else {
log_error(_("error in trailer line\n"));
rc = GPG_ERR_INVALID_ARMOR;
}
#endif
}
}
}
if( !n && !onlypad )
rc = -1;
*retn = n;
return rc;
}
static void
armor_output_buf_as_radix64 (armor_filter_context_t *afx, IOBUF a,
byte *buf, size_t size)
{
byte radbuf[sizeof (afx->radbuf)];
byte outbuf[64 + sizeof (afx->eol)];
unsigned int eollen = strlen (afx->eol);
u32 in, in2;
int idx, idx2;
int i;
idx = afx->idx;
idx2 = afx->idx2;
memcpy (radbuf, afx->radbuf, sizeof (afx->radbuf));
if (size && (idx || idx2))
{
/* preload eol to outbuf buffer */
memcpy (outbuf + 4, afx->eol, sizeof (afx->eol));
for (; size && (idx || idx2); buf++, size--)
{
radbuf[idx++] = *buf;
if (idx > 2)
{
idx = 0;
in = (u32)radbuf[0] << (2 * 8);
in |= (u32)radbuf[1] << (1 * 8);
in |= (u32)radbuf[2] << (0 * 8);
outbuf[0] = bintoasc[(in >> 18) & 077];
outbuf[1] = bintoasc[(in >> 12) & 077];
outbuf[2] = bintoasc[(in >> 6) & 077];
outbuf[3] = bintoasc[(in >> 0) & 077];
if (++idx2 >= (64/4))
{ /* pgp doesn't like 72 here */
idx2=0;
iobuf_write (a, outbuf, 4 + eollen);
}
else
{
iobuf_write (a, outbuf, 4);
}
}
}
}
if (size >= (64/4)*3)
{
/* preload eol to outbuf buffer */
memcpy (outbuf + 64, afx->eol, sizeof(afx->eol));
do
{
/* idx and idx2 == 0 */
for (i = 0; i < (64/8); i++)
{
in = (u32)buf[0] << (2 * 8);
in |= (u32)buf[1] << (1 * 8);
in |= (u32)buf[2] << (0 * 8);
in2 = (u32)buf[3] << (2 * 8);
in2 |= (u32)buf[4] << (1 * 8);
in2 |= (u32)buf[5] << (0 * 8);
outbuf[i*8+0] = bintoasc[(in >> 18) & 077];
outbuf[i*8+1] = bintoasc[(in >> 12) & 077];
outbuf[i*8+2] = bintoasc[(in >> 6) & 077];
outbuf[i*8+3] = bintoasc[(in >> 0) & 077];
outbuf[i*8+4] = bintoasc[(in2 >> 18) & 077];
outbuf[i*8+5] = bintoasc[(in2 >> 12) & 077];
outbuf[i*8+6] = bintoasc[(in2 >> 6) & 077];
outbuf[i*8+7] = bintoasc[(in2 >> 0) & 077];
buf+=6;
size-=6;
}
/* pgp doesn't like 72 here */
iobuf_write (a, outbuf, 64 + eollen);
}
while (size >= (64/4)*3);
/* restore eol for tail handling */
if (size)
memcpy (outbuf + 4, afx->eol, sizeof (afx->eol));
}
for (; size; buf++, size--)
{
radbuf[idx++] = *buf;
if (idx > 2)
{
idx = 0;
in = (u32)radbuf[0] << (2 * 8);
in |= (u32)radbuf[1] << (1 * 8);
in |= (u32)radbuf[2] << (0 * 8);
outbuf[0] = bintoasc[(in >> 18) & 077];
outbuf[1] = bintoasc[(in >> 12) & 077];
outbuf[2] = bintoasc[(in >> 6) & 077];
outbuf[3] = bintoasc[(in >> 0) & 077];
if (++idx2 >= (64/4))
{ /* pgp doesn't like 72 here */
idx2=0;
iobuf_write (a, outbuf, 4 + eollen);
}
else
{
iobuf_write (a, outbuf, 4);
}
}
}
memcpy (afx->radbuf, radbuf, sizeof (afx->radbuf));
afx->idx = idx;
afx->idx2 = idx2;
}
/****************
* This filter is used to handle the armor stuff
*/
static int
armor_filter( void *opaque, int control,
IOBUF a, byte *buf, size_t *ret_len)
{
size_t size = *ret_len;
armor_filter_context_t *afx = opaque;
int rc=0, c;
byte radbuf[3];
int idx, idx2;
size_t n=0;
u32 crc;
#if 0
static FILE *fp ;
if( !fp ) {
fp = fopen("armor.out", "w");
assert(fp);
}
#endif
if( DBG_FILTER )
log_debug("armor-filter: control: %d\n", control );
if( control == IOBUFCTRL_UNDERFLOW && afx->inp_bypass ) {
n = 0;
if( afx->buffer_len ) {
/* Copy the data from AFX->BUFFER to BUF. */
for(; n < size && afx->buffer_pos < afx->buffer_len; n++ )
buf[n++] = afx->buffer[afx->buffer_pos++];
if( afx->buffer_pos >= afx->buffer_len )
afx->buffer_len = 0;
}
/* If there is still space in BUF, read directly into it. */
for(; n < size; n++ ) {
if( (c=iobuf_get(a)) == -1 )
break;
buf[n] = c & 0xff;
}
if( !n )
/* We didn't get any data. EOF. */
rc = -1;
*ret_len = n;
}
else if( control == IOBUFCTRL_UNDERFLOW ) {
/* We need some space for the faked packet. The minmum
* required size is the PARTIAL_CHUNK size plus a byte for the
* length itself */
if( size < PARTIAL_CHUNK+1 )
BUG(); /* supplied buffer too short */
if( afx->faked )
rc = fake_packet( afx, a, &n, buf, size );
else if( !afx->inp_checked ) {
rc = check_input( afx, a );
if( afx->inp_bypass ) {
for(n=0; n < size && afx->buffer_pos < afx->buffer_len; )
buf[n++] = afx->buffer[afx->buffer_pos++];
if( afx->buffer_pos >= afx->buffer_len )
afx->buffer_len = 0;
if( !n )
rc = -1;
}
else if( afx->faked ) {
unsigned int hashes = afx->hashes;
const byte *sesmark;
size_t sesmarklen;
sesmark = get_session_marker( &sesmarklen );
if ( sesmarklen > 20 )
BUG();
/* the buffer is at least 15+n*15 bytes long, so it
* is easy to construct the packets */
hashes &= 1|2|8|16|32|64;
if( !hashes ) {
hashes |= 2; /* Default to SHA-1. */
}
n=0;
/* First a gpg control packet... */
buf[n++] = 0xff; /* new format, type 63, 1 length byte */
n++; /* see below */
memcpy(buf+n, sesmark, sesmarklen ); n+= sesmarklen;
buf[n++] = CTRLPKT_CLEARSIGN_START;
buf[n++] = afx->not_dash_escaped? 0:1; /* sigclass */
if( hashes & 1 )
buf[n++] = DIGEST_ALGO_RMD160;
if( hashes & 2 )
buf[n++] = DIGEST_ALGO_SHA1;
if( hashes & 8 )
buf[n++] = DIGEST_ALGO_SHA224;
if( hashes & 16 )
buf[n++] = DIGEST_ALGO_SHA256;
if( hashes & 32 )
buf[n++] = DIGEST_ALGO_SHA384;
if( hashes & 64 )
buf[n++] = DIGEST_ALGO_SHA512;
buf[1] = n - 2;
/* ...followed by an invented plaintext packet.
Amusingly enough, this packet is not compliant with
2440 as the initial partial length is less than 512
bytes. Of course, we'll accept it anyway ;) */
buf[n++] = 0xCB; /* new packet format, type 11 */
buf[n++] = 0xE1; /* 2^1 == 2 bytes */
buf[n++] = 't'; /* canonical text mode */
buf[n++] = 0; /* namelength */
buf[n++] = 0xE2; /* 2^2 == 4 more bytes */
memset(buf+n, 0, 4); /* timestamp */
n += 4;
}
else if( !rc )
rc = radix64_read( afx, a, &n, buf, size );
}
else
rc = radix64_read( afx, a, &n, buf, size );
#if 0
if( n )
if( fwrite(buf, n, 1, fp ) != 1 )
BUG();
#endif
*ret_len = n;
}
else if( control == IOBUFCTRL_FLUSH && !afx->cancel ) {
if( !afx->status ) { /* write the header line */
const char *s;
strlist_t comment=opt.comments;
if( afx->what >= DIM(head_strings) )
log_bug("afx->what=%d", afx->what);
iobuf_writestr(a, "-----");
iobuf_writestr(a, head_strings[afx->what] );
iobuf_writestr(a, "-----" );
iobuf_writestr(a,afx->eol);
if (opt.emit_version)
{
iobuf_writestr (a, "Version: "GNUPG_NAME" v");
for (s=VERSION; *s && *s != '.'; s++)
iobuf_writebyte (a, *s);
if (opt.emit_version > 1 && *s)
{
iobuf_writebyte (a, *s++);
for (; *s && *s != '.'; s++)
iobuf_writebyte (a, *s);
if (opt.emit_version > 2)
{
for (; *s && *s != '-' && !spacep (s); s++)
iobuf_writebyte (a, *s);
if (opt.emit_version > 3)
iobuf_writestr (a, " (" PRINTABLE_OS_NAME ")");
}
}
iobuf_writestr(a,afx->eol);
}
/* write the comment strings */
- for(s=comment->d;comment;comment=comment->next,s=comment->d)
+ for(;comment;comment=comment->next)
{
iobuf_writestr(a, "Comment: " );
- for( ; *s; s++ )
+ for( s=comment->d; *s; s++ )
{
if( *s == '\n' )
iobuf_writestr(a, "\\n" );
else if( *s == '\r' )
iobuf_writestr(a, "\\r" );
else if( *s == '\v' )
iobuf_writestr(a, "\\v" );
else
iobuf_put(a, *s );
}
iobuf_writestr(a,afx->eol);
}
if ( afx->hdrlines ) {
for ( s = afx->hdrlines; *s; s++ ) {
#ifdef HAVE_DOSISH_SYSTEM
if ( *s == '\n' )
iobuf_put( a, '\r');
#endif
iobuf_put(a, *s );
}
}
iobuf_writestr(a,afx->eol);
afx->status++;
afx->idx = 0;
afx->idx2 = 0;
gcry_md_reset (afx->crc_md);
}
if( size ) {
gcry_md_write (afx->crc_md, buf, size);
armor_output_buf_as_radix64 (afx, a, buf, size);
}
}
else if( control == IOBUFCTRL_INIT )
{
if( !is_initialized )
initialize();
/* Figure out what we're using for line endings if the caller
didn't specify. */
if(afx->eol[0]==0)
{
#ifdef HAVE_DOSISH_SYSTEM
afx->eol[0]='\r';
afx->eol[1]='\n';
#else
afx->eol[0]='\n';
#endif
}
}
else if( control == IOBUFCTRL_CANCEL ) {
afx->cancel = 1;
}
else if( control == IOBUFCTRL_FREE ) {
if( afx->cancel )
;
else if( afx->status ) { /* pad, write cecksum, and bottom line */
gcry_md_final (afx->crc_md);
crc = get_afx_crc (afx);
idx = afx->idx;
idx2 = afx->idx2;
if( idx ) {
c = bintoasc[(afx->radbuf[0]>>2)&077];
iobuf_put(a, c);
if( idx == 1 ) {
c = bintoasc[((afx->radbuf[0] << 4) & 060) & 077];
iobuf_put(a, c);
iobuf_put(a, '=');
iobuf_put(a, '=');
}
else { /* 2 */
c = bintoasc[(((afx->radbuf[0]<<4)&060)
|((afx->radbuf[1]>>4)&017))&077];
iobuf_put(a, c);
c = bintoasc[((afx->radbuf[1] << 2) & 074) & 077];
iobuf_put(a, c);
iobuf_put(a, '=');
}
if( ++idx2 >= (64/4) )
{ /* pgp doesn't like 72 here */
iobuf_writestr(a,afx->eol);
idx2=0;
}
}
/* may need a linefeed */
if( idx2 )
iobuf_writestr(a,afx->eol);
/* write the CRC */
iobuf_put(a, '=');
radbuf[0] = crc >>16;
radbuf[1] = crc >> 8;
radbuf[2] = crc;
c = bintoasc[(*radbuf >> 2) & 077];
iobuf_put(a, c);
c = bintoasc[(((*radbuf<<4)&060)|((radbuf[1] >> 4)&017))&077];
iobuf_put(a, c);
c = bintoasc[(((radbuf[1]<<2)&074)|((radbuf[2]>>6)&03))&077];
iobuf_put(a, c);
c = bintoasc[radbuf[2]&077];
iobuf_put(a, c);
iobuf_writestr(a,afx->eol);
/* and the trailer */
if( afx->what >= DIM(tail_strings) )
log_bug("afx->what=%d", afx->what);
iobuf_writestr(a, "-----");
iobuf_writestr(a, tail_strings[afx->what] );
iobuf_writestr(a, "-----" );
iobuf_writestr(a,afx->eol);
}
else if( !afx->any_data && !afx->inp_bypass ) {
log_error(_("no valid OpenPGP data found.\n"));
afx->no_openpgp_data = 1;
write_status_text( STATUS_NODATA, "1" );
}
if( afx->truncated )
log_info(_("invalid armor: line longer than %d characters\n"),
MAX_LINELEN );
/* issue an error to enforce dissemination of correct software */
if( afx->qp_detected )
log_error(_("quoted printable character in armor - "
"probably a buggy MTA has been used\n") );
xfree( afx->buffer );
afx->buffer = NULL;
release_armor_context (afx);
}
else if( control == IOBUFCTRL_DESC )
mem2str (buf, "armor_filter", *ret_len);
return rc;
}
/****************
* create a radix64 encoded string.
*/
char *
make_radix64_string( const byte *data, size_t len )
{
char *buffer, *p;
buffer = p = xmalloc( (len+2)/3*4 + 1 );
for( ; len >= 3 ; len -= 3, data += 3 ) {
*p++ = bintoasc[(data[0] >> 2) & 077];
*p++ = bintoasc[(((data[0] <<4)&060)|((data[1] >> 4)&017))&077];
*p++ = bintoasc[(((data[1]<<2)&074)|((data[2]>>6)&03))&077];
*p++ = bintoasc[data[2]&077];
}
if( len == 2 ) {
*p++ = bintoasc[(data[0] >> 2) & 077];
*p++ = bintoasc[(((data[0] <<4)&060)|((data[1] >> 4)&017))&077];
*p++ = bintoasc[((data[1]<<2)&074)];
}
else if( len == 1 ) {
*p++ = bintoasc[(data[0] >> 2) & 077];
*p++ = bintoasc[(data[0] <<4)&060];
}
*p = 0;
return buffer;
}
diff --git a/g10/build-packet.c b/g10/build-packet.c
index 07fccb099..2a95df694 100644
--- a/g10/build-packet.c
+++ b/g10/build-packet.c
@@ -1,1908 +1,1914 @@
/* build-packet.c - assemble packets and write them
* Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005,
* 2006, 2010, 2011 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "gpg.h"
#include "../common/util.h"
#include "packet.h"
#include "../common/status.h"
#include "../common/iobuf.h"
#include "../common/i18n.h"
#include "options.h"
#include "../common/host2net.h"
static gpg_error_t do_ring_trust (iobuf_t out, PKT_ring_trust *rt);
static int do_user_id( IOBUF out, int ctb, PKT_user_id *uid );
static int do_key (iobuf_t out, int ctb, PKT_public_key *pk);
static int do_symkey_enc( IOBUF out, int ctb, PKT_symkey_enc *enc );
static int do_pubkey_enc( IOBUF out, int ctb, PKT_pubkey_enc *enc );
static u32 calc_plaintext( PKT_plaintext *pt );
static int do_plaintext( IOBUF out, int ctb, PKT_plaintext *pt );
static int do_encrypted( IOBUF out, int ctb, PKT_encrypted *ed );
static int do_encrypted_mdc( IOBUF out, int ctb, PKT_encrypted *ed );
static int do_encrypted_aead (iobuf_t out, int ctb, PKT_encrypted *ed);
static int do_compressed( IOBUF out, int ctb, PKT_compressed *cd );
static int do_signature( IOBUF out, int ctb, PKT_signature *sig );
static int do_onepass_sig( IOBUF out, int ctb, PKT_onepass_sig *ops );
static int calc_header_length( u32 len, int new_ctb );
static int write_16(IOBUF inp, u16 a);
static int write_32(IOBUF inp, u32 a);
static int write_header( IOBUF out, int ctb, u32 len );
static int write_sign_packet_header( IOBUF out, int ctb, u32 len );
static int write_header2( IOBUF out, int ctb, u32 len, int hdrlen );
static int write_new_header( IOBUF out, int ctb, u32 len, int hdrlen );
/* Returns 1 if CTB is a new format ctb and 0 if CTB is an old format
ctb. */
static int
ctb_new_format_p (int ctb)
{
/* Bit 7 must always be set. */
log_assert ((ctb & (1 << 7)));
/* Bit 6 indicates whether the packet is a new format packet. */
return (ctb & (1 << 6));
}
/* Extract the packet type from a CTB. */
static int
ctb_pkttype (int ctb)
{
if (ctb_new_format_p (ctb))
/* Bits 0 through 5 are the packet type. */
return (ctb & ((1 << 6) - 1));
else
/* Bits 2 through 5 are the packet type. */
return (ctb & ((1 << 6) - 1)) >> 2;
}
/* Build a packet and write it to the stream OUT.
* Returns: 0 on success or on an error code. */
int
build_packet (IOBUF out, PACKET *pkt)
{
int rc = 0;
int new_ctb = 0;
int ctb, pkttype;
if (DBG_PACKET)
log_debug ("build_packet() type=%d\n", pkt->pkttype);
log_assert (pkt->pkt.generic);
switch ((pkttype = pkt->pkttype))
{
case PKT_PUBLIC_KEY:
if (pkt->pkt.public_key->seckey_info)
pkttype = PKT_SECRET_KEY;
break;
case PKT_PUBLIC_SUBKEY:
if (pkt->pkt.public_key->seckey_info)
pkttype = PKT_SECRET_SUBKEY;
break;
case PKT_PLAINTEXT:
new_ctb = pkt->pkt.plaintext->new_ctb;
break;
case PKT_ENCRYPTED:
case PKT_ENCRYPTED_MDC:
case PKT_ENCRYPTED_AEAD:
new_ctb = pkt->pkt.encrypted->new_ctb;
break;
case PKT_COMPRESSED:
new_ctb = pkt->pkt.compressed->new_ctb;
break;
case PKT_USER_ID:
if (pkt->pkt.user_id->attrib_data)
pkttype = PKT_ATTRIBUTE;
break;
default:
break;
}
if (new_ctb || pkttype > 15) /* new format */
ctb = (0xc0 | (pkttype & 0x3f));
else
ctb = (0x80 | ((pkttype & 15)<<2));
switch (pkttype)
{
case PKT_ATTRIBUTE:
case PKT_USER_ID:
rc = do_user_id (out, ctb, pkt->pkt.user_id);
break;
case PKT_OLD_COMMENT:
case PKT_COMMENT:
/* Ignore these. Theoretically, this will never be called as we
* have no way to output comment packets any longer, but just in
* case there is some code path that would end up outputting a
* comment that was written before comments were dropped (in the
* public key?) this is a no-op. */
break;
case PKT_PUBLIC_SUBKEY:
case PKT_PUBLIC_KEY:
case PKT_SECRET_SUBKEY:
case PKT_SECRET_KEY:
rc = do_key (out, ctb, pkt->pkt.public_key);
break;
case PKT_SYMKEY_ENC:
rc = do_symkey_enc (out, ctb, pkt->pkt.symkey_enc);
break;
case PKT_PUBKEY_ENC:
rc = do_pubkey_enc (out, ctb, pkt->pkt.pubkey_enc);
break;
case PKT_PLAINTEXT:
rc = do_plaintext (out, ctb, pkt->pkt.plaintext);
break;
case PKT_ENCRYPTED:
rc = do_encrypted (out, ctb, pkt->pkt.encrypted);
break;
case PKT_ENCRYPTED_MDC:
rc = do_encrypted_mdc (out, ctb, pkt->pkt.encrypted);
break;
case PKT_ENCRYPTED_AEAD:
rc = do_encrypted_aead (out, ctb, pkt->pkt.encrypted);
break;
case PKT_COMPRESSED:
rc = do_compressed (out, ctb, pkt->pkt.compressed);
break;
case PKT_SIGNATURE:
rc = do_signature (out, ctb, pkt->pkt.signature);
break;
case PKT_ONEPASS_SIG:
rc = do_onepass_sig (out, ctb, pkt->pkt.onepass_sig);
break;
case PKT_RING_TRUST:
/* Ignore it (only written by build_packet_and_meta) */
break;
case PKT_MDC:
/* We write it directly, so we should never see it here. */
default:
log_bug ("invalid packet type in build_packet()\n");
break;
}
return rc;
}
/* Build a packet and write it to the stream OUT. This variant also
* writes the meta data using ring trust packets. Returns: 0 on
* success or on error code. */
gpg_error_t
build_packet_and_meta (iobuf_t out, PACKET *pkt)
{
gpg_error_t err;
PKT_ring_trust rt = {0};
err = build_packet (out, pkt);
if (err)
;
else if (pkt->pkttype == PKT_SIGNATURE)
{
PKT_signature *sig = pkt->pkt.signature;
rt.subtype = RING_TRUST_SIG;
/* Note: trustval is not yet used. */
if (sig->flags.checked)
{
rt.sigcache = 1;
if (sig->flags.valid)
rt.sigcache |= 2;
}
err = do_ring_trust (out, &rt);
}
else if (pkt->pkttype == PKT_USER_ID
|| pkt->pkttype == PKT_ATTRIBUTE)
{
PKT_user_id *uid = pkt->pkt.user_id;
rt.subtype = RING_TRUST_UID;
rt.keyorg = uid->keyorg;
rt.keyupdate = uid->keyupdate;
rt.url = uid->updateurl;
err = do_ring_trust (out, &rt);
rt.url = NULL;
}
else if (pkt->pkttype == PKT_PUBLIC_KEY
|| pkt->pkttype == PKT_SECRET_KEY)
{
PKT_public_key *pk = pkt->pkt.public_key;
rt.subtype = RING_TRUST_KEY;
rt.keyorg = pk->keyorg;
rt.keyupdate = pk->keyupdate;
rt.url = pk->updateurl;
err = do_ring_trust (out, &rt);
rt.url = NULL;
}
return err;
}
/*
* Write the mpi A to OUT. If R_NWRITTEN is not NULL the number of
* bytes written is stored there. To only get the number of bytes
* which would be written NULL may be passed for OUT.
*/
gpg_error_t
gpg_mpi_write (iobuf_t out, gcry_mpi_t a, unsigned int *r_nwritten)
{
gpg_error_t err;
unsigned int nwritten = 0;
if (gcry_mpi_get_flag (a, GCRYMPI_FLAG_OPAQUE))
{
unsigned int nbits;
const unsigned char *p;
unsigned char lenhdr[2];
/* gcry_log_debugmpi ("a", a); */
p = gcry_mpi_get_opaque (a, &nbits);
if (p)
{
/* Strip leading zero bits. */
for (; nbits >= 8 && !*p; p++, nbits -= 8)
;
if (nbits >= 8 && !(*p & 0x80))
if (--nbits >= 7 && !(*p & 0x40))
if (--nbits >= 6 && !(*p & 0x20))
if (--nbits >= 5 && !(*p & 0x10))
if (--nbits >= 4 && !(*p & 0x08))
if (--nbits >= 3 && !(*p & 0x04))
if (--nbits >= 2 && !(*p & 0x02))
if (--nbits >= 1 && !(*p & 0x01))
--nbits;
}
/* gcry_log_debug (" [%u bit]\n", nbits); */
/* gcry_log_debughex (" ", p, (nbits+7)/8); */
lenhdr[0] = nbits >> 8;
lenhdr[1] = nbits;
err = out? iobuf_write (out, lenhdr, 2) : 0;
if (!err)
{
nwritten += 2;
if (p)
{
err = out? iobuf_write (out, p, (nbits+7)/8) : 0;
if (!err)
nwritten += (nbits+7)/8;
}
}
}
else
{
char buffer[(MAX_EXTERN_MPI_BITS+7)/8+2]; /* 2 is for the mpi length. */
size_t nbytes;
nbytes = DIM(buffer);
err = gcry_mpi_print (GCRYMPI_FMT_PGP, buffer, nbytes, &nbytes, a );
if (!err)
{
err = out? iobuf_write (out, buffer, nbytes) : 0;
if (!err)
nwritten += nbytes;
}
else if (gpg_err_code (err) == GPG_ERR_TOO_SHORT )
{
log_info ("mpi too large (%u bits)\n", gcry_mpi_get_nbits (a));
/* The buffer was too small. We better tell the user about
* the MPI. */
err = gpg_error (GPG_ERR_TOO_LARGE);
}
}
if (r_nwritten)
*r_nwritten = nwritten;
return err;
}
/*
* Write an opaque MPI to the output stream without length info.
*/
gpg_error_t
gpg_mpi_write_nohdr (iobuf_t out, gcry_mpi_t a)
{
int rc;
if (gcry_mpi_get_flag (a, GCRYMPI_FLAG_OPAQUE))
{
unsigned int nbits;
const void *p;
p = gcry_mpi_get_opaque (a, &nbits);
rc = p ? iobuf_write (out, p, (nbits+7)/8) : 0;
}
else
rc = gpg_error (GPG_ERR_BAD_MPI);
return rc;
}
/* Calculate the length of a packet described by PKT. */
u32
calc_packet_length( PACKET *pkt )
{
u32 n = 0;
int new_ctb = 0;
log_assert (pkt->pkt.generic);
switch (pkt->pkttype)
{
case PKT_PLAINTEXT:
n = calc_plaintext (pkt->pkt.plaintext);
new_ctb = pkt->pkt.plaintext->new_ctb;
break;
case PKT_ATTRIBUTE:
case PKT_USER_ID:
case PKT_COMMENT:
case PKT_PUBLIC_KEY:
case PKT_SECRET_KEY:
case PKT_SYMKEY_ENC:
case PKT_PUBKEY_ENC:
case PKT_ENCRYPTED:
case PKT_SIGNATURE:
case PKT_ONEPASS_SIG:
case PKT_RING_TRUST:
case PKT_COMPRESSED:
default:
log_bug ("invalid packet type in calc_packet_length()");
break;
}
n += calc_header_length (n, new_ctb);
return n;
}
static gpg_error_t
write_fake_data (IOBUF out, gcry_mpi_t a)
{
unsigned int n;
void *p;
if (!a)
return 0;
if (!gcry_mpi_get_flag (a, GCRYMPI_FLAG_OPAQUE))
return 0; /* e.g. due to generating a key with wrong usage. */
p = gcry_mpi_get_opaque ( a, &n);
if (!p)
return 0; /* For example due to a read error in
parse-packet.c:read_rest. */
return iobuf_write (out, p, (n+7)/8 );
}
/* Write a ring trust meta packet. */
static gpg_error_t
do_ring_trust (iobuf_t out, PKT_ring_trust *rt)
{
unsigned int namelen = 0;
unsigned int pktlen = 6;
if (rt->subtype == RING_TRUST_KEY || rt->subtype == RING_TRUST_UID)
{
if (rt->url)
namelen = strlen (rt->url);
pktlen += 1 + 4 + 1 + namelen;
}
write_header (out, (0x80 | ((PKT_RING_TRUST & 15)<<2)), pktlen);
iobuf_put (out, rt->trustval);
iobuf_put (out, rt->sigcache);
iobuf_write (out, "gpg", 3);
iobuf_put (out, rt->subtype);
if (rt->subtype == RING_TRUST_KEY || rt->subtype == RING_TRUST_UID)
{
iobuf_put (out, rt->keyorg);
write_32 (out, rt->keyupdate);
iobuf_put (out, namelen);
if (namelen)
iobuf_write (out, rt->url, namelen);
}
return 0;
}
/* Serialize the user id (RFC 4880, Section 5.11) or the user
* attribute UID (Section 5.12) and write it to OUT.
*
* CTB is the serialization's CTB. It specifies the header format and
* the packet's type. The header length must not be set. */
static int
do_user_id( IOBUF out, int ctb, PKT_user_id *uid )
{
int rc;
int hdrlen;
log_assert (ctb_pkttype (ctb) == PKT_USER_ID
|| ctb_pkttype (ctb) == PKT_ATTRIBUTE);
/* We need to take special care of a user ID with a length of 0:
* Without forcing HDRLEN to 2 in this case an indeterminate length
* packet would be written which is not allowed. Note that we are
* always called with a CTB indicating an old packet header format,
- * so that forcing a 2 octet header works. */
+ * so that forcing a 2 octet header works. We also check for the
+ * maximum allowed packet size by the parser using an arbitrary
+ * extra 10 bytes for header data. */
if (uid->attrib_data)
{
+ if (uid->attrib_len > MAX_ATTR_PACKET_LENGTH - 10)
+ return gpg_error (GPG_ERR_TOO_LARGE);
hdrlen = uid->attrib_len? 0 : 2;
write_header2 (out, ctb, uid->attrib_len, hdrlen);
rc = iobuf_write( out, uid->attrib_data, uid->attrib_len );
}
else
{
+ if (uid->len > MAX_UID_PACKET_LENGTH - 10)
+ return gpg_error (GPG_ERR_TOO_LARGE);
hdrlen = uid->len? 0 : 2;
write_header2 (out, ctb, uid->len, hdrlen);
rc = iobuf_write( out, uid->name, uid->len );
}
return rc;
}
/* Serialize the key (RFC 4880, Section 5.5) described by PK and write
* it to OUT.
*
* This function serializes both primary keys and subkeys with or
* without a secret part.
*
* CTB is the serialization's CTB. It specifies the header format and
* the packet's type. The header length must not be set.
*
* PK->VERSION specifies the serialization format. A value of 0 means
* to use the default version. Currently, only version 4 packets are
* supported.
*/
static int
do_key (iobuf_t out, int ctb, PKT_public_key *pk)
{
gpg_error_t err = 0;
iobuf_t a;
int i, nskey, npkey;
u32 pkbytes = 0;
int is_v5;
log_assert (pk->version == 0 || pk->version == 4 || pk->version == 5);
log_assert (ctb_pkttype (ctb) == PKT_PUBLIC_KEY
|| ctb_pkttype (ctb) == PKT_PUBLIC_SUBKEY
|| ctb_pkttype (ctb) == PKT_SECRET_KEY
|| ctb_pkttype (ctb) == PKT_SECRET_SUBKEY);
/* The length of the body is stored in the packet's header, which
* occurs before the body. Unfortunately, we don't know the length
* of the packet's body until we've written all of the data! To
* work around this, we first write the data into this temporary
* buffer, then generate the header, and finally copy the content
* of this buffer to OUT. */
a = iobuf_temp();
/* Note that the Version number, Timestamp, Algo, and the v5 Key
* material count are written at the end of the function. */
is_v5 = (pk->version == 5);
/* Get number of secret and public parameters. They are held in one
array: the public ones followed by the secret ones. */
nskey = pubkey_get_nskey (pk->pubkey_algo);
npkey = pubkey_get_npkey (pk->pubkey_algo);
/* If we don't have any public parameters - which is for example the
case if we don't know the algorithm used - the parameters are
stored as one blob in a faked (opaque) MPI. */
if (!npkey)
{
write_fake_data (a, pk->pkey[0]);
goto leave;
}
log_assert (npkey < nskey);
for (i=0; i < npkey; i++ )
{
if ( (pk->pubkey_algo == PUBKEY_ALGO_ECDSA && (i == 0))
|| (pk->pubkey_algo == PUBKEY_ALGO_EDDSA && (i == 0))
|| (pk->pubkey_algo == PUBKEY_ALGO_ECDH && (i == 0 || i == 2)))
err = gpg_mpi_write_nohdr (a, pk->pkey[i]);
else
err = gpg_mpi_write (a, pk->pkey[i], NULL);
if (err)
goto leave;
}
/* Record the length of the public key part. */
pkbytes = iobuf_get_temp_length (a);
if (pk->seckey_info)
{
/* This is a secret key packet. */
struct seckey_info *ski = pk->seckey_info;
/* Build the header for protected (encrypted) secret parameters. */
if (ski->is_protected)
{
iobuf_put (a, ski->sha1chk? 0xfe : 0xff); /* S2k usage. */
if (is_v5)
{
/* For a v5 key determine the count of the following
* key-protection material and write it. */
int count = 1; /* Pubkey algo octet. */
if (ski->s2k.mode >= 1000)
count += 6; /* GNU specific mode descriptor. */
else
count += 2; /* Mode and hash algo. */
if (ski->s2k.mode == 1 || ski->s2k.mode == 3)
count += 8; /* Salt. */
if (ski->s2k.mode == 3)
count++; /* S2K.COUNT */
if (ski->s2k.mode != 1001 && ski->s2k.mode != 1002)
count += ski->ivlen;
iobuf_put (a, count);
}
iobuf_put (a, ski->algo); /* Pubkey algo octet. */
if (ski->s2k.mode >= 1000)
{
/* These modes are not possible in OpenPGP, we use them
to implement our extensions, 101 can be viewed as a
private/experimental extension (this is not specified
in rfc2440 but the same scheme is used for all other
algorithm identifiers). */
iobuf_put (a, 101);
iobuf_put (a, ski->s2k.hash_algo);
iobuf_write (a, "GNU", 3 );
iobuf_put (a, ski->s2k.mode - 1000);
}
else
{
iobuf_put (a, ski->s2k.mode);
iobuf_put (a, ski->s2k.hash_algo);
}
if (ski->s2k.mode == 1 || ski->s2k.mode == 3)
iobuf_write (a, ski->s2k.salt, 8);
if (ski->s2k.mode == 3)
iobuf_put (a, ski->s2k.count);
/* For our special modes 1001, 1002 we do not need an IV. */
if (ski->s2k.mode != 1001 && ski->s2k.mode != 1002)
iobuf_write (a, ski->iv, ski->ivlen);
}
else /* Not protected. */
{
iobuf_put (a, 0 ); /* S2K usage = not protected. */
if (is_v5)
iobuf_put (a, 0); /* Zero octets of key-protection
* material follows. */
}
if (ski->s2k.mode == 1001)
{
/* GnuPG extension - don't write a secret key at all. */
if (is_v5)
write_32 (a, 0); /* Zero octets of key material. */
}
else if (ski->s2k.mode == 1002)
{
/* GnuPG extension - divert to OpenPGP smartcard. */
if (is_v5)
write_32 (a, 1 + ski->ivlen);
/* Length of the serial number or 0 for no serial number. */
iobuf_put (a, ski->ivlen );
/* The serial number gets stored in the IV field. */
iobuf_write (a, ski->iv, ski->ivlen);
}
else if (ski->is_protected)
{
/* The secret key is protected - write it out as it is. */
byte *p;
unsigned int ndatabits;
log_assert (gcry_mpi_get_flag (pk->pkey[npkey], GCRYMPI_FLAG_OPAQUE));
p = gcry_mpi_get_opaque (pk->pkey[npkey], &ndatabits);
/* For v5 keys we first write the number of octets of the
* following encrypted key material. */
if (is_v5)
write_32 (a, p? (ndatabits+7)/8 : 0);
if (p)
iobuf_write (a, p, (ndatabits+7)/8 );
}
else
{
/* Non-protected key. */
if (is_v5)
{
unsigned int skbytes = 0;
unsigned int n;
int j;
for (j=i; j < nskey; j++ )
{
if ((err = gpg_mpi_write (NULL, pk->pkey[j], &n)))
goto leave;
skbytes += n;
}
write_32 (a, skbytes);
}
for ( ; i < nskey; i++ )
if ( (err = gpg_mpi_write (a, pk->pkey[i], NULL)))
goto leave;
write_16 (a, ski->csum );
}
}
leave:
if (!err)
{
/* Build the header of the packet - which we must do after
* writing all the other stuff, so that we know the length of
* the packet */
u32 len = iobuf_get_temp_length (a);
len += 1; /* version number */
len += 4; /* timestamp */
len += 1; /* algo */
if (is_v5)
len += 4; /* public key material count */
write_header2 (out, ctb, len, 0);
/* And finally write it out to the real stream. */
iobuf_put (out, pk->version? pk->version : 4); /* version number */
write_32 (out, pk->timestamp );
iobuf_put (out, pk->pubkey_algo); /* algo */
if (is_v5)
write_32 (out, pkbytes); /* public key material count */
err = iobuf_write_temp (out, a); /* pub and sec key material */
}
iobuf_close (a); /* Close the temporary buffer */
return err;
}
/* Serialize the symmetric-key encrypted session key packet (RFC 4880,
* 5.3) described by ENC and write it to OUT.
*
* CTB is the serialization's CTB. It specifies the header format and
* the packet's type. The header length must not be set. */
static int
do_symkey_enc( IOBUF out, int ctb, PKT_symkey_enc *enc )
{
int rc = 0;
IOBUF a = iobuf_temp();
log_assert (ctb_pkttype (ctb) == PKT_SYMKEY_ENC);
log_assert (enc->version == 4 || enc->version == 5);
switch (enc->s2k.mode)
{
case 0: /* Simple S2K. */
case 1: /* Salted S2K. */
case 3: /* Iterated and salted S2K. */
break; /* Reasonable values. */
default:
log_bug ("do_symkey_enc: s2k=%d\n", enc->s2k.mode);
}
iobuf_put (a, enc->version);
iobuf_put (a, enc->cipher_algo);
if (enc->version == 5)
iobuf_put (a, enc->aead_algo);
iobuf_put (a, enc->s2k.mode);
iobuf_put (a, enc->s2k.hash_algo);
if (enc->s2k.mode == 1 || enc->s2k.mode == 3)
{
iobuf_write (a, enc->s2k.salt, 8);
if (enc->s2k.mode == 3)
iobuf_put (a, enc->s2k.count);
}
if (enc->seskeylen)
iobuf_write (a, enc->seskey, enc->seskeylen);
write_header (out, ctb, iobuf_get_temp_length(a));
rc = iobuf_write_temp (out, a);
iobuf_close (a);
return rc;
}
/* Serialize the public-key encrypted session key packet (RFC 4880,
5.1) described by ENC and write it to OUT.
CTB is the serialization's CTB. It specifies the header format and
the packet's type. The header length must not be set. */
static int
do_pubkey_enc( IOBUF out, int ctb, PKT_pubkey_enc *enc )
{
int rc = 0;
int n, i;
IOBUF a = iobuf_temp();
log_assert (ctb_pkttype (ctb) == PKT_PUBKEY_ENC);
iobuf_put (a, 3); /* Version. */
if ( enc->throw_keyid )
{
write_32(a, 0 ); /* Don't tell Eve who can decrypt the message. */
write_32(a, 0 );
}
else
{
write_32(a, enc->keyid[0] );
write_32(a, enc->keyid[1] );
}
iobuf_put(a,enc->pubkey_algo );
n = pubkey_get_nenc( enc->pubkey_algo );
if ( !n )
write_fake_data( a, enc->data[0] );
for (i=0; i < n && !rc ; i++ )
{
if (enc->pubkey_algo == PUBKEY_ALGO_ECDH && i == 1)
rc = gpg_mpi_write_nohdr (a, enc->data[i]);
else
rc = gpg_mpi_write (a, enc->data[i], NULL);
}
if (!rc)
{
write_header (out, ctb, iobuf_get_temp_length(a) );
rc = iobuf_write_temp (out, a);
}
iobuf_close(a);
return rc;
}
/* Calculate the length of the serialized plaintext packet PT (RFC
4480, Section 5.9). */
static u32
calc_plaintext( PKT_plaintext *pt )
{
/* Truncate namelen to the maximum 255 characters. Note this means
that a function that calls build_packet with an illegal literal
packet will get it back legalized. */
if(pt->namelen>255)
pt->namelen=255;
return pt->len? (1 + 1 + pt->namelen + 4 + pt->len) : 0;
}
/* Serialize the plaintext packet (RFC 4880, 5.9) described by PT and
write it to OUT.
The body of the message is stored in PT->BUF. The amount of data
to write is PT->LEN. (PT->BUF should be configured to return EOF
after this much data has been read.) If PT->LEN is 0 and CTB
indicates that this is a new format packet, then partial block mode
is assumed to have been enabled on OUT. On success, partial block
mode is disabled.
If PT->BUF is NULL, the caller must write out the data. In
this case, if PT->LEN was 0, then partial body length mode was
enabled and the caller must disable it by calling
iobuf_set_partial_body_length_mode (out, 0). */
static int
do_plaintext( IOBUF out, int ctb, PKT_plaintext *pt )
{
int rc = 0;
size_t nbytes;
log_assert (ctb_pkttype (ctb) == PKT_PLAINTEXT);
write_header(out, ctb, calc_plaintext( pt ) );
log_assert (pt->mode == 'b' || pt->mode == 't' || pt->mode == 'u'
|| pt->mode == 'm'
|| pt->mode == 'l' || pt->mode == '1');
iobuf_put(out, pt->mode );
iobuf_put(out, pt->namelen );
iobuf_write (out, pt->name, pt->namelen);
rc = write_32(out, pt->timestamp );
if (rc)
return rc;
if (pt->buf)
{
nbytes = iobuf_copy (out, pt->buf);
if(ctb_new_format_p (ctb) && !pt->len)
/* Turn off partial body length mode. */
iobuf_set_partial_body_length_mode (out, 0);
if( pt->len && nbytes != pt->len )
log_error("do_plaintext(): wrote %lu bytes but expected %lu bytes\n",
(ulong)nbytes, (ulong)pt->len );
}
return rc;
}
/* Serialize the symmetrically encrypted data packet (RFC 4880,
Section 5.7) described by ED and write it to OUT.
Note: this only writes the packets header! The call must then
follow up and write the initial random data and the body to OUT.
(If you use the encryption iobuf filter (cipher_filter), then this
is done automatically.) */
static int
do_encrypted( IOBUF out, int ctb, PKT_encrypted *ed )
{
int rc = 0;
u32 n;
log_assert (! ed->mdc_method);
log_assert (ctb_pkttype (ctb) == PKT_ENCRYPTED);
n = ed->len ? (ed->len + ed->extralen) : 0;
write_header(out, ctb, n );
/* This is all. The caller has to write the real data */
return rc;
}
/* Serialize the symmetrically encrypted integrity protected data
packet (RFC 4880, Section 5.13) described by ED and write it to
OUT.
Note: this only writes the packet's header! The caller must then
follow up and write the initial random data, the body and the MDC
packet to OUT. (If you use the encryption iobuf filter
(cipher_filter), then this is done automatically.) */
static int
do_encrypted_mdc( IOBUF out, int ctb, PKT_encrypted *ed )
{
int rc = 0;
u32 n;
log_assert (ed->mdc_method);
log_assert (ctb_pkttype (ctb) == PKT_ENCRYPTED_MDC);
/* Take version number and the following MDC packet in account. */
n = ed->len ? (ed->len + ed->extralen + 1 + 22) : 0;
write_header(out, ctb, n );
iobuf_put(out, 1 ); /* version */
/* This is all. The caller has to write the real data */
return rc;
}
/* Serialize the symmetrically AEAD encrypted data packet
* (rfc4880bis-03, Section 5.16) described by ED and write it to OUT.
*
* Note: this only writes only packet's header. The caller must then
* follow up and write the actual encrypted data. This should be done
* by pushing the the cipher_filter_aead. */
static int
do_encrypted_aead (iobuf_t out, int ctb, PKT_encrypted *ed)
{
u32 n;
log_assert (ctb_pkttype (ctb) == PKT_ENCRYPTED_AEAD);
n = ed->len ? (ed->len + ed->extralen + 4) : 0;
write_header (out, ctb, n );
iobuf_writebyte (out, 1); /* Version. */
iobuf_writebyte (out, ed->cipher_algo);
iobuf_writebyte (out, ed->aead_algo);
iobuf_writebyte (out, ed->chunkbyte);
/* This is all. The caller has to write the encrypted data */
return 0;
}
/* Serialize the compressed packet (RFC 4880, Section 5.6) described
by CD and write it to OUT.
Note: this only writes the packet's header! The caller must then
follow up and write the body to OUT. */
static int
do_compressed( IOBUF out, int ctb, PKT_compressed *cd )
{
int rc = 0;
log_assert (ctb_pkttype (ctb) == PKT_COMPRESSED);
/* We must use the old convention and don't use blockmode for the
sake of PGP 2 compatibility. However if the new_ctb flag was
set, CTB is already formatted as new style and write_header2
does create a partial length encoding using new the new
style. */
write_header2(out, ctb, 0, 0);
iobuf_put(out, cd->algorithm );
/* This is all. The caller has to write the real data */
return rc;
}
/****************
* Delete all subpackets of type REQTYPE and return a bool whether a packet
* was deleted.
*/
int
delete_sig_subpkt (subpktarea_t *area, sigsubpkttype_t reqtype )
{
int buflen;
sigsubpkttype_t type;
byte *buffer, *bufstart;
size_t n;
size_t unused = 0;
int okay = 0;
if( !area )
return 0;
buflen = area->len;
buffer = area->data;
for(;;) {
if( !buflen ) {
okay = 1;
break;
}
bufstart = buffer;
n = *buffer++; buflen--;
if( n == 255 ) {
if( buflen < 4 )
break;
n = buf32_to_size_t (buffer);
buffer += 4;
buflen -= 4;
}
else if( n >= 192 ) {
if( buflen < 2 )
break;
n = (( n - 192 ) << 8) + *buffer + 192;
buffer++;
buflen--;
}
if( buflen < n )
break;
type = *buffer & 0x7f;
if( type == reqtype ) {
buffer++;
buflen--;
n--;
if( n > buflen )
break;
buffer += n; /* point to next subpkt */
buflen -= n;
memmove (bufstart, buffer, buflen); /* shift */
unused += buffer - bufstart;
buffer = bufstart;
}
else {
buffer += n; buflen -=n;
}
}
if (!okay)
log_error ("delete_subpkt: buffer shorter than subpacket\n");
log_assert (unused <= area->len);
area->len -= unused;
return !!unused;
}
/****************
* Create or update a signature subpacket for SIG of TYPE. This
* functions knows where to put the data (hashed or unhashed). The
* function may move data from the unhashed part to the hashed one.
* Note: All pointers into sig->[un]hashed (e.g. returned by
* parse_sig_subpkt) are not valid after a call to this function. The
* data to put into the subpaket should be in a buffer with a length
* of buflen.
*/
void
build_sig_subpkt (PKT_signature *sig, sigsubpkttype_t type,
const byte *buffer, size_t buflen )
{
byte *p;
int critical, hashed;
subpktarea_t *oldarea, *newarea;
size_t nlen, n, n0;
critical = (type & SIGSUBPKT_FLAG_CRITICAL);
type &= ~SIGSUBPKT_FLAG_CRITICAL;
/* Sanity check buffer sizes */
if(parse_one_sig_subpkt(buffer,buflen,type)<0)
BUG();
switch(type)
{
case SIGSUBPKT_NOTATION:
case SIGSUBPKT_POLICY:
case SIGSUBPKT_REV_KEY:
case SIGSUBPKT_SIGNATURE:
/* we do allow multiple subpackets */
break;
default:
/* we don't allow multiple subpackets */
delete_sig_subpkt(sig->hashed,type);
delete_sig_subpkt(sig->unhashed,type);
break;
}
/* Any special magic that needs to be done for this type so the
packet doesn't need to be reparsed? */
switch(type)
{
case SIGSUBPKT_NOTATION:
sig->flags.notation=1;
break;
case SIGSUBPKT_POLICY:
sig->flags.policy_url=1;
break;
case SIGSUBPKT_PREF_KS:
sig->flags.pref_ks=1;
break;
case SIGSUBPKT_EXPORTABLE:
if(buffer[0])
sig->flags.exportable=1;
else
sig->flags.exportable=0;
break;
case SIGSUBPKT_REVOCABLE:
if(buffer[0])
sig->flags.revocable=1;
else
sig->flags.revocable=0;
break;
case SIGSUBPKT_TRUST:
sig->trust_depth=buffer[0];
sig->trust_value=buffer[1];
break;
case SIGSUBPKT_REGEXP:
sig->trust_regexp=buffer;
break;
/* This should never happen since we don't currently allow
creating such a subpacket, but just in case... */
case SIGSUBPKT_SIG_EXPIRE:
if(buf32_to_u32(buffer)+sig->timestamp<=make_timestamp())
sig->flags.expired=1;
else
sig->flags.expired=0;
break;
default:
break;
}
if( (buflen+1) >= 8384 )
nlen = 5; /* write 5 byte length header */
else if( (buflen+1) >= 192 )
nlen = 2; /* write 2 byte length header */
else
nlen = 1; /* just a 1 byte length header */
switch( type )
{
/* The issuer being unhashed is a historical oddity. It
should work equally as well hashed. Of course, if even an
unhashed issuer is tampered with, it makes it awfully hard
to verify the sig... */
case SIGSUBPKT_ISSUER:
case SIGSUBPKT_SIGNATURE:
hashed = 0;
break;
default:
hashed = 1;
break;
}
if( critical )
type |= SIGSUBPKT_FLAG_CRITICAL;
oldarea = hashed? sig->hashed : sig->unhashed;
/* Calculate new size of the area and allocate */
n0 = oldarea? oldarea->len : 0;
n = n0 + nlen + 1 + buflen; /* length, type, buffer */
if (oldarea && n <= oldarea->size) { /* fits into the unused space */
newarea = oldarea;
/*log_debug ("updating area for type %d\n", type );*/
}
else if (oldarea) {
newarea = xrealloc (oldarea, sizeof (*newarea) + n - 1);
newarea->size = n;
/*log_debug ("reallocating area for type %d\n", type );*/
}
else {
newarea = xmalloc (sizeof (*newarea) + n - 1);
newarea->size = n;
/*log_debug ("allocating area for type %d\n", type );*/
}
newarea->len = n;
p = newarea->data + n0;
if (nlen == 5) {
*p++ = 255;
*p++ = (buflen+1) >> 24;
*p++ = (buflen+1) >> 16;
*p++ = (buflen+1) >> 8;
*p++ = (buflen+1);
*p++ = type;
memcpy (p, buffer, buflen);
}
else if (nlen == 2) {
*p++ = (buflen+1-192) / 256 + 192;
*p++ = (buflen+1-192) % 256;
*p++ = type;
memcpy (p, buffer, buflen);
}
else {
*p++ = buflen+1;
*p++ = type;
memcpy (p, buffer, buflen);
}
if (hashed)
sig->hashed = newarea;
else
sig->unhashed = newarea;
}
/*
* Put all the required stuff from SIG into subpackets of sig.
* PKSK is the signing key.
* Hmmm, should we delete those subpackets which are in a wrong area?
*/
void
build_sig_subpkt_from_sig (PKT_signature *sig, PKT_public_key *pksk)
{
u32 u;
byte buf[1+MAX_FINGERPRINT_LEN];
size_t fprlen;
/* For v4 keys we need to write the ISSUER subpacket. We do not
* want that for a future v5 format. */
if (pksk->version < 5)
{
u = sig->keyid[0];
buf[0] = (u >> 24) & 0xff;
buf[1] = (u >> 16) & 0xff;
buf[2] = (u >> 8) & 0xff;
buf[3] = u & 0xff;
u = sig->keyid[1];
buf[4] = (u >> 24) & 0xff;
buf[5] = (u >> 16) & 0xff;
buf[6] = (u >> 8) & 0xff;
buf[7] = u & 0xff;
build_sig_subpkt (sig, SIGSUBPKT_ISSUER, buf, 8);
}
/* Write the new ISSUER_FPR subpacket. */
fingerprint_from_pk (pksk, buf+1, &fprlen);
if (fprlen == 20 || fprlen == 32)
{
buf[0] = pksk->version;
build_sig_subpkt (sig, SIGSUBPKT_ISSUER_FPR, buf, fprlen + 1);
}
/* Write the timestamp. */
u = sig->timestamp;
buf[0] = (u >> 24) & 0xff;
buf[1] = (u >> 16) & 0xff;
buf[2] = (u >> 8) & 0xff;
buf[3] = u & 0xff;
build_sig_subpkt( sig, SIGSUBPKT_SIG_CREATED, buf, 4 );
if(sig->expiredate)
{
if(sig->expiredate>sig->timestamp)
u=sig->expiredate-sig->timestamp;
else
u=1; /* A 1-second expiration time is the shortest one
OpenPGP has */
buf[0] = (u >> 24) & 0xff;
buf[1] = (u >> 16) & 0xff;
buf[2] = (u >> 8) & 0xff;
buf[3] = u & 0xff;
/* Mark this CRITICAL, so if any implementation doesn't
understand sigs that can expire, it'll just disregard this
sig altogether. */
build_sig_subpkt( sig, SIGSUBPKT_SIG_EXPIRE | SIGSUBPKT_FLAG_CRITICAL,
buf, 4 );
}
}
void
build_attribute_subpkt(PKT_user_id *uid,byte type,
const void *buf,u32 buflen,
const void *header,u32 headerlen)
{
byte *attrib;
int idx;
if(1+headerlen+buflen>8383)
idx=5;
else if(1+headerlen+buflen>191)
idx=2;
else
idx=1;
/* realloc uid->attrib_data to the right size */
uid->attrib_data=xrealloc(uid->attrib_data,
uid->attrib_len+idx+1+headerlen+buflen);
attrib=&uid->attrib_data[uid->attrib_len];
if(idx==5)
{
attrib[0]=255;
attrib[1]=(1+headerlen+buflen) >> 24;
attrib[2]=(1+headerlen+buflen) >> 16;
attrib[3]=(1+headerlen+buflen) >> 8;
attrib[4]=1+headerlen+buflen;
}
else if(idx==2)
{
attrib[0]=(1+headerlen+buflen-192) / 256 + 192;
attrib[1]=(1+headerlen+buflen-192) % 256;
}
else
attrib[0]=1+headerlen+buflen; /* Good luck finding a JPEG this small! */
attrib[idx++]=type;
/* Tack on our data at the end */
if(headerlen>0)
memcpy(&attrib[idx],header,headerlen);
memcpy(&attrib[idx+headerlen],buf,buflen);
uid->attrib_len+=idx+headerlen+buflen;
}
/* Returns a human-readable string corresponding to the notation.
This ignores notation->value. The caller must free the result. */
static char *
notation_value_to_human_readable_string (struct notation *notation)
{
if(notation->bdat)
/* Binary data. */
{
size_t len = notation->blen;
int i;
char preview[20];
for (i = 0; i < len && i < sizeof (preview) - 1; i ++)
if (isprint (notation->bdat[i]))
preview[i] = notation->bdat[i];
else
preview[i] = '?';
preview[i] = 0;
return xasprintf (_("[ not human readable (%zu bytes: %s%s) ]"),
len, preview, i < len ? "..." : "");
}
else
/* The value is human-readable. */
return xstrdup (notation->value);
}
/* Turn the notation described by the string STRING into a notation.
STRING has the form:
- -name - Delete the notation.
- name@domain.name=value - Normal notation
- !name@domain.name=value - Notation with critical bit set.
The caller must free the result using free_notation(). */
struct notation *
string_to_notation(const char *string,int is_utf8)
{
const char *s;
int saw_at=0;
struct notation *notation;
notation=xmalloc_clear(sizeof(*notation));
if(*string=='-')
{
notation->flags.ignore=1;
string++;
}
if(*string=='!')
{
notation->flags.critical=1;
string++;
}
/* If and when the IETF assigns some official name tags, we'll have
to add them here. */
for( s=string ; *s != '='; s++ )
{
if( *s=='@')
saw_at++;
/* -notationname is legal without an = sign */
if(!*s && notation->flags.ignore)
break;
if( !*s || !isascii (*s) || (!isgraph(*s) && !isspace(*s)) )
{
log_error(_("a notation name must have only printable characters"
" or spaces, and end with an '='\n") );
goto fail;
}
}
notation->name=xmalloc((s-string)+1);
memcpy(notation->name,string,s-string);
notation->name[s-string]='\0';
if(!saw_at && !opt.expert)
{
log_error(_("a user notation name must contain the '@' character\n"));
goto fail;
}
if (saw_at > 1)
{
log_error(_("a notation name must not contain more than"
" one '@' character\n"));
goto fail;
}
if(*s)
{
const char *i=s+1;
int highbit=0;
/* we only support printable text - therefore we enforce the use
of only printable characters (an empty value is valid) */
for(s++; *s ; s++ )
{
if ( !isascii (*s) )
highbit=1;
else if (iscntrl(*s))
{
log_error(_("a notation value must not use any"
" control characters\n"));
goto fail;
}
}
if(!highbit || is_utf8)
notation->value=xstrdup(i);
else
notation->value=native_to_utf8(i);
}
return notation;
fail:
free_notation(notation);
return NULL;
}
/* Like string_to_notation, but store opaque data rather than human
readable data. */
struct notation *
blob_to_notation(const char *name, const char *data, size_t len)
{
const char *s;
int saw_at=0;
struct notation *notation;
notation=xmalloc_clear(sizeof(*notation));
if(*name=='-')
{
notation->flags.ignore=1;
name++;
}
if(*name=='!')
{
notation->flags.critical=1;
name++;
}
/* If and when the IETF assigns some official name tags, we'll have
to add them here. */
for( s=name ; *s; s++ )
{
if( *s=='@')
saw_at++;
/* -notationname is legal without an = sign */
if(!*s && notation->flags.ignore)
break;
if (*s == '=')
{
log_error(_("a notation name may not contain an '=' character\n"));
goto fail;
}
if (!isascii (*s) || (!isgraph(*s) && !isspace(*s)))
{
log_error(_("a notation name must have only printable characters"
" or spaces\n") );
goto fail;
}
}
notation->name=xstrdup (name);
if(!saw_at && !opt.expert)
{
log_error(_("a user notation name must contain the '@' character\n"));
goto fail;
}
if (saw_at > 1)
{
log_error(_("a notation name must not contain more than"
" one '@' character\n"));
goto fail;
}
notation->bdat = xmalloc (len);
memcpy (notation->bdat, data, len);
notation->blen = len;
notation->value = notation_value_to_human_readable_string (notation);
return notation;
fail:
free_notation(notation);
return NULL;
}
struct notation *
sig_to_notation(PKT_signature *sig)
{
const byte *p;
size_t len;
int seq = 0;
int crit;
notation_t list = NULL;
/* See RFC 4880, 5.2.3.16 for the format of notation data. In
short, a notation has:
- 4 bytes of flags
- 2 byte name length (n1)
- 2 byte value length (n2)
- n1 bytes of name data
- n2 bytes of value data
*/
while((p=enum_sig_subpkt(sig->hashed,SIGSUBPKT_NOTATION,&len,&seq,&crit)))
{
int n1,n2;
struct notation *n=NULL;
if(len<8)
{
log_info(_("WARNING: invalid notation data found\n"));
continue;
}
/* name length. */
n1=(p[4]<<8)|p[5];
/* value length. */
n2=(p[6]<<8)|p[7];
if(8+n1+n2!=len)
{
log_info(_("WARNING: invalid notation data found\n"));
continue;
}
n=xmalloc_clear(sizeof(*n));
n->name=xmalloc(n1+1);
memcpy(n->name,&p[8],n1);
n->name[n1]='\0';
if(p[0]&0x80)
/* The value is human-readable. */
{
n->value=xmalloc(n2+1);
memcpy(n->value,&p[8+n1],n2);
n->value[n2]='\0';
n->flags.human = 1;
}
else
/* Binary data. */
{
n->bdat=xmalloc(n2);
n->blen=n2;
memcpy(n->bdat,&p[8+n1],n2);
n->value = notation_value_to_human_readable_string (n);
}
n->flags.critical=crit;
n->next=list;
list=n;
}
return list;
}
/* Release the resources associated with the *list* of notations. To
release a single notation, make sure that notation->next is
NULL. */
void
free_notation(struct notation *notation)
{
while(notation)
{
struct notation *n=notation;
xfree(n->name);
xfree(n->value);
xfree(n->altvalue);
xfree(n->bdat);
notation=n->next;
xfree(n);
}
}
/* Serialize the signature packet (RFC 4880, Section 5.2) described by
SIG and write it to OUT. */
static int
do_signature( IOBUF out, int ctb, PKT_signature *sig )
{
int rc = 0;
int n, i;
IOBUF a = iobuf_temp();
log_assert (ctb_pkttype (ctb) == PKT_SIGNATURE);
if ( !sig->version || sig->version == 3)
{
iobuf_put( a, 3 );
/* Version 3 packets don't support subpackets. */
log_assert (! sig->hashed);
log_assert (! sig->unhashed);
}
else
iobuf_put( a, sig->version );
if ( sig->version < 4 )
iobuf_put (a, 5 ); /* Constant used by pre-v4 signatures. */
iobuf_put (a, sig->sig_class );
if ( sig->version < 4 )
{
write_32(a, sig->timestamp );
write_32(a, sig->keyid[0] );
write_32(a, sig->keyid[1] );
}
iobuf_put(a, sig->pubkey_algo );
iobuf_put(a, sig->digest_algo );
if ( sig->version >= 4 )
{
size_t nn;
/* Timestamp and keyid must have been packed into the subpackets
prior to the call of this function, because these subpackets
are hashed. */
nn = sig->hashed? sig->hashed->len : 0;
write_16(a, nn);
if (nn)
iobuf_write( a, sig->hashed->data, nn );
nn = sig->unhashed? sig->unhashed->len : 0;
write_16(a, nn);
if (nn)
iobuf_write( a, sig->unhashed->data, nn );
}
iobuf_put(a, sig->digest_start[0] );
iobuf_put(a, sig->digest_start[1] );
n = pubkey_get_nsig( sig->pubkey_algo );
if ( !n )
write_fake_data( a, sig->data[0] );
for (i=0; i < n && !rc ; i++ )
rc = gpg_mpi_write (a, sig->data[i], NULL);
if (!rc)
{
if ( is_RSA(sig->pubkey_algo) && sig->version < 4 )
write_sign_packet_header(out, ctb, iobuf_get_temp_length(a) );
else
write_header(out, ctb, iobuf_get_temp_length(a) );
rc = iobuf_write_temp( out, a );
}
iobuf_close(a);
return rc;
}
/* Serialize the one-pass signature packet (RFC 4880, Section 5.4)
described by OPS and write it to OUT. */
static int
do_onepass_sig( IOBUF out, int ctb, PKT_onepass_sig *ops )
{
log_assert (ctb_pkttype (ctb) == PKT_ONEPASS_SIG);
write_header(out, ctb, 4 + 8 + 1);
iobuf_put (out, 3); /* Version. */
iobuf_put(out, ops->sig_class );
iobuf_put(out, ops->digest_algo );
iobuf_put(out, ops->pubkey_algo );
write_32(out, ops->keyid[0] );
write_32(out, ops->keyid[1] );
iobuf_put(out, ops->last );
return 0;
}
/* Write a 16-bit quantity to OUT in big endian order. */
static int
write_16(IOBUF out, u16 a)
{
iobuf_put(out, a>>8);
if( iobuf_put(out,a) )
return -1;
return 0;
}
/* Write a 32-bit quantity to OUT in big endian order. */
static int
write_32(IOBUF out, u32 a)
{
iobuf_put(out, a>> 24);
iobuf_put(out, a>> 16);
iobuf_put(out, a>> 8);
return iobuf_put(out, a);
}
/****************
* calculate the length of a header.
*
* LEN is the length of the packet's body. NEW_CTB is whether we are
* using a new or old format packet.
*
* This function does not handle indeterminate lengths or partial body
* lengths. (If you pass LEN as 0, then this function assumes you
* really mean an empty body.)
*/
static int
calc_header_length( u32 len, int new_ctb )
{
if( new_ctb ) {
if( len < 192 )
return 2;
if( len < 8384 )
return 3;
else
return 6;
}
if( len < 256 )
return 2;
if( len < 65536 )
return 3;
return 5;
}
/****************
* Write the CTB and the packet length
*/
static int
write_header( IOBUF out, int ctb, u32 len )
{
return write_header2( out, ctb, len, 0 );
}
static int
write_sign_packet_header (IOBUF out, int ctb, u32 len)
{
(void)ctb;
/* Work around a bug in the pgp read function for signature packets,
which are not correctly coded and silently assume at some point 2
byte length headers.*/
iobuf_put (out, 0x89 );
iobuf_put (out, len >> 8 );
return iobuf_put (out, len) == -1 ? -1:0;
}
/****************
* Write a packet header to OUT.
*
* CTB is the ctb. It determines whether a new or old format packet
* header should be written. The length field is adjusted, but the
* CTB is otherwise written out as is.
*
* LEN is the length of the packet's body.
*
* If HDRLEN is set, then we don't necessarily use the most efficient
* encoding to store LEN, but the specified length. (If this is not
* possible, this is a bug.) In this case, LEN=0 means a 0 length
* packet. Note: setting HDRLEN is only supported for old format
* packets!
*
* If HDRLEN is not set, then the shortest encoding is used. In this
* case, LEN=0 means the body has an indeterminate length and a
* partial body length header (if a new format packet) or an
* indeterminate length header (if an old format packet) is written
* out. Further, if using partial body lengths, this enables partial
* body length mode on OUT.
*/
static int
write_header2( IOBUF out, int ctb, u32 len, int hdrlen )
{
if (ctb_new_format_p (ctb))
return write_new_header( out, ctb, len, hdrlen );
/* An old format packet. Refer to RFC 4880, Section 4.2.1 to
understand how lengths are encoded in this case. */
/* The length encoding is stored in the two least significant bits.
Make sure they are cleared. */
log_assert ((ctb & 3) == 0);
log_assert (hdrlen == 0 || hdrlen == 2 || hdrlen == 3 || hdrlen == 5);
if (hdrlen)
/* Header length is given. */
{
if( hdrlen == 2 && len < 256 )
/* 00 => 1 byte length. */
;
else if( hdrlen == 3 && len < 65536 )
/* 01 => 2 byte length. If len < 256, this is not the most
compact encoding, but it is a correct encoding. */
ctb |= 1;
else if (hdrlen == 5)
/* 10 => 4 byte length. If len < 65536, this is not the most
compact encoding, but it is a correct encoding. */
ctb |= 2;
else
log_bug ("Can't encode length=%d in a %d byte header!\n",
len, hdrlen);
}
else
{
if( !len )
/* 11 => Indeterminate length. */
ctb |= 3;
else if( len < 256 )
/* 00 => 1 byte length. */
;
else if( len < 65536 )
/* 01 => 2 byte length. */
ctb |= 1;
else
/* 10 => 4 byte length. */
ctb |= 2;
}
if( iobuf_put(out, ctb ) )
return -1;
if( len || hdrlen )
{
if( ctb & 2 )
{
if(iobuf_put(out, len >> 24 ))
return -1;
if(iobuf_put(out, len >> 16 ))
return -1;
}
if( ctb & 3 )
if(iobuf_put(out, len >> 8 ))
return -1;
if( iobuf_put(out, len ) )
return -1;
}
return 0;
}
/* Write a new format header to OUT.
CTB is the ctb.
LEN is the length of the packet's body. If LEN is 0, then enables
partial body length mode (i.e., the body is of an indeterminant
length) on OUT. Note: this function cannot be used to generate a
header for a zero length packet.
HDRLEN is the length of the packet's header. If HDRLEN is 0, the
shortest encoding is chosen based on the length of the packet's
body. Currently, values other than 0 are not supported.
Returns 0 on success. */
static int
write_new_header( IOBUF out, int ctb, u32 len, int hdrlen )
{
if( hdrlen )
log_bug("can't cope with hdrlen yet\n");
if( iobuf_put(out, ctb ) )
return -1;
if( !len ) {
iobuf_set_partial_body_length_mode(out, 512 );
}
else {
if( len < 192 ) {
if( iobuf_put(out, len ) )
return -1;
}
else if( len < 8384 ) {
len -= 192;
if( iobuf_put( out, (len / 256) + 192) )
return -1;
if( iobuf_put( out, (len % 256) ) )
return -1;
}
else {
if( iobuf_put( out, 0xff ) )
return -1;
if( iobuf_put( out, (len >> 24)&0xff ) )
return -1;
if( iobuf_put( out, (len >> 16)&0xff ) )
return -1;
if( iobuf_put( out, (len >> 8)&0xff ) )
return -1;
if( iobuf_put( out, len & 0xff ) )
return -1;
}
}
return 0;
}
diff --git a/g10/call-agent.c b/g10/call-agent.c
index 83777534e..19deb73d7 100644
--- a/g10/call-agent.c
+++ b/g10/call-agent.c
@@ -1,2413 +1,2688 @@
/* call-agent.c - Divert GPG operations to the agent.
* Copyright (C) 2001-2003, 2006-2011, 2013 Free Software Foundation, Inc.
* Copyright (C) 2013-2015 Werner Koch
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <time.h>
#ifdef HAVE_LOCALE_H
#include <locale.h>
#endif
#include "gpg.h"
#include <assuan.h>
#include "../common/util.h"
#include "../common/membuf.h"
#include "options.h"
#include "../common/i18n.h"
#include "../common/asshelp.h"
#include "../common/sysutils.h"
#include "call-agent.h"
#include "../common/status.h"
#include "../common/shareddefs.h"
#include "../common/host2net.h"
+#include "../common/ttyio.h"
#define CONTROL_D ('D' - 'A' + 1)
static assuan_context_t agent_ctx = NULL;
static int did_early_card_test;
+struct confirm_parm_s
+{
+ char *desc;
+ char *ok;
+ char *notok;
+};
+
struct default_inq_parm_s
{
ctrl_t ctrl;
assuan_context_t ctx;
struct {
u32 *keyid;
u32 *mainkeyid;
int pubkey_algo;
} keyinfo;
+ struct confirm_parm_s *confirm;
};
struct cipher_parm_s
{
struct default_inq_parm_s *dflt;
assuan_context_t ctx;
unsigned char *ciphertext;
size_t ciphertextlen;
};
struct writecert_parm_s
{
struct default_inq_parm_s *dflt;
const unsigned char *certdata;
size_t certdatalen;
};
struct writekey_parm_s
{
struct default_inq_parm_s *dflt;
const unsigned char *keydata;
size_t keydatalen;
};
struct genkey_parm_s
{
struct default_inq_parm_s *dflt;
const char *keyparms;
const char *passphrase;
};
struct import_key_parm_s
{
struct default_inq_parm_s *dflt;
const void *key;
size_t keylen;
};
struct cache_nonce_parm_s
{
char **cache_nonce_addr;
char **passwd_nonce_addr;
};
static gpg_error_t learn_status_cb (void *opaque, const char *line);
/* If RC is not 0, write an appropriate status message. */
static void
status_sc_op_failure (int rc)
{
switch (gpg_err_code (rc))
{
case 0:
break;
case GPG_ERR_CANCELED:
case GPG_ERR_FULLY_CANCELED:
write_status_text (STATUS_SC_OP_FAILURE, "1");
break;
case GPG_ERR_BAD_PIN:
write_status_text (STATUS_SC_OP_FAILURE, "2");
break;
default:
write_status (STATUS_SC_OP_FAILURE);
break;
}
}
/* This is the default inquiry callback. It mainly handles the
Pinentry notifications. */
static gpg_error_t
default_inq_cb (void *opaque, const char *line)
{
gpg_error_t err = 0;
struct default_inq_parm_s *parm = opaque;
+ const char *s;
if (has_leading_keyword (line, "PINENTRY_LAUNCHED"))
{
err = gpg_proxy_pinentry_notify (parm->ctrl, line);
if (err)
log_error (_("failed to proxy %s inquiry to client\n"),
"PINENTRY_LAUNCHED");
/* We do not pass errors to avoid breaking other code. */
}
else if ((has_leading_keyword (line, "PASSPHRASE")
|| has_leading_keyword (line, "NEW_PASSPHRASE"))
&& opt.pinentry_mode == PINENTRY_MODE_LOOPBACK)
{
if (have_static_passphrase ())
{
- const char *s = get_static_passphrase ();
+ s = get_static_passphrase ();
err = assuan_send_data (parm->ctx, s, strlen (s));
}
else
{
char *pw;
char buf[32];
if (parm->keyinfo.keyid)
emit_status_need_passphrase (parm->ctrl,
parm->keyinfo.keyid,
parm->keyinfo.mainkeyid,
parm->keyinfo.pubkey_algo);
snprintf (buf, sizeof (buf), "%u", 100);
write_status_text (STATUS_INQUIRE_MAXLEN, buf);
pw = cpr_get_hidden ("passphrase.enter", _("Enter passphrase: "));
cpr_kill_prompt ();
if (*pw == CONTROL_D && !pw[1])
err = gpg_error (GPG_ERR_CANCELED);
else
err = assuan_send_data (parm->ctx, pw, strlen (pw));
xfree (pw);
}
}
+ else if ((s = has_leading_keyword (line, "CONFIRM"))
+ && opt.pinentry_mode == PINENTRY_MODE_LOOPBACK
+ && parm->confirm)
+ {
+ int ask = atoi (s);
+ int yes;
+
+ if (ask)
+ {
+ yes = cpr_get_answer_is_yes (NULL, parm->confirm->desc);
+ if (yes)
+ err = assuan_send_data (parm->ctx, NULL, 0);
+ else
+ err = gpg_error (GPG_ERR_NOT_CONFIRMED);
+ }
+ else
+ {
+ tty_printf ("%s", parm->confirm->desc);
+ err = assuan_send_data (parm->ctx, NULL, 0);
+ }
+ }
else
log_debug ("ignoring gpg-agent inquiry '%s'\n", line);
return err;
}
/* Print a warning if the server's version number is less than our
version number. Returns an error code on a connection problem. */
static gpg_error_t
warn_version_mismatch (assuan_context_t ctx, const char *servername, int mode)
{
gpg_error_t err;
char *serverversion;
const char *myversion = strusage (13);
err = get_assuan_server_version (ctx, mode, &serverversion);
if (err)
log_log (gpg_err_code (err) == GPG_ERR_NOT_SUPPORTED?
GPGRT_LOGLVL_INFO : GPGRT_LOGLVL_ERROR,
_("error getting version from '%s': %s\n"),
servername, gpg_strerror (err));
else if (compare_version_strings (serverversion, myversion) < 0)
{
char *warn;
warn = xtryasprintf (_("server '%s' is older than us (%s < %s)"),
servername, serverversion, myversion);
if (!warn)
err = gpg_error_from_syserror ();
else
{
log_info (_("WARNING: %s\n"), warn);
if (!opt.quiet)
{
log_info (_("Note: Outdated servers may lack important"
" security fixes.\n"));
log_info (_("Note: Use the command \"%s\" to restart them.\n"),
"gpgconf --kill all");
}
write_status_strings (STATUS_WARNING, "server_version_mismatch 0",
" ", warn, NULL);
xfree (warn);
}
}
xfree (serverversion);
return err;
}
#define FLAG_FOR_CARD_SUPPRESS_ERRORS 2
/* 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 (ctrl_t ctrl, int flag_for_card)
{
int rc;
(void)ctrl; /* Not yet used. */
/* Fixme: We need a context for each thread or serialize the access
to the agent. */
if (agent_ctx)
rc = 0;
else
{
rc = start_new_gpg_agent (&agent_ctx,
GPG_ERR_SOURCE_DEFAULT,
opt.agent_program,
opt.lc_ctype, opt.lc_messages,
opt.session_env,
opt.autostart, opt.verbose, DBG_IPC,
NULL, NULL);
if (!opt.autostart && gpg_err_code (rc) == GPG_ERR_NO_AGENT)
{
static int shown;
if (!shown)
{
shown = 1;
log_info (_("no gpg-agent running in this session\n"));
}
}
else if (!rc
&& !(rc = warn_version_mismatch (agent_ctx, GPG_AGENT_NAME, 0)))
{
/* Tell the agent that we support Pinentry notifications.
No error checking so that it will work also with older
agents. */
assuan_transact (agent_ctx, "OPTION allow-pinentry-notify",
NULL, NULL, NULL, NULL, NULL, NULL);
/* Tell the agent about what version we are aware. This is
here used to indirectly enable GPG_ERR_FULLY_CANCELED. */
assuan_transact (agent_ctx, "OPTION agent-awareness=2.1.0",
NULL, NULL, NULL, NULL, NULL, NULL);
/* Pass on the pinentry mode. */
if (opt.pinentry_mode)
{
char *tmp = xasprintf ("OPTION pinentry-mode=%s",
str_pinentry_mode (opt.pinentry_mode));
rc = assuan_transact (agent_ctx, tmp,
NULL, NULL, NULL, NULL, NULL, NULL);
xfree (tmp);
if (rc)
{
log_error ("setting pinentry mode '%s' failed: %s\n",
str_pinentry_mode (opt.pinentry_mode),
gpg_strerror (rc));
write_status_error ("set_pinentry_mode", rc);
}
}
/* Pass on the request origin. */
if (opt.request_origin)
{
char *tmp = xasprintf ("OPTION pretend-request-origin=%s",
str_request_origin (opt.request_origin));
rc = assuan_transact (agent_ctx, tmp,
NULL, NULL, NULL, NULL, NULL, NULL);
xfree (tmp);
if (rc)
{
log_error ("setting request origin '%s' failed: %s\n",
str_request_origin (opt.request_origin),
gpg_strerror (rc));
write_status_error ("set_request_origin", rc);
}
}
/* In DE_VS mode under Windows we require that the JENT RNG
* is active. */
#ifdef HAVE_W32_SYSTEM
if (!rc && opt.compliance == CO_DE_VS)
{
if (assuan_transact (agent_ctx, "GETINFO jent_active",
NULL, NULL, NULL, NULL, NULL, NULL))
{
rc = gpg_error (GPG_ERR_FORBIDDEN);
log_error (_("%s is not compliant with %s mode\n"),
GPG_AGENT_NAME,
gnupg_compliance_option_string (opt.compliance));
write_status_error ("random-compliance", rc);
}
}
#endif /*HAVE_W32_SYSTEM*/
}
}
if (!rc && flag_for_card && !did_early_card_test)
{
/* Request the serial number of the card for an early test. */
struct agent_card_info_s info;
memset (&info, 0, sizeof info);
if (!(flag_for_card & FLAG_FOR_CARD_SUPPRESS_ERRORS))
rc = warn_version_mismatch (agent_ctx, SCDAEMON_NAME, 2);
if (!rc)
- rc = assuan_transact (agent_ctx, "SCD SERIALNO openpgp",
+ rc = assuan_transact (agent_ctx, "SCD SERIALNO",
NULL, NULL, NULL, NULL,
learn_status_cb, &info);
if (rc && !(flag_for_card & FLAG_FOR_CARD_SUPPRESS_ERRORS))
{
switch (gpg_err_code (rc))
{
case GPG_ERR_NOT_SUPPORTED:
case GPG_ERR_NO_SCDAEMON:
write_status_text (STATUS_CARDCTRL, "6");
break;
case GPG_ERR_OBJ_TERM_STATE:
write_status_text (STATUS_CARDCTRL, "7");
break;
default:
write_status_text (STATUS_CARDCTRL, "4");
- log_info ("selecting openpgp failed: %s\n", gpg_strerror (rc));
+ log_info ("selecting card failed: %s\n", gpg_strerror (rc));
break;
}
}
if (!rc && is_status_enabled () && info.serialno)
{
char *buf;
buf = xasprintf ("3 %s", info.serialno);
write_status_text (STATUS_CARDCTRL, buf);
xfree (buf);
}
agent_release_card_info (&info);
if (!rc)
did_early_card_test = 1;
}
return rc;
}
/* Return a new malloced string by unescaping the string S. Escaping
is percent escaping and '+'/space mapping. A binary nul will
silently be replaced by a 0xFF. Function returns NULL to indicate
an out of memory status. */
static char *
unescape_status_string (const unsigned char *s)
{
return percent_plus_unescape (s, 0xff);
}
/* Take a 20 or 32 byte hexencoded string and put it into the provided
* FPRLEN byte long buffer FPR in binary format. Returns the actual
* used length of the FPR buffer or 0 on error. */
static unsigned int
unhexify_fpr (const char *hexstr, unsigned char *fpr, unsigned int fprlen)
{
const char *s;
int n;
for (s=hexstr, n=0; hexdigitp (s); s++, n++)
;
if ((*s && *s != ' ') || !(n == 40 || n == 64))
return 0; /* no fingerprint (invalid or wrong length). */
for (s=hexstr, n=0; *s && n < fprlen; s += 2, n++)
fpr[n] = xtoi_2 (s);
return (n == 20 || n == 32)? n : 0;
}
/* Take the serial number from LINE and return it verbatim in a newly
allocated string. We make sure that only hex characters are
returned. */
static char *
store_serialno (const char *line)
{
const char *s;
char *p;
for (s=line; hexdigitp (s); s++)
;
p = xtrymalloc (s + 1 - line);
if (p)
{
memcpy (p, line, s-line);
p[s-line] = 0;
}
return p;
}
/* This is a dummy data line callback. */
static gpg_error_t
dummy_data_cb (void *opaque, const void *buffer, size_t length)
{
(void)opaque;
(void)buffer;
(void)length;
return 0;
}
/* A simple callback used to return the serialnumber of a card. */
static gpg_error_t
get_serialno_cb (void *opaque, const char *line)
{
char **serialno = opaque;
const char *keyword = line;
const char *s;
int keywordlen, n;
for (keywordlen=0; *line && !spacep (line); line++, keywordlen++)
;
while (spacep (line))
line++;
if (keywordlen == 8 && !memcmp (keyword, "SERIALNO", keywordlen))
{
if (*serialno)
return gpg_error (GPG_ERR_CONFLICT); /* Unexpected status line. */
for (n=0,s=line; hexdigitp (s); s++, n++)
;
if (!n || (n&1)|| !(spacep (s) || !*s) )
return gpg_error (GPG_ERR_ASS_PARAMETER);
*serialno = xtrymalloc (n+1);
if (!*serialno)
return out_of_core ();
memcpy (*serialno, line, n);
(*serialno)[n] = 0;
}
return 0;
}
/* Release the card info structure INFO. */
void
agent_release_card_info (struct agent_card_info_s *info)
{
int i;
if (!info)
return;
xfree (info->reader); info->reader = NULL;
xfree (info->serialno); info->serialno = NULL;
xfree (info->apptype); info->apptype = NULL;
xfree (info->disp_name); info->disp_name = NULL;
xfree (info->disp_lang); info->disp_lang = NULL;
xfree (info->pubkey_url); info->pubkey_url = NULL;
xfree (info->login_data); info->login_data = NULL;
info->cafpr1len = info->cafpr2len = info->cafpr3len = 0;
info->fpr1len = info->fpr2len = info->fpr3len = 0;
for (i=0; i < DIM(info->private_do); i++)
{
xfree (info->private_do[i]);
info->private_do[i] = NULL;
}
}
static gpg_error_t
learn_status_cb (void *opaque, const char *line)
{
struct agent_card_info_s *parm = opaque;
const char *keyword = line;
int keywordlen;
int i;
for (keywordlen=0; *line && !spacep (line); line++, keywordlen++)
;
while (spacep (line))
line++;
if (keywordlen == 6 && !memcmp (keyword, "READER", keywordlen))
{
xfree (parm->reader);
parm->reader = unescape_status_string (line);
}
else if (keywordlen == 8 && !memcmp (keyword, "SERIALNO", keywordlen))
{
xfree (parm->serialno);
parm->serialno = store_serialno (line);
parm->is_v2 = (strlen (parm->serialno) >= 16
&& xtoi_2 (parm->serialno+12) >= 2 );
}
else if (keywordlen == 7 && !memcmp (keyword, "APPTYPE", keywordlen))
{
xfree (parm->apptype);
parm->apptype = unescape_status_string (line);
}
else if (keywordlen == 9 && !memcmp (keyword, "DISP-NAME", keywordlen))
{
xfree (parm->disp_name);
parm->disp_name = unescape_status_string (line);
}
else if (keywordlen == 9 && !memcmp (keyword, "DISP-LANG", keywordlen))
{
xfree (parm->disp_lang);
parm->disp_lang = unescape_status_string (line);
}
else if (keywordlen == 8 && !memcmp (keyword, "DISP-SEX", keywordlen))
{
parm->disp_sex = *line == '1'? 1 : *line == '2' ? 2: 0;
}
else if (keywordlen == 10 && !memcmp (keyword, "PUBKEY-URL", keywordlen))
{
xfree (parm->pubkey_url);
parm->pubkey_url = unescape_status_string (line);
}
else if (keywordlen == 10 && !memcmp (keyword, "LOGIN-DATA", keywordlen))
{
xfree (parm->login_data);
parm->login_data = unescape_status_string (line);
}
else if (keywordlen == 11 && !memcmp (keyword, "SIG-COUNTER", keywordlen))
{
parm->sig_counter = strtoul (line, NULL, 0);
}
else if (keywordlen == 10 && !memcmp (keyword, "CHV-STATUS", keywordlen))
{
char *p, *buf;
buf = p = unescape_status_string (line);
if (buf)
{
while (spacep (p))
p++;
parm->chv1_cached = atoi (p);
while (*p && !spacep (p))
p++;
while (spacep (p))
p++;
for (i=0; *p && i < 3; i++)
{
parm->chvmaxlen[i] = atoi (p);
while (*p && !spacep (p))
p++;
while (spacep (p))
p++;
}
for (i=0; *p && i < 3; i++)
{
parm->chvretry[i] = atoi (p);
while (*p && !spacep (p))
p++;
while (spacep (p))
p++;
}
xfree (buf);
}
}
else if (keywordlen == 6 && !memcmp (keyword, "EXTCAP", keywordlen))
{
char *p, *p2, *buf;
int abool;
buf = p = unescape_status_string (line);
if (buf)
{
for (p = strtok (buf, " "); p; p = strtok (NULL, " "))
{
p2 = strchr (p, '=');
if (p2)
{
*p2++ = 0;
abool = (*p2 == '1');
if (!strcmp (p, "ki"))
parm->extcap.ki = abool;
else if (!strcmp (p, "aac"))
parm->extcap.aac = abool;
else if (!strcmp (p, "bt"))
parm->extcap.bt = abool;
else if (!strcmp (p, "kdf"))
parm->extcap.kdf = abool;
else if (!strcmp (p, "si"))
parm->status_indicator = strtoul (p2, NULL, 10);
}
}
xfree (buf);
}
}
else if (keywordlen == 7 && !memcmp (keyword, "KEY-FPR", keywordlen))
{
int no = atoi (line);
while (*line && !spacep (line))
line++;
while (spacep (line))
line++;
if (no == 1)
parm->fpr1len = unhexify_fpr (line, parm->fpr1, sizeof parm->fpr1);
else if (no == 2)
parm->fpr2len = unhexify_fpr (line, parm->fpr2, sizeof parm->fpr2);
else if (no == 3)
parm->fpr3len = unhexify_fpr (line, parm->fpr3, sizeof parm->fpr3);
}
else if (keywordlen == 8 && !memcmp (keyword, "KEY-TIME", keywordlen))
{
int no = atoi (line);
while (* line && !spacep (line))
line++;
while (spacep (line))
line++;
if (no == 1)
parm->fpr1time = strtoul (line, NULL, 10);
else if (no == 2)
parm->fpr2time = strtoul (line, NULL, 10);
else if (no == 3)
parm->fpr3time = strtoul (line, NULL, 10);
}
else if (keywordlen == 11 && !memcmp (keyword, "KEYPAIRINFO", keywordlen))
{
const char *hexgrp = line;
int no;
while (*line && !spacep (line))
line++;
while (spacep (line))
line++;
if (strncmp (line, "OPENPGP.", 8))
;
else if ((no = atoi (line+8)) == 1)
unhexify_fpr (hexgrp, parm->grp1, sizeof parm->grp1);
else if (no == 2)
unhexify_fpr (hexgrp, parm->grp2, sizeof parm->grp2);
else if (no == 3)
unhexify_fpr (hexgrp, parm->grp3, sizeof parm->grp3);
}
else if (keywordlen == 6 && !memcmp (keyword, "CA-FPR", keywordlen))
{
int no = atoi (line);
while (*line && !spacep (line))
line++;
while (spacep (line))
line++;
if (no == 1)
parm->cafpr1len = unhexify_fpr (line, parm->cafpr1,sizeof parm->cafpr1);
else if (no == 2)
parm->cafpr2len = unhexify_fpr (line, parm->cafpr2,sizeof parm->cafpr2);
else if (no == 3)
parm->cafpr3len = unhexify_fpr (line, parm->cafpr3,sizeof parm->cafpr3);
}
else if (keywordlen == 8 && !memcmp (keyword, "KEY-ATTR", keywordlen))
{
int keyno = 0;
int algo = PUBKEY_ALGO_RSA;
int n = 0;
sscanf (line, "%d %d %n", &keyno, &algo, &n);
keyno--;
if (keyno < 0 || keyno >= DIM (parm->key_attr))
return 0;
parm->key_attr[keyno].algo = algo;
if (algo == PUBKEY_ALGO_RSA)
parm->key_attr[keyno].nbits = strtoul (line+n+3, NULL, 10);
else if (algo == PUBKEY_ALGO_ECDH || algo == PUBKEY_ALGO_ECDSA
|| algo == PUBKEY_ALGO_EDDSA)
parm->key_attr[keyno].curve = openpgp_is_curve_supported (line + n,
NULL, NULL);
}
else if (keywordlen == 12 && !memcmp (keyword, "PRIVATE-DO-", 11)
&& strchr("1234", keyword[11]))
{
int no = keyword[11] - '1';
log_assert (no >= 0 && no <= 3);
xfree (parm->private_do[no]);
parm->private_do[no] = unescape_status_string (line);
}
else if (keywordlen == 3 && !memcmp (keyword, "KDF", 3))
{
parm->kdf_do_enabled = 1;
}
else if (keywordlen == 5 && !memcmp (keyword, "UIF-", 4)
&& strchr("123", keyword[4]))
{
unsigned char *data;
int no = keyword[4] - '1';
log_assert (no >= 0 && no <= 2);
data = unescape_status_string (line);
parm->uif[no] = (data[0] != 0xff);
xfree (data);
}
return 0;
}
-/* Call the scdaemon to learn about a smartcard */
+
+/* Call the scdaemon to learn about a smartcard. Note that in
+ * contradiction to the function's name, gpg-agent's LEARN command is
+ * used and not the low-level "SCD LEARN".
+ * Used by:
+ * card-util.c
+ * keyedit_menu
+ * card_store_key_with_backup (Woth force to remove secret key data)
+ */
int
agent_scd_learn (struct agent_card_info_s *info, int force)
{
int rc;
struct default_inq_parm_s parm;
struct agent_card_info_s dummyinfo;
if (!info)
info = &dummyinfo;
memset (info, 0, sizeof *info);
memset (&parm, 0, sizeof parm);
rc = start_agent (NULL, 1);
if (rc)
return rc;
parm.ctx = agent_ctx;
rc = assuan_transact (agent_ctx,
force ? "LEARN --sendinfo --force" : "LEARN --sendinfo",
dummy_data_cb, NULL, default_inq_cb, &parm,
learn_status_cb, info);
/* Also try to get the key attributes. */
if (!rc)
agent_scd_getattr ("KEY-ATTR", info);
if (info == &dummyinfo)
agent_release_card_info (info);
return rc;
}
+
+/* Callback for the agent_scd_keypairinfo function. */
+static gpg_error_t
+scd_keypairinfo_status_cb (void *opaque, const char *line)
+{
+ strlist_t *listaddr = opaque;
+ const char *keyword = line;
+ int keywordlen;
+ strlist_t sl;
+ char *p;
+
+ for (keywordlen=0; *line && !spacep (line); line++, keywordlen++)
+ ;
+ while (spacep (line))
+ line++;
+
+ if (keywordlen == 11 && !memcmp (keyword, "KEYPAIRINFO", keywordlen))
+ {
+ sl = append_to_strlist (listaddr, line);
+ p = sl->d;
+ /* Make sure that we only have two tokens so that future
+ * extensions of the format won't change the format expected by
+ * the caller. */
+ while (*p && !spacep (p))
+ p++;
+ if (*p)
+ {
+ while (spacep (p))
+ p++;
+ while (*p && !spacep (p))
+ p++;
+ if (*p)
+ {
+ *p++ = 0;
+ while (spacep (p))
+ p++;
+ while (*p && !spacep (p))
+ {
+ switch (*p++)
+ {
+ case 'c': sl->flags |= GCRY_PK_USAGE_CERT; break;
+ case 's': sl->flags |= GCRY_PK_USAGE_SIGN; break;
+ case 'e': sl->flags |= GCRY_PK_USAGE_ENCR; break;
+ case 'a': sl->flags |= GCRY_PK_USAGE_AUTH; break;
+ }
+ }
+ }
+ }
+ }
+
+ return 0;
+}
+
+
+/* Read the keypairinfo lines of the current card directly from
+ * scdaemon. The list is returned as a string made up of the keygrip,
+ * a space and the keyref. The flags of the string carry the usage
+ * bits. If KEYREF is not NULL, only a single string is returned
+ * which matches the given keyref. */
+gpg_error_t
+agent_scd_keypairinfo (ctrl_t ctrl, const char *keyref, strlist_t *r_list)
+{
+ gpg_error_t err;
+ strlist_t list = NULL;
+ struct default_inq_parm_s inq_parm;
+ char line[ASSUAN_LINELENGTH];
+
+ *r_list = NULL;
+ err= start_agent (ctrl, 1);
+ if (err)
+ return err;
+ memset (&inq_parm, 0, sizeof inq_parm);
+ inq_parm.ctx = agent_ctx;
+
+ if (keyref)
+ snprintf (line, DIM(line), "SCD READKEY --info-only %s", keyref);
+ else
+ snprintf (line, DIM(line), "SCD LEARN --keypairinfo");
+
+ err = assuan_transact (agent_ctx, line,
+ NULL, NULL,
+ default_inq_cb, &inq_parm,
+ scd_keypairinfo_status_cb, &list);
+ if (!err && !list)
+ err = gpg_error (GPG_ERR_NO_DATA);
+ if (err)
+ {
+ free_strlist (list);
+ return err;
+ }
+ *r_list = list;
+ return 0;
+}
+
+
+
/* Send an APDU to the current card. On success the status word is
- stored at R_SW. With HEXAPDU being NULL only a RESET command is
- send to scd. With HEXAPDU being the string "undefined" the command
- "SERIALNO undefined" is send to scd. */
+ * stored at R_SW. With HEXAPDU being NULL only a RESET command is
+ * send to scd. With HEXAPDU being the string "undefined" the command
+ * "SERIALNO undefined" is send to scd.
+ * Used by:
+ * card-util.c
+ */
gpg_error_t
agent_scd_apdu (const char *hexapdu, unsigned int *r_sw)
{
gpg_error_t err;
/* Start the agent but not with the card flag so that we do not
autoselect the openpgp application. */
err = start_agent (NULL, 0);
if (err)
return err;
if (!hexapdu)
{
err = assuan_transact (agent_ctx, "SCD RESET",
NULL, NULL, NULL, NULL, NULL, NULL);
}
else if (!strcmp (hexapdu, "undefined"))
{
err = assuan_transact (agent_ctx, "SCD SERIALNO undefined",
NULL, NULL, NULL, NULL, NULL, NULL);
}
else
{
char line[ASSUAN_LINELENGTH];
membuf_t mb;
unsigned char *data;
size_t datalen;
init_membuf (&mb, 256);
snprintf (line, DIM(line), "SCD APDU %s", hexapdu);
err = assuan_transact (agent_ctx, line,
put_membuf_cb, &mb, NULL, NULL, NULL, NULL);
if (!err)
{
data = get_membuf (&mb, &datalen);
if (!data)
err = gpg_error_from_syserror ();
else if (datalen < 2) /* Ooops */
err = gpg_error (GPG_ERR_CARD);
else
{
*r_sw = buf16_to_uint (data+datalen-2);
}
xfree (data);
}
}
return err;
}
+/* Used by:
+ * card_store_subkey
+ * card_store_key_with_backup
+ */
int
agent_keytocard (const char *hexgrip, int keyno, int force,
const char *serialno, const char *timestamp)
{
int rc;
char line[ASSUAN_LINELENGTH];
struct default_inq_parm_s parm;
memset (&parm, 0, sizeof parm);
snprintf (line, DIM(line), "KEYTOCARD %s%s %s OPENPGP.%d %s",
force?"--force ": "", hexgrip, serialno, keyno, timestamp);
rc = start_agent (NULL, 1);
if (rc)
return rc;
parm.ctx = agent_ctx;
rc = assuan_transact (agent_ctx, line, NULL, NULL, default_inq_cb, &parm,
NULL, NULL);
if (rc)
return rc;
return rc;
}
+
+/* Object used with the agent_scd_getattr_one. */
+struct getattr_one_parm_s {
+ const char *keyword; /* Keyword to look for. */
+ char *data; /* Malloced and unescaped data. */
+ gpg_error_t err; /* Error code or 0 on success. */
+};
+
+
+/* Callback for agent_scd_getattr_one. */
+static gpg_error_t
+getattr_one_status_cb (void *opaque, const char *line)
+{
+ struct getattr_one_parm_s *parm = opaque;
+ const char *s;
+
+ if (parm->data)
+ return 0; /* We want only the first occurrence. */
+
+ if ((s=has_leading_keyword (line, parm->keyword)))
+ {
+ parm->data = percent_plus_unescape (s, 0xff);
+ if (!parm->data)
+ parm->err = gpg_error_from_syserror ();
+ }
+
+ return 0;
+}
+
+
+/* Simplified version of agent_scd_getattr. This function returns
+ * only the first occurance of the attribute NAME and stores it at
+ * R_VALUE. A nul in the result is silennly replaced by 0xff. On
+ * error NULL is stored at R_VALUE. */
+gpg_error_t
+agent_scd_getattr_one (const char *name, char **r_value)
+{
+ gpg_error_t err;
+ char line[ASSUAN_LINELENGTH];
+ struct default_inq_parm_s inqparm;
+ struct getattr_one_parm_s parm;
+
+ *r_value = NULL;
+
+ if (!*name)
+ return gpg_error (GPG_ERR_INV_VALUE);
+
+ memset (&inqparm, 0, sizeof inqparm);
+ inqparm.ctx = agent_ctx;
+
+ memset (&parm, 0, sizeof parm);
+ parm.keyword = name;
+
+ /* We assume that NAME does not need escaping. */
+ if (12 + strlen (name) > DIM(line)-1)
+ return gpg_error (GPG_ERR_TOO_LARGE);
+ stpcpy (stpcpy (line, "SCD GETATTR "), name);
+
+ err = start_agent (NULL, 1);
+ if (err)
+ return err;
+
+ err = assuan_transact (agent_ctx, line,
+ NULL, NULL,
+ default_inq_cb, &inqparm,
+ getattr_one_status_cb, &parm);
+ if (!err && parm.err)
+ err = parm.err;
+ else if (!err && !parm.data)
+ err = gpg_error (GPG_ERR_NO_DATA);
+
+ if (!err)
+ *r_value = parm.data;
+ else
+ xfree (parm.data);
+
+ return err;
+}
+
+
/* Call the agent to retrieve a data object. This function returns
- the data in the same structure as used by the learn command. It is
- allowed to update such a structure using this command. */
+ * the data in the same structure as used by the learn command. It is
+ * allowed to update such a structure using this command.
+ *
+ * Used by:
+ * build_sk_list
+ * enum_secret_keys
+ * get_signature_count
+ * card-util.c
+ * generate_keypair (KEY-ATTR)
+ * card_store_key_with_backup (SERIALNO)
+ * generate_card_subkeypair (KEY-ATTR)
+ */
int
agent_scd_getattr (const char *name, struct agent_card_info_s *info)
{
int rc;
char line[ASSUAN_LINELENGTH];
struct default_inq_parm_s parm;
memset (&parm, 0, sizeof parm);
if (!*name)
return gpg_error (GPG_ERR_INV_VALUE);
/* We assume that NAME does not need escaping. */
if (12 + strlen (name) > DIM(line)-1)
return gpg_error (GPG_ERR_TOO_LARGE);
stpcpy (stpcpy (line, "SCD GETATTR "), name);
rc = start_agent (NULL, 1);
if (rc)
return rc;
parm.ctx = agent_ctx;
rc = assuan_transact (agent_ctx, line, NULL, NULL, default_inq_cb, &parm,
learn_status_cb, info);
return rc;
}
+
-/* Send an setattr command to the SCdaemon. SERIALNO is not actually
- used here but required by gpg 1.4's implementation of this code in
- cardglue.c. */
-int
-agent_scd_setattr (const char *name,
- const unsigned char *value, size_t valuelen,
- const char *serialno)
+/* Send an setattr command to the SCdaemon.
+ * Used by:
+ * card-util.c
+ */
+gpg_error_t
+agent_scd_setattr (const char *name, const void *value_arg, size_t valuelen)
{
- int rc;
+ gpg_error_t err;
+ const unsigned char *value = value_arg;
char line[ASSUAN_LINELENGTH];
char *p;
struct default_inq_parm_s parm;
memset (&parm, 0, sizeof parm);
- (void)serialno;
-
if (!*name || !valuelen)
return gpg_error (GPG_ERR_INV_VALUE);
/* We assume that NAME does not need escaping. */
if (12 + strlen (name) > DIM(line)-1)
return gpg_error (GPG_ERR_TOO_LARGE);
p = stpcpy (stpcpy (line, "SCD SETATTR "), name);
*p++ = ' ';
for (; valuelen; value++, valuelen--)
{
if (p >= line + DIM(line)-5 )
return gpg_error (GPG_ERR_TOO_LARGE);
if (*value < ' ' || *value == '+' || *value == '%')
{
sprintf (p, "%%%02X", *value);
p += 3;
}
else if (*value == ' ')
*p++ = '+';
else
*p++ = *value;
}
*p = 0;
- rc = start_agent (NULL, 1);
- if (!rc)
+ err = start_agent (NULL, 1);
+ if (!err)
{
parm.ctx = agent_ctx;
- rc = assuan_transact (agent_ctx, line, NULL, NULL,
+ err = assuan_transact (agent_ctx, line, NULL, NULL,
default_inq_cb, &parm, NULL, NULL);
}
- status_sc_op_failure (rc);
- return rc;
+ status_sc_op_failure (err);
+ return err;
}
/* Handle a CERTDATA inquiry. Note, we only send the data,
assuan_transact takes care of flushing and writing the END
command. */
static gpg_error_t
inq_writecert_parms (void *opaque, const char *line)
{
int rc;
struct writecert_parm_s *parm = opaque;
if (has_leading_keyword (line, "CERTDATA"))
{
rc = assuan_send_data (parm->dflt->ctx,
parm->certdata, parm->certdatalen);
}
else
rc = default_inq_cb (parm->dflt, line);
return rc;
}
-/* Send a WRITECERT command to the SCdaemon. */
+/* Send a WRITECERT command to the SCdaemon.
+ * Used by:
+ * card-util.c
+ */
int
agent_scd_writecert (const char *certidstr,
const unsigned char *certdata, size_t certdatalen)
{
int rc;
char line[ASSUAN_LINELENGTH];
struct writecert_parm_s parms;
struct default_inq_parm_s dfltparm;
memset (&dfltparm, 0, sizeof dfltparm);
rc = start_agent (NULL, 1);
if (rc)
return rc;
memset (&parms, 0, sizeof parms);
snprintf (line, DIM(line), "SCD WRITECERT %s", certidstr);
dfltparm.ctx = agent_ctx;
parms.dflt = &dfltparm;
parms.certdata = certdata;
parms.certdatalen = certdatalen;
rc = assuan_transact (agent_ctx, line, NULL, NULL,
inq_writecert_parms, &parms, NULL, NULL);
return rc;
}
-
-/* Handle a KEYDATA inquiry. Note, we only send the data,
- assuan_transact takes care of flushing and writing the end */
-static gpg_error_t
-inq_writekey_parms (void *opaque, const char *line)
-{
- int rc;
- struct writekey_parm_s *parm = opaque;
-
- if (has_leading_keyword (line, "KEYDATA"))
- {
- rc = assuan_send_data (parm->dflt->ctx, parm->keydata, parm->keydatalen);
- }
- else
- rc = default_inq_cb (parm->dflt, line);
-
- return rc;
-}
-
-
-/* Send a WRITEKEY command to the SCdaemon. */
-int
-agent_scd_writekey (int keyno, const char *serialno,
- const unsigned char *keydata, size_t keydatalen)
-{
- int rc;
- char line[ASSUAN_LINELENGTH];
- struct writekey_parm_s parms;
- struct default_inq_parm_s dfltparm;
-
- memset (&dfltparm, 0, sizeof dfltparm);
-
- (void)serialno;
-
- rc = start_agent (NULL, 1);
- if (rc)
- return rc;
-
- memset (&parms, 0, sizeof parms);
-
- snprintf (line, DIM(line), "SCD WRITEKEY --force OPENPGP.%d", keyno);
- dfltparm.ctx = agent_ctx;
- parms.dflt = &dfltparm;
- parms.keydata = keydata;
- parms.keydatalen = keydatalen;
-
- rc = assuan_transact (agent_ctx, line, NULL, NULL,
- inq_writekey_parms, &parms, NULL, NULL);
-
- status_sc_op_failure (rc);
- return rc;
-}
-
-
/* Status callback for the SCD GENKEY command. */
static gpg_error_t
scd_genkey_cb (void *opaque, const char *line)
{
u32 *createtime = opaque;
const char *keyword = line;
int keywordlen;
for (keywordlen=0; *line && !spacep (line); line++, keywordlen++)
;
while (spacep (line))
line++;
if (keywordlen == 14 && !memcmp (keyword,"KEY-CREATED-AT", keywordlen))
{
*createtime = (u32)strtoul (line, NULL, 10);
}
else if (keywordlen == 8 && !memcmp (keyword, "PROGRESS", keywordlen))
{
write_status_text (STATUS_PROGRESS, line);
}
return 0;
}
/* Send a GENKEY command to the SCdaemon. If *CREATETIME is not 0,
- the value will be passed to SCDAEMON with --timestamp option so that
- the key is created with this. Otherwise, timestamp was generated by
- SCDEAMON. On success, creation time is stored back to
- CREATETIME. */
+ * the value will be passed to SCDAEMON with --timestamp option so that
+ * the key is created with this. Otherwise, timestamp was generated by
+ * SCDEAMON. On success, creation time is stored back to
+ * CREATETIME.
+ * Used by:
+ * gen_card_key
+ */
int
agent_scd_genkey (int keyno, int force, u32 *createtime)
{
int rc;
char line[ASSUAN_LINELENGTH];
gnupg_isotime_t tbuf;
struct default_inq_parm_s dfltparm;
memset (&dfltparm, 0, sizeof dfltparm);
rc = start_agent (NULL, 1);
if (rc)
return rc;
if (*createtime)
epoch2isotime (tbuf, *createtime);
else
*tbuf = 0;
snprintf (line, DIM(line), "SCD GENKEY %s%s %s %d",
*tbuf? "--timestamp=":"", tbuf,
force? "--force":"",
keyno);
dfltparm.ctx = agent_ctx;
rc = assuan_transact (agent_ctx, line,
NULL, NULL, default_inq_cb, &dfltparm,
scd_genkey_cb, createtime);
status_sc_op_failure (rc);
return rc;
}
+
+
/* Return the serial number of the card or an appropriate error. The
- serial number is returned as a hexstring. */
+ * serial number is returned as a hexstring. With DEMAND the active
+ * card is switched to the card with that serialno.
+ * Used by:
+ * card-util.c
+ * build_sk_list
+ * enum_secret_keys
+ */
int
agent_scd_serialno (char **r_serialno, const char *demand)
{
int err;
char *serialno = NULL;
char line[ASSUAN_LINELENGTH];
- err = start_agent (NULL, 1 | FLAG_FOR_CARD_SUPPRESS_ERRORS);
+ err = start_agent (NULL, (1 | FLAG_FOR_CARD_SUPPRESS_ERRORS));
if (err)
return err;
if (!demand)
strcpy (line, "SCD SERIALNO");
else
snprintf (line, DIM(line), "SCD SERIALNO --demand=%s", demand);
err = assuan_transact (agent_ctx, line,
NULL, NULL, NULL, NULL,
get_serialno_cb, &serialno);
if (err)
{
xfree (serialno);
return err;
}
*r_serialno = serialno;
return 0;
}
+
+
-/* Send a READCERT command to the SCdaemon. */
+/* Send a READCERT command to the SCdaemon.
+ * Used by:
+ * card-util.c
+ */
int
agent_scd_readcert (const char *certidstr,
void **r_buf, size_t *r_buflen)
{
int rc;
char line[ASSUAN_LINELENGTH];
membuf_t data;
size_t len;
struct default_inq_parm_s dfltparm;
memset (&dfltparm, 0, sizeof dfltparm);
*r_buf = NULL;
rc = start_agent (NULL, 1);
if (rc)
return rc;
dfltparm.ctx = agent_ctx;
init_membuf (&data, 2048);
snprintf (line, DIM(line), "SCD READCERT %s", certidstr);
rc = assuan_transact (agent_ctx, line,
put_membuf_cb, &data,
default_inq_cb, &dfltparm,
NULL, NULL);
if (rc)
{
xfree (get_membuf (&data, &len));
return rc;
}
*r_buf = get_membuf (&data, r_buflen);
if (!*r_buf)
return gpg_error (GPG_ERR_ENOMEM);
return 0;
}
+
+
+/* This is a variant of agent_readkey which sends a READKEY command
+ * directly Scdaemon. On success a new s-expression is stored at
+ * R_RESULT. */
+gpg_error_t
+agent_scd_readkey (const char *keyrefstr, gcry_sexp_t *r_result)
+{
+ gpg_error_t err;
+ char line[ASSUAN_LINELENGTH];
+ membuf_t data;
+ unsigned char *buf;
+ size_t len, buflen;
+ struct default_inq_parm_s dfltparm;
+
+ memset (&dfltparm, 0, sizeof dfltparm);
+ dfltparm.ctx = agent_ctx;
+
+ *r_result = NULL;
+ err = start_agent (NULL, 1);
+ if (err)
+ return err;
+
+ init_membuf (&data, 1024);
+ snprintf (line, DIM(line), "SCD READKEY %s", keyrefstr);
+ err = assuan_transact (agent_ctx, line,
+ put_membuf_cb, &data,
+ default_inq_cb, &dfltparm,
+ NULL, NULL);
+ if (err)
+ {
+ xfree (get_membuf (&data, &len));
+ return err;
+ }
+ buf = get_membuf (&data, &buflen);
+ if (!buf)
+ return gpg_error_from_syserror ();
+
+ err = gcry_sexp_new (r_result, buf, buflen, 0);
+ xfree (buf);
+
+ return err;
+}
+
+
struct card_cardlist_parm_s {
int error;
strlist_t list;
};
/* Callback function for agent_card_cardlist. */
static gpg_error_t
card_cardlist_cb (void *opaque, const char *line)
{
struct card_cardlist_parm_s *parm = opaque;
const char *keyword = line;
int keywordlen;
for (keywordlen=0; *line && !spacep (line); line++, keywordlen++)
;
while (spacep (line))
line++;
if (keywordlen == 8 && !memcmp (keyword, "SERIALNO", keywordlen))
{
const char *s;
int n;
for (n=0,s=line; hexdigitp (s); s++, n++)
;
if (!n || (n&1) || *s)
parm->error = gpg_error (GPG_ERR_ASS_PARAMETER);
else
add_to_strlist (&parm->list, line);
}
return 0;
}
-/* Return cardlist. */
+
+/* Return a list of currently available cards.
+ * Used by:
+ * card-util.c
+ * skclist.c
+ */
int
agent_scd_cardlist (strlist_t *result)
{
int err;
char line[ASSUAN_LINELENGTH];
struct card_cardlist_parm_s parm;
memset (&parm, 0, sizeof parm);
*result = NULL;
err = start_agent (NULL, 1 | FLAG_FOR_CARD_SUPPRESS_ERRORS);
if (err)
return err;
strcpy (line, "SCD GETINFO card_list");
err = assuan_transact (agent_ctx, line,
NULL, NULL, NULL, NULL,
card_cardlist_cb, &parm);
if (!err && parm.error)
err = parm.error;
if (!err)
*result = parm.list;
else
free_strlist (parm.list);
return 0;
}
+
+
/* Change the PIN of an OpenPGP card or reset the retry counter.
- CHVNO 1: Change the PIN
- 2: For v1 cards: Same as 1.
- For v2 cards: Reset the PIN using the Reset Code.
- 3: Change the admin PIN
- 101: Set a new PIN and reset the retry counter
- 102: For v1 cars: Same as 101.
- For v2 cards: Set a new Reset Code.
- SERIALNO is not used.
+ * CHVNO 1: Change the PIN
+ * 2: For v1 cards: Same as 1.
+ * For v2 cards: Reset the PIN using the Reset Code.
+ * 3: Change the admin PIN
+ * 101: Set a new PIN and reset the retry counter
+ * 102: For v1 cars: Same as 101.
+ * For v2 cards: Set a new Reset Code.
+ * SERIALNO is not used.
+ * Used by:
+ * card-util.c
*/
int
agent_scd_change_pin (int chvno, const char *serialno)
{
int rc;
char line[ASSUAN_LINELENGTH];
const char *reset = "";
struct default_inq_parm_s dfltparm;
memset (&dfltparm, 0, sizeof dfltparm);
(void)serialno;
if (chvno >= 100)
reset = "--reset";
chvno %= 100;
rc = start_agent (NULL, 1);
if (rc)
return rc;
dfltparm.ctx = agent_ctx;
snprintf (line, DIM(line), "SCD PASSWD %s %d", reset, chvno);
rc = assuan_transact (agent_ctx, line,
NULL, NULL,
default_inq_cb, &dfltparm,
NULL, NULL);
status_sc_op_failure (rc);
return rc;
}
/* Perform a CHECKPIN operation. SERIALNO should be the serial
- number of the card - optionally followed by the fingerprint;
- however the fingerprint is ignored here. */
+ * number of the card - optionally followed by the fingerprint;
+ * however the fingerprint is ignored here.
+ * Used by:
+ * card-util.c
+ */
int
agent_scd_checkpin (const char *serialno)
{
int rc;
char line[ASSUAN_LINELENGTH];
struct default_inq_parm_s dfltparm;
memset (&dfltparm, 0, sizeof dfltparm);
rc = start_agent (NULL, 1);
if (rc)
return rc;
dfltparm.ctx = agent_ctx;
snprintf (line, DIM(line), "SCD CHECKPIN %s", serialno);
rc = assuan_transact (agent_ctx, line,
NULL, NULL,
default_inq_cb, &dfltparm,
NULL, NULL);
status_sc_op_failure (rc);
return rc;
}
-/* Dummy function, only used by the gpg 1.4 implementation. */
-void
-agent_clear_pin_cache (const char *sn)
-{
- (void)sn;
-}
-
-
-
/* Note: All strings shall be UTF-8. On success the caller needs to
free the string stored at R_PASSPHRASE. On error NULL will be
stored at R_PASSPHRASE and an appropriate fpf error code
returned. */
gpg_error_t
agent_get_passphrase (const char *cache_id,
const char *err_msg,
const char *prompt,
const char *desc_msg,
int repeat,
int check,
char **r_passphrase)
{
int rc;
char line[ASSUAN_LINELENGTH];
char *arg1 = NULL;
char *arg2 = NULL;
char *arg3 = NULL;
char *arg4 = NULL;
membuf_t data;
struct default_inq_parm_s dfltparm;
memset (&dfltparm, 0, sizeof dfltparm);
*r_passphrase = NULL;
rc = start_agent (NULL, 0);
if (rc)
return rc;
dfltparm.ctx = agent_ctx;
/* Check that the gpg-agent understands the repeat option. */
if (assuan_transact (agent_ctx,
"GETINFO cmd_has_option GET_PASSPHRASE repeat",
NULL, NULL, NULL, NULL, NULL, NULL))
return gpg_error (GPG_ERR_NOT_SUPPORTED);
if (cache_id && *cache_id)
if (!(arg1 = percent_plus_escape (cache_id)))
goto no_mem;
if (err_msg && *err_msg)
if (!(arg2 = percent_plus_escape (err_msg)))
goto no_mem;
if (prompt && *prompt)
if (!(arg3 = percent_plus_escape (prompt)))
goto no_mem;
if (desc_msg && *desc_msg)
if (!(arg4 = percent_plus_escape (desc_msg)))
goto no_mem;
snprintf (line, DIM(line),
"GET_PASSPHRASE --data --repeat=%d%s -- %s %s %s %s",
repeat,
check? " --check --qualitybar":"",
arg1? arg1:"X",
arg2? arg2:"X",
arg3? arg3:"X",
arg4? arg4:"X");
xfree (arg1);
xfree (arg2);
xfree (arg3);
xfree (arg4);
init_membuf_secure (&data, 64);
rc = assuan_transact (agent_ctx, line,
put_membuf_cb, &data,
default_inq_cb, &dfltparm,
NULL, NULL);
if (rc)
xfree (get_membuf (&data, NULL));
else
{
put_membuf (&data, "", 1);
*r_passphrase = get_membuf (&data, NULL);
if (!*r_passphrase)
rc = gpg_error_from_syserror ();
}
return rc;
no_mem:
rc = gpg_error_from_syserror ();
xfree (arg1);
xfree (arg2);
xfree (arg3);
xfree (arg4);
return rc;
}
gpg_error_t
agent_clear_passphrase (const char *cache_id)
{
int rc;
char line[ASSUAN_LINELENGTH];
struct default_inq_parm_s dfltparm;
memset (&dfltparm, 0, sizeof dfltparm);
if (!cache_id || !*cache_id)
return 0;
rc = start_agent (NULL, 0);
if (rc)
return rc;
dfltparm.ctx = agent_ctx;
snprintf (line, DIM(line), "CLEAR_PASSPHRASE %s", cache_id);
return assuan_transact (agent_ctx, line,
NULL, NULL,
default_inq_cb, &dfltparm,
NULL, NULL);
}
/* Ask the agent to pop up a confirmation dialog with the text DESC
and an okay and cancel button. */
gpg_error_t
gpg_agent_get_confirmation (const char *desc)
{
int rc;
char *tmp;
char line[ASSUAN_LINELENGTH];
struct default_inq_parm_s dfltparm;
memset (&dfltparm, 0, sizeof dfltparm);
rc = start_agent (NULL, 0);
if (rc)
return rc;
dfltparm.ctx = agent_ctx;
tmp = percent_plus_escape (desc);
if (!tmp)
return gpg_error_from_syserror ();
snprintf (line, DIM(line), "GET_CONFIRMATION %s", tmp);
xfree (tmp);
rc = assuan_transact (agent_ctx, line,
NULL, NULL,
default_inq_cb, &dfltparm,
NULL, NULL);
return rc;
}
/* Return the S2K iteration count as computed by gpg-agent. On error
* print a warning and return a default value. */
unsigned long
agent_get_s2k_count (void)
{
gpg_error_t err;
membuf_t data;
char *buf;
unsigned long count = 0;
err = start_agent (NULL, 0);
if (err)
goto leave;
init_membuf (&data, 32);
err = assuan_transact (agent_ctx, "GETINFO s2k_count",
put_membuf_cb, &data,
NULL, NULL, NULL, NULL);
if (err)
xfree (get_membuf (&data, NULL));
else
{
put_membuf (&data, "", 1);
buf = get_membuf (&data, NULL);
if (!buf)
err = gpg_error_from_syserror ();
else
{
count = strtoul (buf, NULL, 10);
xfree (buf);
}
}
leave:
if (err || count < 65536)
{
/* Don't print an error if an older agent is used. */
if (err && gpg_err_code (err) != GPG_ERR_ASS_PARAMETER)
log_error (_("problem with the agent: %s\n"), gpg_strerror (err));
/* Default to 65536 which was used up to 2.0.13. */
count = 65536;
}
return count;
}
/* Ask the agent whether a secret key for the given public key is
available. Returns 0 if available. */
gpg_error_t
agent_probe_secret_key (ctrl_t ctrl, PKT_public_key *pk)
{
gpg_error_t err;
char line[ASSUAN_LINELENGTH];
char *hexgrip;
err = start_agent (ctrl, 0);
if (err)
return err;
err = hexkeygrip_from_pk (pk, &hexgrip);
if (err)
return err;
snprintf (line, sizeof line, "HAVEKEY %s", hexgrip);
xfree (hexgrip);
err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
return err;
}
/* Ask the agent whether a secret key is available for any of the
keys (primary or sub) in KEYBLOCK. Returns 0 if available. */
gpg_error_t
agent_probe_any_secret_key (ctrl_t ctrl, kbnode_t keyblock)
{
gpg_error_t err;
char line[ASSUAN_LINELENGTH];
char *p;
kbnode_t kbctx, node;
int nkeys;
unsigned char grip[KEYGRIP_LEN];
err = start_agent (ctrl, 0);
if (err)
return err;
err = gpg_error (GPG_ERR_NO_SECKEY); /* Just in case no key was
found in KEYBLOCK. */
p = stpcpy (line, "HAVEKEY");
for (kbctx=NULL, nkeys=0; (node = walk_kbnode (keyblock, &kbctx, 0)); )
if (node->pkt->pkttype == PKT_PUBLIC_KEY
|| node->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| node->pkt->pkttype == PKT_SECRET_KEY
|| node->pkt->pkttype == PKT_SECRET_SUBKEY)
{
if (nkeys && ((p - line) + 41) > (ASSUAN_LINELENGTH - 2))
{
err = assuan_transact (agent_ctx, line,
NULL, NULL, NULL, NULL, NULL, NULL);
if (err != gpg_err_code (GPG_ERR_NO_SECKEY))
break; /* Seckey available or unexpected error - ready. */
p = stpcpy (line, "HAVEKEY");
nkeys = 0;
}
err = keygrip_from_pk (node->pkt->pkt.public_key, grip);
if (err)
return err;
*p++ = ' ';
bin2hex (grip, 20, p);
p += 40;
nkeys++;
}
if (!err && nkeys)
err = assuan_transact (agent_ctx, line,
NULL, NULL, NULL, NULL, NULL, NULL);
return err;
}
struct keyinfo_data_parm_s
{
char *serialno;
int cleartext;
};
static gpg_error_t
keyinfo_status_cb (void *opaque, const char *line)
{
struct keyinfo_data_parm_s *data = opaque;
int is_smartcard;
char *s;
if ((s = has_leading_keyword (line, "KEYINFO")) && data)
{
/* Parse the arguments:
* 0 1 2 3 4 5
* <keygrip> <type> <serialno> <idstr> <cached> <protection>
*/
char *fields[6];
if (split_fields (s, fields, DIM (fields)) == 6)
{
is_smartcard = (fields[1][0] == 'T');
if (is_smartcard && !data->serialno && strcmp (fields[2], "-"))
data->serialno = xtrystrdup (fields[2]);
/* 'P' for protected, 'C' for clear */
data->cleartext = (fields[5][0] == 'C');
}
}
return 0;
}
/* Return the serial number for a secret key. If the returned serial
number is NULL, the key is not stored on a smartcard. Caller needs
to free R_SERIALNO.
if r_cleartext is not NULL, the referenced int will be set to 1 if
the agent's copy of the key is stored in the clear, or 0 otherwise
*/
gpg_error_t
agent_get_keyinfo (ctrl_t ctrl, const char *hexkeygrip,
char **r_serialno, int *r_cleartext)
{
gpg_error_t err;
char line[ASSUAN_LINELENGTH];
struct keyinfo_data_parm_s keyinfo;
memset (&keyinfo, 0,sizeof keyinfo);
*r_serialno = NULL;
err = start_agent (ctrl, 0);
if (err)
return err;
if (!hexkeygrip || strlen (hexkeygrip) != 40)
return gpg_error (GPG_ERR_INV_VALUE);
snprintf (line, DIM(line), "KEYINFO %s", hexkeygrip);
err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL,
keyinfo_status_cb, &keyinfo);
if (!err && keyinfo.serialno)
{
/* Sanity check for bad characters. */
if (strpbrk (keyinfo.serialno, ":\n\r"))
err = GPG_ERR_INV_VALUE;
}
if (err)
xfree (keyinfo.serialno);
else
{
*r_serialno = keyinfo.serialno;
if (r_cleartext)
*r_cleartext = keyinfo.cleartext;
}
return err;
}
/* Status callback for agent_import_key, agent_export_key and
agent_genkey. */
static gpg_error_t
cache_nonce_status_cb (void *opaque, const char *line)
{
struct cache_nonce_parm_s *parm = opaque;
const char *s;
if ((s = has_leading_keyword (line, "CACHE_NONCE")))
{
if (parm->cache_nonce_addr)
{
xfree (*parm->cache_nonce_addr);
*parm->cache_nonce_addr = xtrystrdup (s);
}
}
else if ((s = has_leading_keyword (line, "PASSWD_NONCE")))
{
if (parm->passwd_nonce_addr)
{
xfree (*parm->passwd_nonce_addr);
*parm->passwd_nonce_addr = xtrystrdup (s);
}
}
else if ((s = has_leading_keyword (line, "PROGRESS")))
{
if (opt.enable_progress_filter)
write_status_text (STATUS_PROGRESS, s);
}
return 0;
}
/* Handle a KEYPARMS inquiry. Note, we only send the data,
assuan_transact takes care of flushing and writing the end */
static gpg_error_t
inq_genkey_parms (void *opaque, const char *line)
{
struct genkey_parm_s *parm = opaque;
gpg_error_t err;
if (has_leading_keyword (line, "KEYPARAM"))
{
err = assuan_send_data (parm->dflt->ctx,
parm->keyparms, strlen (parm->keyparms));
}
else if (has_leading_keyword (line, "NEWPASSWD") && parm->passphrase)
{
err = assuan_send_data (parm->dflt->ctx,
parm->passphrase, strlen (parm->passphrase));
}
else
err = default_inq_cb (parm->dflt, line);
return err;
}
/* Call the agent to generate a new key. KEYPARMS is the usual
S-expression giving the parameters of the key. gpg-agent passes it
gcry_pk_genkey. If NO_PROTECTION is true the agent is advised not
to protect the generated key. If NO_PROTECTION is not set and
PASSPHRASE is not NULL the agent is requested to protect the key
with that passphrase instead of asking for one. */
gpg_error_t
agent_genkey (ctrl_t ctrl, char **cache_nonce_addr, char **passwd_nonce_addr,
const char *keyparms, int no_protection,
const char *passphrase, gcry_sexp_t *r_pubkey)
{
gpg_error_t err;
struct genkey_parm_s gk_parm;
struct cache_nonce_parm_s cn_parm;
struct default_inq_parm_s dfltparm;
membuf_t data;
size_t len;
unsigned char *buf;
char line[ASSUAN_LINELENGTH];
memset (&dfltparm, 0, sizeof dfltparm);
dfltparm.ctrl = ctrl;
*r_pubkey = NULL;
err = start_agent (ctrl, 0);
if (err)
return err;
dfltparm.ctx = agent_ctx;
if (passwd_nonce_addr && *passwd_nonce_addr)
; /* A RESET would flush the passwd nonce cache. */
else
{
err = assuan_transact (agent_ctx, "RESET",
NULL, NULL, NULL, NULL, NULL, NULL);
if (err)
return err;
}
init_membuf (&data, 1024);
gk_parm.dflt = &dfltparm;
gk_parm.keyparms = keyparms;
gk_parm.passphrase = passphrase;
snprintf (line, sizeof line, "GENKEY%s%s%s%s%s",
no_protection? " --no-protection" :
passphrase ? " --inq-passwd" :
/* */ "",
passwd_nonce_addr && *passwd_nonce_addr? " --passwd-nonce=":"",
passwd_nonce_addr && *passwd_nonce_addr? *passwd_nonce_addr:"",
cache_nonce_addr && *cache_nonce_addr? " ":"",
cache_nonce_addr && *cache_nonce_addr? *cache_nonce_addr:"");
cn_parm.cache_nonce_addr = cache_nonce_addr;
cn_parm.passwd_nonce_addr = NULL;
err = assuan_transact (agent_ctx, line,
put_membuf_cb, &data,
inq_genkey_parms, &gk_parm,
cache_nonce_status_cb, &cn_parm);
if (err)
{
xfree (get_membuf (&data, &len));
return err;
}
buf = get_membuf (&data, &len);
if (!buf)
err = gpg_error_from_syserror ();
else
{
err = gcry_sexp_sscan (r_pubkey, NULL, buf, len);
xfree (buf);
}
return err;
}
/* Call the agent to read the public key part for a given keygrip. If
FROMCARD is true, the key is directly read from the current
smartcard. In this case HEXKEYGRIP should be the keyID
(e.g. OPENPGP.3). */
gpg_error_t
agent_readkey (ctrl_t ctrl, int fromcard, const char *hexkeygrip,
unsigned char **r_pubkey)
{
gpg_error_t err;
membuf_t data;
size_t len;
unsigned char *buf;
char line[ASSUAN_LINELENGTH];
struct default_inq_parm_s dfltparm;
memset (&dfltparm, 0, sizeof dfltparm);
dfltparm.ctrl = ctrl;
*r_pubkey = NULL;
err = start_agent (ctrl, 0);
if (err)
return err;
dfltparm.ctx = agent_ctx;
err = assuan_transact (agent_ctx, "RESET",NULL, NULL, NULL, NULL, NULL, NULL);
if (err)
return err;
snprintf (line, DIM(line), "READKEY %s%s", fromcard? "--card ":"",
hexkeygrip);
init_membuf (&data, 1024);
err = assuan_transact (agent_ctx, line,
put_membuf_cb, &data,
default_inq_cb, &dfltparm,
NULL, NULL);
if (err)
{
xfree (get_membuf (&data, &len));
return err;
}
buf = get_membuf (&data, &len);
if (!buf)
return gpg_error_from_syserror ();
if (!gcry_sexp_canon_len (buf, len, NULL, NULL))
{
xfree (buf);
return gpg_error (GPG_ERR_INV_SEXP);
}
*r_pubkey = buf;
return 0;
}
/* Call the agent to do a sign operation using the key identified by
the hex string KEYGRIP. DESC is a description of the key to be
displayed if the agent needs to ask for the PIN. DIGEST and
DIGESTLEN is the hash value to sign and DIGESTALGO the algorithm id
used to compute the digest. If CACHE_NONCE is used the agent is
advised to first try a passphrase associated with that nonce. */
gpg_error_t
agent_pksign (ctrl_t ctrl, const char *cache_nonce,
const char *keygrip, const char *desc,
u32 *keyid, u32 *mainkeyid, int pubkey_algo,
unsigned char *digest, size_t digestlen, int digestalgo,
gcry_sexp_t *r_sigval)
{
gpg_error_t err;
char line[ASSUAN_LINELENGTH];
membuf_t data;
struct default_inq_parm_s dfltparm;
memset (&dfltparm, 0, sizeof dfltparm);
dfltparm.ctrl = ctrl;
dfltparm.keyinfo.keyid = keyid;
dfltparm.keyinfo.mainkeyid = mainkeyid;
dfltparm.keyinfo.pubkey_algo = pubkey_algo;
*r_sigval = NULL;
err = start_agent (ctrl, 0);
if (err)
return err;
dfltparm.ctx = agent_ctx;
if (digestlen*2 + 50 > DIM(line))
return gpg_error (GPG_ERR_GENERAL);
err = assuan_transact (agent_ctx, "RESET",
NULL, NULL, NULL, NULL, NULL, NULL);
if (err)
return err;
snprintf (line, DIM(line), "SIGKEY %s", keygrip);
err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
if (err)
return err;
if (desc)
{
snprintf (line, DIM(line), "SETKEYDESC %s", desc);
err = assuan_transact (agent_ctx, line,
NULL, NULL, NULL, NULL, NULL, NULL);
if (err)
return err;
}
snprintf (line, sizeof line, "SETHASH %d ", digestalgo);
bin2hex (digest, digestlen, line + strlen (line));
err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
if (err)
return err;
init_membuf (&data, 1024);
snprintf (line, sizeof line, "PKSIGN%s%s",
cache_nonce? " -- ":"",
cache_nonce? cache_nonce:"");
if (DBG_CLOCK)
log_clock ("enter signing");
err = assuan_transact (agent_ctx, line,
put_membuf_cb, &data,
default_inq_cb, &dfltparm,
NULL, NULL);
if (DBG_CLOCK)
log_clock ("leave signing");
if (err)
xfree (get_membuf (&data, NULL));
else
{
unsigned char *buf;
size_t len;
buf = get_membuf (&data, &len);
if (!buf)
err = gpg_error_from_syserror ();
else
{
err = gcry_sexp_sscan (r_sigval, NULL, buf, len);
xfree (buf);
}
}
return err;
}
/* Handle a CIPHERTEXT inquiry. Note, we only send the data,
assuan_transact takes care of flushing and writing the END. */
static gpg_error_t
inq_ciphertext_cb (void *opaque, const char *line)
{
struct cipher_parm_s *parm = opaque;
int rc;
if (has_leading_keyword (line, "CIPHERTEXT"))
{
assuan_begin_confidential (parm->ctx);
rc = assuan_send_data (parm->dflt->ctx,
parm->ciphertext, parm->ciphertextlen);
assuan_end_confidential (parm->ctx);
}
else
rc = default_inq_cb (parm->dflt, line);
return rc;
}
/* Check whether there is any padding info from the agent. */
static gpg_error_t
padding_info_cb (void *opaque, const char *line)
{
int *r_padding = opaque;
const char *s;
if ((s=has_leading_keyword (line, "PADDING")))
{
*r_padding = atoi (s);
}
return 0;
}
/* Call the agent to do a decrypt operation using the key identified
by the hex string KEYGRIP and the input data S_CIPHERTEXT. On the
success the decoded value is stored verbatim at R_BUF and its
length at R_BUF; the callers needs to release it. KEYID, MAINKEYID
and PUBKEY_ALGO are used to construct additional promots or status
messages. The padding information is stored at R_PADDING with -1
for not known. */
gpg_error_t
agent_pkdecrypt (ctrl_t ctrl, const char *keygrip, const char *desc,
u32 *keyid, u32 *mainkeyid, int pubkey_algo,
gcry_sexp_t s_ciphertext,
unsigned char **r_buf, size_t *r_buflen, int *r_padding)
{
gpg_error_t err;
char line[ASSUAN_LINELENGTH];
membuf_t data;
size_t n, len;
char *p, *buf, *endp;
struct default_inq_parm_s dfltparm;
memset (&dfltparm, 0, sizeof dfltparm);
dfltparm.ctrl = ctrl;
dfltparm.keyinfo.keyid = keyid;
dfltparm.keyinfo.mainkeyid = mainkeyid;
dfltparm.keyinfo.pubkey_algo = pubkey_algo;
if (!keygrip || strlen(keygrip) != 40
|| !s_ciphertext || !r_buf || !r_buflen || !r_padding)
return gpg_error (GPG_ERR_INV_VALUE);
*r_buf = NULL;
*r_padding = -1;
err = start_agent (ctrl, 0);
if (err)
return err;
dfltparm.ctx = agent_ctx;
err = assuan_transact (agent_ctx, "RESET",
NULL, NULL, NULL, NULL, NULL, NULL);
if (err)
return err;
snprintf (line, sizeof line, "SETKEY %s", keygrip);
err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
if (err)
return err;
if (desc)
{
snprintf (line, DIM(line), "SETKEYDESC %s", desc);
err = assuan_transact (agent_ctx, line,
NULL, NULL, NULL, NULL, NULL, NULL);
if (err)
return err;
}
init_membuf_secure (&data, 1024);
{
struct cipher_parm_s parm;
parm.dflt = &dfltparm;
parm.ctx = agent_ctx;
err = make_canon_sexp (s_ciphertext, &parm.ciphertext, &parm.ciphertextlen);
if (err)
return err;
err = assuan_transact (agent_ctx, "PKDECRYPT",
put_membuf_cb, &data,
inq_ciphertext_cb, &parm,
padding_info_cb, r_padding);
xfree (parm.ciphertext);
}
if (err)
{
xfree (get_membuf (&data, &len));
return err;
}
put_membuf (&data, "", 1); /* Make sure it is 0 terminated. */
buf = get_membuf (&data, &len);
if (!buf)
return gpg_error_from_syserror ();
log_assert (len); /* (we forced Nul termination.) */
if (*buf != '(')
{
xfree (buf);
return gpg_error (GPG_ERR_INV_SEXP);
}
if (len < 13 || memcmp (buf, "(5:value", 8) ) /* "(5:valueN:D)\0" */
{
xfree (buf);
return gpg_error (GPG_ERR_INV_SEXP);
}
len -= 10; /* Count only the data of the second part. */
p = buf + 8; /* Skip leading parenthesis and the value tag. */
n = strtoul (p, &endp, 10);
if (!n || *endp != ':')
{
xfree (buf);
return gpg_error (GPG_ERR_INV_SEXP);
}
endp++;
if (endp-p+n > len)
{
xfree (buf);
return gpg_error (GPG_ERR_INV_SEXP); /* Oops: Inconsistent S-Exp. */
}
memmove (buf, endp, n);
*r_buflen = n;
*r_buf = buf;
return 0;
}
/* Retrieve a key encryption key from the agent. With FOREXPORT true
the key shall be used for export, with false for import. On success
the new key is stored at R_KEY and its length at R_KEKLEN. */
gpg_error_t
agent_keywrap_key (ctrl_t ctrl, int forexport, void **r_kek, size_t *r_keklen)
{
gpg_error_t err;
membuf_t data;
size_t len;
unsigned char *buf;
char line[ASSUAN_LINELENGTH];
struct default_inq_parm_s dfltparm;
memset (&dfltparm, 0, sizeof dfltparm);
dfltparm.ctrl = ctrl;
*r_kek = NULL;
err = start_agent (ctrl, 0);
if (err)
return err;
dfltparm.ctx = agent_ctx;
snprintf (line, DIM(line), "KEYWRAP_KEY %s",
forexport? "--export":"--import");
init_membuf_secure (&data, 64);
err = assuan_transact (agent_ctx, line,
put_membuf_cb, &data,
default_inq_cb, &dfltparm,
NULL, NULL);
if (err)
{
xfree (get_membuf (&data, &len));
return err;
}
buf = get_membuf (&data, &len);
if (!buf)
return gpg_error_from_syserror ();
*r_kek = buf;
*r_keklen = len;
return 0;
}
/* Handle the inquiry for an IMPORT_KEY command. */
static gpg_error_t
inq_import_key_parms (void *opaque, const char *line)
{
struct import_key_parm_s *parm = opaque;
gpg_error_t err;
if (has_leading_keyword (line, "KEYDATA"))
{
err = assuan_send_data (parm->dflt->ctx, parm->key, parm->keylen);
}
else
err = default_inq_cb (parm->dflt, line);
return err;
}
/* Call the agent to import a key into the agent. */
gpg_error_t
agent_import_key (ctrl_t ctrl, const char *desc, char **cache_nonce_addr,
const void *key, size_t keylen, int unattended, int force,
u32 *keyid, u32 *mainkeyid, int pubkey_algo)
{
gpg_error_t err;
struct import_key_parm_s parm;
struct cache_nonce_parm_s cn_parm;
char line[ASSUAN_LINELENGTH];
struct default_inq_parm_s dfltparm;
memset (&dfltparm, 0, sizeof dfltparm);
dfltparm.ctrl = ctrl;
dfltparm.keyinfo.keyid = keyid;
dfltparm.keyinfo.mainkeyid = mainkeyid;
dfltparm.keyinfo.pubkey_algo = pubkey_algo;
err = start_agent (ctrl, 0);
if (err)
return err;
dfltparm.ctx = agent_ctx;
if (desc)
{
snprintf (line, DIM(line), "SETKEYDESC %s", desc);
err = assuan_transact (agent_ctx, line,
NULL, NULL, NULL, NULL, NULL, NULL);
if (err)
return err;
}
parm.dflt = &dfltparm;
parm.key = key;
parm.keylen = keylen;
snprintf (line, sizeof line, "IMPORT_KEY%s%s%s%s",
unattended? " --unattended":"",
force? " --force":"",
cache_nonce_addr && *cache_nonce_addr? " ":"",
cache_nonce_addr && *cache_nonce_addr? *cache_nonce_addr:"");
cn_parm.cache_nonce_addr = cache_nonce_addr;
cn_parm.passwd_nonce_addr = NULL;
err = assuan_transact (agent_ctx, line,
NULL, NULL,
inq_import_key_parms, &parm,
cache_nonce_status_cb, &cn_parm);
return err;
}
/* Receive a secret key from the agent. HEXKEYGRIP is the hexified
keygrip, DESC a prompt to be displayed with the agent's passphrase
question (needs to be plus+percent escaped). if OPENPGP_PROTECTED
is not zero, ensure that the key material is returned in RFC
4880-compatible passphrased-protected form. If CACHE_NONCE_ADDR is
not NULL the agent is advised to first try a passphrase associated
with that nonce. On success the key is stored as a canonical
S-expression at R_RESULT and R_RESULTLEN. */
gpg_error_t
agent_export_key (ctrl_t ctrl, const char *hexkeygrip, const char *desc,
int openpgp_protected, char **cache_nonce_addr,
unsigned char **r_result, size_t *r_resultlen,
u32 *keyid, u32 *mainkeyid, int pubkey_algo)
{
gpg_error_t err;
struct cache_nonce_parm_s cn_parm;
membuf_t data;
size_t len;
unsigned char *buf;
char line[ASSUAN_LINELENGTH];
struct default_inq_parm_s dfltparm;
memset (&dfltparm, 0, sizeof dfltparm);
dfltparm.ctrl = ctrl;
dfltparm.keyinfo.keyid = keyid;
dfltparm.keyinfo.mainkeyid = mainkeyid;
dfltparm.keyinfo.pubkey_algo = pubkey_algo;
*r_result = NULL;
err = start_agent (ctrl, 0);
if (err)
return err;
dfltparm.ctx = agent_ctx;
if (desc)
{
snprintf (line, DIM(line), "SETKEYDESC %s", desc);
err = assuan_transact (agent_ctx, line,
NULL, NULL, NULL, NULL, NULL, NULL);
if (err)
return err;
}
snprintf (line, DIM(line), "EXPORT_KEY %s%s%s %s",
openpgp_protected ? "--openpgp ":"",
cache_nonce_addr && *cache_nonce_addr? "--cache-nonce=":"",
cache_nonce_addr && *cache_nonce_addr? *cache_nonce_addr:"",
hexkeygrip);
init_membuf_secure (&data, 1024);
cn_parm.cache_nonce_addr = cache_nonce_addr;
cn_parm.passwd_nonce_addr = NULL;
err = assuan_transact (agent_ctx, line,
put_membuf_cb, &data,
default_inq_cb, &dfltparm,
cache_nonce_status_cb, &cn_parm);
if (err)
{
xfree (get_membuf (&data, &len));
return err;
}
buf = get_membuf (&data, &len);
if (!buf)
return gpg_error_from_syserror ();
*r_result = buf;
*r_resultlen = len;
return 0;
}
+/* Status callback for handling confirmation. */
+static gpg_error_t
+confirm_status_cb (void *opaque, const char *line)
+{
+ struct confirm_parm_s *parm = opaque;
+ const char *s;
+
+ if ((s = has_leading_keyword (line, "SETDESC")))
+ {
+ xfree (parm->desc);
+ parm->desc = unescape_status_string (s);
+ }
+ else if ((s = has_leading_keyword (line, "SETOK")))
+ {
+ xfree (parm->ok);
+ parm->ok = unescape_status_string (s);
+ }
+ else if ((s = has_leading_keyword (line, "SETNOTOK")))
+ {
+ xfree (parm->notok);
+ parm->notok = unescape_status_string (s);
+ }
+
+ return 0;
+}
/* Ask the agent to delete the key identified by HEXKEYGRIP. If DESC
is not NULL, display DESC instead of the default description
message. If FORCE is true the agent is advised not to ask for
confirmation. */
gpg_error_t
agent_delete_key (ctrl_t ctrl, const char *hexkeygrip, const char *desc,
int force)
{
gpg_error_t err;
char line[ASSUAN_LINELENGTH];
struct default_inq_parm_s dfltparm;
+ struct confirm_parm_s confirm_parm;
+ memset (&confirm_parm, 0, sizeof confirm_parm);
memset (&dfltparm, 0, sizeof dfltparm);
dfltparm.ctrl = ctrl;
+ dfltparm.confirm = &confirm_parm;
err = start_agent (ctrl, 0);
if (err)
return err;
if (!hexkeygrip || strlen (hexkeygrip) != 40)
return gpg_error (GPG_ERR_INV_VALUE);
if (desc)
{
snprintf (line, DIM(line), "SETKEYDESC %s", desc);
err = assuan_transact (agent_ctx, line,
NULL, NULL, NULL, NULL, NULL, NULL);
if (err)
return err;
}
snprintf (line, DIM(line), "DELETE_KEY%s %s",
force? " --force":"", hexkeygrip);
err = assuan_transact (agent_ctx, line, NULL, NULL,
default_inq_cb, &dfltparm,
- NULL, NULL);
+ confirm_status_cb, &confirm_parm);
+ xfree (confirm_parm.desc);
+ xfree (confirm_parm.ok);
+ xfree (confirm_parm.notok);
return err;
}
/* Ask the agent to change the passphrase of the key identified by
* HEXKEYGRIP. If DESC is not NULL, display DESC instead of the
* default description message. If CACHE_NONCE_ADDR is not NULL the
* agent is advised to first try a passphrase associated with that
* nonce. If PASSWD_NONCE_ADDR is not NULL the agent will try to use
* the passphrase associated with that nonce for the new passphrase.
* If VERIFY is true the passphrase is only verified. */
gpg_error_t
agent_passwd (ctrl_t ctrl, const char *hexkeygrip, const char *desc, int verify,
char **cache_nonce_addr, char **passwd_nonce_addr)
{
gpg_error_t err;
struct cache_nonce_parm_s cn_parm;
char line[ASSUAN_LINELENGTH];
struct default_inq_parm_s dfltparm;
memset (&dfltparm, 0, sizeof dfltparm);
dfltparm.ctrl = ctrl;
err = start_agent (ctrl, 0);
if (err)
return err;
dfltparm.ctx = agent_ctx;
if (!hexkeygrip || strlen (hexkeygrip) != 40)
return gpg_error (GPG_ERR_INV_VALUE);
if (desc)
{
snprintf (line, DIM(line), "SETKEYDESC %s", desc);
err = assuan_transact (agent_ctx, line,
NULL, NULL, NULL, NULL, NULL, NULL);
if (err)
return err;
}
if (verify)
snprintf (line, DIM(line), "PASSWD %s%s --verify %s",
cache_nonce_addr && *cache_nonce_addr? "--cache-nonce=":"",
cache_nonce_addr && *cache_nonce_addr? *cache_nonce_addr:"",
hexkeygrip);
else
snprintf (line, DIM(line), "PASSWD %s%s %s%s %s",
cache_nonce_addr && *cache_nonce_addr? "--cache-nonce=":"",
cache_nonce_addr && *cache_nonce_addr? *cache_nonce_addr:"",
passwd_nonce_addr && *passwd_nonce_addr? "--passwd-nonce=":"",
passwd_nonce_addr && *passwd_nonce_addr? *passwd_nonce_addr:"",
hexkeygrip);
cn_parm.cache_nonce_addr = cache_nonce_addr;
cn_parm.passwd_nonce_addr = passwd_nonce_addr;
err = assuan_transact (agent_ctx, line, NULL, NULL,
default_inq_cb, &dfltparm,
cache_nonce_status_cb, &cn_parm);
return err;
}
/* Return the version reported by gpg-agent. */
gpg_error_t
agent_get_version (ctrl_t ctrl, char **r_version)
{
gpg_error_t err;
err = start_agent (ctrl, 0);
if (err)
return err;
err = get_assuan_server_version (agent_ctx, 0, r_version);
return err;
}
diff --git a/g10/call-agent.h b/g10/call-agent.h
index 8619a34f8..c4d0a9de1 100644
--- a/g10/call-agent.h
+++ b/g10/call-agent.h
@@ -1,219 +1,220 @@
/* call-agent.h - Divert operations to the agent
* Copyright (C) 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 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#ifndef GNUPG_G10_CALL_AGENT_H
#define GNUPG_G10_CALL_AGENT_H
struct key_attr {
int algo; /* Algorithm identifier. */
union {
unsigned int nbits; /* Supported keysize. */
const char *curve; /* Name of curve. */
};
};
struct agent_card_info_s
{
int error; /* private. */
char *reader; /* Reader information. */
char *apptype; /* Malloced application type string. */
char *serialno; /* malloced hex string. */
char *disp_name; /* malloced. */
char *disp_lang; /* malloced. */
int disp_sex; /* 0 = unspecified, 1 = male, 2 = female */
char *pubkey_url; /* malloced. */
char *login_data; /* malloced. */
char *private_do[4]; /* malloced. */
char cafpr1len; /* Length of the CA-fingerprint or 0 if invalid. */
char cafpr2len;
char cafpr3len;
char cafpr1[20];
char cafpr2[20];
char cafpr3[20];
unsigned char fpr1len; /* Length of the fingerprint or 0 if invalid. */
unsigned char fpr2len;
unsigned char fpr3len;
char fpr1[20];
char fpr2[20];
char fpr3[20];
u32 fpr1time;
u32 fpr2time;
u32 fpr3time;
char grp1[20]; /* The keygrip for OPENPGP.1 */
char grp2[20]; /* The keygrip for OPENPGP.2 */
char grp3[20]; /* The keygrip for OPENPGP.3 */
unsigned long sig_counter;
int chv1_cached; /* True if a PIN is not required for each
signing. Note that the gpg-agent might cache
it anyway. */
int is_v2; /* True if this is a v2 card. */
int chvmaxlen[3]; /* Maximum allowed length of a CHV. */
int chvretry[3]; /* Allowed retries for the CHV; 0 = blocked. */
struct key_attr key_attr[3];
struct {
unsigned int ki:1; /* Key import available. */
unsigned int aac:1; /* Algorithm attributes are changeable. */
unsigned int kdf:1; /* KDF object to support PIN hashing available. */
unsigned int bt:1; /* Button for confirmation available. */
} extcap;
unsigned int status_indicator;
int kdf_do_enabled; /* True if card has a KDF object. */
int uif[3]; /* True if User Interaction Flag is on. */
};
/* Release the card info structure. */
void agent_release_card_info (struct agent_card_info_s *info);
/* Return card info. */
int agent_scd_learn (struct agent_card_info_s *info, int force);
+/* Get the keypariinfo directly from scdaemon. */
+gpg_error_t agent_scd_keypairinfo (ctrl_t ctrl, const char *keyref,
+ strlist_t *r_list);
+
/* Return list of cards. */
int agent_scd_cardlist (strlist_t *result);
/* Return the serial number, possibly select by DEMAND. */
int agent_scd_serialno (char **r_serialno, const char *demand);
/* Send an APDU to the card. */
gpg_error_t agent_scd_apdu (const char *hexapdu, unsigned int *r_sw);
+/* Get attribute NAME from the card and store at R_VALUE. */
+gpg_error_t agent_scd_getattr_one (const char *name, char **r_value);
+
/* Update INFO with the attribute NAME. */
int agent_scd_getattr (const char *name, struct agent_card_info_s *info);
/* Send the KEYTOCARD command. */
int agent_keytocard (const char *hexgrip, int keyno, int force,
const char *serialno, const char *timestamp);
/* Send a SETATTR command to the SCdaemon. */
-int agent_scd_setattr (const char *name,
- const unsigned char *value, size_t valuelen,
- const char *serialno);
+gpg_error_t agent_scd_setattr (const char *name,
+ const void *value, size_t valuelen);
/* Send a WRITECERT command to the SCdaemon. */
int agent_scd_writecert (const char *certidstr,
const unsigned char *certdata, size_t certdatalen);
-/* Send a WRITEKEY command to the SCdaemon. */
-int agent_scd_writekey (int keyno, const char *serialno,
- const unsigned char *keydata, size_t keydatalen);
-
/* Send a GENKEY command to the SCdaemon. */
int agent_scd_genkey (int keyno, int force, u32 *createtime);
-/* Send a READKEY command to the SCdaemon. */
+/* Send a READCERT command to the SCdaemon. */
int agent_scd_readcert (const char *certidstr,
void **r_buf, size_t *r_buflen);
+/* Send a READKEY command to the SCdaemon. */
+gpg_error_t agent_scd_readkey (const char *keyrefstr, gcry_sexp_t *r_result);
+
/* Change the PIN of an OpenPGP card or reset the retry counter. */
int agent_scd_change_pin (int chvno, const char *serialno);
/* Send the CHECKPIN command to the SCdaemon. */
int agent_scd_checkpin (const char *serialno);
-/* Dummy function, only implemented by gpg 1.4. */
-void agent_clear_pin_cache (const char *sn);
-
-
/* Send the GET_PASSPHRASE command to the agent. */
gpg_error_t agent_get_passphrase (const char *cache_id,
const char *err_msg,
const char *prompt,
const char *desc_msg,
int repeat,
int check,
char **r_passphrase);
/* Send the CLEAR_PASSPHRASE command to the agent. */
gpg_error_t agent_clear_passphrase (const char *cache_id);
/* Present the prompt DESC and ask the user to confirm. */
gpg_error_t gpg_agent_get_confirmation (const char *desc);
/* Return the S2K iteration count as computed by gpg-agent. */
unsigned long agent_get_s2k_count (void);
/* Check whether a secret key for public key PK is available. Returns
0 if the secret key is available. */
gpg_error_t agent_probe_secret_key (ctrl_t ctrl, PKT_public_key *pk);
/* Ask the agent whether a secret key is availabale for any of the
keys (primary or sub) in KEYBLOCK. Returns 0 if available. */
gpg_error_t agent_probe_any_secret_key (ctrl_t ctrl, kbnode_t keyblock);
/* Return infos about the secret key with HEXKEYGRIP. */
gpg_error_t agent_get_keyinfo (ctrl_t ctrl, const char *hexkeygrip,
char **r_serialno, int *r_cleartext);
/* Generate a new key. */
gpg_error_t agent_genkey (ctrl_t ctrl,
char **cache_nonce_addr, char **passwd_nonce_addr,
const char *keyparms, int no_protection,
const char *passphrase,
gcry_sexp_t *r_pubkey);
/* Read a public key. */
gpg_error_t agent_readkey (ctrl_t ctrl, int fromcard, const char *hexkeygrip,
unsigned char **r_pubkey);
/* Create a signature. */
gpg_error_t agent_pksign (ctrl_t ctrl, const char *cache_nonce,
const char *hexkeygrip, const char *desc,
u32 *keyid, u32 *mainkeyid, int pubkey_algo,
unsigned char *digest, size_t digestlen,
int digestalgo,
gcry_sexp_t *r_sigval);
/* Decrypt a ciphertext. */
gpg_error_t agent_pkdecrypt (ctrl_t ctrl, const char *keygrip, const char *desc,
u32 *keyid, u32 *mainkeyid, int pubkey_algo,
gcry_sexp_t s_ciphertext,
unsigned char **r_buf, size_t *r_buflen,
int *r_padding);
/* Retrieve a key encryption key. */
gpg_error_t agent_keywrap_key (ctrl_t ctrl, int forexport,
void **r_kek, size_t *r_keklen);
/* Send a key to the agent. */
gpg_error_t agent_import_key (ctrl_t ctrl, const char *desc,
char **cache_nonce_addr, const void *key,
size_t keylen, int unattended, int force,
u32 *keyid, u32 *mainkeyid, int pubkey_algo);
/* Receive a key from the agent. */
gpg_error_t agent_export_key (ctrl_t ctrl, const char *keygrip,
const char *desc, int openpgp_protected,
char **cache_nonce_addr,
unsigned char **r_result, size_t *r_resultlen,
u32 *keyid, u32 *mainkeyid, int pubkey_algo);
/* Delete a key from the agent. */
gpg_error_t agent_delete_key (ctrl_t ctrl, const char *hexkeygrip,
const char *desc, int force);
/* Change the passphrase of a key. */
gpg_error_t agent_passwd (ctrl_t ctrl, const char *hexkeygrip, const char *desc,
int verify,
char **cache_nonce_addr, char **passwd_nonce_addr);
/* Get the version reported by gpg-agent. */
gpg_error_t agent_get_version (ctrl_t ctrl, char **r_version);
#endif /*GNUPG_G10_CALL_AGENT_H*/
diff --git a/g10/card-util.c b/g10/card-util.c
index 08844bae3..1b9461e0a 100644
--- a/g10/card-util.c
+++ b/g10/card-util.c
@@ -1,2538 +1,2546 @@
/* card-util.c - Utility functions for the OpenPGP card.
* Copyright (C) 2003-2005, 2009 Free Software Foundation, Inc.
* Copyright (C) 2003-2005, 2009 Werner Koch
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#ifdef HAVE_LIBREADLINE
# define GNUPG_LIBREADLINE_H_INCLUDED
# include <readline/readline.h>
#endif /*HAVE_LIBREADLINE*/
#if GNUPG_MAJOR_VERSION != 1
# include "gpg.h"
#endif /*GNUPG_MAJOR_VERSION != 1*/
#include "../common/util.h"
#include "../common/i18n.h"
#include "../common/ttyio.h"
#include "../common/status.h"
#include "options.h"
#include "main.h"
#include "keyserver-internal.h"
#if GNUPG_MAJOR_VERSION == 1
# include "cardglue.h"
#else /*GNUPG_MAJOR_VERSION!=1*/
# include "call-agent.h"
#endif /*GNUPG_MAJOR_VERSION!=1*/
#define CONTROL_D ('D' - 'A' + 1)
static void
write_sc_op_status (gpg_error_t err)
{
switch (gpg_err_code (err))
{
case 0:
write_status (STATUS_SC_OP_SUCCESS);
break;
#if GNUPG_MAJOR_VERSION != 1
case GPG_ERR_CANCELED:
case GPG_ERR_FULLY_CANCELED:
write_status_text (STATUS_SC_OP_FAILURE, "1");
break;
case GPG_ERR_BAD_PIN:
write_status_text (STATUS_SC_OP_FAILURE, "2");
break;
default:
write_status (STATUS_SC_OP_FAILURE);
break;
#endif /* GNUPG_MAJOR_VERSION != 1 */
}
}
/* Change the PIN of an OpenPGP card. This is an interactive
function. */
void
change_pin (int unblock_v2, int allow_admin)
{
struct agent_card_info_s info;
int rc;
rc = agent_scd_learn (&info, 0);
if (rc)
{
log_error (_("OpenPGP card not available: %s\n"),
gpg_strerror (rc));
return;
}
log_info (_("OpenPGP card no. %s detected\n"),
info.serialno? info.serialno : "[none]");
- agent_clear_pin_cache (info.serialno);
-
if (opt.batch)
{
agent_release_card_info (&info);
log_error (_("can't do this in batch mode\n"));
return;
}
if (unblock_v2)
{
if (!info.is_v2)
log_error (_("This command is only available for version 2 cards\n"));
else if (!info.chvretry[1])
log_error (_("Reset Code not or not anymore available\n"));
else
{
rc = agent_scd_change_pin (2, info.serialno);
write_sc_op_status (rc);
if (rc)
tty_printf ("Error changing the PIN: %s\n", gpg_strerror (rc));
else
tty_printf ("PIN changed.\n");
}
}
else if (!allow_admin)
{
rc = agent_scd_change_pin (1, info.serialno);
write_sc_op_status (rc);
if (rc)
tty_printf ("Error changing the PIN: %s\n", gpg_strerror (rc));
else
tty_printf ("PIN changed.\n");
}
else
for (;;)
{
char *answer;
tty_printf ("\n");
tty_printf ("1 - change PIN\n"
"2 - unblock PIN\n"
"3 - change Admin PIN\n"
"4 - set the Reset Code\n"
"Q - quit\n");
tty_printf ("\n");
answer = cpr_get("cardutil.change_pin.menu",_("Your selection? "));
cpr_kill_prompt();
if (strlen (answer) != 1)
continue;
if (*answer == '1')
{
/* Change PIN. */
rc = agent_scd_change_pin (1, info.serialno);
write_sc_op_status (rc);
if (rc)
tty_printf ("Error changing the PIN: %s\n", gpg_strerror (rc));
else
tty_printf ("PIN changed.\n");
}
else if (*answer == '2')
{
/* Unblock PIN. */
rc = agent_scd_change_pin (101, info.serialno);
write_sc_op_status (rc);
if (rc)
tty_printf ("Error unblocking the PIN: %s\n", gpg_strerror (rc));
else
tty_printf ("PIN unblocked and new PIN set.\n");
}
else if (*answer == '3')
{
/* Change Admin PIN. */
rc = agent_scd_change_pin (3, info.serialno);
write_sc_op_status (rc);
if (rc)
tty_printf ("Error changing the PIN: %s\n", gpg_strerror (rc));
else
tty_printf ("PIN changed.\n");
}
else if (*answer == '4')
{
/* Set a new Reset Code. */
rc = agent_scd_change_pin (102, info.serialno);
write_sc_op_status (rc);
if (rc)
tty_printf ("Error setting the Reset Code: %s\n",
gpg_strerror (rc));
else
tty_printf ("Reset Code set.\n");
}
else if (*answer == 'q' || *answer == 'Q')
{
break;
}
}
agent_release_card_info (&info);
}
static const char *
get_manufacturer (unsigned int no)
{
/* Note: Make sure that there is no colon or linefeed in the string. */
switch (no)
{
case 0x0001: return "PPC Card Systems";
case 0x0002: return "Prism";
case 0x0003: return "OpenFortress";
case 0x0004: return "Wewid";
case 0x0005: return "ZeitControl";
case 0x0006: return "Yubico";
case 0x0007: return "OpenKMS";
case 0x0008: return "LogoEmail";
case 0x0009: return "Fidesmo";
case 0x000A: return "Dangerous Things";
case 0x002A: return "Magrathea";
case 0x0042: return "GnuPG e.V.";
case 0x1337: return "Warsaw Hackerspace";
case 0x2342: return "warpzone"; /* hackerspace Muenster. */
case 0x4354: return "Confidential Technologies"; /* cotech.de */
case 0x63AF: return "Trustica";
case 0xBD0E: return "Paranoidlabs";
case 0xF517: return "FSIJ";
/* 0x0000 and 0xFFFF are defined as test cards per spec,
0xFF00 to 0xFFFE are assigned for use with randomly created
serial numbers. */
case 0x0000:
case 0xffff: return "test card";
default: return (no & 0xff00) == 0xff00? "unmanaged S/N range":"unknown";
}
}
static void
print_shax_fpr (estream_t fp, const unsigned char *fpr, unsigned int fprlen)
{
int i;
if (fpr)
{
/* FIXME: Fix formatting for FPRLEN != 20 */
for (i=0; i < fprlen ; i+=2, fpr += 2 )
{
if (i == 10 )
tty_fprintf (fp, " ");
tty_fprintf (fp, " %02X%02X", *fpr, fpr[1]);
}
}
else
tty_fprintf (fp, " [none]");
tty_fprintf (fp, "\n");
}
static void
print_shax_fpr_colon (estream_t fp,
const unsigned char *fpr, unsigned int fprlen)
{
int i;
if (fpr)
{
for (i=0; i < fprlen ; i++, fpr++)
es_fprintf (fp, "%02X", *fpr);
}
es_putc (':', fp);
}
static void
print_keygrip (estream_t fp, const unsigned char *grp)
{
int i;
if (opt.with_keygrip)
{
tty_fprintf (fp, " keygrip ....: ");
for (i=0; i < 20 ; i++, grp++)
tty_fprintf (fp, "%02X", *grp);
tty_fprintf (fp, "\n");
}
}
static void
print_name (estream_t fp, const char *text, const char *name)
{
tty_fprintf (fp, "%s", text);
/* FIXME: tty_printf_utf8_string2 eats everything after and
including an @ - e.g. when printing an url. */
if (name && *name)
{
if (fp)
print_utf8_buffer2 (fp, name, strlen (name), '\n');
else
tty_print_utf8_string2 (NULL, name, strlen (name), 0);
}
else
tty_fprintf (fp, _("[not set]"));
tty_fprintf (fp, "\n");
}
static void
print_isoname (estream_t fp, const char *text,
const char *tag, const char *name)
{
if (opt.with_colons)
es_fprintf (fp, "%s:", tag);
else
tty_fprintf (fp, "%s", text);
if (name && *name)
{
char *p, *given, *buf = xstrdup (name);
given = strstr (buf, "<<");
for (p=buf; *p; p++)
if (*p == '<')
*p = ' ';
if (given && given[2])
{
*given = 0;
given += 2;
if (opt.with_colons)
es_write_sanitized (fp, given, strlen (given), ":", NULL);
else if (fp)
print_utf8_buffer2 (fp, given, strlen (given), '\n');
else
tty_print_utf8_string2 (NULL, given, strlen (given), 0);
if (opt.with_colons)
es_putc (':', fp);
else if (*buf)
tty_fprintf (fp, " ");
}
if (opt.with_colons)
es_write_sanitized (fp, buf, strlen (buf), ":", NULL);
else if (fp)
print_utf8_buffer2 (fp, buf, strlen (buf), '\n');
else
tty_print_utf8_string2 (NULL, buf, strlen (buf), 0);
xfree (buf);
}
else
{
if (opt.with_colons)
es_putc (':', fp);
else
tty_fprintf (fp, _("[not set]"));
}
if (opt.with_colons)
es_fputs (":\n", fp);
else
tty_fprintf (fp, "\n");
}
/* Return true if the SHA1 fingerprint FPR consists only of zeroes. */
static int
fpr_is_zero (const char *fpr, unsigned int fprlen)
{
int i;
for (i=0; i < fprlen && !fpr[i]; i++)
;
return (i == fprlen);
}
/* Return true if the fingerprint FPR consists only of 0xFF. */
static int
fpr_is_ff (const char *fpr, unsigned int fprlen)
{
int i;
for (i=0; i < fprlen && fpr[i] == '\xff'; i++)
;
return (i == fprlen);
}
/* Print all available information about the current card. */
static void
current_card_status (ctrl_t ctrl, estream_t fp,
char *serialno, size_t serialnobuflen)
{
struct agent_card_info_s info;
PKT_public_key *pk = xcalloc (1, sizeof *pk);
kbnode_t keyblock = NULL;
int rc;
unsigned int uval;
const unsigned char *thefpr;
unsigned int thefprlen;
int i;
if (serialno && serialnobuflen)
*serialno = 0;
rc = agent_scd_learn (&info, 0);
if (rc)
{
if (opt.with_colons)
es_fputs ("AID:::\n", fp);
log_error (_("OpenPGP card not available: %s\n"), gpg_strerror (rc));
xfree (pk);
return;
}
if (opt.with_colons)
es_fprintf (fp, "Reader:%s:", info.reader? info.reader : "");
else
tty_fprintf (fp, "Reader ...........: %s\n",
info.reader? info.reader : "[none]");
if (opt.with_colons)
es_fprintf (fp, "AID:%s:", info.serialno? info.serialno : "");
else
tty_fprintf (fp, "Application ID ...: %s\n",
info.serialno? info.serialno : "[none]");
if (!info.serialno || strncmp (info.serialno, "D27600012401", 12)
|| strlen (info.serialno) != 32 )
{
+ const char *name1, *name2;
if (info.apptype && !strcmp (info.apptype, "NKS"))
{
- if (opt.with_colons)
- es_fputs ("netkey-card:\n", fp);
- log_info ("this is a NetKey card\n");
+ name1 = "netkey";
+ name2 = "NetKey";
}
else if (info.apptype && !strcmp (info.apptype, "DINSIG"))
{
- if (opt.with_colons)
- es_fputs ("dinsig-card:\n", fp);
- log_info ("this is a DINSIG compliant card\n");
+ name1 = "dinsig";
+ name2 = "DINSIG";
}
else if (info.apptype && !strcmp (info.apptype, "P15"))
{
- if (opt.with_colons)
- es_fputs ("pkcs15-card:\n", fp);
- log_info ("this is a PKCS#15 compliant card\n");
+ name1 = "pkcs15";
+ name2 = "PKCS#15";
}
else if (info.apptype && !strcmp (info.apptype, "GELDKARTE"))
{
- if (opt.with_colons)
- es_fputs ("geldkarte-card:\n", fp);
- log_info ("this is a Geldkarte compliant card\n");
+ name1 = "geldkarte";
+ name2 = "Geldkarte";
+ }
+ else if (info.apptype && !strcmp (info.apptype, "PIV"))
+ {
+ name1 = "piv";
+ name2 = "PIV";
}
else
{
- if (opt.with_colons)
- es_fputs ("unknown:\n", fp);
+ name1 = "unknown";
+ name2 = "Unknown";
}
- log_info ("not an OpenPGP card\n");
+
+ if (opt.with_colons)
+ es_fprintf (fp, "%s-card:\n", name1);
+ else
+ tty_fprintf (fp, "Application type .: %s\n", name2);
+
agent_release_card_info (&info);
xfree (pk);
return;
}
if (!serialno)
;
else if (strlen (info.serialno)+1 > serialnobuflen)
log_error ("serial number longer than expected\n");
else
strcpy (serialno, info.serialno);
if (opt.with_colons)
es_fputs ("openpgp-card:\n", fp);
+ else
+ tty_fprintf (fp, "Application type .: %s\n", "OpenPGP");
if (opt.with_colons)
{
es_fprintf (fp, "version:%.4s:\n", info.serialno+12);
uval = xtoi_2(info.serialno+16)*256 + xtoi_2 (info.serialno+18);
es_fprintf (fp, "vendor:%04x:%s:\n", uval, get_manufacturer (uval));
es_fprintf (fp, "serial:%.8s:\n", info.serialno+20);
print_isoname (fp, "Name of cardholder: ", "name", info.disp_name);
es_fputs ("lang:", fp);
if (info.disp_lang)
es_write_sanitized (fp, info.disp_lang, strlen (info.disp_lang),
":", NULL);
es_fputs (":\n", fp);
es_fprintf (fp, "sex:%c:\n", (info.disp_sex == 1? 'm':
info.disp_sex == 2? 'f' : 'u'));
es_fputs ("url:", fp);
if (info.pubkey_url)
es_write_sanitized (fp, info.pubkey_url, strlen (info.pubkey_url),
":", NULL);
es_fputs (":\n", fp);
es_fputs ("login:", fp);
if (info.login_data)
es_write_sanitized (fp, info.login_data, strlen (info.login_data),
":", NULL);
es_fputs (":\n", fp);
es_fprintf (fp, "forcepin:%d:::\n", !info.chv1_cached);
for (i=0; i < DIM (info.key_attr); i++)
if (info.key_attr[i].algo == PUBKEY_ALGO_RSA)
es_fprintf (fp, "keyattr:%d:%d:%u:\n", i+1,
info.key_attr[i].algo, info.key_attr[i].nbits);
else if (info.key_attr[i].algo == PUBKEY_ALGO_ECDH
|| info.key_attr[i].algo == PUBKEY_ALGO_ECDSA
|| info.key_attr[i].algo == PUBKEY_ALGO_EDDSA)
es_fprintf (fp, "keyattr:%d:%d:%s:\n", i+1,
info.key_attr[i].algo, info.key_attr[i].curve);
es_fprintf (fp, "maxpinlen:%d:%d:%d:\n",
info.chvmaxlen[0], info.chvmaxlen[1], info.chvmaxlen[2]);
es_fprintf (fp, "pinretry:%d:%d:%d:\n",
info.chvretry[0], info.chvretry[1], info.chvretry[2]);
es_fprintf (fp, "sigcount:%lu:::\n", info.sig_counter);
if (info.extcap.kdf)
{
es_fprintf (fp, "kdf:%s:\n", info.kdf_do_enabled ? "on" : "off");
}
if (info.extcap.bt)
{
es_fprintf (fp, "uif:%d:%d:%d:\n",
info.uif[0], info.uif[1], info.uif[2]);
}
for (i=0; i < 4; i++)
{
if (info.private_do[i])
{
es_fprintf (fp, "private_do:%d:", i+1);
es_write_sanitized (fp, info.private_do[i],
strlen (info.private_do[i]), ":", NULL);
es_fputs (":\n", fp);
}
}
es_fputs ("cafpr:", fp);
print_shax_fpr_colon (fp, info.cafpr1len? info.cafpr1:NULL,
info.cafpr2len);
print_shax_fpr_colon (fp, info.cafpr2len? info.cafpr2:NULL,
info.cafpr2len);
print_shax_fpr_colon (fp, info.cafpr3len? info.cafpr3:NULL,
info.cafpr3len);
es_putc ('\n', fp);
es_fputs ("fpr:", fp);
print_shax_fpr_colon (fp, info.fpr1len? info.fpr1:NULL, info.fpr1len);
print_shax_fpr_colon (fp, info.fpr2len? info.fpr2:NULL, info.fpr2len);
print_shax_fpr_colon (fp, info.fpr3len? info.fpr3:NULL, info.fpr3len);
es_putc ('\n', fp);
es_fprintf (fp, "fprtime:%lu:%lu:%lu:\n",
(unsigned long)info.fpr1time, (unsigned long)info.fpr2time,
(unsigned long)info.fpr3time);
es_fputs ("grp:", fp);
print_shax_fpr_colon (fp, info.grp1, sizeof info.grp1);
print_shax_fpr_colon (fp, info.grp2, sizeof info.grp2);
print_shax_fpr_colon (fp, info.grp3, sizeof info.grp3);
es_putc ('\n', fp);
}
else
{
tty_fprintf (fp, "Version ..........: %.1s%c.%.1s%c\n",
info.serialno[12] == '0'?"":info.serialno+12,
info.serialno[13],
info.serialno[14] == '0'?"":info.serialno+14,
info.serialno[15]);
tty_fprintf (fp, "Manufacturer .....: %s\n",
get_manufacturer (xtoi_2(info.serialno+16)*256
+ xtoi_2 (info.serialno+18)));
tty_fprintf (fp, "Serial number ....: %.8s\n", info.serialno+20);
print_isoname (fp, "Name of cardholder: ", "name", info.disp_name);
print_name (fp, "Language prefs ...: ", info.disp_lang);
tty_fprintf (fp, "Salutation .......: %s\n",
info.disp_sex == 1? _("Mr."):
info.disp_sex == 2? _("Mrs.") : "");
print_name (fp, "URL of public key : ", info.pubkey_url);
print_name (fp, "Login data .......: ", info.login_data);
if (info.private_do[0])
print_name (fp, "Private DO 1 .....: ", info.private_do[0]);
if (info.private_do[1])
print_name (fp, "Private DO 2 .....: ", info.private_do[1]);
if (info.private_do[2])
print_name (fp, "Private DO 3 .....: ", info.private_do[2]);
if (info.private_do[3])
print_name (fp, "Private DO 4 .....: ", info.private_do[3]);
if (info.cafpr1len)
{
tty_fprintf (fp, "CA fingerprint %d .:", 1);
print_shax_fpr (fp, info.cafpr1, info.cafpr1len);
}
if (info.cafpr2len)
{
tty_fprintf (fp, "CA fingerprint %d .:", 2);
print_shax_fpr (fp, info.cafpr2, info.cafpr2len);
}
if (info.cafpr3len)
{
tty_fprintf (fp, "CA fingerprint %d .:", 3);
print_shax_fpr (fp, info.cafpr3, info.cafpr3len);
}
tty_fprintf (fp, "Signature PIN ....: %s\n",
info.chv1_cached? _("not forced"): _("forced"));
if (info.key_attr[0].algo)
{
tty_fprintf (fp, "Key attributes ...:");
for (i=0; i < DIM (info.key_attr); i++)
if (info.key_attr[i].algo == PUBKEY_ALGO_RSA)
tty_fprintf (fp, " rsa%u", info.key_attr[i].nbits);
else if (info.key_attr[i].algo == PUBKEY_ALGO_ECDH
|| info.key_attr[i].algo == PUBKEY_ALGO_ECDSA
|| info.key_attr[i].algo == PUBKEY_ALGO_EDDSA)
{
const char *curve_for_print = "?";
if (info.key_attr[i].curve)
{
const char *oid;
oid = openpgp_curve_to_oid (info.key_attr[i].curve, NULL);
if (oid)
curve_for_print = openpgp_oid_to_curve (oid, 0);
}
tty_fprintf (fp, " %s", curve_for_print);
}
tty_fprintf (fp, "\n");
}
tty_fprintf (fp, "Max. PIN lengths .: %d %d %d\n",
info.chvmaxlen[0], info.chvmaxlen[1], info.chvmaxlen[2]);
tty_fprintf (fp, "PIN retry counter : %d %d %d\n",
info.chvretry[0], info.chvretry[1], info.chvretry[2]);
tty_fprintf (fp, "Signature counter : %lu\n", info.sig_counter);
if (info.extcap.kdf)
{
tty_fprintf (fp, "KDF setting ......: %s\n",
info.kdf_do_enabled ? "on" : "off");
}
if (info.extcap.bt)
{
tty_fprintf (fp, "UIF setting ......: Sign=%s Decrypt=%s Auth=%s\n",
info.uif[0] ? "on" : "off", info.uif[1] ? "on" : "off",
info.uif[2] ? "on" : "off");
}
tty_fprintf (fp, "Signature key ....:");
print_shax_fpr (fp, info.fpr1len? info.fpr1:NULL, info.fpr1len);
if (info.fpr1len && info.fpr1time)
{
tty_fprintf (fp, " created ....: %s\n",
isotimestamp (info.fpr1time));
print_keygrip (fp, info.grp1);
}
tty_fprintf (fp, "Encryption key....:");
print_shax_fpr (fp, info.fpr2len? info.fpr2:NULL, info.fpr2len);
if (info.fpr2len && info.fpr2time)
{
tty_fprintf (fp, " created ....: %s\n",
isotimestamp (info.fpr2time));
print_keygrip (fp, info.grp2);
}
tty_fprintf (fp, "Authentication key:");
print_shax_fpr (fp, info.fpr3len? info.fpr3:NULL, info.fpr3len);
if (info.fpr3len && info.fpr3time)
{
tty_fprintf (fp, " created ....: %s\n",
isotimestamp (info.fpr3time));
print_keygrip (fp, info.grp3);
}
tty_fprintf (fp, "General key info..: ");
thefpr = (info.fpr1len? info.fpr1 : info.fpr2len? info.fpr2 :
info.fpr3len? info.fpr3 : NULL);
thefprlen = (info.fpr1len? info.fpr1len : info.fpr2len? info.fpr2len :
info.fpr3len? info.fpr3len : 0);
/* If the fingerprint is all 0xff, the key has no associated
OpenPGP certificate. */
if ( thefpr && !fpr_is_ff (thefpr, thefprlen)
&& !get_pubkey_byfprint (ctrl, pk, &keyblock, thefpr, thefprlen))
{
- print_pubkey_info (ctrl, fp, pk);
- if (keyblock)
- print_card_key_info (fp, keyblock);
+ print_key_info (ctrl, fp, 0, pk, 0);
+ print_card_key_info (fp, keyblock);
}
else
tty_fprintf (fp, "[none]\n");
}
release_kbnode (keyblock);
free_public_key (pk);
agent_release_card_info (&info);
}
/* Print all available information for specific card with SERIALNO.
Print all available information for current card when SERIALNO is NULL.
Or print for all cards when SERIALNO is "all". */
void
card_status (ctrl_t ctrl, estream_t fp, const char *serialno)
{
int err;
strlist_t card_list, sl;
char *serialno0, *serialno1;
int all_cards = 0;
+ int any_card = 0;
if (serialno == NULL)
{
current_card_status (ctrl, fp, NULL, 0);
return;
}
if (!strcmp (serialno, "all"))
all_cards = 1;
err = agent_scd_serialno (&serialno0, NULL);
if (err)
{
if (gpg_err_code (err) != GPG_ERR_ENODEV && opt.verbose)
log_info (_("error getting serial number of card: %s\n"),
gpg_strerror (err));
/* Nothing available. */
return;
}
err = agent_scd_cardlist (&card_list);
for (sl = card_list; sl; sl = sl->next)
{
if (!all_cards && strcmp (serialno, sl->d))
continue;
+ if (any_card && !opt.with_colons)
+ tty_fprintf (fp, "\n");
+ any_card = 1;
+
err = agent_scd_serialno (&serialno1, sl->d);
if (err)
{
if (opt.verbose)
log_info (_("error getting serial number of card: %s\n"),
gpg_strerror (err));
continue;
}
current_card_status (ctrl, fp, NULL, 0);
xfree (serialno1);
if (!all_cards)
goto leave;
}
/* Select the original card again. */
err = agent_scd_serialno (&serialno1, serialno0);
xfree (serialno1);
leave:
xfree (serialno0);
free_strlist (card_list);
}
static char *
get_one_name (const char *prompt1, const char *prompt2)
{
char *name;
int i;
for (;;)
{
name = cpr_get (prompt1, prompt2);
if (!name)
return NULL;
trim_spaces (name);
cpr_kill_prompt ();
for (i=0; name[i] && name[i] >= ' ' && name[i] <= 126; i++)
;
/* The name must be in Latin-1 and not UTF-8 - lacking the code
to ensure this we restrict it to ASCII. */
if (name[i])
tty_printf (_("Error: Only plain ASCII is currently allowed.\n"));
else if (strchr (name, '<'))
tty_printf (_("Error: The \"<\" character may not be used.\n"));
else if (strstr (name, " "))
tty_printf (_("Error: Double spaces are not allowed.\n"));
else
return name;
xfree (name);
}
}
static int
change_name (void)
{
char *surname = NULL, *givenname = NULL;
char *isoname, *p;
int rc;
surname = get_one_name ("keygen.smartcard.surname",
_("Cardholder's surname: "));
givenname = get_one_name ("keygen.smartcard.givenname",
_("Cardholder's given name: "));
if (!surname || !givenname || (!*surname && !*givenname))
{
xfree (surname);
xfree (givenname);
return -1; /*canceled*/
}
isoname = xmalloc ( strlen (surname) + 2 + strlen (givenname) + 1);
strcpy (stpcpy (stpcpy (isoname, surname), "<<"), givenname);
xfree (surname);
xfree (givenname);
for (p=isoname; *p; p++)
if (*p == ' ')
*p = '<';
if (strlen (isoname) > 39 )
{
tty_printf (_("Error: Combined name too long "
"(limit is %d characters).\n"), 39);
xfree (isoname);
return -1;
}
- rc = agent_scd_setattr ("DISP-NAME", isoname, strlen (isoname), NULL );
+ rc = agent_scd_setattr ("DISP-NAME", isoname, strlen (isoname));
if (rc)
log_error ("error setting Name: %s\n", gpg_strerror (rc));
xfree (isoname);
return rc;
}
static int
change_url (void)
{
char *url;
int rc;
url = cpr_get ("cardedit.change_url", _("URL to retrieve public key: "));
if (!url)
return -1;
trim_spaces (url);
cpr_kill_prompt ();
- rc = agent_scd_setattr ("PUBKEY-URL", url, strlen (url), NULL );
+ rc = agent_scd_setattr ("PUBKEY-URL", url, strlen (url));
if (rc)
log_error ("error setting URL: %s\n", gpg_strerror (rc));
xfree (url);
write_sc_op_status (rc);
return rc;
}
/* Fetch the key from the URL given on the card or try to get it from
the default keyserver. */
static int
fetch_url (ctrl_t ctrl)
{
int rc;
struct agent_card_info_s info;
memset(&info,0,sizeof(info));
rc=agent_scd_getattr("PUBKEY-URL",&info);
if(rc)
log_error("error retrieving URL from card: %s\n",gpg_strerror(rc));
else
{
rc=agent_scd_getattr("KEY-FPR",&info);
if(rc)
log_error("error retrieving key fingerprint from card: %s\n",
gpg_strerror(rc));
else if (info.pubkey_url && *info.pubkey_url)
{
strlist_t sl = NULL;
add_to_strlist (&sl, info.pubkey_url);
rc = keyserver_fetch (ctrl, sl, KEYORG_URL);
free_strlist (sl);
}
else if (info.fpr1len)
{
rc = keyserver_import_fprint (ctrl, info.fpr1, info.fpr1len,
opt.keyserver, 0);
}
}
agent_release_card_info (&info);
return rc;
}
#define MAX_GET_DATA_FROM_FILE 16384
/* Read data from file FNAME up to MAX_GET_DATA_FROM_FILE characters.
On error return -1 and store NULL at R_BUFFER; on success return
the number of bytes read and store the address of a newly allocated
buffer at R_BUFFER. */
static int
get_data_from_file (const char *fname, char **r_buffer)
{
estream_t fp;
char *data;
int n;
*r_buffer = NULL;
fp = es_fopen (fname, "rb");
#if GNUPG_MAJOR_VERSION == 1
if (fp && is_secured_file (fileno (fp)))
{
fclose (fp);
fp = NULL;
errno = EPERM;
}
#endif
if (!fp)
{
tty_printf (_("can't open '%s': %s\n"), fname, strerror (errno));
return -1;
}
data = xtrymalloc (MAX_GET_DATA_FROM_FILE);
if (!data)
{
tty_printf (_("error allocating enough memory: %s\n"), strerror (errno));
es_fclose (fp);
return -1;
}
n = es_fread (data, 1, MAX_GET_DATA_FROM_FILE, fp);
es_fclose (fp);
if (n < 0)
{
tty_printf (_("error reading '%s': %s\n"), fname, strerror (errno));
xfree (data);
return -1;
}
*r_buffer = data;
return n;
}
/* Write LENGTH bytes from BUFFER to file FNAME. Return 0 on
success. */
static int
put_data_to_file (const char *fname, const void *buffer, size_t length)
{
estream_t fp;
fp = es_fopen (fname, "wb");
#if GNUPG_MAJOR_VERSION == 1
if (fp && is_secured_file (fileno (fp)))
{
fclose (fp);
fp = NULL;
errno = EPERM;
}
#endif
if (!fp)
{
tty_printf (_("can't create '%s': %s\n"), fname, strerror (errno));
return -1;
}
if (length && es_fwrite (buffer, length, 1, fp) != 1)
{
tty_printf (_("error writing '%s': %s\n"), fname, strerror (errno));
es_fclose (fp);
return -1;
}
es_fclose (fp);
return 0;
}
static int
change_login (const char *args)
{
char *data;
int n;
int rc;
if (args && *args == '<') /* Read it from a file */
{
for (args++; spacep (args); args++)
;
n = get_data_from_file (args, &data);
if (n < 0)
return -1;
}
else
{
data = cpr_get ("cardedit.change_login",
_("Login data (account name): "));
if (!data)
return -1;
trim_spaces (data);
cpr_kill_prompt ();
n = strlen (data);
}
- rc = agent_scd_setattr ("LOGIN-DATA", data, n, NULL );
+ rc = agent_scd_setattr ("LOGIN-DATA", data, n);
if (rc)
log_error ("error setting login data: %s\n", gpg_strerror (rc));
xfree (data);
write_sc_op_status (rc);
return rc;
}
static int
change_private_do (const char *args, int nr)
{
char do_name[] = "PRIVATE-DO-X";
char *data;
int n;
int rc;
log_assert (nr >= 1 && nr <= 4);
do_name[11] = '0' + nr;
if (args && (args = strchr (args, '<'))) /* Read it from a file */
{
for (args++; spacep (args); args++)
;
n = get_data_from_file (args, &data);
if (n < 0)
return -1;
}
else
{
data = cpr_get ("cardedit.change_private_do",
_("Private DO data: "));
if (!data)
return -1;
trim_spaces (data);
cpr_kill_prompt ();
n = strlen (data);
}
- rc = agent_scd_setattr (do_name, data, n, NULL );
+ rc = agent_scd_setattr (do_name, data, n);
if (rc)
log_error ("error setting private DO: %s\n", gpg_strerror (rc));
xfree (data);
write_sc_op_status (rc);
return rc;
}
static int
change_cert (const char *args)
{
char *data;
int n;
int rc;
if (args && *args == '<') /* Read it from a file */
{
for (args++; spacep (args); args++)
;
n = get_data_from_file (args, &data);
if (n < 0)
return -1;
}
else
{
tty_printf ("usage error: redirection to file required\n");
return -1;
}
rc = agent_scd_writecert ("OPENPGP.3", data, n);
if (rc)
log_error ("error writing certificate to card: %s\n", gpg_strerror (rc));
xfree (data);
write_sc_op_status (rc);
return rc;
}
static int
read_cert (const char *args)
{
const char *fname;
void *buffer;
size_t length;
int rc;
if (args && *args == '>') /* Write it to a file */
{
for (args++; spacep (args); args++)
;
fname = args;
}
else
{
tty_printf ("usage error: redirection to file required\n");
return -1;
}
rc = agent_scd_readcert ("OPENPGP.3", &buffer, &length);
if (rc)
log_error ("error reading certificate from card: %s\n", gpg_strerror (rc));
else
rc = put_data_to_file (fname, buffer, length);
xfree (buffer);
write_sc_op_status (rc);
return rc;
}
static int
change_lang (void)
{
char *data, *p;
int rc;
data = cpr_get ("cardedit.change_lang",
_("Language preferences: "));
if (!data)
return -1;
trim_spaces (data);
cpr_kill_prompt ();
if (strlen (data) > 8 || (strlen (data) & 1))
{
tty_printf (_("Error: invalid length of preference string.\n"));
xfree (data);
return -1;
}
for (p=data; *p && *p >= 'a' && *p <= 'z'; p++)
;
if (*p)
{
tty_printf (_("Error: invalid characters in preference string.\n"));
xfree (data);
return -1;
}
- rc = agent_scd_setattr ("DISP-LANG", data, strlen (data), NULL );
+ rc = agent_scd_setattr ("DISP-LANG", data, strlen (data));
if (rc)
log_error ("error setting lang: %s\n", gpg_strerror (rc));
xfree (data);
write_sc_op_status (rc);
return rc;
}
static int
change_sex (void)
{
char *data;
const char *str;
int rc;
data = cpr_get ("cardedit.change_sex",
_("Salutation (M = Mr., F = Mrs., or space): "));
if (!data)
return -1;
trim_spaces (data);
cpr_kill_prompt ();
if (!*data)
str = "9";
else if ((*data == 'M' || *data == 'm') && !data[1])
str = "1";
else if ((*data == 'F' || *data == 'f') && !data[1])
str = "2";
else
{
tty_printf (_("Error: invalid response.\n"));
xfree (data);
return -1;
}
- rc = agent_scd_setattr ("DISP-SEX", str, 1, NULL );
+ rc = agent_scd_setattr ("DISP-SEX", str, 1);
if (rc)
log_error ("error setting salutation: %s\n", gpg_strerror (rc));
xfree (data);
write_sc_op_status (rc);
return rc;
}
static int
change_cafpr (int fprno)
{
char *data;
const char *s;
int i, c, rc;
unsigned char fpr[MAX_FINGERPRINT_LEN];
int fprlen;
data = cpr_get ("cardedit.change_cafpr", _("CA fingerprint: "));
if (!data)
return -1;
trim_spaces (data);
cpr_kill_prompt ();
for (i=0, s=data; i < MAX_FINGERPRINT_LEN && *s; )
{
while (spacep(s))
s++;
if (*s == ':')
s++;
while (spacep(s))
s++;
c = hextobyte (s);
if (c == -1)
break;
fpr[i++] = c;
s += 2;
}
fprlen = i;
xfree (data);
if ((fprlen != 20 && fprlen != 32) || *s)
{
tty_printf (_("Error: invalid formatted fingerprint.\n"));
return -1;
}
rc = agent_scd_setattr (fprno==1?"CA-FPR-1":
fprno==2?"CA-FPR-2":
- fprno==3?"CA-FPR-3":"x", fpr, fprlen, NULL );
+ fprno==3?"CA-FPR-3":"x", fpr, fprlen);
if (rc)
log_error ("error setting cafpr: %s\n", gpg_strerror (rc));
write_sc_op_status (rc);
return rc;
}
static void
toggle_forcesig (void)
{
struct agent_card_info_s info;
int rc;
int newstate;
memset (&info, 0, sizeof info);
rc = agent_scd_getattr ("CHV-STATUS", &info);
if (rc)
{
log_error ("error getting current status: %s\n", gpg_strerror (rc));
return;
}
newstate = !info.chv1_cached;
agent_release_card_info (&info);
- rc = agent_scd_setattr ("CHV-STATUS-1", newstate? "\x01":"", 1, NULL);
+ rc = agent_scd_setattr ("CHV-STATUS-1", newstate? "\x01":"", 1);
if (rc)
log_error ("error toggling signature PIN flag: %s\n", gpg_strerror (rc));
write_sc_op_status (rc);
}
/* Helper for the key generation/edit functions. */
static int
get_info_for_key_operation (struct agent_card_info_s *info)
{
int rc;
memset (info, 0, sizeof *info);
rc = agent_scd_getattr ("SERIALNO", info);
if (rc || !info->serialno || strncmp (info->serialno, "D27600012401", 12)
|| strlen (info->serialno) != 32 )
{
log_error (_("key operation not possible: %s\n"),
rc ? gpg_strerror (rc) : _("not an OpenPGP card"));
return rc? rc: -1;
}
rc = agent_scd_getattr ("KEY-FPR", info);
if (!rc)
rc = agent_scd_getattr ("CHV-STATUS", info);
if (!rc)
rc = agent_scd_getattr ("DISP-NAME", info);
if (!rc)
rc = agent_scd_getattr ("EXTCAP", info);
if (!rc)
rc = agent_scd_getattr ("KEY-ATTR", info);
if (rc)
log_error (_("error getting current key info: %s\n"), gpg_strerror (rc));
return rc;
}
/* Helper for the key generation/edit functions. */
static int
check_pin_for_key_operation (struct agent_card_info_s *info, int *forced_chv1)
{
int rc = 0;
- agent_clear_pin_cache (info->serialno);
-
*forced_chv1 = !info->chv1_cached;
if (*forced_chv1)
{ /* Switch off the forced mode so that during key generation we
don't get bothered with PIN queries for each
self-signature. */
- rc = agent_scd_setattr ("CHV-STATUS-1", "\x01", 1, info->serialno);
+ rc = agent_scd_setattr ("CHV-STATUS-1", "\x01", 1);
if (rc)
{
log_error ("error clearing forced signature PIN flag: %s\n",
gpg_strerror (rc));
*forced_chv1 = 0;
}
}
if (!rc)
{
/* Check the PIN now, so that we won't get asked later for each
binding signature. */
rc = agent_scd_checkpin (info->serialno);
if (rc)
{
log_error ("error checking the PIN: %s\n", gpg_strerror (rc));
write_sc_op_status (rc);
}
}
return rc;
}
/* Helper for the key generation/edit functions. */
static void
restore_forced_chv1 (int *forced_chv1)
{
int rc;
if (*forced_chv1)
{ /* Switch back to forced state. */
- rc = agent_scd_setattr ("CHV-STATUS-1", "", 1, NULL);
+ rc = agent_scd_setattr ("CHV-STATUS-1", "", 1);
if (rc)
{
log_error ("error setting forced signature PIN flag: %s\n",
gpg_strerror (rc));
}
}
}
/* Helper for the key generation/edit functions. */
static void
show_card_key_info (struct agent_card_info_s *info)
{
tty_fprintf (NULL, "Signature key ....:");
print_shax_fpr (NULL, info->fpr1len? info->fpr1:NULL, info->fpr1len);
tty_fprintf (NULL, "Encryption key....:");
print_shax_fpr (NULL, info->fpr2len? info->fpr2:NULL, info->fpr2len);
tty_fprintf (NULL, "Authentication key:");
print_shax_fpr (NULL, info->fpr3len? info->fpr3:NULL, info->fpr3len);
tty_printf ("\n");
}
/* Helper for the key generation/edit functions. */
static int
replace_existing_key_p (struct agent_card_info_s *info, int keyno)
{
log_assert (keyno >= 0 && keyno <= 3);
if ((keyno == 1 && info->fpr1len)
|| (keyno == 2 && info->fpr2len)
|| (keyno == 3 && info->fpr3len))
{
tty_printf ("\n");
log_info ("WARNING: such a key has already been stored on the card!\n");
tty_printf ("\n");
if ( !cpr_get_answer_is_yes( "cardedit.genkeys.replace_key",
_("Replace existing key? (y/N) ")))
return -1;
return 1;
}
return 0;
}
static void
show_keysize_warning (void)
{
static int shown;
if (shown)
return;
shown = 1;
tty_printf
(_("Note: There is no guarantee that the card supports the requested\n"
" key type or size. If the key generation does not succeed,\n"
" please check the documentation of your card to see which\n"
" key types and sizes are supported.\n")
);
}
/* Ask for the size of a card key. NBITS is the current size
configured for the card. Returns 0 to use the default size
(i.e. NBITS) or the selected size. */
static unsigned int
ask_card_rsa_keysize (unsigned int nbits)
{
unsigned int min_nbits = 1024;
unsigned int max_nbits = 4096;
char *prompt, *answer;
unsigned int req_nbits;
for (;;)
{
prompt = xasprintf (_("What keysize do you want? (%u) "), nbits);
answer = cpr_get ("cardedit.genkeys.size", prompt);
cpr_kill_prompt ();
req_nbits = *answer? atoi (answer): nbits;
xfree (prompt);
xfree (answer);
if (req_nbits != nbits && (req_nbits % 32) )
{
req_nbits = ((req_nbits + 31) / 32) * 32;
tty_printf (_("rounded up to %u bits\n"), req_nbits);
}
if (req_nbits == nbits)
return 0; /* Use default. */
if (req_nbits < min_nbits || req_nbits > max_nbits)
{
tty_printf (_("%s keysizes must be in the range %u-%u\n"),
"RSA", min_nbits, max_nbits);
}
else
return req_nbits;
}
}
/* Ask for the key attribute of a card key. CURRENT is the current
attribute configured for the card. KEYNO is the number of the key
used to select the prompt. Returns NULL to use the default
attribute or the selected attribute structure. */
static struct key_attr *
ask_card_keyattr (int keyno, const struct key_attr *current)
{
struct key_attr *key_attr = NULL;
char *answer = NULL;
int algo;
tty_printf (_("Changing card key attribute for: "));
if (keyno == 0)
tty_printf (_("Signature key\n"));
else if (keyno == 1)
tty_printf (_("Encryption key\n"));
else
tty_printf (_("Authentication key\n"));
tty_printf (_("Please select what kind of key you want:\n"));
tty_printf (_(" (%d) RSA\n"), 1 );
tty_printf (_(" (%d) ECC\n"), 2 );
for (;;)
{
xfree (answer);
answer = cpr_get ("cardedit.genkeys.algo", _("Your selection? "));
cpr_kill_prompt ();
algo = *answer? atoi (answer) : 0;
if (!*answer || algo == 1 || algo == 2)
break;
else
tty_printf (_("Invalid selection.\n"));
}
if (algo == 0)
goto leave;
key_attr = xmalloc (sizeof (struct key_attr));
if (algo == 1)
{
unsigned int nbits, result_nbits;
if (current->algo == PUBKEY_ALGO_RSA)
nbits = current->nbits;
else
nbits = 2048;
result_nbits = ask_card_rsa_keysize (nbits);
if (result_nbits == 0)
{
if (current->algo == PUBKEY_ALGO_RSA)
{
xfree (key_attr);
key_attr = NULL;
}
else
result_nbits = nbits;
}
if (key_attr)
{
key_attr->algo = PUBKEY_ALGO_RSA;
key_attr->nbits = result_nbits;
}
}
else
{
const char *curve;
const char *oid_str;
if (current->algo == PUBKEY_ALGO_RSA)
{
if (keyno == 1)
/* Encryption key */
algo = PUBKEY_ALGO_ECDH;
else /* Signature key or Authentication key */
algo = PUBKEY_ALGO_ECDSA;
curve = NULL;
}
else
{
algo = current->algo;
curve = current->curve;
}
curve = ask_curve (&algo, NULL, curve);
if (curve)
{
key_attr->algo = algo;
oid_str = openpgp_curve_to_oid (curve, NULL);
key_attr->curve = openpgp_oid_to_curve (oid_str, 0);
}
else
{
xfree (key_attr);
key_attr = NULL;
}
}
leave:
if (key_attr)
{
if (key_attr->algo == PUBKEY_ALGO_RSA)
tty_printf (_("The card will now be re-configured"
" to generate a key of %u bits\n"), key_attr->nbits);
else if (key_attr->algo == PUBKEY_ALGO_ECDH
|| key_attr->algo == PUBKEY_ALGO_ECDSA
|| key_attr->algo == PUBKEY_ALGO_EDDSA)
tty_printf (_("The card will now be re-configured"
" to generate a key of type: %s\n"), key_attr->curve),
show_keysize_warning ();
}
return key_attr;
}
/* Change the key attribute of key KEYNO (0..2) and show an error
* message if that fails. */
static gpg_error_t
do_change_keyattr (int keyno, const struct key_attr *key_attr)
{
gpg_error_t err = 0;
char args[100];
if (key_attr->algo == PUBKEY_ALGO_RSA)
snprintf (args, sizeof args, "--force %d 1 rsa%u", keyno+1,
key_attr->nbits);
else if (key_attr->algo == PUBKEY_ALGO_ECDH
|| key_attr->algo == PUBKEY_ALGO_ECDSA
|| key_attr->algo == PUBKEY_ALGO_EDDSA)
snprintf (args, sizeof args, "--force %d %d %s",
keyno+1, key_attr->algo, key_attr->curve);
else
{
log_error (_("public key algorithm %d (%s) is not supported\n"),
key_attr->algo, gcry_pk_algo_name (key_attr->algo));
return gpg_error (GPG_ERR_PUBKEY_ALGO);
}
- err = agent_scd_setattr ("KEY-ATTR", args, strlen (args), NULL);
+ err = agent_scd_setattr ("KEY-ATTR", args, strlen (args));
if (err)
log_error (_("error changing key attribute for key %d: %s\n"),
keyno+1, gpg_strerror (err));
return err;
}
static void
key_attr (void)
{
struct agent_card_info_s info;
gpg_error_t err;
int keyno;
err = get_info_for_key_operation (&info);
if (err)
{
log_error (_("error getting card info: %s\n"), gpg_strerror (err));
return;
}
if (!(info.is_v2 && info.extcap.aac))
{
log_error (_("This command is not supported by this card\n"));
goto leave;
}
for (keyno = 0; keyno < DIM (info.key_attr); keyno++)
{
struct key_attr *key_attr;
if ((key_attr = ask_card_keyattr (keyno, &info.key_attr[keyno])))
{
err = do_change_keyattr (keyno, key_attr);
xfree (key_attr);
if (err)
{
/* Error: Better read the default key attribute again. */
agent_release_card_info (&info);
if (get_info_for_key_operation (&info))
goto leave;
/* Ask again for this key. */
keyno--;
}
}
}
leave:
agent_release_card_info (&info);
}
static void
generate_card_keys (ctrl_t ctrl)
{
struct agent_card_info_s info;
int forced_chv1;
int want_backup;
if (get_info_for_key_operation (&info))
return;
if (info.extcap.ki)
{
char *answer;
/* FIXME: Should be something like cpr_get_bool so that a status
GET_BOOL will be emitted. */
answer = cpr_get ("cardedit.genkeys.backup_enc",
_("Make off-card backup of encryption key? (Y/n) "));
want_backup = answer_is_yes_no_default (answer, 1/*(default to Yes)*/);
cpr_kill_prompt ();
xfree (answer);
}
else
want_backup = 0;
if ( (info.fpr1len && !fpr_is_zero (info.fpr1, info.fpr1len))
|| (info.fpr2len && !fpr_is_zero (info.fpr2, info.fpr2len))
|| (info.fpr3len && !fpr_is_zero (info.fpr3, info.fpr3len)))
{
tty_printf ("\n");
log_info (_("Note: keys are already stored on the card!\n"));
tty_printf ("\n");
if ( !cpr_get_answer_is_yes ("cardedit.genkeys.replace_keys",
_("Replace existing keys? (y/N) ")))
{
agent_release_card_info (&info);
return;
}
}
/* If no displayed name has been set, we assume that this is a fresh
card and print a hint about the default PINs. */
if (!info.disp_name || !*info.disp_name)
{
tty_printf ("\n");
tty_printf (_("Please note that the factory settings of the PINs are\n"
" PIN = '%s' Admin PIN = '%s'\n"
"You should change them using the command --change-pin\n"),
"123456", "12345678");
tty_printf ("\n");
}
if (check_pin_for_key_operation (&info, &forced_chv1))
goto leave;
generate_keypair (ctrl, 1, NULL, info.serialno, want_backup);
leave:
agent_release_card_info (&info);
restore_forced_chv1 (&forced_chv1);
}
/* This function is used by the key edit menu to generate an arbitrary
subkey. */
gpg_error_t
card_generate_subkey (ctrl_t ctrl, kbnode_t pub_keyblock)
{
gpg_error_t err;
struct agent_card_info_s info;
int forced_chv1 = 0;
int keyno;
err = get_info_for_key_operation (&info);
if (err)
return err;
show_card_key_info (&info);
tty_printf (_("Please select the type of key to generate:\n"));
tty_printf (_(" (1) Signature key\n"));
tty_printf (_(" (2) Encryption key\n"));
tty_printf (_(" (3) Authentication key\n"));
for (;;)
{
char *answer = cpr_get ("cardedit.genkeys.subkeytype",
_("Your selection? "));
cpr_kill_prompt();
if (*answer == CONTROL_D)
{
xfree (answer);
err = gpg_error (GPG_ERR_CANCELED);
goto leave;
}
keyno = *answer? atoi(answer): 0;
xfree(answer);
if (keyno >= 1 && keyno <= 3)
break; /* Okay. */
tty_printf(_("Invalid selection.\n"));
}
if (replace_existing_key_p (&info, keyno) < 0)
{
err = gpg_error (GPG_ERR_CANCELED);
goto leave;
}
err = check_pin_for_key_operation (&info, &forced_chv1);
if (err)
goto leave;
err = generate_card_subkeypair (ctrl, pub_keyblock, keyno, info.serialno);
leave:
agent_release_card_info (&info);
restore_forced_chv1 (&forced_chv1);
return err;
}
/* Store the key at NODE into the smartcard and modify NODE to
carry the serialno stuff instead of the actual secret key
parameters. USE is the usage for that key; 0 means any
usage. */
int
card_store_subkey (KBNODE node, int use)
{
struct agent_card_info_s info;
int okay = 0;
unsigned int nbits;
int allow_keyno[3];
int keyno;
PKT_public_key *pk;
gpg_error_t err;
char *hexgrip;
int rc;
gnupg_isotime_t timebuf;
log_assert (node->pkt->pkttype == PKT_PUBLIC_KEY
|| node->pkt->pkttype == PKT_PUBLIC_SUBKEY);
pk = node->pkt->pkt.public_key;
if (get_info_for_key_operation (&info))
return 0;
if (!info.extcap.ki)
{
tty_printf ("The card does not support the import of keys\n");
tty_printf ("\n");
goto leave;
}
nbits = nbits_from_pk (pk);
if (!info.is_v2 && nbits != 1024)
{
tty_printf ("You may only store a 1024 bit RSA key on the card\n");
tty_printf ("\n");
goto leave;
}
allow_keyno[0] = (!use || (use & (PUBKEY_USAGE_SIG|PUBKEY_USAGE_CERT)));
allow_keyno[1] = (!use || (use & (PUBKEY_USAGE_ENC)));
allow_keyno[2] = (!use || (use & (PUBKEY_USAGE_SIG|PUBKEY_USAGE_AUTH)));
tty_printf (_("Please select where to store the key:\n"));
if (allow_keyno[0])
tty_printf (_(" (1) Signature key\n"));
if (allow_keyno[1])
tty_printf (_(" (2) Encryption key\n"));
if (allow_keyno[2])
tty_printf (_(" (3) Authentication key\n"));
for (;;)
{
char *answer = cpr_get ("cardedit.genkeys.storekeytype",
_("Your selection? "));
cpr_kill_prompt();
if (*answer == CONTROL_D || !*answer)
{
xfree (answer);
goto leave;
}
keyno = *answer? atoi(answer): 0;
xfree(answer);
if (keyno >= 1 && keyno <= 3 && allow_keyno[keyno-1])
{
if (info.is_v2 && !info.extcap.aac
&& info.key_attr[keyno-1].nbits != nbits)
{
tty_printf ("Key does not match the card's capability.\n");
}
else
break; /* Okay. */
}
else
tty_printf(_("Invalid selection.\n"));
}
if ((rc = replace_existing_key_p (&info, keyno)) < 0)
goto leave;
err = hexkeygrip_from_pk (pk, &hexgrip);
if (err)
goto leave;
epoch2isotime (timebuf, (time_t)pk->timestamp);
rc = agent_keytocard (hexgrip, keyno, rc, info.serialno, timebuf);
if (rc)
log_error (_("KEYTOCARD failed: %s\n"), gpg_strerror (rc));
else
okay = 1;
xfree (hexgrip);
leave:
agent_release_card_info (&info);
return okay;
}
/* Direct sending of an hex encoded APDU with error printing. */
static gpg_error_t
send_apdu (const char *hexapdu, const char *desc, unsigned int ignore)
{
gpg_error_t err;
unsigned int sw;
err = agent_scd_apdu (hexapdu, &sw);
if (err)
tty_printf ("sending card command %s failed: %s\n", desc,
gpg_strerror (err));
else if (!hexapdu || !strcmp (hexapdu, "undefined"))
;
else if (ignore == 0xffff)
; /* Ignore all status words. */
else if (sw != 0x9000)
{
switch (sw)
{
case 0x6285: err = gpg_error (GPG_ERR_OBJ_TERM_STATE); break;
case 0x6982: err = gpg_error (GPG_ERR_BAD_PIN); break;
case 0x6985: err = gpg_error (GPG_ERR_USE_CONDITIONS); break;
default: err = gpg_error (GPG_ERR_CARD);
}
if (!(ignore && ignore == sw))
tty_printf ("card command %s failed: %s (0x%04x)\n", desc,
gpg_strerror (err), sw);
}
return err;
}
/* Do a factory reset after confirmation. */
static void
factory_reset (void)
{
struct agent_card_info_s info;
gpg_error_t err;
char *answer = NULL;
int termstate = 0;
int i;
/* The code below basically does the same what this
gpg-connect-agent script does:
scd reset
scd serialno undefined
scd apdu 00 A4 04 00 06 D2 76 00 01 24 01
scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40
scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40
scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40
scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40
scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40
scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40
scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40
scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40
scd apdu 00 e6 00 00
scd apdu 00 44 00 00
scd reset
/echo Card has been reset to factory defaults
but tries to find out something about the card first.
*/
err = agent_scd_learn (&info, 0);
if (gpg_err_code (err) == GPG_ERR_OBJ_TERM_STATE
&& gpg_err_source (err) == GPG_ERR_SOURCE_SCD)
termstate = 1;
else if (err)
{
log_error (_("OpenPGP card not available: %s\n"), gpg_strerror (err));
goto leave;
}
if (!termstate)
{
log_info (_("OpenPGP card no. %s detected\n"),
info.serialno? info.serialno : "[none]");
if (!(info.status_indicator == 3 || info.status_indicator == 5))
{
/* Note: We won't see status-indicator 3 here because it is not
possible to select a card application in termination state. */
log_error (_("This command is not supported by this card\n"));
goto leave;
}
tty_printf ("\n");
log_info (_("Note: This command destroys all keys stored on the card!\n"));
tty_printf ("\n");
if (!cpr_get_answer_is_yes ("cardedit.factory-reset.proceed",
_("Continue? (y/N) ")))
goto leave;
answer = cpr_get ("cardedit.factory-reset.really",
_("Really do a factory reset? (enter \"yes\") "));
cpr_kill_prompt ();
trim_spaces (answer);
if (strcmp (answer, "yes"))
goto leave;
/* We need to select a card application before we can send APDUs
to the card without scdaemon doing anything on its own. */
err = send_apdu (NULL, "RESET", 0);
if (err)
goto leave;
err = send_apdu ("undefined", "dummy select ", 0);
if (err)
goto leave;
/* Select the OpenPGP application. */
err = send_apdu ("00A4040006D27600012401", "SELECT AID", 0);
if (err)
goto leave;
/* Do some dummy verifies with wrong PINs to set the retry
counter to zero. We can't easily use the card version 2.1
feature of presenting the admin PIN to allow the terminate
command because there is no machinery in scdaemon to catch
the verify command and ask for the PIN when the "APDU"
command is used. */
/* Here, the length of dummy wrong PIN is 32-byte, also
supporting authentication with KDF DO. */
for (i=0; i < 4; i++)
send_apdu ("0020008120"
"40404040404040404040404040404040"
"40404040404040404040404040404040", "VERIFY", 0xffff);
for (i=0; i < 4; i++)
send_apdu ("0020008320"
"40404040404040404040404040404040"
"40404040404040404040404040404040", "VERIFY", 0xffff);
/* Send terminate datafile command. */
err = send_apdu ("00e60000", "TERMINATE DF", 0x6985);
if (err)
goto leave;
}
/* Send activate datafile command. This is used without
confirmation if the card is already in termination state. */
err = send_apdu ("00440000", "ACTIVATE DF", 0);
if (err)
goto leave;
/* Finally we reset the card reader once more. */
err = send_apdu (NULL, "RESET", 0);
/* Then, connect the card again. */
if (!err)
{
char *serialno0;
err = agent_scd_serialno (&serialno0, NULL);
if (!err)
xfree (serialno0);
}
leave:
xfree (answer);
agent_release_card_info (&info);
}
#define USER_PIN_DEFAULT "123456"
#define ADMIN_PIN_DEFAULT "12345678"
#define KDF_DATA_LENGTH_MIN 90
#define KDF_DATA_LENGTH_MAX 110
/* Generate KDF data. */
static gpg_error_t
gen_kdf_data (unsigned char *data, int single_salt)
{
const unsigned char h0[] = { 0x81, 0x01, 0x03,
0x82, 0x01, 0x08,
0x83, 0x04 };
const unsigned char h1[] = { 0x84, 0x08 };
const unsigned char h2[] = { 0x85, 0x08 };
const unsigned char h3[] = { 0x86, 0x08 };
const unsigned char h4[] = { 0x87, 0x20 };
const unsigned char h5[] = { 0x88, 0x20 };
unsigned char *p, *salt_user, *salt_admin;
unsigned char s2k_char;
unsigned int iterations;
unsigned char count_4byte[4];
gpg_error_t err = 0;
p = data;
s2k_char = encode_s2k_iterations (agent_get_s2k_count ());
iterations = S2K_DECODE_COUNT (s2k_char);
count_4byte[0] = (iterations >> 24) & 0xff;
count_4byte[1] = (iterations >> 16) & 0xff;
count_4byte[2] = (iterations >> 8) & 0xff;
count_4byte[3] = (iterations & 0xff);
memcpy (p, h0, sizeof h0);
p += sizeof h0;
memcpy (p, count_4byte, sizeof count_4byte);
p += sizeof count_4byte;
memcpy (p, h1, sizeof h1);
salt_user = (p += sizeof h1);
gcry_randomize (p, 8, GCRY_STRONG_RANDOM);
p += 8;
if (single_salt)
salt_admin = salt_user;
else
{
memcpy (p, h2, sizeof h2);
p += sizeof h2;
gcry_randomize (p, 8, GCRY_STRONG_RANDOM);
p += 8;
memcpy (p, h3, sizeof h3);
salt_admin = (p += sizeof h3);
gcry_randomize (p, 8, GCRY_STRONG_RANDOM);
p += 8;
}
memcpy (p, h4, sizeof h4);
p += sizeof h4;
err = gcry_kdf_derive (USER_PIN_DEFAULT, strlen (USER_PIN_DEFAULT),
GCRY_KDF_ITERSALTED_S2K, DIGEST_ALGO_SHA256,
salt_user, 8, iterations, 32, p);
p += 32;
if (!err)
{
memcpy (p, h5, sizeof h5);
p += sizeof h5;
err = gcry_kdf_derive (ADMIN_PIN_DEFAULT, strlen (ADMIN_PIN_DEFAULT),
GCRY_KDF_ITERSALTED_S2K, DIGEST_ALGO_SHA256,
salt_admin, 8, iterations, 32, p);
}
return err;
}
/* Setup KDF data object which is used for PIN authentication. */
static void
kdf_setup (const char *args)
{
struct agent_card_info_s info;
gpg_error_t err;
unsigned char kdf_data[KDF_DATA_LENGTH_MAX];
int single = (*args != 0);
memset (&info, 0, sizeof info);
err = agent_scd_getattr ("EXTCAP", &info);
if (err)
{
log_error (_("error getting card info: %s\n"), gpg_strerror (err));
return;
}
if (!info.extcap.kdf)
{
log_error (_("This command is not supported by this card\n"));
goto leave;
}
err = gen_kdf_data (kdf_data, single);
if (err)
goto leave_error;
err = agent_scd_setattr ("KDF", kdf_data,
- single ? KDF_DATA_LENGTH_MIN : KDF_DATA_LENGTH_MAX,
- NULL);
+ single ? KDF_DATA_LENGTH_MIN : KDF_DATA_LENGTH_MAX);
if (err)
goto leave_error;
err = agent_scd_getattr ("KDF", &info);
leave_error:
if (err)
log_error (_("error for setup KDF: %s\n"), gpg_strerror (err));
leave:
agent_release_card_info (&info);
}
static void
uif (int arg_number, const char *arg_rest)
{
struct agent_card_info_s info;
int feature_available;
gpg_error_t err;
char name[100];
unsigned char data[2];
memset (&info, 0, sizeof info);
err = agent_scd_getattr ("EXTCAP", &info);
if (err)
{
log_error (_("error getting card info: %s\n"), gpg_strerror (err));
return;
}
feature_available = info.extcap.bt;
agent_release_card_info (&info);
if (!feature_available)
{
log_error (_("This command is not supported by this card\n"));
tty_printf ("\n");
return;
}
snprintf (name, sizeof name, "UIF-%d", arg_number);
if ( !strcmp (arg_rest, "off") )
data[0] = 0x00;
else if ( !strcmp (arg_rest, "on") )
data[0] = 0x01;
else if ( !strcmp (arg_rest, "permanent") )
data[0] = 0x02;
data[1] = 0x20;
- err = agent_scd_setattr (name, data, 2, NULL);
+ err = agent_scd_setattr (name, data, 2);
if (err)
log_error (_("error for setup UIF: %s\n"), gpg_strerror (err));
}
/* Data used by the command parser. This needs to be outside of the
function scope to allow readline based command completion. */
enum cmdids
{
cmdNOP = 0,
cmdQUIT, cmdADMIN, cmdHELP, cmdLIST, cmdDEBUG, cmdVERIFY,
cmdNAME, cmdURL, cmdFETCH, cmdLOGIN, cmdLANG, cmdSEX, cmdCAFPR,
cmdFORCESIG, cmdGENERATE, cmdPASSWD, cmdPRIVATEDO, cmdWRITECERT,
cmdREADCERT, cmdUNBLOCK, cmdFACTORYRESET, cmdKDFSETUP,
cmdKEYATTR, cmdUIF,
cmdINVCMD
};
static struct
{
const char *name;
enum cmdids id;
int admin_only;
const char *desc;
} cmds[] =
{
{ "quit" , cmdQUIT , 0, N_("quit this menu")},
{ "q" , cmdQUIT , 0, NULL },
{ "admin" , cmdADMIN , 0, N_("show admin commands")},
{ "help" , cmdHELP , 0, N_("show this help")},
{ "?" , cmdHELP , 0, NULL },
{ "list" , cmdLIST , 0, N_("list all available data")},
{ "l" , cmdLIST , 0, NULL },
{ "debug" , cmdDEBUG , 0, NULL },
{ "name" , cmdNAME , 1, N_("change card holder's name")},
{ "url" , cmdURL , 1, N_("change URL to retrieve key")},
{ "fetch" , cmdFETCH , 0, N_("fetch the key specified in the card URL")},
{ "login" , cmdLOGIN , 1, N_("change the login name")},
{ "lang" , cmdLANG , 1, N_("change the language preferences")},
{ "salutation",cmdSEX , 1, N_("change card holder's salutation")},
{ "sex" ,cmdSEX , 1, NULL }, /* Backward compatibility. */
{ "cafpr" , cmdCAFPR , 1, N_("change a CA fingerprint")},
{ "forcesig", cmdFORCESIG, 1, N_("toggle the signature force PIN flag")},
{ "generate", cmdGENERATE, 1, N_("generate new keys")},
{ "passwd" , cmdPASSWD, 0, N_("menu to change or unblock the PIN")},
{ "verify" , cmdVERIFY, 0, N_("verify the PIN and list all data")},
{ "unblock" , cmdUNBLOCK,0, N_("unblock the PIN using a Reset Code")},
{ "factory-reset", cmdFACTORYRESET, 1, N_("destroy all keys and data")},
{ "kdf-setup", cmdKDFSETUP, 1, N_("setup KDF for PIN authentication")},
{ "key-attr", cmdKEYATTR, 1, N_("change the key attribute")},
{ "uif", cmdUIF, 1, N_("change the User Interaction Flag")},
/* Note, that we do not announce these command yet. */
{ "privatedo", cmdPRIVATEDO, 0, NULL },
{ "readcert", cmdREADCERT, 0, NULL },
{ "writecert", cmdWRITECERT, 1, NULL },
{ NULL, cmdINVCMD, 0, NULL }
};
#ifdef HAVE_LIBREADLINE
/* These two functions are used by readline for command completion. */
static char *
command_generator(const char *text,int state)
{
static int list_index,len;
const char *name;
/* If this is a new word to complete, initialize now. This includes
saving the length of TEXT for efficiency, and initializing the
index variable to 0. */
if(!state)
{
list_index=0;
len=strlen(text);
}
/* Return the next partial match */
while((name=cmds[list_index].name))
{
/* Only complete commands that have help text */
if(cmds[list_index++].desc && strncmp(name,text,len)==0)
return strdup(name);
}
return NULL;
}
static char **
card_edit_completion(const char *text, int start, int end)
{
(void)end;
/* If we are at the start of a line, we try and command-complete.
If not, just do nothing for now. */
if(start==0)
return rl_completion_matches(text,command_generator);
rl_attempted_completion_over=1;
return NULL;
}
#endif /*HAVE_LIBREADLINE*/
/* Menu to edit all user changeable values on an OpenPGP card. Only
Key creation is not handled here. */
void
card_edit (ctrl_t ctrl, strlist_t commands)
{
enum cmdids cmd = cmdNOP;
int have_commands = !!commands;
int redisplay = 1;
char *answer = NULL;
int allow_admin=0;
char serialnobuf[50];
if (opt.command_fd != -1)
;
else if (opt.batch && !have_commands)
{
log_error(_("can't do this in batch mode\n"));
goto leave;
}
for (;;)
{
int arg_number;
const char *arg_string = "";
const char *arg_rest = "";
char *p;
int i;
int cmd_admin_only;
tty_printf("\n");
if (redisplay)
{
if (opt.with_colons)
{
current_card_status (ctrl, es_stdout,
serialnobuf, DIM (serialnobuf));
fflush (stdout);
}
else
{
current_card_status (ctrl, NULL,
serialnobuf, DIM (serialnobuf));
tty_printf("\n");
}
redisplay = 0;
}
do
{
xfree (answer);
if (have_commands)
{
if (commands)
{
answer = xstrdup (commands->d);
commands = commands->next;
}
else if (opt.batch)
{
answer = xstrdup ("quit");
}
else
have_commands = 0;
}
if (!have_commands)
{
tty_enable_completion (card_edit_completion);
answer = cpr_get_no_help("cardedit.prompt", _("gpg/card> "));
cpr_kill_prompt();
tty_disable_completion ();
}
trim_spaces(answer);
}
while ( *answer == '#' );
arg_number = 0; /* Yes, here is the init which egcc complains about */
cmd_admin_only = 0;
if (!*answer)
cmd = cmdLIST; /* Default to the list command */
else if (*answer == CONTROL_D)
cmd = cmdQUIT;
else
{
if ((p=strchr (answer,' ')))
{
*p++ = 0;
trim_spaces (answer);
trim_spaces (p);
arg_number = atoi(p);
arg_string = p;
arg_rest = p;
while (digitp (arg_rest))
arg_rest++;
while (spacep (arg_rest))
arg_rest++;
}
for (i=0; cmds[i].name; i++ )
if (!ascii_strcasecmp (answer, cmds[i].name ))
break;
cmd = cmds[i].id;
cmd_admin_only = cmds[i].admin_only;
}
if (!allow_admin && cmd_admin_only)
{
tty_printf ("\n");
tty_printf (_("Admin-only command\n"));
continue;
}
switch (cmd)
{
case cmdHELP:
for (i=0; cmds[i].name; i++ )
if(cmds[i].desc
&& (!cmds[i].admin_only || (cmds[i].admin_only && allow_admin)))
tty_printf("%-14s %s\n", cmds[i].name, _(cmds[i].desc) );
break;
case cmdADMIN:
if ( !strcmp (arg_string, "on") )
allow_admin = 1;
else if ( !strcmp (arg_string, "off") )
allow_admin = 0;
else if ( !strcmp (arg_string, "verify") )
{
/* Force verification of the Admin Command. However,
this is only done if the retry counter is at initial
state. */
char *tmp = xmalloc (strlen (serialnobuf) + 6 + 1);
strcpy (stpcpy (tmp, serialnobuf), "[CHV3]");
allow_admin = !agent_scd_checkpin (tmp);
xfree (tmp);
}
else /* Toggle. */
allow_admin=!allow_admin;
if(allow_admin)
tty_printf(_("Admin commands are allowed\n"));
else
tty_printf(_("Admin commands are not allowed\n"));
break;
case cmdVERIFY:
agent_scd_checkpin (serialnobuf);
redisplay = 1;
break;
case cmdLIST:
redisplay = 1;
break;
case cmdNAME:
change_name ();
break;
case cmdURL:
change_url ();
break;
case cmdFETCH:
fetch_url (ctrl);
break;
case cmdLOGIN:
change_login (arg_string);
break;
case cmdLANG:
change_lang ();
break;
case cmdSEX:
change_sex ();
break;
case cmdCAFPR:
if ( arg_number < 1 || arg_number > 3 )
tty_printf ("usage: cafpr N\n"
" 1 <= N <= 3\n");
else
change_cafpr (arg_number);
break;
case cmdPRIVATEDO:
if ( arg_number < 1 || arg_number > 4 )
tty_printf ("usage: privatedo N\n"
" 1 <= N <= 4\n");
else
change_private_do (arg_string, arg_number);
break;
case cmdWRITECERT:
if ( arg_number != 3 )
tty_printf ("usage: writecert 3 < FILE\n");
else
change_cert (arg_rest);
break;
case cmdREADCERT:
if ( arg_number != 3 )
tty_printf ("usage: readcert 3 > FILE\n");
else
read_cert (arg_rest);
break;
case cmdFORCESIG:
toggle_forcesig ();
break;
case cmdGENERATE:
generate_card_keys (ctrl);
break;
case cmdPASSWD:
change_pin (0, allow_admin);
break;
case cmdUNBLOCK:
change_pin (1, allow_admin);
break;
case cmdFACTORYRESET:
factory_reset ();
break;
case cmdKDFSETUP:
kdf_setup (arg_string);
break;
case cmdKEYATTR:
key_attr ();
break;
case cmdUIF:
if ( arg_number < 1 || arg_number > 3 )
tty_printf ("usage: uif N [on|off|permanent]\n"
" 1 <= N <= 3\n");
else
uif (arg_number, arg_rest);
break;
case cmdQUIT:
goto leave;
case cmdNOP:
break;
case cmdINVCMD:
default:
tty_printf ("\n");
tty_printf (_("Invalid command (try \"help\")\n"));
break;
} /* End command switch. */
} /* End of main menu loop. */
leave:
xfree (answer);
}
diff --git a/g10/cpr.c b/g10/cpr.c
index 3d39d6bda..d502e8b52 100644
--- a/g10/cpr.c
+++ b/g10/cpr.c
@@ -1,664 +1,664 @@
/* status.c - Status message and command-fd interface
* Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003,
* 2004, 2005, 2006, 2010 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#ifdef HAVE_SIGNAL_H
# include <signal.h>
#endif
#include "gpg.h"
#include "../common/util.h"
#include "../common/status.h"
#include "../common/ttyio.h"
#include "options.h"
#include "main.h"
#include "../common/i18n.h"
#define CONTROL_D ('D' - 'A' + 1)
/* The stream to output the status information. Output is disabled if
this is NULL. */
static estream_t statusfp;
static void
progress_cb (void *ctx, const char *what, int printchar,
int current, int total)
{
char buf[50];
(void)ctx;
if ( printchar == '\n' && !strcmp (what, "primegen") )
snprintf (buf, sizeof buf, "%.20s X 100 100", what );
else
snprintf (buf, sizeof buf, "%.20s %c %d %d",
what, printchar=='\n'?'X':printchar, current, total );
write_status_text (STATUS_PROGRESS, buf);
}
/* Return true if the status message NO may currently be issued. We
- need this to avoid syncronisation problem while auto retrieving a
+ need this to avoid synchronization problem while auto retrieving a
key. There it may happen that a status NODATA is issued for a non
available key and the user may falsely interpret this has a missing
signature. */
static int
status_currently_allowed (int no)
{
if (!glo_ctrl.in_auto_key_retrieve)
return 1; /* Yes. */
/* We allow some statis anyway, so that import statistics are
correct and to avoid problems if the retrieval subsystem will
prompt the user. */
switch (no)
{
case STATUS_GET_BOOL:
case STATUS_GET_LINE:
case STATUS_GET_HIDDEN:
case STATUS_GOT_IT:
case STATUS_IMPORTED:
case STATUS_IMPORT_OK:
case STATUS_IMPORT_CHECK:
case STATUS_IMPORT_RES:
return 1; /* Yes. */
default:
break;
}
return 0; /* No. */
}
void
set_status_fd (int fd)
{
static int last_fd = -1;
if (fd != -1 && last_fd == fd)
return;
if (statusfp && statusfp != es_stdout && statusfp != es_stderr )
es_fclose (statusfp);
statusfp = NULL;
if (fd == -1)
return;
if (! gnupg_fd_valid (fd))
log_fatal ("status-fd is invalid: %s\n", strerror (errno));
if (fd == 1)
statusfp = es_stdout;
else if (fd == 2)
statusfp = es_stderr;
else
statusfp = es_fdopen (fd, "w");
if (!statusfp)
{
log_fatal ("can't open fd %d for status output: %s\n",
fd, strerror (errno));
}
last_fd = fd;
gcry_set_progress_handler (progress_cb, NULL);
}
int
is_status_enabled ()
{
return !!statusfp;
}
void
write_status ( int no )
{
write_status_text( no, NULL );
}
/* Write a status line with code NO followed by the string TEXT and
* directly followed by the remaining strings up to a NULL. Embedded
* CR and LFs in the strings (but not in TEXT) are C-style escaped.*/
void
write_status_strings (int no, const char *text, ...)
{
va_list arg_ptr;
const char *s;
if (!statusfp || !status_currently_allowed (no) )
return; /* Not enabled or allowed. */
es_fputs ("[GNUPG:] ", statusfp);
es_fputs (get_status_string (no), statusfp);
if ( text )
{
es_putc ( ' ', statusfp);
va_start (arg_ptr, text);
s = text;
do
{
for (; *s; s++)
{
if (*s == '\n')
es_fputs ("\\n", statusfp);
else if (*s == '\r')
es_fputs ("\\r", statusfp);
else
es_fputc (*(const byte *)s, statusfp);
}
}
while ((s = va_arg (arg_ptr, const char*)));
va_end (arg_ptr);
}
es_putc ('\n', statusfp);
if (es_fflush (statusfp) && opt.exit_on_status_write_error)
g10_exit (0);
}
void
write_status_text (int no, const char *text)
{
write_status_strings (no, text, NULL);
}
/* Write a status line with code NO followed by the output of the
* printf style FORMAT. Embedded CR and LFs are C-style escaped. */
void
write_status_printf (int no, const char *format, ...)
{
va_list arg_ptr;
char *buf;
if (!statusfp || !status_currently_allowed (no) )
return; /* Not enabled or allowed. */
es_fputs ("[GNUPG:] ", statusfp);
es_fputs (get_status_string (no), statusfp);
if (format)
{
es_putc ( ' ', statusfp);
va_start (arg_ptr, format);
buf = gpgrt_vbsprintf (format, arg_ptr);
if (!buf)
log_error ("error printing status line: %s\n",
gpg_strerror (gpg_err_code_from_syserror ()));
else
{
if (strpbrk (buf, "\r\n"))
{
const byte *s;
for (s=buf; *s; s++)
{
if (*s == '\n')
es_fputs ("\\n", statusfp);
else if (*s == '\r')
es_fputs ("\\r", statusfp);
else
es_fputc (*s, statusfp);
}
}
else
es_fputs (buf, statusfp);
gpgrt_free (buf);
}
va_end (arg_ptr);
}
es_putc ('\n', statusfp);
if (es_fflush (statusfp) && opt.exit_on_status_write_error)
g10_exit (0);
}
/* Write an ERROR status line using a full gpg-error error value. */
void
write_status_error (const char *where, gpg_error_t err)
{
if (!statusfp || !status_currently_allowed (STATUS_ERROR))
return; /* Not enabled or allowed. */
es_fprintf (statusfp, "[GNUPG:] %s %s %u\n",
get_status_string (STATUS_ERROR), where, err);
if (es_fflush (statusfp) && opt.exit_on_status_write_error)
g10_exit (0);
}
/* Same as above but outputs the error code only. */
void
write_status_errcode (const char *where, int errcode)
{
if (!statusfp || !status_currently_allowed (STATUS_ERROR))
return; /* Not enabled or allowed. */
es_fprintf (statusfp, "[GNUPG:] %s %s %u\n",
get_status_string (STATUS_ERROR), where, gpg_err_code (errcode));
if (es_fflush (statusfp) && opt.exit_on_status_write_error)
g10_exit (0);
}
/* Write a FAILURE status line. */
void
write_status_failure (const char *where, gpg_error_t err)
{
static int any_failure_printed;
if (!statusfp || !status_currently_allowed (STATUS_FAILURE))
return; /* Not enabled or allowed. */
if (any_failure_printed)
return;
any_failure_printed = 1;
es_fprintf (statusfp, "[GNUPG:] %s %s %u\n",
get_status_string (STATUS_FAILURE), where, err);
if (es_fflush (statusfp) && opt.exit_on_status_write_error)
g10_exit (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 || !status_currently_allowed (no))
return; /* Not enabled or allowed. */
if (wrap == -1)
{
lower_limit--;
wrap = 0;
}
text = get_status_string (no);
count = dowrap = first = 1;
do
{
if (dowrap)
{
es_fprintf (statusfp, "[GNUPG:] %s ", text);
count = dowrap = 0;
if (first && string)
{
es_fputs (string, statusfp);
count += strlen (string);
/* Make sure that there is a space after the string. */
if (*string && string[strlen (string)-1] != ' ')
{
es_putc (' ', statusfp);
count++;
}
}
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)
es_fwrite (buffer, s-buffer, 1, statusfp);
if ( esc )
{
es_fprintf (statusfp, "%%%02X", *(const byte*)s );
s++; n--;
}
buffer = s;
len = n;
if (dowrap && len)
es_putc ('\n', statusfp);
}
while (len);
es_putc ('\n',statusfp);
if (es_fflush (statusfp) && opt.exit_on_status_write_error)
g10_exit (0);
}
void
write_status_buffer (int no, const char *buffer, size_t len, int wrap)
{
write_status_text_and_buffer (no, NULL, buffer, len, wrap);
}
/* Print the BEGIN_SIGNING status message. If MD is not NULL it is
used to retrieve the hash algorithms used for the message. */
void
write_status_begin_signing (gcry_md_hd_t md)
{
if (md)
{
char buf[100];
size_t buflen;
int i, ga;
buflen = 0;
for (i=1; i <= 110; i++)
{
ga = map_md_openpgp_to_gcry (i);
if (ga && gcry_md_is_enabled (md, ga) && buflen+10 < DIM(buf))
{
snprintf (buf+buflen, DIM(buf) - buflen,
"%sH%d", buflen? " ":"",i);
buflen += strlen (buf+buflen);
}
}
write_status_text (STATUS_BEGIN_SIGNING, buf);
}
else
write_status ( STATUS_BEGIN_SIGNING );
}
static int
myread(int fd, void *buf, size_t count)
{
int rc;
do
{
rc = read( fd, buf, count );
}
while (rc == -1 && errno == EINTR);
if (!rc && count)
{
static int eof_emmited=0;
if ( eof_emmited < 3 )
{
*(char*)buf = CONTROL_D;
rc = 1;
eof_emmited++;
}
else /* Ctrl-D not caught - do something reasonable */
{
#ifdef HAVE_DOSISH_SYSTEM
#ifndef HAVE_W32CE_SYSTEM
raise (SIGINT); /* Nothing to hangup under DOS. */
#endif
#else
raise (SIGHUP); /* No more input data. */
#endif
}
}
return rc;
}
/* Request a string from the client over the command-fd. If GETBOOL
is set the function returns a static string (do not free) if the
entered value was true or NULL if the entered value was false. */
static char *
do_get_from_fd ( const char *keyword, int hidden, int getbool )
{
int i, len;
char *string;
if (statusfp != es_stdout)
es_fflush (es_stdout);
write_status_text (getbool? STATUS_GET_BOOL :
hidden? STATUS_GET_HIDDEN : STATUS_GET_LINE, keyword);
for (string = NULL, i = len = 200; ; i++ )
{
if (i >= len-1 )
{
/* On the first iteration allocate a new buffer. If that
* buffer is too short at further iterations do a poor man's
* realloc. */
char *save = string;
len += 100;
string = hidden? xmalloc_secure ( len ) : xmalloc ( len );
if (save)
{
memcpy (string, save, i);
xfree (save);
}
else
i = 0;
}
/* Fixme: why not use our read_line function here? */
if ( myread( opt.command_fd, string+i, 1) != 1 || string[i] == '\n' )
break;
else if ( string[i] == CONTROL_D )
{
/* Found ETX - Cancel the line and return a sole ETX. */
string[0] = CONTROL_D;
i = 1;
break;
}
}
string[i] = 0;
write_status (STATUS_GOT_IT);
if (getbool) /* Fixme: is this correct??? */
return (string[0] == 'Y' || string[0] == 'y') ? "" : NULL;
return string;
}
int
cpr_enabled()
{
if( opt.command_fd != -1 )
return 1;
return 0;
}
char *
cpr_get_no_help( const char *keyword, const char *prompt )
{
char *p;
if( opt.command_fd != -1 )
return do_get_from_fd ( keyword, 0, 0 );
for(;;) {
p = tty_get( prompt );
return p;
}
}
char *
cpr_get( const char *keyword, const char *prompt )
{
char *p;
if( opt.command_fd != -1 )
return do_get_from_fd ( keyword, 0, 0 );
for(;;) {
p = tty_get( prompt );
if( *p=='?' && !p[1] && !(keyword && !*keyword)) {
xfree(p);
display_online_help( keyword );
}
else
return p;
}
}
char *
cpr_get_utf8( const char *keyword, const char *prompt )
{
char *p;
p = cpr_get( keyword, prompt );
if( p ) {
char *utf8 = native_to_utf8( p );
xfree( p );
p = utf8;
}
return p;
}
char *
cpr_get_hidden( const char *keyword, const char *prompt )
{
char *p;
if( opt.command_fd != -1 )
return do_get_from_fd ( keyword, 1, 0 );
for(;;) {
p = tty_get_hidden( prompt );
if( *p == '?' && !p[1] ) {
xfree(p);
display_online_help( keyword );
}
else
return p;
}
}
void
cpr_kill_prompt(void)
{
if( opt.command_fd != -1 )
return;
tty_kill_prompt();
return;
}
int
cpr_get_answer_is_yes_def (const char *keyword, const char *prompt, int def_yes)
{
int yes;
char *p;
if( opt.command_fd != -1 )
return !!do_get_from_fd ( keyword, 0, 1 );
for(;;) {
p = tty_get( prompt );
trim_spaces(p); /* it is okay to do this here */
if( *p == '?' && !p[1] ) {
xfree(p);
display_online_help( keyword );
}
else {
tty_kill_prompt();
yes = answer_is_yes_no_default (p, def_yes);
xfree(p);
return yes;
}
}
}
int
cpr_get_answer_is_yes (const char *keyword, const char *prompt)
{
return cpr_get_answer_is_yes_def (keyword, prompt, 0);
}
int
cpr_get_answer_yes_no_quit( const char *keyword, const char *prompt )
{
int yes;
char *p;
if( opt.command_fd != -1 )
return !!do_get_from_fd ( keyword, 0, 1 );
for(;;) {
p = tty_get( prompt );
trim_spaces(p); /* it is okay to do this here */
if( *p == '?' && !p[1] ) {
xfree(p);
display_online_help( keyword );
}
else {
tty_kill_prompt();
yes = answer_is_yes_no_quit(p);
xfree(p);
return yes;
}
}
}
int
cpr_get_answer_okay_cancel (const char *keyword,
const char *prompt,
int def_answer)
{
int yes;
char *answer = NULL;
char *p;
if( opt.command_fd != -1 )
answer = do_get_from_fd ( keyword, 0, 0 );
if (answer)
{
yes = answer_is_okay_cancel (answer, def_answer);
xfree (answer);
return yes;
}
for(;;)
{
p = tty_get( prompt );
trim_spaces(p); /* it is okay to do this here */
if (*p == '?' && !p[1])
{
xfree(p);
display_online_help (keyword);
}
else
{
tty_kill_prompt();
yes = answer_is_okay_cancel (p, def_answer);
xfree(p);
return yes;
}
}
}
diff --git a/g10/decrypt-data.c b/g10/decrypt-data.c
index 4d9dc86d9..c73d5fb45 100644
--- a/g10/decrypt-data.c
+++ b/g10/decrypt-data.c
@@ -1,1012 +1,1012 @@
/* decrypt-data.c - Decrypt an encrypted data packet
* Copyright (C) 1998-2001, 2005-2006, 2009 Free Software Foundation, Inc.
* Copyright (C) 1998-2001, 2005-2006, 2009, 2018 Werner Koch
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "gpg.h"
#include "../common/util.h"
#include "packet.h"
#include "options.h"
#include "../common/i18n.h"
#include "../common/status.h"
#include "../common/compliance.h"
static int aead_decode_filter (void *opaque, int control, iobuf_t a,
byte *buf, size_t *ret_len);
static int mdc_decode_filter ( void *opaque, int control, IOBUF a,
byte *buf, size_t *ret_len);
static int decode_filter ( void *opaque, int control, IOBUF a,
byte *buf, size_t *ret_len);
/* Our context object. */
struct decode_filter_context_s
{
/* Recounter (max value is 2). We need it because we do not know
* whether the iobuf or the outer control code frees this object
* first. */
int refcount;
/* The cipher handle. */
gcry_cipher_hd_t cipher_hd;
/* The hash handle for use in MDC mode. */
gcry_md_hd_t mdc_hash;
/* The start IV for AEAD encryption. */
byte startiv[16];
/* The holdback buffer and its used length. For AEAD we need 32+1
* bytes but we use 48 byte. For MDC we need 22 bytes. */
char holdback[48];
unsigned int holdbacklen;
/* Working on a partial length packet. */
unsigned int partial : 1;
/* EOF indicator with these true values:
* 1 = normal EOF
* 2 = premature EOF (tag incomplete)
* 3 = premature EOF (general) */
unsigned int eof_seen : 2;
/* The actually used cipher algo for AEAD. */
byte cipher_algo;
/* The AEAD algo. */
byte aead_algo;
/* The encoded chunk byte for AEAD. */
byte chunkbyte;
/* The decoded CHUNKBYTE. */
uint64_t chunksize;
/* The chunk index for AEAD. */
uint64_t chunkindex;
/* The number of bytes in the current chunk. */
uint64_t chunklen;
/* The total count of decrypted plaintext octets. */
uint64_t total;
/* Remaining bytes in the packet according to the packet header.
* Not used if PARTIAL is true. */
size_t length;
};
typedef struct decode_filter_context_s *decode_filter_ctx_t;
/* Helper to release the decode context. */
static void
release_dfx_context (decode_filter_ctx_t dfx)
{
if (!dfx)
return;
log_assert (dfx->refcount);
if ( !--dfx->refcount )
{
gcry_cipher_close (dfx->cipher_hd);
dfx->cipher_hd = NULL;
gcry_md_close (dfx->mdc_hash);
dfx->mdc_hash = NULL;
xfree (dfx);
}
}
/* Set the nonce and the additional data for the current chunk. This
* also reset the decryption machinery * so that the handle can be
* used for a new chunk. If FINAL is set the final AEAD chunk is
* processed. */
static gpg_error_t
aead_set_nonce_and_ad (decode_filter_ctx_t dfx, int final)
{
gpg_error_t err;
unsigned char ad[21];
unsigned char nonce[16];
int i;
switch (dfx->aead_algo)
{
case AEAD_ALGO_OCB:
memcpy (nonce, dfx->startiv, 15);
i = 7;
break;
case AEAD_ALGO_EAX:
memcpy (nonce, dfx->startiv, 16);
i = 8;
break;
default:
BUG ();
}
nonce[i++] ^= dfx->chunkindex >> 56;
nonce[i++] ^= dfx->chunkindex >> 48;
nonce[i++] ^= dfx->chunkindex >> 40;
nonce[i++] ^= dfx->chunkindex >> 32;
nonce[i++] ^= dfx->chunkindex >> 24;
nonce[i++] ^= dfx->chunkindex >> 16;
nonce[i++] ^= dfx->chunkindex >> 8;
nonce[i++] ^= dfx->chunkindex;
if (DBG_CRYPTO)
log_printhex (nonce, i, "nonce:");
err = gcry_cipher_setiv (dfx->cipher_hd, nonce, i);
if (err)
return err;
ad[0] = (0xc0 | PKT_ENCRYPTED_AEAD);
ad[1] = 1;
ad[2] = dfx->cipher_algo;
ad[3] = dfx->aead_algo;
ad[4] = dfx->chunkbyte;
ad[5] = dfx->chunkindex >> 56;
ad[6] = dfx->chunkindex >> 48;
ad[7] = dfx->chunkindex >> 40;
ad[8] = dfx->chunkindex >> 32;
ad[9] = dfx->chunkindex >> 24;
ad[10]= dfx->chunkindex >> 16;
ad[11]= dfx->chunkindex >> 8;
ad[12]= dfx->chunkindex;
if (final)
{
ad[13] = dfx->total >> 56;
ad[14] = dfx->total >> 48;
ad[15] = dfx->total >> 40;
ad[16] = dfx->total >> 32;
ad[17] = dfx->total >> 24;
ad[18] = dfx->total >> 16;
ad[19] = dfx->total >> 8;
ad[20] = dfx->total;
}
if (DBG_CRYPTO)
log_printhex (ad, final? 21 : 13, "authdata:");
return gcry_cipher_authenticate (dfx->cipher_hd, ad, final? 21 : 13);
}
/* Helper to check the 16 byte tag in TAGBUF. The FINAL flag is only
* for debug messages. */
static gpg_error_t
aead_checktag (decode_filter_ctx_t dfx, int final, const void *tagbuf)
{
gpg_error_t err;
if (DBG_FILTER)
log_printhex (tagbuf, 16, "tag:");
err = gcry_cipher_checktag (dfx->cipher_hd, tagbuf, 16);
if (err)
{
log_error ("gcry_cipher_checktag%s failed: %s\n",
final? " (final)":"", gpg_strerror (err));
return err;
}
if (DBG_FILTER)
log_debug ("%stag is valid\n", final?"final ":"");
return 0;
}
/****************
* Decrypt the data, specified by ED with the key DEK.
*/
int
decrypt_data (ctrl_t ctrl, void *procctx, PKT_encrypted *ed, DEK *dek)
{
decode_filter_ctx_t dfx;
byte *p;
int rc=0, c, i;
byte temp[32];
unsigned int blocksize;
unsigned int nprefix;
dfx = xtrycalloc (1, sizeof *dfx);
if (!dfx)
return gpg_error_from_syserror ();
dfx->refcount = 1;
if ( opt.verbose && !dek->algo_info_printed )
{
if (!openpgp_cipher_test_algo (dek->algo))
log_info (_("%s.%s encrypted data\n"),
openpgp_cipher_algo_name (dek->algo),
ed->aead_algo? openpgp_aead_algo_name (ed->aead_algo)
/**/ : "CFB");
else
log_info (_("encrypted with unknown algorithm %d\n"), dek->algo );
dek->algo_info_printed = 1;
}
/* Check compliance. */
if (! gnupg_cipher_is_allowed (opt.compliance, 0, dek->algo,
GCRY_CIPHER_MODE_CFB))
{
log_error (_("cipher algorithm '%s' may not be used in %s mode\n"),
openpgp_cipher_algo_name (dek->algo),
gnupg_compliance_option_string (opt.compliance));
rc = gpg_error (GPG_ERR_CIPHER_ALGO);
goto leave;
}
write_status_printf (STATUS_DECRYPTION_INFO, "%d %d %d",
ed->mdc_method, dek->algo, ed->aead_algo);
if (opt.show_session_key)
{
char numbuf[30];
char *hexbuf;
if (ed->aead_algo)
snprintf (numbuf, sizeof numbuf, "%d.%u:", dek->algo, ed->aead_algo);
else
snprintf (numbuf, sizeof numbuf, "%d:", dek->algo);
hexbuf = bin2hex (dek->key, dek->keylen, NULL);
if (!hexbuf)
{
rc = gpg_error_from_syserror ();
goto leave;
}
log_info ("session key: '%s%s'\n", numbuf, hexbuf);
write_status_strings (STATUS_SESSION_KEY, numbuf, hexbuf, NULL);
xfree (hexbuf);
}
rc = openpgp_cipher_test_algo (dek->algo);
if (rc)
goto leave;
blocksize = openpgp_cipher_get_algo_blklen (dek->algo);
if ( !blocksize || blocksize > 16 )
log_fatal ("unsupported blocksize %u\n", blocksize );
if (ed->aead_algo)
{
enum gcry_cipher_modes ciphermode;
unsigned int startivlen;
if (blocksize != 16)
{
rc = gpg_error (GPG_ERR_CIPHER_ALGO);
goto leave;
}
rc = openpgp_aead_algo_info (ed->aead_algo, &ciphermode, &startivlen);
if (rc)
goto leave;
log_assert (startivlen <= sizeof dfx->startiv);
if (ed->chunkbyte > 56)
{
log_error ("invalid AEAD chunkbyte %u\n", ed->chunkbyte);
rc = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
/* Read the Start-IV. */
if (ed->len)
{
for (i=0; i < startivlen && ed->len; i++, ed->len--)
{
if ((c=iobuf_get (ed->buf)) == -1)
break;
dfx->startiv[i] = c;
}
}
else
{
for (i=0; i < startivlen; i++ )
if ( (c=iobuf_get (ed->buf)) == -1 )
break;
else
dfx->startiv[i] = c;
}
if (i != startivlen)
{
log_error ("Start-IV in AEAD packet too short (%d/%u)\n",
i, startivlen);
rc = gpg_error (GPG_ERR_TOO_SHORT);
goto leave;
}
dfx->cipher_algo = ed->cipher_algo;
dfx->aead_algo = ed->aead_algo;
dfx->chunkbyte = ed->chunkbyte;
dfx->chunksize = (uint64_t)1 << (dfx->chunkbyte + 6);
if (dek->algo != dfx->cipher_algo)
log_info ("Note: different cipher algorithms used (%s/%s)\n",
openpgp_cipher_algo_name (dek->algo),
openpgp_cipher_algo_name (dfx->cipher_algo));
rc = openpgp_cipher_open (&dfx->cipher_hd,
dfx->cipher_algo,
ciphermode,
GCRY_CIPHER_SECURE);
if (rc)
goto leave; /* Should never happen. */
if (DBG_CRYPTO)
log_printhex (dek->key, dek->keylen, "thekey:");
rc = gcry_cipher_setkey (dfx->cipher_hd, dek->key, dek->keylen);
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;
}
else if (rc)
{
log_error("key setup failed: %s\n", gpg_strerror (rc));
goto leave;
}
if (!ed->buf)
{
log_error(_("problem handling encrypted packet\n"));
goto leave;
}
}
else /* CFB encryption. */
{
nprefix = blocksize;
if ( ed->len && ed->len < (nprefix+2) )
{
/* An invalid message. We can't check that during parsing
because we may not know the used cipher then. */
rc = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
if ( ed->mdc_method )
{
if (gcry_md_open (&dfx->mdc_hash, ed->mdc_method, 0 ))
BUG ();
if ( DBG_HASHING )
gcry_md_debug (dfx->mdc_hash, "checkmdc");
}
rc = openpgp_cipher_open (&dfx->cipher_hd, dek->algo,
GCRY_CIPHER_MODE_CFB,
(GCRY_CIPHER_SECURE
| ((ed->mdc_method || dek->algo >= 100)?
0 : GCRY_CIPHER_ENABLE_SYNC)));
if (rc)
{
/* We should never get an error here cause we already checked
* that the algorithm is available. */
BUG();
}
/* log_hexdump( "thekey", dek->key, dek->keylen );*/
rc = gcry_cipher_setkey (dfx->cipher_hd, dek->key, dek->keylen);
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;
}
else if( rc )
{
log_error("key setup failed: %s\n", gpg_strerror (rc) );
goto leave;
}
if (!ed->buf)
{
log_error(_("problem handling encrypted packet\n"));
goto leave;
}
gcry_cipher_setiv (dfx->cipher_hd, NULL, 0);
if ( ed->len )
{
for (i=0; i < (nprefix+2) && ed->len; i++, ed->len-- )
{
if ( (c=iobuf_get(ed->buf)) == -1 )
break;
else
temp[i] = c;
}
}
else
{
for (i=0; i < (nprefix+2); i++ )
if ( (c=iobuf_get(ed->buf)) == -1 )
break;
else
temp[i] = c;
}
gcry_cipher_decrypt (dfx->cipher_hd, temp, nprefix+2, NULL, 0);
gcry_cipher_sync (dfx->cipher_hd);
p = temp;
/* log_hexdump( "prefix", temp, nprefix+2 ); */
if (dek->symmetric
&& (p[nprefix-2] != p[nprefix] || p[nprefix-1] != p[nprefix+1]) )
{
rc = gpg_error (GPG_ERR_BAD_KEY);
goto leave;
}
if ( dfx->mdc_hash )
gcry_md_write (dfx->mdc_hash, temp, nprefix+2);
}
dfx->refcount++;
dfx->partial = !!ed->is_partial;
dfx->length = ed->len;
if (ed->aead_algo)
iobuf_push_filter ( ed->buf, aead_decode_filter, dfx );
else if (ed->mdc_method)
iobuf_push_filter ( ed->buf, mdc_decode_filter, dfx );
else
iobuf_push_filter ( ed->buf, decode_filter, dfx );
if (opt.unwrap_encryption)
{
char *filename = NULL;
estream_t fp;
+
rc = get_output_file ("", 0, ed->buf, &filename, &fp);
if (! rc)
{
iobuf_t output = iobuf_esopen (fp, "w", 0);
armor_filter_context_t *afx = NULL;
if (opt.armor)
{
afx = new_armor_context ();
push_armor_filter (afx, output);
}
iobuf_copy (output, ed->buf);
if ((rc = iobuf_error (ed->buf)))
log_error (_("error reading '%s': %s\n"),
filename, gpg_strerror (rc));
else if ((rc = iobuf_error (output)))
log_error (_("error writing '%s': %s\n"),
filename, gpg_strerror (rc));
iobuf_close (output);
- if (afx)
- release_armor_context (afx);
+ release_armor_context (afx);
}
xfree (filename);
}
else
proc_packets (ctrl, procctx, ed->buf );
ed->buf = NULL;
if (dfx->eof_seen > 1 )
rc = gpg_error (GPG_ERR_INV_PACKET);
else if ( ed->mdc_method )
{
/* We used to let parse-packet.c handle the MDC packet but this
turned out to be a problem with compressed packets: With old
style packets there is no length information available and
the decompressor uses an implicit end. However we can't know
this implicit end beforehand (:-) and thus may feed the
decompressor with more bytes than actually needed. It would
be possible to unread the extra bytes but due to our weird
iobuf system any unread is non reliable due to filters
already popped off. The easy and sane solution is to care
about the MDC packet only here and never pass it to the
packet parser. Fortunatley the OpenPGP spec requires a
strict format for the MDC packet so that we know that 22
bytes are appended. */
int datalen = gcry_md_get_algo_dlen (ed->mdc_method);
log_assert (dfx->cipher_hd);
log_assert (dfx->mdc_hash);
gcry_cipher_decrypt (dfx->cipher_hd, dfx->holdback, 22, NULL, 0);
gcry_md_write (dfx->mdc_hash, dfx->holdback, 2);
gcry_md_final (dfx->mdc_hash);
if ( dfx->holdback[0] != '\xd3'
|| dfx->holdback[1] != '\x14'
|| datalen != 20
|| memcmp (gcry_md_read (dfx->mdc_hash, 0), dfx->holdback+2, datalen))
rc = gpg_error (GPG_ERR_BAD_SIGNATURE);
/* log_printhex("MDC message:", dfx->holdback, 22); */
/* log_printhex("MDC calc:", gcry_md_read (dfx->mdc_hash,0), datalen); */
}
leave:
release_dfx_context (dfx);
return rc;
}
/* Fill BUFFER with up to NBYTES-OFFSET from STREAM utilizing
* information from the context DFX. Returns the new offset which is
* the number of bytes read plus the original offset. On EOF the
* respective flag in DFX is set. */
static size_t
fill_buffer (decode_filter_ctx_t dfx, iobuf_t stream,
byte *buffer, size_t nbytes, size_t offset)
{
size_t nread = offset;
size_t curr;
int ret;
if (dfx->partial)
{
while (nread < nbytes)
{
curr = nbytes - nread;
ret = iobuf_read (stream, &buffer[nread], curr);
if (ret == -1)
{
dfx->eof_seen = 1; /* Normal EOF. */
break;
}
nread += ret;
}
}
else
{
while (nread < nbytes && dfx->length)
{
curr = nbytes - nread;
if (curr > dfx->length)
curr = dfx->length;
ret = iobuf_read (stream, &buffer[nread], curr);
if (ret == -1)
{
dfx->eof_seen = 3; /* Premature EOF. */
break;
}
nread += ret;
dfx->length -= ret;
}
if (!dfx->length)
dfx->eof_seen = 1; /* Normal EOF. */
}
return nread;
}
/* The core of the AEAD decryption. This is the underflow function of
* the aead_decode_filter. */
static gpg_error_t
aead_underflow (decode_filter_ctx_t dfx, iobuf_t a, byte *buf, size_t *ret_len)
{
const size_t size = *ret_len; /* The allocated size of BUF. */
gpg_error_t err;
size_t totallen = 0; /* The number of bytes to return on success or EOF. */
size_t off = 0; /* The offset into the buffer. */
size_t len; /* The current number of bytes in BUF+OFF. */
log_assert (size > 48); /* Our code requires at least this size. */
/* Copy the rest from the last call of this function into BUF. */
len = dfx->holdbacklen;
dfx->holdbacklen = 0;
memcpy (buf, dfx->holdback, len);
if (DBG_FILTER)
log_debug ("aead_underflow: size=%zu len=%zu%s%s\n", size, len,
dfx->partial? " partial":"", dfx->eof_seen? " eof":"");
/* Read and fill up BUF. We need to watch out for an EOF so that we
* can detect the last chunk which is commonly shorter than the
* chunksize. After the last data byte from the last chunk 32 more
* bytes are expected for the last chunk's tag and the following
* final chunk's tag. To detect the EOF we need to try reading at least
* one further byte; however we try to read 16 extra bytes to avoid
* single byte reads in some lower layers. The outcome is that we
* have up to 48 extra extra octets which we will later put into the
* holdback buffer for the next invocation (which handles the EOF
* case). */
len = fill_buffer (dfx, a, buf, size, len);
if (len < 32)
{
/* Not enough data for the last two tags. */
err = gpg_error (GPG_ERR_TRUNCATED);
goto leave;
}
if (dfx->eof_seen)
{
/* If have seen an EOF we copy only the last two auth tags into
* the holdback buffer. */
dfx->holdbacklen = 32;
memcpy (dfx->holdback, buf+len-32, 32);
len -= 32;
}
else
{
/* If have not seen an EOF we copy the entire extra 48 bytes
* into the holdback buffer for processing at the next call of
* this function. */
dfx->holdbacklen = len > 48? 48 : len;
memcpy (dfx->holdback, buf+len-dfx->holdbacklen, dfx->holdbacklen);
len -= dfx->holdbacklen;
}
/* log_printhex (dfx->holdback, dfx->holdbacklen, "holdback:"); */
/* Decrypt the buffer. This first requires a loop to handle the
* case when a chunk ends within the buffer. */
if (DBG_FILTER)
log_debug ("decrypt: chunklen=%ju total=%ju size=%zu len=%zu%s\n",
dfx->chunklen, dfx->total, size, len,
dfx->eof_seen? " eof":"");
while (len && dfx->chunklen + len >= dfx->chunksize)
{
size_t n = dfx->chunksize - dfx->chunklen;
byte tagbuf[16];
if (DBG_FILTER)
log_debug ("chunksize will be reached: n=%zu\n", n);
if (!dfx->chunklen)
{
/* First data for this chunk - prepare. */
err = aead_set_nonce_and_ad (dfx, 0);
if (err)
goto leave;
}
/* log_printhex (buf, n, "ciph:"); */
gcry_cipher_final (dfx->cipher_hd);
err = gcry_cipher_decrypt (dfx->cipher_hd, buf+off, n, NULL, 0);
if (err)
{
log_error ("gcry_cipher_decrypt failed (1): %s\n",
gpg_strerror (err));
goto leave;
}
/* log_printhex (buf, n, "plai:"); */
totallen += n;
dfx->chunklen += n;
dfx->total += n;
off += n;
len -= n;
if (DBG_FILTER)
log_debug ("ndecrypted: %zu (nchunk=%ju) bytes left: %zu at off=%zu\n",
totallen, dfx->chunklen, len, off);
/* Check the tag. */
if (len < 16)
{
/* The tag is not entirely in the buffer. Read the rest of
* the tag from the holdback buffer. Then shift the holdback
* buffer and fill it up again. */
memcpy (tagbuf, buf+off, len);
memcpy (tagbuf + len, dfx->holdback, 16 - len);
dfx->holdbacklen -= 16-len;
memmove (dfx->holdback, dfx->holdback + (16-len), dfx->holdbacklen);
if (dfx->eof_seen)
{
/* We should have the last chunk's tag in TAGBUF and the
* final tag in HOLDBACKBUF. */
if (len || dfx->holdbacklen != 16)
{
/* Not enough data for the last two tags. */
err = gpg_error (GPG_ERR_TRUNCATED);
goto leave;
}
}
else
{
len = 0;
dfx->holdbacklen = fill_buffer (dfx, a, dfx->holdback, 48,
dfx->holdbacklen);
if (dfx->holdbacklen < 32)
{
/* Not enough data for the last two tags. */
err = gpg_error (GPG_ERR_TRUNCATED);
goto leave;
}
}
}
else /* We already have the full tag. */
{
memcpy (tagbuf, buf+off, 16);
/* Remove that tag from the output. */
memmove (buf + off, buf + off + 16, len - 16);
len -= 16;
}
err = aead_checktag (dfx, 0, tagbuf);
if (err)
goto leave;
dfx->chunklen = 0;
dfx->chunkindex++;
continue;
}
/* The bulk decryption of our buffer. */
if (len)
{
if (!dfx->chunklen)
{
/* First data for this chunk - prepare. */
err = aead_set_nonce_and_ad (dfx, 0);
if (err)
goto leave;
}
if (dfx->eof_seen)
{
/* This is the last block of the last chunk. Its length may
* not be a multiple of the block length. */
gcry_cipher_final (dfx->cipher_hd);
}
err = gcry_cipher_decrypt (dfx->cipher_hd, buf + off, len, NULL, 0);
if (err)
{
log_error ("gcry_cipher_decrypt failed (2): %s\n",
gpg_strerror (err));
goto leave;
}
totallen += len;
dfx->chunklen += len;
dfx->total += len;
if (DBG_FILTER)
log_debug ("ndecrypted: %zu (nchunk=%ju)\n", totallen, dfx->chunklen);
}
if (dfx->eof_seen)
{
if (dfx->chunklen)
{
if (DBG_FILTER)
log_debug ("eof seen: holdback has the last and final tag\n");
log_assert (dfx->holdbacklen >= 32);
err = aead_checktag (dfx, 0, dfx->holdback);
if (err)
goto leave;
dfx->chunklen = 0;
dfx->chunkindex++;
off = 16;
}
else
{
if (DBG_FILTER)
log_debug ("eof seen: holdback has the final tag\n");
log_assert (dfx->holdbacklen >= 16);
off = 0;
}
/* Check the final chunk. */
err = aead_set_nonce_and_ad (dfx, 1);
if (err)
goto leave;
gcry_cipher_final (dfx->cipher_hd);
/* Decrypt an empty string (using HOLDBACK as a dummy). */
err = gcry_cipher_decrypt (dfx->cipher_hd, dfx->holdback, 0, NULL, 0);
if (err)
{
log_error ("gcry_cipher_decrypt failed (final): %s\n",
gpg_strerror (err));
goto leave;
}
err = aead_checktag (dfx, 1, dfx->holdback+off);
if (err)
goto leave;
err = gpg_error (GPG_ERR_EOF);
}
leave:
if (DBG_FILTER)
log_debug ("aead_underflow: returning %zu (%s)\n",
totallen, gpg_strerror (err));
/* In case of an auth error we map the error code to the same as
* used by the MDC decryption. */
if (gpg_err_code (err) == GPG_ERR_CHECKSUM)
err = gpg_error (GPG_ERR_BAD_SIGNATURE);
/* In case of an error we better wipe out the buffer than to convey
* partly decrypted data. */
if (err && gpg_err_code (err) != GPG_ERR_EOF)
memset (buf, 0, size);
*ret_len = totallen;
return err;
}
/* The IOBUF filter used to decrypt AEAD encrypted data. */
static int
aead_decode_filter (void *opaque, int control, IOBUF a,
byte *buf, size_t *ret_len)
{
decode_filter_ctx_t dfx = opaque;
int rc = 0;
if ( control == IOBUFCTRL_UNDERFLOW && dfx->eof_seen )
{
*ret_len = 0;
rc = -1;
}
else if ( control == IOBUFCTRL_UNDERFLOW )
{
log_assert (a);
rc = aead_underflow (dfx, a, buf, ret_len);
if (gpg_err_code (rc) == GPG_ERR_EOF)
rc = -1; /* We need to use the old convention in the filter. */
}
else if ( control == IOBUFCTRL_FREE )
{
release_dfx_context (dfx);
}
else if ( control == IOBUFCTRL_DESC )
{
mem2str (buf, "aead_decode_filter", *ret_len);
}
return rc;
}
static int
mdc_decode_filter (void *opaque, int control, IOBUF a,
byte *buf, size_t *ret_len)
{
decode_filter_ctx_t dfx = opaque;
size_t n, size = *ret_len;
int rc = 0;
/* Note: We need to distinguish between a partial and a fixed length
packet. The first is the usual case as created by GPG. However
for short messages the format degrades to a fixed length packet
and other implementations might use fixed length as well. Only
looking for the EOF on fixed data works only if the encrypted
packet is not followed by other data. This used to be a long
standing bug which was fixed on 2009-10-02. */
if ( control == IOBUFCTRL_UNDERFLOW && dfx->eof_seen )
{
*ret_len = 0;
rc = -1;
}
else if( control == IOBUFCTRL_UNDERFLOW )
{
log_assert (a);
log_assert (size > 44); /* Our code requires at least this size. */
/* Get at least 22 bytes and put it ahead in the buffer. */
n = fill_buffer (dfx, a, buf, 44, 22);
if (n == 44)
{
/* We have enough stuff - flush the holdback buffer. */
if ( !dfx->holdbacklen ) /* First time. */
{
memcpy (buf, buf+22, 22);
n = 22;
}
else
{
memcpy (buf, dfx->holdback, 22);
}
/* Fill up the buffer. */
n = fill_buffer (dfx, a, buf, size, n);
/* Move the trailing 22 bytes back to the holdback buffer. We
have at least 44 bytes thus a memmove is not needed. */
n -= 22;
memcpy (dfx->holdback, buf+n, 22 );
dfx->holdbacklen = 22;
}
else if ( !dfx->holdbacklen ) /* EOF seen but empty holdback. */
{
/* This is bad because it means an incomplete hash. */
n -= 22;
memcpy (buf, buf+22, n );
dfx->eof_seen = 2; /* EOF with incomplete hash. */
}
else /* EOF seen (i.e. read less than 22 bytes). */
{
memcpy (buf, dfx->holdback, 22 );
n -= 22;
memcpy (dfx->holdback, buf+n, 22 );
dfx->eof_seen = 1; /* Normal EOF. */
}
if ( n )
{
if ( dfx->cipher_hd )
gcry_cipher_decrypt (dfx->cipher_hd, buf, n, NULL, 0);
if ( dfx->mdc_hash )
gcry_md_write (dfx->mdc_hash, buf, n);
}
else
{
log_assert ( dfx->eof_seen );
rc = -1; /* Return EOF. */
}
*ret_len = n;
}
else if ( control == IOBUFCTRL_FREE )
{
release_dfx_context (dfx);
}
else if ( control == IOBUFCTRL_DESC )
{
mem2str (buf, "mdc_decode_filter", *ret_len);
}
return rc;
}
static int
decode_filter( void *opaque, int control, IOBUF a, byte *buf, size_t *ret_len)
{
decode_filter_ctx_t fc = opaque;
size_t size = *ret_len;
size_t n;
int rc = 0;
if ( control == IOBUFCTRL_UNDERFLOW && fc->eof_seen )
{
*ret_len = 0;
rc = -1;
}
else if ( control == IOBUFCTRL_UNDERFLOW )
{
log_assert (a);
n = fill_buffer (fc, a, buf, size, 0);
if (n)
{
if (fc->cipher_hd)
gcry_cipher_decrypt (fc->cipher_hd, buf, n, NULL, 0);
}
else
{
if (!fc->eof_seen)
fc->eof_seen = 1;
rc = -1; /* Return EOF. */
}
*ret_len = n;
}
else if ( control == IOBUFCTRL_FREE )
{
release_dfx_context (fc);
}
else if ( control == IOBUFCTRL_DESC )
{
mem2str (buf, "decode_filter", *ret_len);
}
return rc;
}
diff --git a/g10/decrypt.c b/g10/decrypt.c
index 4d6734d40..9589aff58 100644
--- a/g10/decrypt.c
+++ b/g10/decrypt.c
@@ -1,281 +1,282 @@
/* decrypt.c - decrypt and verify data
* Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006,
* 2007, 2009 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include "gpg.h"
#include "options.h"
#include "packet.h"
#include "../common/status.h"
#include "../common/iobuf.h"
#include "keydb.h"
#include "../common/util.h"
#include "main.h"
#include "../common/status.h"
#include "../common/i18n.h"
/* Assume that the input is an encrypted message and decrypt
* (and if signed, verify the signature on) it.
* This command differs from the default operation, as it never
* writes to the filename which is included in the file and it
* rejects files which don't begin with an encrypted message.
*/
int
decrypt_message (ctrl_t ctrl, const char *filename)
{
IOBUF fp;
armor_filter_context_t *afx = NULL;
progress_filter_context_t *pfx;
int rc;
- int no_out = 0;
pfx = new_progress_context ();
/* Open the message file. */
fp = iobuf_open (filename);
if (fp && is_secured_file (iobuf_get_fd (fp)))
{
iobuf_close (fp);
fp = NULL;
gpg_err_set_errno (EPERM);
}
if ( !fp )
{
rc = gpg_error_from_syserror ();
log_error (_("can't open '%s': %s\n"), print_fname_stdin(filename),
gpg_strerror (rc));
release_progress_context (pfx);
return rc;
}
handle_progress (pfx, fp, filename);
if ( !opt.no_armor )
{
if ( use_armor_filter( fp ) )
{
afx = new_armor_context ();
push_armor_filter ( afx, fp );
}
}
if (!opt.outfile)
{
- no_out = 1;
opt.outfile = "-";
+ opt.flags.dummy_outfile = 1;
}
+ else
+ opt.flags.dummy_outfile = 0;
rc = proc_encryption_packets (ctrl, NULL, fp );
- if (no_out)
+ if (opt.flags.dummy_outfile)
opt.outfile = NULL;
iobuf_close (fp);
release_armor_context (afx);
release_progress_context (pfx);
return rc;
}
/* Same as decrypt_message but takes a file descriptor for input and
output. */
gpg_error_t
decrypt_message_fd (ctrl_t ctrl, int input_fd, int output_fd)
{
#ifdef HAVE_W32_SYSTEM
/* No server mode yet. */
(void)ctrl;
(void)input_fd;
(void)output_fd;
return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
#else
gpg_error_t err;
IOBUF fp;
armor_filter_context_t *afx = NULL;
progress_filter_context_t *pfx;
if (opt.outfp)
return gpg_error (GPG_ERR_BUG);
pfx = new_progress_context ();
/* Open the message file. */
fp = iobuf_fdopen_nc (FD2INT(input_fd), "rb");
if (fp && is_secured_file (iobuf_get_fd (fp)))
{
iobuf_close (fp);
fp = NULL;
gpg_err_set_errno (EPERM);
}
if (!fp)
{
char xname[64];
err = gpg_error_from_syserror ();
snprintf (xname, sizeof xname, "[fd %d]", input_fd);
log_error (_("can't open '%s': %s\n"), xname, gpg_strerror (err));
release_progress_context (pfx);
return err;
}
#ifdef HAVE_W32CE_SYSTEM
#warning Need to fix this if we want to use g13
opt.outfp = NULL;
#else
opt.outfp = es_fdopen_nc (output_fd, "wb");
#endif
if (!opt.outfp)
{
char xname[64];
err = gpg_error_from_syserror ();
snprintf (xname, sizeof xname, "[fd %d]", output_fd);
log_error (_("can't open '%s': %s\n"), xname, gpg_strerror (err));
iobuf_close (fp);
release_progress_context (pfx);
return err;
}
if (!opt.no_armor)
{
if (use_armor_filter (fp))
{
afx = new_armor_context ();
push_armor_filter ( afx, fp );
}
}
err = proc_encryption_packets (ctrl, NULL, fp );
iobuf_close (fp);
es_fclose (opt.outfp);
opt.outfp = NULL;
release_armor_context (afx);
release_progress_context (pfx);
return err;
#endif
}
void
decrypt_messages (ctrl_t ctrl, int nfiles, char *files[])
{
IOBUF fp;
progress_filter_context_t *pfx;
char *p, *output = NULL;
int rc=0,use_stdin=0;
unsigned int lno=0;
if (opt.outfile)
{
log_error(_("--output doesn't work for this command\n"));
return;
}
pfx = new_progress_context ();
if(!nfiles)
use_stdin=1;
for(;;)
{
char line[2048];
char *filename=NULL;
if(use_stdin)
{
if(fgets(line, DIM(line), stdin))
{
lno++;
if (!*line || line[strlen(line)-1] != '\n')
log_error("input line %u too long or missing LF\n", lno);
else
{
line[strlen(line)-1] = '\0';
filename=line;
}
}
}
else
{
if(nfiles)
{
filename=*files;
nfiles--;
files++;
}
}
if(filename==NULL)
break;
print_file_status(STATUS_FILE_START, filename, 3);
output = make_outfile_name(filename);
if (!output)
goto next_file;
fp = iobuf_open(filename);
if (fp)
iobuf_ioctl (fp, IOBUF_IOCTL_NO_CACHE, 1, NULL);
if (fp && is_secured_file (iobuf_get_fd (fp)))
{
iobuf_close (fp);
fp = NULL;
gpg_err_set_errno (EPERM);
}
if (!fp)
{
log_error(_("can't open '%s'\n"), print_fname_stdin(filename));
goto next_file;
}
handle_progress (pfx, fp, filename);
if (!opt.no_armor)
{
if (use_armor_filter(fp))
{
armor_filter_context_t *afx = new_armor_context ();
rc = push_armor_filter (afx, fp);
if (rc)
log_error("failed to push armor filter");
release_armor_context (afx);
}
}
rc = proc_packets (ctrl,NULL, fp);
iobuf_close(fp);
if (rc)
log_error("%s: decryption failed: %s\n", print_fname_stdin(filename),
gpg_strerror (rc));
p = get_last_passphrase();
set_next_passphrase(p);
xfree (p);
next_file:
/* Note that we emit file_done even after an error. */
write_status( STATUS_FILE_DONE );
xfree(output);
reset_literals_seen();
}
set_next_passphrase(NULL);
release_progress_context (pfx);
}
diff --git a/g10/delkey.c b/g10/delkey.c
index cc5673846..b5ab47434 100644
--- a/g10/delkey.c
+++ b/g10/delkey.c
@@ -1,292 +1,379 @@
/* delkey.c - delete keys
* Copyright (C) 1998, 1999, 2000, 2001, 2002, 2004,
* 2005, 2006 Free Software Foundation, Inc.
- * Copyright (C) 2014 Werner Koch
+ * Copyright (C) 2014, 2019 Werner Koch
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#include "gpg.h"
#include "options.h"
#include "packet.h"
#include "../common/status.h"
#include "../common/iobuf.h"
#include "keydb.h"
#include "../common/util.h"
#include "main.h"
#include "trustdb.h"
#include "filter.h"
#include "../common/ttyio.h"
#include "../common/i18n.h"
#include "call-agent.h"
/****************
* Delete a public or secret key from a keyring.
* r_sec_avail will be set if a secret key is available and the public
* key can't be deleted for that reason.
*/
static gpg_error_t
do_delete_key (ctrl_t ctrl, const char *username, int secret, int force,
int *r_sec_avail)
{
gpg_error_t err;
kbnode_t keyblock = NULL;
kbnode_t node, kbctx;
+ kbnode_t targetnode;
KEYDB_HANDLE hd;
PKT_public_key *pk = NULL;
u32 keyid[2];
int okay=0;
int yes;
KEYDB_SEARCH_DESC desc;
- int exactmatch;
+ int exactmatch; /* True if key was found by fingerprint. */
+ int thiskeyonly; /* 0 = false, 1 = is primary key, 2 = is a subkey. */
*r_sec_avail = 0;
hd = keydb_new ();
if (!hd)
return gpg_error_from_syserror ();
/* Search the userid. */
err = classify_user_id (username, &desc, 1);
exactmatch = (desc.mode == KEYDB_SEARCH_MODE_FPR);
+ thiskeyonly = desc.exact;
if (!err)
err = keydb_search (hd, &desc, 1, NULL);
if (err)
{
log_error (_("key \"%s\" not found: %s\n"), username, gpg_strerror (err));
write_status_text (STATUS_DELETE_PROBLEM, "1");
goto leave;
}
/* Read the keyblock. */
err = keydb_get_keyblock (hd, &keyblock);
if (err)
{
log_error (_("error reading keyblock: %s\n"), gpg_strerror (err) );
goto leave;
}
/* Get the keyid from the keyblock. */
node = find_kbnode( keyblock, PKT_PUBLIC_KEY );
if (!node)
{
log_error ("Oops; key not found anymore!\n");
err = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
- pk = node->pkt->pkt.public_key;
+
+ /* If an operation only on a subkey is requested, find that subkey
+ * now. */
+ if (thiskeyonly)
+ {
+ kbnode_t tmpnode;
+
+ for (kbctx=NULL; (tmpnode = walk_kbnode (keyblock, &kbctx, 0)); )
+ {
+ if (!(tmpnode->pkt->pkttype == PKT_PUBLIC_KEY
+ || tmpnode->pkt->pkttype == PKT_PUBLIC_SUBKEY))
+ continue;
+ if (exact_subkey_match_p (&desc, tmpnode))
+ break;
+ }
+ if (!tmpnode)
+ {
+ log_error ("Oops; requested subkey not found anymore!\n");
+ err = gpg_error (GPG_ERR_GENERAL);
+ goto leave;
+ }
+ /* Set NODE to this specific subkey or primary key. */
+ thiskeyonly = node == tmpnode? 1 : 2;
+ targetnode = tmpnode;
+ }
+ else
+ targetnode = node;
+
+ pk = targetnode->pkt->pkt.public_key;
keyid_from_pk (pk, keyid);
if (!secret && !force)
{
if (have_secret_key_with_kid (keyid))
{
*r_sec_avail = 1;
err = gpg_error (GPG_ERR_EOF);
goto leave;
}
else
err = 0;
}
if (secret && !have_secret_key_with_kid (keyid))
{
err = gpg_error (GPG_ERR_NOT_FOUND);
log_error (_("key \"%s\" not found\n"), username);
write_status_text (STATUS_DELETE_PROBLEM, "1");
goto leave;
}
if (opt.batch && exactmatch)
okay++;
else if (opt.batch && secret)
{
log_error(_("can't do this in batch mode\n"));
log_info (_("(unless you specify the key by fingerprint)\n"));
}
else if (opt.batch && opt.answer_yes)
okay++;
else if (opt.batch)
{
log_error(_("can't do this in batch mode without \"--yes\"\n"));
log_info (_("(unless you specify the key by fingerprint)\n"));
}
else
{
- if (secret)
- print_seckey_info (ctrl, pk);
- else
- print_pubkey_info (ctrl, NULL, pk );
- tty_printf( "\n" );
+ print_key_info (ctrl, NULL, 0, pk, secret);
+ tty_printf ("\n");
+ if (thiskeyonly == 1 && !secret)
+ {
+ /* We need to delete the entire public key despite the use
+ * of the thiskeyonly request. */
+ tty_printf (_("Note: The public primary key and all its subkeys"
+ " will be deleted.\n"));
+ }
+ else if (thiskeyonly == 2 && !secret)
+ {
+ tty_printf (_("Note: Only the shown public subkey"
+ " will be deleted.\n"));
+ }
+ if (thiskeyonly == 1 && secret)
+ {
+ tty_printf (_("Note: Only the secret part of the shown primary"
+ " key will be deleted.\n"));
+ }
+ else if (thiskeyonly == 2 && secret)
+ {
+ tty_printf (_("Note: Only the secret part of the shown subkey"
+ " will be deleted.\n"));
+ }
+
+ if (thiskeyonly)
+ tty_printf ("\n");
yes = cpr_get_answer_is_yes
(secret? "delete_key.secret.okay": "delete_key.okay",
_("Delete this key from the keyring? (y/N) "));
if (!cpr_enabled() && secret && yes)
{
/* I think it is not required to check a passphrase; if the
* user is so stupid as to let others access his secret
* keyring (and has no backup) - it is up him to read some
* very basic texts about security. */
yes = cpr_get_answer_is_yes
("delete_key.secret.okay",
_("This is a secret key! - really delete? (y/N) "));
}
if (yes)
okay++;
}
if (okay)
{
if (secret)
{
char *prompt;
gpg_error_t firsterr = 0;
char *hexgrip;
setup_main_keyids (keyblock);
for (kbctx=NULL; (node = walk_kbnode (keyblock, &kbctx, 0)); )
{
if (!(node->pkt->pkttype == PKT_PUBLIC_KEY
|| node->pkt->pkttype == PKT_PUBLIC_SUBKEY))
continue;
+ if (thiskeyonly && targetnode != node)
+ continue;
+
if (agent_probe_secret_key (NULL, node->pkt->pkt.public_key))
continue; /* No secret key for that public (sub)key. */
prompt = gpg_format_keydesc (ctrl,
node->pkt->pkt.public_key,
FORMAT_KEYDESC_DELKEY, 1);
err = hexkeygrip_from_pk (node->pkt->pkt.public_key, &hexgrip);
/* NB: We require --yes to advise the agent not to
* request a confirmation. The rationale for this extra
* pre-caution is that since 2.1 the secret key may also
* be used for other protocols and thus deleting it from
* the gpg would also delete the key for other tools. */
- if (!err)
+ if (!err && !opt.dry_run)
err = agent_delete_key (NULL, hexgrip, prompt,
opt.answer_yes);
xfree (prompt);
xfree (hexgrip);
if (err)
{
if (gpg_err_code (err) == GPG_ERR_KEY_ON_CARD)
write_status_text (STATUS_DELETE_PROBLEM, "1");
log_error (_("deleting secret %s failed: %s\n"),
(node->pkt->pkttype == PKT_PUBLIC_KEY
? _("key"):_("subkey")),
gpg_strerror (err));
if (!firsterr)
firsterr = err;
if (gpg_err_code (err) == GPG_ERR_CANCELED
|| gpg_err_code (err) == GPG_ERR_FULLY_CANCELED)
{
write_status_error ("delete_key.secret", err);
break;
}
}
}
err = firsterr;
if (firsterr)
goto leave;
}
+ else if (thiskeyonly == 2)
+ {
+ int selected = 0;
+
+ /* Delete the specified public subkey. */
+ for (kbctx=NULL; (node = walk_kbnode (keyblock, &kbctx, 0)); )
+ {
+ if (thiskeyonly && targetnode != node)
+ continue;
+
+ if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
+ {
+ selected = targetnode == node;
+ if (selected)
+ delete_kbnode (node);
+ }
+ else if (selected && node->pkt->pkttype == PKT_SIGNATURE)
+ delete_kbnode (node);
+ else
+ selected = 0;
+ }
+ commit_kbnode (&keyblock);
+ err = keydb_update_keyblock (ctrl, hd, keyblock);
+ if (err)
+ {
+ log_error (_("update failed: %s\n"), gpg_strerror (err));
+ goto leave;
+ }
+ }
else
{
err = keydb_delete_keyblock (hd);
if (err)
{
log_error (_("deleting keyblock failed: %s\n"),
gpg_strerror (err));
goto leave;
}
}
/* Note that the ownertrust being cleared will trigger a
revalidation_mark(). This makes sense - only deleting keys
that have ownertrust set should trigger this. */
- if (!secret && pk && clear_ownertrusts (ctrl, pk))
+ if (!secret && pk && !opt.dry_run && thiskeyonly != 2
+ && clear_ownertrusts (ctrl, pk))
{
if (opt.verbose)
log_info (_("ownertrust information cleared\n"));
}
}
leave:
keydb_release (hd);
release_kbnode (keyblock);
return err;
}
-/****************
+
+/*
* Delete a public or secret key from a keyring.
*/
gpg_error_t
delete_keys (ctrl_t ctrl, strlist_t names, int secret, int allow_both)
{
gpg_error_t err;
int avail;
int force = (!allow_both && !secret && opt.expert);
/* Force allows us to delete a public key even if a secret key
exists. */
for ( ;names ; names=names->next )
{
err = do_delete_key (ctrl, names->d, secret, force, &avail);
if (err && avail)
{
if (allow_both)
{
err = do_delete_key (ctrl, names->d, 1, 0, &avail);
if (!err)
err = do_delete_key (ctrl, names->d, 0, 0, &avail);
}
else
{
log_error (_("there is a secret key for public key \"%s\"!\n"),
names->d);
log_info(_("use option \"--delete-secret-keys\" to delete"
" it first.\n"));
write_status_text (STATUS_DELETE_PROBLEM, "2");
return err;
}
}
if (err)
{
log_error ("%s: delete key failed: %s\n",
names->d, gpg_strerror (err));
return err;
}
}
return 0;
}
diff --git a/g10/exec.c b/g10/exec.c
index 74a83970e..3e5dc278b 100644
--- a/g10/exec.c
+++ b/g10/exec.c
@@ -1,635 +1,697 @@
/* exec.c - generic call-a-program code
* Copyright (C) 2001, 2002, 2003, 2004, 2005 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
/*
FIXME: We should replace most code in this module by our
spawn implementation from common/exechelp.c.
*/
#include <config.h>
#include <stdlib.h>
#include <stdarg.h>
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#ifndef EXEC_TEMPFILE_ONLY
#include <sys/wait.h>
#endif
#ifdef HAVE_DOSISH_SYSTEM
# ifdef HAVE_WINSOCK2_H
# include <winsock2.h>
# endif
# include <windows.h>
#endif
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include "gpg.h"
#include "options.h"
#include "../common/i18n.h"
#include "../common/iobuf.h"
#include "../common/util.h"
#include "../common/membuf.h"
#include "../common/sysutils.h"
#include "exec.h"
#ifdef NO_EXEC
int
exec_write(struct exec_info **info,const char *program,
const char *args_in,const char *name,int writeonly,int binary)
{
log_error(_("no remote program execution supported\n"));
return GPG_ERR_GENERAL;
}
int
exec_read(struct exec_info *info) { return GPG_ERR_GENERAL; }
int
exec_finish(struct exec_info *info) { return GPG_ERR_GENERAL; }
int
set_exec_path(const char *path) { return GPG_ERR_GENERAL; }
#else /* ! NO_EXEC */
#if defined (_WIN32)
/* This is a nicer system() for windows that waits for programs to
return before returning control to the caller. I hate helpful
computers. */
static int
w32_system(const char *command)
{
-#ifdef HAVE_W32CE_SYSTEM
-#warning Change this code to use common/exechelp.c
-#else
- PROCESS_INFORMATION pi;
- STARTUPINFO si;
- char *string;
+ if (!strncmp (command, "!ShellExecute ", 14))
+ {
+ SHELLEXECUTEINFOW see;
+ wchar_t *wname;
+ int waitms;
+
+ command = command + 14;
+ while (spacep (command))
+ command++;
+ waitms = atoi (command);
+ if (waitms < 0)
+ waitms = 0;
+ else if (waitms > 60*1000)
+ waitms = 60000;
+ while (*command && !spacep (command))
+ command++;
+ while (spacep (command))
+ command++;
+
+ wname = utf8_to_wchar (command);
+ if (!wname)
+ return -1;
+
+ memset (&see, 0, sizeof see);
+ see.cbSize = sizeof see;
+ see.fMask = (SEE_MASK_NOCLOSEPROCESS
+ | SEE_MASK_NOASYNC
+ | SEE_MASK_FLAG_NO_UI
+ | SEE_MASK_NO_CONSOLE);
+ see.lpVerb = L"open";
+ see.lpFile = (LPCWSTR)wname;
+ see.nShow = SW_SHOW;
+
+ if (DBG_EXTPROG)
+ log_debug ("running ShellExecuteEx(open,'%s')\n", command);
+ if (!ShellExecuteExW (&see))
+ {
+ if (DBG_EXTPROG)
+ log_debug ("ShellExecuteEx failed: rc=%d\n", (int)GetLastError ());
+ xfree (wname);
+ return -1;
+ }
+ if (DBG_EXTPROG)
+ log_debug ("ShellExecuteEx succeeded (hProcess=%p,hInstApp=%d)\n",
+ see.hProcess, (int)see.hInstApp);
+
+ if (!see.hProcess)
+ {
+ gnupg_usleep (waitms*1000);
+ if (DBG_EXTPROG)
+ log_debug ("ShellExecuteEx ready (wait=%dms)\n", waitms);
+ }
+ else
+ {
+ WaitForSingleObject (see.hProcess, INFINITE);
+ if (DBG_EXTPROG)
+ log_debug ("ShellExecuteEx ready\n");
+ }
+ CloseHandle (see.hProcess);
+
+ xfree (wname);
+ }
+ else
+ {
+ char *string;
+ PROCESS_INFORMATION pi;
+ STARTUPINFO si;
- /* We must use a copy of the command as CreateProcess modifies this
- argument. */
- string=xstrdup(command);
+ /* We must use a copy of the command as CreateProcess modifies
+ * this argument. */
+ string = xstrdup (command);
- memset(&pi,0,sizeof(pi));
- memset(&si,0,sizeof(si));
- si.cb=sizeof(si);
+ memset (&pi, 0, sizeof(pi));
+ memset (&si, 0, sizeof(si));
+ si.cb = sizeof (si);
- if(!CreateProcess(NULL,string,NULL,NULL,FALSE,
- DETACHED_PROCESS,
- NULL,NULL,&si,&pi))
- return -1;
+ if (!CreateProcess (NULL, string, NULL, NULL, FALSE,
+ DETACHED_PROCESS,
+ NULL, NULL, &si, &pi))
+ return -1;
- /* Wait for the child to exit */
- WaitForSingleObject(pi.hProcess,INFINITE);
+ /* Wait for the child to exit */
+ WaitForSingleObject (pi.hProcess, INFINITE);
- CloseHandle(pi.hProcess);
- CloseHandle(pi.hThread);
- xfree(string);
+ CloseHandle (pi.hProcess);
+ CloseHandle (pi.hThread);
+ xfree (string);
+ }
return 0;
-#endif
}
-#endif
+#endif /*_W32*/
+
/* Replaces current $PATH */
int
set_exec_path(const char *path)
{
#ifdef HAVE_W32CE_SYSTEM
#warning Change this code to use common/exechelp.c
#else
char *p;
p=xmalloc(5+strlen(path)+1);
strcpy(p,"PATH=");
strcat(p,path);
if(DBG_EXTPROG)
log_debug("set_exec_path: %s\n",p);
/* Notice that path is never freed. That is intentional due to the
way putenv() works. This leaks a few bytes if we call
set_exec_path multiple times. */
if(putenv(p)!=0)
return GPG_ERR_GENERAL;
else
return 0;
#endif
}
/* Makes a temp directory and filenames */
static int
make_tempdir(struct exec_info *info)
{
char *tmp=opt.temp_dir,*namein=info->name,*nameout;
if(!namein)
namein=info->flags.binary?"tempin" EXTSEP_S "bin":"tempin" EXTSEP_S "txt";
nameout=info->flags.binary?"tempout" EXTSEP_S "bin":"tempout" EXTSEP_S "txt";
/* Make up the temp dir and files in case we need them */
if(tmp==NULL)
{
#if defined (_WIN32)
int err;
tmp=xmalloc(MAX_PATH+2);
err=GetTempPath(MAX_PATH+1,tmp);
if(err==0 || err>MAX_PATH+1)
strcpy(tmp,"c:\\windows\\temp");
else
{
int len=strlen(tmp);
/* GetTempPath may return with \ on the end */
while(len>0 && tmp[len-1]=='\\')
{
tmp[len-1]='\0';
len--;
}
}
#else /* More unixish systems */
tmp=getenv("TMPDIR");
if(tmp==NULL)
{
tmp=getenv("TMP");
if(tmp==NULL)
{
#ifdef __riscos__
tmp="<Wimp$ScrapDir>.GnuPG";
mkdir(tmp,0700); /* Error checks occur later on */
#else
tmp="/tmp";
#endif
}
}
#endif
}
info->tempdir=xmalloc(strlen(tmp)+strlen(DIRSEP_S)+10+1);
sprintf(info->tempdir,"%s" DIRSEP_S "gpg-XXXXXX",tmp);
#if defined (_WIN32)
xfree(tmp);
#endif
if (!gnupg_mkdtemp(info->tempdir))
log_error(_("can't create directory '%s': %s\n"),
info->tempdir,strerror(errno));
else
{
info->flags.madedir=1;
info->tempfile_in=xmalloc(strlen(info->tempdir)+
strlen(DIRSEP_S)+strlen(namein)+1);
sprintf(info->tempfile_in,"%s" DIRSEP_S "%s",info->tempdir,namein);
if(!info->flags.writeonly)
{
info->tempfile_out=xmalloc(strlen(info->tempdir)+
strlen(DIRSEP_S)+strlen(nameout)+1);
sprintf(info->tempfile_out,"%s" DIRSEP_S "%s",info->tempdir,nameout);
}
}
return info->flags.madedir? 0 : GPG_ERR_GENERAL;
}
/* Expands %i and %o in the args to the full temp files within the
temp directory. */
static int
expand_args(struct exec_info *info,const char *args_in)
{
const char *ch = args_in;
membuf_t command;
info->flags.use_temp_files=0;
info->flags.keep_temp_files=0;
if(DBG_EXTPROG)
log_debug("expanding string \"%s\"\n",args_in);
init_membuf (&command, 100);
while(*ch!='\0')
{
if(*ch=='%')
{
char *append=NULL;
ch++;
switch(*ch)
{
case 'O':
info->flags.keep_temp_files=1;
/* fall through */
case 'o': /* out */
if(!info->flags.madedir)
{
if(make_tempdir(info))
goto fail;
}
append=info->tempfile_out;
info->flags.use_temp_files=1;
break;
case 'I':
info->flags.keep_temp_files=1;
/* fall through */
case 'i': /* in */
if(!info->flags.madedir)
{
if(make_tempdir(info))
goto fail;
}
append=info->tempfile_in;
info->flags.use_temp_files=1;
break;
case '%':
append="%";
break;
}
if(append)
put_membuf_str (&command, append);
}
else
put_membuf (&command, ch, 1);
ch++;
}
put_membuf (&command, "", 1); /* Terminate string. */
info->command = get_membuf (&command, NULL);
if (!info->command)
return gpg_error_from_syserror ();
if(DBG_EXTPROG)
log_debug("args expanded to \"%s\", use %u, keep %u\n",info->command,
info->flags.use_temp_files,info->flags.keep_temp_files);
return 0;
fail:
xfree (get_membuf (&command, NULL));
return GPG_ERR_GENERAL;
}
/* Either handles the tempfile creation, or the fork/exec. If it
returns ok, then info->tochild is a FILE * that can be written to.
The rules are: if there are no args, then it's a fork/exec/pipe.
If there are args, but no tempfiles, then it's a fork/exec/pipe via
shell -c. If there are tempfiles, then it's a system. */
int
exec_write(struct exec_info **info,const char *program,
const char *args_in,const char *name,int writeonly,int binary)
{
int ret = GPG_ERR_GENERAL;
if(opt.exec_disable && !opt.no_perm_warn)
{
log_info(_("external program calls are disabled due to unsafe "
"options file permissions\n"));
return ret;
}
#if defined(HAVE_GETUID) && defined(HAVE_GETEUID)
/* There should be no way to get to this spot while still carrying
setuid privs. Just in case, bomb out if we are. */
if ( getuid () != geteuid ())
BUG ();
#endif
if(program==NULL && args_in==NULL)
BUG();
*info=xmalloc_clear(sizeof(struct exec_info));
if(name)
(*info)->name=xstrdup(name);
(*info)->flags.binary=binary;
(*info)->flags.writeonly=writeonly;
/* Expand the args, if any */
if(args_in && expand_args(*info,args_in))
goto fail;
#ifdef EXEC_TEMPFILE_ONLY
if(!(*info)->flags.use_temp_files)
{
log_error(_("this platform requires temporary files when calling"
" external programs\n"));
goto fail;
}
#else /* !EXEC_TEMPFILE_ONLY */
/* If there are no args, or there are args, but no temp files, we
can use fork/exec/pipe */
if(args_in==NULL || (*info)->flags.use_temp_files==0)
{
int to[2],from[2];
if(pipe(to)==-1)
goto fail;
if(pipe(from)==-1)
{
close(to[0]);
close(to[1]);
goto fail;
}
if(((*info)->child=fork())==-1)
{
close(to[0]);
close(to[1]);
close(from[0]);
close(from[1]);
goto fail;
}
if((*info)->child==0)
{
char *shell=getenv("SHELL");
if(shell==NULL)
shell="/bin/sh";
/* I'm the child */
/* If the program isn't going to respond back, they get to
keep their stdout/stderr */
if(!(*info)->flags.writeonly)
{
/* implied close of STDERR */
if(dup2(STDOUT_FILENO,STDERR_FILENO)==-1)
_exit(1);
/* implied close of STDOUT */
close(from[0]);
if(dup2(from[1],STDOUT_FILENO)==-1)
_exit(1);
}
/* implied close of STDIN */
close(to[1]);
if(dup2(to[0],STDIN_FILENO)==-1)
_exit(1);
if(args_in==NULL)
{
if(DBG_EXTPROG)
log_debug("execlp: %s\n",program);
execlp(program,program,(void *)NULL);
}
else
{
if(DBG_EXTPROG)
log_debug("execlp: %s -c %s\n",shell,(*info)->command);
execlp(shell,shell,"-c",(*info)->command,(void *)NULL);
}
/* If we get this far the exec failed. Clean up and return. */
if(args_in==NULL)
log_error(_("unable to execute program '%s': %s\n"),
program,strerror(errno));
else
log_error(_("unable to execute shell '%s': %s\n"),
shell,strerror(errno));
/* This mimics the POSIX sh behavior - 127 means "not found"
from the shell. */
if(errno==ENOENT)
_exit(127);
_exit(1);
}
/* I'm the parent */
close(to[0]);
(*info)->tochild=fdopen(to[1],binary?"wb":"w");
if((*info)->tochild==NULL)
{
ret = gpg_error_from_syserror ();
close(to[1]);
goto fail;
}
close(from[1]);
(*info)->fromchild=iobuf_fdopen(from[0],"r");
if((*info)->fromchild==NULL)
{
ret = gpg_error_from_syserror ();
close(from[0]);
goto fail;
}
/* fd iobufs are cached! */
iobuf_ioctl((*info)->fromchild, IOBUF_IOCTL_NO_CACHE, 1, NULL);
return 0;
}
#endif /* !EXEC_TEMPFILE_ONLY */
if(DBG_EXTPROG)
log_debug("using temp file '%s'\n",(*info)->tempfile_in);
/* It's not fork/exec/pipe, so create a temp file */
if( is_secured_filename ((*info)->tempfile_in) )
{
(*info)->tochild = NULL;
gpg_err_set_errno (EPERM);
}
else
(*info)->tochild=fopen((*info)->tempfile_in,binary?"wb":"w");
if((*info)->tochild==NULL)
{
ret = gpg_error_from_syserror ();
log_error(_("can't create '%s': %s\n"),
(*info)->tempfile_in,strerror(errno));
goto fail;
}
ret=0;
fail:
if (ret)
{
xfree (*info);
*info = NULL;
}
return ret;
}
int
exec_read(struct exec_info *info)
{
int ret = GPG_ERR_GENERAL;
fclose(info->tochild);
info->tochild=NULL;
if(info->flags.use_temp_files)
{
if(DBG_EXTPROG)
- log_debug("system() command is %s\n",info->command);
+ log_debug ("running command: %s\n",info->command);
#if defined (_WIN32)
info->progreturn=w32_system(info->command);
#else
info->progreturn=system(info->command);
#endif
if(info->progreturn==-1)
{
log_error(_("system error while calling external program: %s\n"),
strerror(errno));
info->progreturn=127;
goto fail;
}
#if defined(WIFEXITED) && defined(WEXITSTATUS)
if(WIFEXITED(info->progreturn))
info->progreturn=WEXITSTATUS(info->progreturn);
else
{
log_error(_("unnatural exit of external program\n"));
info->progreturn=127;
goto fail;
}
#else
/* If we don't have the macros, do the best we can. */
info->progreturn = (info->progreturn & 0xff00) >> 8;
#endif
/* 127 is the magic value returned from system() to indicate
that the shell could not be executed, or from /bin/sh to
indicate that the program could not be executed. */
if(info->progreturn==127)
{
log_error(_("unable to execute external program\n"));
goto fail;
}
if(!info->flags.writeonly)
{
info->fromchild=iobuf_open(info->tempfile_out);
if (info->fromchild
&& is_secured_file (iobuf_get_fd (info->fromchild)))
{
iobuf_close (info->fromchild);
info->fromchild = NULL;
gpg_err_set_errno (EPERM);
}
if(info->fromchild==NULL)
{
ret = gpg_error_from_syserror ();
log_error(_("unable to read external program response: %s\n"),
strerror(errno));
goto fail;
}
/* Do not cache this iobuf on close */
iobuf_ioctl(info->fromchild, IOBUF_IOCTL_NO_CACHE, 1, NULL);
}
}
ret=0;
fail:
return ret;
}
int
exec_finish(struct exec_info *info)
{
int ret=info->progreturn;
if(info->fromchild)
iobuf_close(info->fromchild);
if(info->tochild)
fclose(info->tochild);
#ifndef EXEC_TEMPFILE_ONLY
if(info->child>0)
{
if(waitpid(info->child,&info->progreturn,0)!=0 &&
WIFEXITED(info->progreturn))
ret=WEXITSTATUS(info->progreturn);
else
{
log_error(_("unnatural exit of external program\n"));
ret=127;
}
}
#endif
if(info->flags.madedir && !info->flags.keep_temp_files)
{
if(info->tempfile_in)
{
if(unlink(info->tempfile_in)==-1)
log_info(_("WARNING: unable to remove tempfile (%s) '%s': %s\n"),
"in",info->tempfile_in,strerror(errno));
}
if(info->tempfile_out)
{
if(unlink(info->tempfile_out)==-1)
log_info(_("WARNING: unable to remove tempfile (%s) '%s': %s\n"),
"out",info->tempfile_out,strerror(errno));
}
if(rmdir(info->tempdir)==-1)
log_info(_("WARNING: unable to remove temp directory '%s': %s\n"),
info->tempdir,strerror(errno));
}
xfree(info->command);
xfree(info->name);
xfree(info->tempdir);
xfree(info->tempfile_in);
xfree(info->tempfile_out);
xfree(info);
return ret;
}
#endif /* ! NO_EXEC */
diff --git a/g10/export.c b/g10/export.c
index 4f6c9137e..e8bf14cf5 100644
--- a/g10/export.c
+++ b/g10/export.c
@@ -1,2434 +1,2437 @@
/* export.c - Export keys in the OpenPGP defined format.
* Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004,
* 2005, 2010 Free Software Foundation, Inc.
* Copyright (C) 1998-2016 Werner Koch
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include "gpg.h"
#include "options.h"
#include "packet.h"
#include "../common/status.h"
#include "keydb.h"
#include "../common/util.h"
#include "main.h"
#include "../common/i18n.h"
#include "../common/membuf.h"
#include "../common/host2net.h"
#include "../common/zb32.h"
#include "../common/recsel.h"
#include "../common/mbox-util.h"
#include "../common/init.h"
#include "trustdb.h"
#include "call-agent.h"
#include "key-clean.h"
/* An object to keep track of subkeys. */
struct subkey_list_s
{
struct subkey_list_s *next;
u32 kid[2];
};
typedef struct subkey_list_s *subkey_list_t;
/* An object to track statistics for export operations. */
struct export_stats_s
{
ulong count; /* Number of processed keys. */
ulong secret_count; /* Number of secret keys seen. */
ulong exported; /* Number of actual exported keys. */
};
/* A global variable to store the selector created from
* --export-filter keep-uid=EXPR.
* --export-filter drop-subkey=EXPR.
*
* FIXME: We should put this into the CTRL object but that requires a
* lot more changes right now.
*/
static recsel_expr_t export_keep_uid;
static recsel_expr_t export_drop_subkey;
/* Local prototypes. */
static int do_export (ctrl_t ctrl, strlist_t users, int secret,
unsigned int options, export_stats_t stats);
static int do_export_stream (ctrl_t ctrl, iobuf_t out,
strlist_t users, int secret,
kbnode_t *keyblock_out, unsigned int options,
export_stats_t stats, int *any);
static gpg_error_t print_pka_or_dane_records
/**/ (iobuf_t out, kbnode_t keyblock, PKT_public_key *pk,
const void *data, size_t datalen,
int print_pka, int print_dane);
static void
cleanup_export_globals (void)
{
recsel_release (export_keep_uid);
export_keep_uid = NULL;
recsel_release (export_drop_subkey);
export_drop_subkey = NULL;
}
/* Option parser for export options. See parse_options for
details. */
int
parse_export_options(char *str,unsigned int *options,int noisy)
{
struct parse_options export_opts[]=
{
{"export-local-sigs",EXPORT_LOCAL_SIGS,NULL,
N_("export signatures that are marked as local-only")},
{"export-attributes",EXPORT_ATTRIBUTES,NULL,
N_("export attribute user IDs (generally photo IDs)")},
{"export-sensitive-revkeys",EXPORT_SENSITIVE_REVKEYS,NULL,
N_("export revocation keys marked as \"sensitive\"")},
{"export-clean",EXPORT_CLEAN,NULL,
N_("remove unusable parts from key during export")},
{"export-minimal",EXPORT_MINIMAL|EXPORT_CLEAN,NULL,
N_("remove as much as possible from key during export")},
{"export-drop-uids", EXPORT_DROP_UIDS, NULL,
N_("Do not export user id or attribute packets")},
{"export-pka", EXPORT_PKA_FORMAT, NULL, NULL },
{"export-dane", EXPORT_DANE_FORMAT, NULL, NULL },
{"backup", EXPORT_BACKUP, NULL,
N_("use the GnuPG key backup format")},
{"export-backup", EXPORT_BACKUP, NULL, NULL },
/* Aliases for backward compatibility */
{"include-local-sigs",EXPORT_LOCAL_SIGS,NULL,NULL},
{"include-attributes",EXPORT_ATTRIBUTES,NULL,NULL},
{"include-sensitive-revkeys",EXPORT_SENSITIVE_REVKEYS,NULL,NULL},
/* dummy */
{"export-unusable-sigs",0,NULL,NULL},
{"export-clean-sigs",0,NULL,NULL},
{"export-clean-uids",0,NULL,NULL},
{NULL,0,NULL,NULL}
/* add tags for include revoked and disabled? */
};
int rc;
rc = parse_options (str, options, export_opts, noisy);
if (!rc)
return 0;
/* Alter other options we want or don't want for restore. */
if ((*options & EXPORT_BACKUP))
{
*options |= (EXPORT_LOCAL_SIGS | EXPORT_ATTRIBUTES
| EXPORT_SENSITIVE_REVKEYS);
*options &= ~(EXPORT_CLEAN | EXPORT_MINIMAL
| EXPORT_PKA_FORMAT | EXPORT_DANE_FORMAT);
}
/* Dropping uids also means to drop attributes. */
if ((*options & EXPORT_DROP_UIDS))
*options &= ~(EXPORT_ATTRIBUTES);
return rc;
}
/* Parse and set an export filter from string. STRING has the format
* "NAME=EXPR" with NAME being the name of the filter. Spaces before
* and after NAME are not allowed. If this function is called several
* times all expressions for the same NAME are concatenated.
* Supported filter names are:
*
* - keep-uid :: If the expression evaluates to true for a certain
* user ID packet, that packet and all it dependencies
* will be exported. The expression may use these
* variables:
*
* - uid :: The entire user ID.
* - mbox :: The mail box part of the user ID.
* - primary :: Evaluate to true for the primary user ID.
*
* - drop-subkey :: If the expression evaluates to true for a subkey
* packet that subkey and all it dependencies will be
* remove from the keyblock. The expression may use these
* variables:
*
* - secret :: 1 for a secret subkey, else 0.
* - key_algo :: Public key algorithm id
*/
gpg_error_t
parse_and_set_export_filter (const char *string)
{
gpg_error_t err;
/* Auto register the cleanup function. */
register_mem_cleanup_func (cleanup_export_globals);
if (!strncmp (string, "keep-uid=", 9))
err = recsel_parse_expr (&export_keep_uid, string+9);
else if (!strncmp (string, "drop-subkey=", 12))
err = recsel_parse_expr (&export_drop_subkey, string+12);
else
err = gpg_error (GPG_ERR_INV_NAME);
return err;
}
/* Create a new export stats object initialized to zero. On error
returns NULL and sets ERRNO. */
export_stats_t
export_new_stats (void)
{
export_stats_t stats;
return xtrycalloc (1, sizeof *stats);
}
/* Release an export stats object. */
void
export_release_stats (export_stats_t stats)
{
xfree (stats);
}
/* Print export statistics using the status interface. */
void
export_print_stats (export_stats_t stats)
{
if (!stats)
return;
if (is_status_enabled ())
{
char buf[15*20];
snprintf (buf, sizeof buf, "%lu %lu %lu",
stats->count,
stats->secret_count,
stats->exported );
write_status_text (STATUS_EXPORT_RES, buf);
}
}
/*
* Export public keys (to stdout or to --output FILE).
*
* Depending on opt.armor the output is armored. OPTIONS are defined
* in main.h. If USERS is NULL, all keys will be exported. STATS is
* either an export stats object for update or NULL.
*
* This function is the core of "gpg --export".
*/
int
export_pubkeys (ctrl_t ctrl, strlist_t users, unsigned int options,
export_stats_t stats)
{
return do_export (ctrl, users, 0, options, stats);
}
/*
* Export secret keys (to stdout or to --output FILE).
*
* Depending on opt.armor the output is armored. OPTIONS are defined
* in main.h. If USERS is NULL, all secret keys will be exported.
* STATS is either an export stats object for update or NULL.
*
* This function is the core of "gpg --export-secret-keys".
*/
int
export_seckeys (ctrl_t ctrl, strlist_t users, unsigned int options,
export_stats_t stats)
{
return do_export (ctrl, users, 1, options, stats);
}
/*
* Export secret sub keys (to stdout or to --output FILE).
*
* This is the same as export_seckeys but replaces the primary key by
* a stub key. Depending on opt.armor the output is armored. OPTIONS
* are defined in main.h. If USERS is NULL, all secret subkeys will
* be exported. STATS is either an export stats object for update or
* NULL.
*
* This function is the core of "gpg --export-secret-subkeys".
*/
int
export_secsubkeys (ctrl_t ctrl, strlist_t users, unsigned int options,
export_stats_t stats)
{
return do_export (ctrl, users, 2, options, stats);
}
/*
* Export a single key into a memory buffer. STATS is either an
* export stats object for update or NULL.
*/
gpg_error_t
export_pubkey_buffer (ctrl_t ctrl, const char *keyspec, unsigned int options,
export_stats_t stats,
kbnode_t *r_keyblock, void **r_data, size_t *r_datalen)
{
gpg_error_t err;
iobuf_t iobuf;
int any;
strlist_t helplist;
*r_keyblock = NULL;
*r_data = NULL;
*r_datalen = 0;
helplist = NULL;
if (!add_to_strlist_try (&helplist, keyspec))
return gpg_error_from_syserror ();
iobuf = iobuf_temp ();
err = do_export_stream (ctrl, iobuf, helplist, 0, r_keyblock, options,
stats, &any);
if (!err && !any)
err = gpg_error (GPG_ERR_NOT_FOUND);
if (!err)
{
const void *src;
size_t datalen;
iobuf_flush_temp (iobuf);
src = iobuf_get_temp_buffer (iobuf);
datalen = iobuf_get_temp_length (iobuf);
if (!datalen)
err = gpg_error (GPG_ERR_NO_PUBKEY);
else if (!(*r_data = xtrymalloc (datalen)))
err = gpg_error_from_syserror ();
else
{
memcpy (*r_data, src, datalen);
*r_datalen = datalen;
}
}
iobuf_close (iobuf);
free_strlist (helplist);
if (err && *r_keyblock)
{
release_kbnode (*r_keyblock);
*r_keyblock = NULL;
}
return err;
}
/* Export the keys identified by the list of strings in USERS. If
Secret is false public keys will be exported. With secret true
secret keys will be exported; in this case 1 means the entire
secret keyblock and 2 only the subkeys. OPTIONS are the export
options to apply. */
static int
do_export (ctrl_t ctrl, strlist_t users, int secret, unsigned int options,
export_stats_t stats)
{
IOBUF out = NULL;
int any, rc;
armor_filter_context_t *afx = NULL;
compress_filter_context_t zfx;
memset( &zfx, 0, sizeof zfx);
rc = open_outfile (-1, NULL, 0, !!secret, &out );
if (rc)
return rc;
if ( opt.armor && !(options & (EXPORT_PKA_FORMAT|EXPORT_DANE_FORMAT)) )
{
afx = new_armor_context ();
afx->what = secret? 5 : 1;
push_armor_filter (afx, out);
}
rc = do_export_stream (ctrl, out, users, secret, NULL, options, stats, &any);
if ( rc || !any )
iobuf_cancel (out);
else
iobuf_close (out);
release_armor_context (afx);
return rc;
}
/* Release an entire subkey list. */
static void
release_subkey_list (subkey_list_t list)
{
while (list)
{
subkey_list_t tmp = list->next;;
xfree (list);
list = tmp;
}
}
/* Returns true if NODE is a subkey and contained in LIST. */
static int
subkey_in_list_p (subkey_list_t list, KBNODE node)
{
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| node->pkt->pkttype == PKT_SECRET_SUBKEY )
{
u32 kid[2];
keyid_from_pk (node->pkt->pkt.public_key, kid);
for (; list; list = list->next)
if (list->kid[0] == kid[0] && list->kid[1] == kid[1])
return 1;
}
return 0;
}
/* Allocate a new subkey list item from NODE. */
static subkey_list_t
new_subkey_list_item (KBNODE node)
{
subkey_list_t list = xcalloc (1, sizeof *list);
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| node->pkt->pkttype == PKT_SECRET_SUBKEY)
keyid_from_pk (node->pkt->pkt.public_key, list->kid);
return list;
}
/* Helper function to check whether the subkey at NODE actually
matches the description at DESC. The function returns true if the
key under question has been specified by an exact specification
(keyID or fingerprint) and does match the one at NODE. It is
assumed that the packet at NODE is either a public or secret
subkey. */
-static int
-exact_subkey_match_p (KEYDB_SEARCH_DESC *desc, KBNODE node)
+int
+exact_subkey_match_p (KEYDB_SEARCH_DESC *desc, kbnode_t node)
{
u32 kid[2];
byte fpr[MAX_FINGERPRINT_LEN];
size_t fprlen;
int result = 0;
switch(desc->mode)
{
case KEYDB_SEARCH_MODE_SHORT_KID:
case KEYDB_SEARCH_MODE_LONG_KID:
keyid_from_pk (node->pkt->pkt.public_key, kid);
break;
case KEYDB_SEARCH_MODE_FPR:
fingerprint_from_pk (node->pkt->pkt.public_key, fpr, &fprlen);
break;
default:
break;
}
switch(desc->mode)
{
case KEYDB_SEARCH_MODE_SHORT_KID:
if (desc->u.kid[1] == kid[1])
result = 1;
break;
case KEYDB_SEARCH_MODE_LONG_KID:
if (desc->u.kid[0] == kid[0] && desc->u.kid[1] == kid[1])
result = 1;
break;
case KEYDB_SEARCH_MODE_FPR:
if (fprlen == desc->fprlen && !memcmp (desc->u.fpr, fpr, desc->fprlen))
result = 1;
break;
default:
break;
}
return result;
}
/* Return an error if the key represented by the S-expression S_KEY
* and the OpenPGP key represented by PK do not use the same curve. */
static gpg_error_t
match_curve_skey_pk (gcry_sexp_t s_key, PKT_public_key *pk)
{
gcry_sexp_t curve = NULL;
gcry_sexp_t flags = NULL;
char *curve_str = NULL;
char *flag;
const char *oidstr = NULL;
gcry_mpi_t curve_as_mpi = NULL;
gpg_error_t err;
int is_eddsa = 0;
int idx = 0;
if (!(pk->pubkey_algo==PUBKEY_ALGO_ECDH
|| pk->pubkey_algo==PUBKEY_ALGO_ECDSA
|| pk->pubkey_algo==PUBKEY_ALGO_EDDSA))
return gpg_error (GPG_ERR_PUBKEY_ALGO);
curve = gcry_sexp_find_token (s_key, "curve", 0);
if (!curve)
{
log_error ("no reported curve\n");
return gpg_error (GPG_ERR_UNKNOWN_CURVE);
}
curve_str = gcry_sexp_nth_string (curve, 1);
gcry_sexp_release (curve); curve = NULL;
if (!curve_str)
{
log_error ("no curve name\n");
return gpg_error (GPG_ERR_UNKNOWN_CURVE);
}
oidstr = openpgp_curve_to_oid (curve_str, NULL);
if (!oidstr)
{
log_error ("no OID known for curve '%s'\n", curve_str);
xfree (curve_str);
return gpg_error (GPG_ERR_UNKNOWN_CURVE);
}
xfree (curve_str);
err = openpgp_oid_from_str (oidstr, &curve_as_mpi);
if (err)
return err;
if (gcry_mpi_cmp (pk->pkey[0], curve_as_mpi))
{
log_error ("curves do not match\n");
gcry_mpi_release (curve_as_mpi);
return gpg_error (GPG_ERR_INV_CURVE);
}
gcry_mpi_release (curve_as_mpi);
flags = gcry_sexp_find_token (s_key, "flags", 0);
if (flags)
{
for (idx = 1; idx < gcry_sexp_length (flags); idx++)
{
flag = gcry_sexp_nth_string (flags, idx);
if (flag && (strcmp ("eddsa", flag) == 0))
is_eddsa = 1;
gcry_free (flag);
}
}
if (is_eddsa != (pk->pubkey_algo == PUBKEY_ALGO_EDDSA))
{
log_error ("disagreement about EdDSA\n");
err = gpg_error (GPG_ERR_INV_CURVE);
}
return err;
}
/* Return a canonicalized public key algoithms. This is used to
compare different flavors of algorithms (e.g. ELG and ELG_E are
considered the same). */
static enum gcry_pk_algos
canon_pk_algo (enum gcry_pk_algos algo)
{
switch (algo)
{
case GCRY_PK_RSA:
case GCRY_PK_RSA_E:
case GCRY_PK_RSA_S: return GCRY_PK_RSA;
case GCRY_PK_ELG:
case GCRY_PK_ELG_E: return GCRY_PK_ELG;
case GCRY_PK_ECC:
case GCRY_PK_ECDSA:
case GCRY_PK_ECDH: return GCRY_PK_ECC;
default: return algo;
}
}
/* Take a cleartext dump of a secret key in PK and change the
* parameter array in PK to include the secret parameters. */
static gpg_error_t
cleartext_secret_key_to_openpgp (gcry_sexp_t s_key, PKT_public_key *pk)
{
gpg_error_t err;
gcry_sexp_t top_list;
gcry_sexp_t key = NULL;
char *key_type = NULL;
enum gcry_pk_algos pk_algo;
struct seckey_info *ski;
int idx, sec_start;
gcry_mpi_t pub_params[10] = { NULL };
/* we look for a private-key, then the first element in it tells us
the type */
top_list = gcry_sexp_find_token (s_key, "private-key", 0);
if (!top_list)
goto bad_seckey;
- if (gcry_sexp_length(top_list) != 2)
+
+ /* ignore all S-expression after the first sublist -- we assume that
+ they are comments or otherwise irrelevant to OpenPGP */
+ if (gcry_sexp_length(top_list) < 2)
goto bad_seckey;
key = gcry_sexp_nth (top_list, 1);
if (!key)
goto bad_seckey;
key_type = gcry_sexp_nth_string(key, 0);
pk_algo = gcry_pk_map_name (key_type);
log_assert (!pk->seckey_info);
pk->seckey_info = ski = xtrycalloc (1, sizeof *ski);
if (!ski)
{
err = gpg_error_from_syserror ();
goto leave;
}
switch (canon_pk_algo (pk_algo))
{
case GCRY_PK_RSA:
if (!is_RSA (pk->pubkey_algo))
goto bad_pubkey_algo;
err = gcry_sexp_extract_param (key, NULL, "ne",
&pub_params[0],
&pub_params[1],
NULL);
for (idx=0; idx < 2 && !err; idx++)
if (gcry_mpi_cmp(pk->pkey[idx], pub_params[idx]))
err = gpg_error (GPG_ERR_BAD_PUBKEY);
if (!err)
{
for (idx = 2; idx < 6 && !err; idx++)
{
gcry_mpi_release (pk->pkey[idx]);
pk->pkey[idx] = NULL;
}
err = gcry_sexp_extract_param (key, NULL, "dpqu",
&pk->pkey[2],
&pk->pkey[3],
&pk->pkey[4],
&pk->pkey[5],
NULL);
}
if (!err)
{
for (idx = 2; idx < 6; idx++)
ski->csum += checksum_mpi (pk->pkey[idx]);
}
break;
case GCRY_PK_DSA:
if (!is_DSA (pk->pubkey_algo))
goto bad_pubkey_algo;
err = gcry_sexp_extract_param (key, NULL, "pqgy",
&pub_params[0],
&pub_params[1],
&pub_params[2],
&pub_params[3],
NULL);
for (idx=0; idx < 4 && !err; idx++)
if (gcry_mpi_cmp(pk->pkey[idx], pub_params[idx]))
err = gpg_error (GPG_ERR_BAD_PUBKEY);
if (!err)
{
gcry_mpi_release (pk->pkey[4]);
pk->pkey[4] = NULL;
err = gcry_sexp_extract_param (key, NULL, "x",
&pk->pkey[4],
NULL);
}
if (!err)
ski->csum += checksum_mpi (pk->pkey[4]);
break;
case GCRY_PK_ELG:
if (!is_ELGAMAL (pk->pubkey_algo))
goto bad_pubkey_algo;
err = gcry_sexp_extract_param (key, NULL, "pgy",
&pub_params[0],
&pub_params[1],
&pub_params[2],
NULL);
for (idx=0; idx < 3 && !err; idx++)
if (gcry_mpi_cmp(pk->pkey[idx], pub_params[idx]))
err = gpg_error (GPG_ERR_BAD_PUBKEY);
if (!err)
{
gcry_mpi_release (pk->pkey[3]);
pk->pkey[3] = NULL;
err = gcry_sexp_extract_param (key, NULL, "x",
&pk->pkey[3],
NULL);
}
if (!err)
ski->csum += checksum_mpi (pk->pkey[3]);
break;
case GCRY_PK_ECC:
err = match_curve_skey_pk (key, pk);
if (err)
goto leave;
if (!err)
err = gcry_sexp_extract_param (key, NULL, "q",
&pub_params[0],
NULL);
if (!err && (gcry_mpi_cmp(pk->pkey[1], pub_params[0])))
err = gpg_error (GPG_ERR_BAD_PUBKEY);
sec_start = 2;
if (pk->pubkey_algo == PUBKEY_ALGO_ECDH)
sec_start += 1;
if (!err)
{
gcry_mpi_release (pk->pkey[sec_start]);
pk->pkey[sec_start] = NULL;
err = gcry_sexp_extract_param (key, NULL, "d",
&pk->pkey[sec_start],
NULL);
}
if (!err)
ski->csum += checksum_mpi (pk->pkey[sec_start]);
break;
default:
pk->seckey_info = NULL;
xfree (ski);
err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
break;
}
leave:
gcry_sexp_release (top_list);
gcry_sexp_release (key);
gcry_free (key_type);
for (idx=0; idx < DIM(pub_params); idx++)
gcry_mpi_release (pub_params[idx]);
return err;
bad_pubkey_algo:
err = gpg_error (GPG_ERR_PUBKEY_ALGO);
goto leave;
bad_seckey:
err = gpg_error (GPG_ERR_BAD_SECKEY);
goto leave;
}
/* Use the key transfer format given in S_PGP to create the secinfo
structure in PK and change the parameter array in PK to include the
secret parameters. */
static gpg_error_t
transfer_format_to_openpgp (gcry_sexp_t s_pgp, PKT_public_key *pk)
{
gpg_error_t err;
gcry_sexp_t top_list;
gcry_sexp_t list = NULL;
char *curve = NULL;
const char *value;
size_t valuelen;
char *string;
int idx;
int is_v4, is_protected;
enum gcry_pk_algos pk_algo;
int protect_algo = 0;
char iv[16];
int ivlen = 0;
int s2k_mode = 0;
int s2k_algo = 0;
byte s2k_salt[8];
u32 s2k_count = 0;
int is_ecdh = 0;
size_t npkey, nskey;
gcry_mpi_t skey[10]; /* We support up to 9 parameters. */
int skeyidx = 0;
struct seckey_info *ski;
/* gcry_log_debugsxp ("transferkey", s_pgp); */
top_list = gcry_sexp_find_token (s_pgp, "openpgp-private-key", 0);
if (!top_list)
goto bad_seckey;
list = gcry_sexp_find_token (top_list, "version", 0);
if (!list)
goto bad_seckey;
value = gcry_sexp_nth_data (list, 1, &valuelen);
if (!value || valuelen != 1 || !(value[0] == '3' || value[0] == '4'))
goto bad_seckey;
is_v4 = (value[0] == '4');
gcry_sexp_release (list);
list = gcry_sexp_find_token (top_list, "protection", 0);
if (!list)
goto bad_seckey;
value = gcry_sexp_nth_data (list, 1, &valuelen);
if (!value)
goto bad_seckey;
if (valuelen == 4 && !memcmp (value, "sha1", 4))
is_protected = 2;
else if (valuelen == 3 && !memcmp (value, "sum", 3))
is_protected = 1;
else if (valuelen == 4 && !memcmp (value, "none", 4))
is_protected = 0;
else
goto bad_seckey;
if (is_protected)
{
string = gcry_sexp_nth_string (list, 2);
if (!string)
goto bad_seckey;
protect_algo = gcry_cipher_map_name (string);
xfree (string);
value = gcry_sexp_nth_data (list, 3, &valuelen);
if (!value || !valuelen || valuelen > sizeof iv)
goto bad_seckey;
memcpy (iv, value, valuelen);
ivlen = valuelen;
string = gcry_sexp_nth_string (list, 4);
if (!string)
goto bad_seckey;
s2k_mode = strtol (string, NULL, 10);
xfree (string);
string = gcry_sexp_nth_string (list, 5);
if (!string)
goto bad_seckey;
s2k_algo = gcry_md_map_name (string);
xfree (string);
value = gcry_sexp_nth_data (list, 6, &valuelen);
if (!value || !valuelen || valuelen > sizeof s2k_salt)
goto bad_seckey;
memcpy (s2k_salt, value, valuelen);
string = gcry_sexp_nth_string (list, 7);
if (!string)
goto bad_seckey;
s2k_count = strtoul (string, NULL, 10);
xfree (string);
}
/* Parse the gcrypt PK algo and check that it is okay. */
gcry_sexp_release (list);
list = gcry_sexp_find_token (top_list, "algo", 0);
if (!list)
goto bad_seckey;
string = gcry_sexp_nth_string (list, 1);
if (!string)
goto bad_seckey;
pk_algo = gcry_pk_map_name (string);
xfree (string); string = NULL;
if (gcry_pk_algo_info (pk_algo, GCRYCTL_GET_ALGO_NPKEY, NULL, &npkey)
|| gcry_pk_algo_info (pk_algo, GCRYCTL_GET_ALGO_NSKEY, NULL, &nskey)
|| !npkey || npkey >= nskey)
goto bad_seckey;
/* Check that the pubkey algo matches the one from the public key. */
switch (canon_pk_algo (pk_algo))
{
case GCRY_PK_RSA:
if (!is_RSA (pk->pubkey_algo))
pk_algo = 0; /* Does not match. */
break;
case GCRY_PK_DSA:
if (!is_DSA (pk->pubkey_algo))
pk_algo = 0; /* Does not match. */
break;
case GCRY_PK_ELG:
if (!is_ELGAMAL (pk->pubkey_algo))
pk_algo = 0; /* Does not match. */
break;
case GCRY_PK_ECC:
if (pk->pubkey_algo == PUBKEY_ALGO_ECDSA)
;
else if (pk->pubkey_algo == PUBKEY_ALGO_ECDH)
is_ecdh = 1;
else if (pk->pubkey_algo == PUBKEY_ALGO_EDDSA)
;
else
pk_algo = 0; /* Does not match. */
/* For ECC we do not have the domain parameters thus fix our info. */
npkey = 1;
nskey = 2;
break;
default:
pk_algo = 0; /* Oops. */
break;
}
if (!pk_algo)
{
err = gpg_error (GPG_ERR_PUBKEY_ALGO);
goto leave;
}
/* This check has to go after the ecc adjustments. */
if (nskey > PUBKEY_MAX_NSKEY)
goto bad_seckey;
/* Parse the key parameters. */
gcry_sexp_release (list);
list = gcry_sexp_find_token (top_list, "skey", 0);
if (!list)
goto bad_seckey;
for (idx=0;;)
{
int is_enc;
value = gcry_sexp_nth_data (list, ++idx, &valuelen);
if (!value && skeyidx >= npkey)
break; /* Ready. */
/* Check for too many parameters. Note that depending on the
protection mode and version number we may see less than NSKEY
(but at least NPKEY+1) parameters. */
if (idx >= 2*nskey)
goto bad_seckey;
if (skeyidx >= DIM (skey)-1)
goto bad_seckey;
if (!value || valuelen != 1 || !(value[0] == '_' || value[0] == 'e'))
goto bad_seckey;
is_enc = (value[0] == 'e');
value = gcry_sexp_nth_data (list, ++idx, &valuelen);
if (!value || !valuelen)
goto bad_seckey;
if (is_enc)
{
void *p = xtrymalloc (valuelen);
if (!p)
goto outofmem;
memcpy (p, value, valuelen);
skey[skeyidx] = gcry_mpi_set_opaque (NULL, p, valuelen*8);
if (!skey[skeyidx])
goto outofmem;
}
else
{
if (gcry_mpi_scan (skey + skeyidx, GCRYMPI_FMT_STD,
value, valuelen, NULL))
goto bad_seckey;
}
skeyidx++;
}
skey[skeyidx++] = NULL;
gcry_sexp_release (list); list = NULL;
/* We have no need for the CSUM value thus we don't parse it. */
/* list = gcry_sexp_find_token (top_list, "csum", 0); */
/* if (list) */
/* { */
/* string = gcry_sexp_nth_string (list, 1); */
/* if (!string) */
/* goto bad_seckey; */
/* desired_csum = strtoul (string, NULL, 10); */
/* xfree (string); */
/* } */
/* else */
/* desired_csum = 0; */
/* gcry_sexp_release (list); list = NULL; */
/* Get the curve name if any, */
list = gcry_sexp_find_token (top_list, "curve", 0);
if (list)
{
curve = gcry_sexp_nth_string (list, 1);
gcry_sexp_release (list); list = NULL;
}
gcry_sexp_release (top_list); top_list = NULL;
/* log_debug ("XXX is_v4=%d\n", is_v4); */
/* log_debug ("XXX pubkey_algo=%d\n", pubkey_algo); */
/* log_debug ("XXX is_protected=%d\n", is_protected); */
/* log_debug ("XXX protect_algo=%d\n", protect_algo); */
/* log_printhex ("XXX iv", iv, ivlen); */
/* log_debug ("XXX ivlen=%d\n", ivlen); */
/* log_debug ("XXX s2k_mode=%d\n", s2k_mode); */
/* log_debug ("XXX s2k_algo=%d\n", s2k_algo); */
/* log_printhex ("XXX s2k_salt", s2k_salt, sizeof s2k_salt); */
/* log_debug ("XXX s2k_count=%lu\n", (unsigned long)s2k_count); */
/* for (idx=0; skey[idx]; idx++) */
/* { */
/* int is_enc = gcry_mpi_get_flag (skey[idx], GCRYMPI_FLAG_OPAQUE); */
/* log_info ("XXX skey[%d]%s:", idx, is_enc? " (enc)":""); */
/* if (is_enc) */
/* { */
/* void *p; */
/* unsigned int nbits; */
/* p = gcry_mpi_get_opaque (skey[idx], &nbits); */
/* log_printhex (NULL, p, (nbits+7)/8); */
/* } */
/* else */
/* gcry_mpi_dump (skey[idx]); */
/* log_printf ("\n"); */
/* } */
if (!is_v4 || is_protected != 2 )
{
/* We only support the v4 format and a SHA-1 checksum. */
err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
goto leave;
}
/* We need to change the received parameters for ECC algorithms.
The transfer format has the curve name and the parameters
separate. We put them all into the SKEY array. */
if (canon_pk_algo (pk_algo) == GCRY_PK_ECC)
{
const char *oidstr;
/* Assert that all required parameters are available. We also
check that the array does not contain more parameters than
needed (this was used by some beta versions of 2.1. */
if (!curve || !skey[0] || !skey[1] || skey[2])
{
err = gpg_error (GPG_ERR_INTERNAL);
goto leave;
}
oidstr = openpgp_curve_to_oid (curve, NULL);
if (!oidstr)
{
log_error ("no OID known for curve '%s'\n", curve);
err = gpg_error (GPG_ERR_UNKNOWN_CURVE);
goto leave;
}
/* Put the curve's OID into the MPI array. This requires
that we shift Q and D. For ECDH also insert the KDF parms. */
if (is_ecdh)
{
skey[4] = NULL;
skey[3] = skey[1];
skey[2] = gcry_mpi_copy (pk->pkey[2]);
}
else
{
skey[3] = NULL;
skey[2] = skey[1];
}
skey[1] = skey[0];
skey[0] = NULL;
err = openpgp_oid_from_str (oidstr, skey + 0);
if (err)
goto leave;
/* Fixup the NPKEY and NSKEY to match OpenPGP reality. */
npkey = 2 + is_ecdh;
nskey = 3 + is_ecdh;
/* for (idx=0; skey[idx]; idx++) */
/* { */
/* log_info ("YYY skey[%d]:", idx); */
/* if (gcry_mpi_get_flag (skey[idx], GCRYMPI_FLAG_OPAQUE)) */
/* { */
/* void *p; */
/* unsigned int nbits; */
/* p = gcry_mpi_get_opaque (skey[idx], &nbits); */
/* log_printhex (NULL, p, (nbits+7)/8); */
/* } */
/* else */
/* gcry_mpi_dump (skey[idx]); */
/* log_printf ("\n"); */
/* } */
}
/* Do some sanity checks. */
if (s2k_count > 255)
{
/* We expect an already encoded S2K count. */
err = gpg_error (GPG_ERR_INV_DATA);
goto leave;
}
err = openpgp_cipher_test_algo (protect_algo);
if (err)
goto leave;
err = openpgp_md_test_algo (s2k_algo);
if (err)
goto leave;
/* Check that the public key parameters match. Note that since
Libgcrypt 1.5 gcry_mpi_cmp handles opaque MPI correctly. */
for (idx=0; idx < npkey; idx++)
if (gcry_mpi_cmp (pk->pkey[idx], skey[idx]))
{
err = gpg_error (GPG_ERR_BAD_PUBKEY);
goto leave;
}
/* Check that the first secret key parameter in SKEY is encrypted
and that there are no more secret key parameters. The latter is
guaranteed by the v4 packet format. */
if (!gcry_mpi_get_flag (skey[npkey], GCRYMPI_FLAG_OPAQUE))
goto bad_seckey;
if (npkey+1 < DIM (skey) && skey[npkey+1])
goto bad_seckey;
/* Check that the secret key parameters in PK are all set to NULL. */
for (idx=npkey; idx < nskey; idx++)
if (pk->pkey[idx])
goto bad_seckey;
/* Now build the protection info. */
pk->seckey_info = ski = xtrycalloc (1, sizeof *ski);
if (!ski)
{
err = gpg_error_from_syserror ();
goto leave;
}
ski->is_protected = 1;
ski->sha1chk = 1;
ski->algo = protect_algo;
ski->s2k.mode = s2k_mode;
ski->s2k.hash_algo = s2k_algo;
log_assert (sizeof ski->s2k.salt == sizeof s2k_salt);
memcpy (ski->s2k.salt, s2k_salt, sizeof s2k_salt);
ski->s2k.count = s2k_count;
log_assert (ivlen <= sizeof ski->iv);
memcpy (ski->iv, iv, ivlen);
ski->ivlen = ivlen;
/* Store the protected secret key parameter. */
pk->pkey[npkey] = skey[npkey];
skey[npkey] = NULL;
/* That's it. */
leave:
gcry_free (curve);
gcry_sexp_release (list);
gcry_sexp_release (top_list);
for (idx=0; idx < skeyidx; idx++)
gcry_mpi_release (skey[idx]);
return err;
bad_seckey:
err = gpg_error (GPG_ERR_BAD_SECKEY);
goto leave;
outofmem:
err = gpg_error (GPG_ERR_ENOMEM);
goto leave;
}
/* Print an "EXPORTED" status line. PK is the primary public key. */
static void
print_status_exported (PKT_public_key *pk)
{
char *hexfpr;
if (!is_status_enabled ())
return;
hexfpr = hexfingerprint (pk, NULL, 0);
write_status_text (STATUS_EXPORTED, hexfpr? hexfpr : "[?]");
xfree (hexfpr);
}
/*
* Receive a secret key from agent specified by HEXGRIP.
*
* Since the key data from the agent is encrypted, decrypt it using
* CIPHERHD context. Then, parse the decrypted key data into transfer
* format, and put secret parameters into PK.
*
* If CLEARTEXT is 0, store the secret key material
* passphrase-protected. Otherwise, store secret key material in the
* clear.
*
* CACHE_NONCE_ADDR is used to share nonce for multiple key retrievals.
*/
gpg_error_t
receive_seckey_from_agent (ctrl_t ctrl, gcry_cipher_hd_t cipherhd,
int cleartext,
char **cache_nonce_addr, const char *hexgrip,
PKT_public_key *pk)
{
gpg_error_t err = 0;
unsigned char *wrappedkey = NULL;
size_t wrappedkeylen;
unsigned char *key = NULL;
size_t keylen, realkeylen;
gcry_sexp_t s_skey;
char *prompt;
if (opt.verbose)
log_info ("key %s: asking agent for the secret parts\n", hexgrip);
prompt = gpg_format_keydesc (ctrl, pk, FORMAT_KEYDESC_EXPORT,1);
err = agent_export_key (ctrl, hexgrip, prompt, !cleartext, cache_nonce_addr,
&wrappedkey, &wrappedkeylen,
pk->keyid, pk->main_keyid, pk->pubkey_algo);
xfree (prompt);
if (err)
goto unwraperror;
if (wrappedkeylen < 24)
{
err = gpg_error (GPG_ERR_INV_LENGTH);
goto unwraperror;
}
keylen = wrappedkeylen - 8;
key = xtrymalloc_secure (keylen);
if (!key)
{
err = gpg_error_from_syserror ();
goto unwraperror;
}
err = gcry_cipher_decrypt (cipherhd, key, keylen, wrappedkey, wrappedkeylen);
if (err)
goto unwraperror;
realkeylen = gcry_sexp_canon_len (key, keylen, NULL, &err);
if (!realkeylen)
goto unwraperror; /* Invalid csexp. */
err = gcry_sexp_sscan (&s_skey, NULL, key, realkeylen);
if (!err)
{
if (cleartext)
err = cleartext_secret_key_to_openpgp (s_skey, pk);
else
err = transfer_format_to_openpgp (s_skey, pk);
gcry_sexp_release (s_skey);
}
unwraperror:
xfree (key);
xfree (wrappedkey);
if (err)
{
log_error ("key %s: error receiving key from agent:"
" %s%s\n", hexgrip, gpg_strerror (err),
gpg_err_code (err) == GPG_ERR_FULLY_CANCELED?
"":_(" - skipped"));
}
return err;
}
/* Write KEYBLOCK either to stdout or to the file set with the
* --output option. This is a simplified version of do_export_stream
* which supports only a few export options. */
gpg_error_t
write_keyblock_to_output (kbnode_t keyblock, int with_armor,
unsigned int options)
{
gpg_error_t err;
const char *fname;
iobuf_t out;
kbnode_t node;
armor_filter_context_t *afx = NULL;
iobuf_t out_help = NULL;
PKT_public_key *pk = NULL;
fname = opt.outfile? opt.outfile : "-";
if (is_secured_filename (fname) )
return gpg_error (GPG_ERR_EPERM);
out = iobuf_create (fname, 0);
if (!out)
{
err = gpg_error_from_syserror ();
log_error(_("can't create '%s': %s\n"), fname, gpg_strerror (err));
return err;
}
if (opt.verbose)
log_info (_("writing to '%s'\n"), iobuf_get_fname_nonnull (out));
if ((options & (EXPORT_PKA_FORMAT|EXPORT_DANE_FORMAT)))
{
with_armor = 0;
out_help = iobuf_temp ();
}
if (with_armor)
{
afx = new_armor_context ();
afx->what = 1;
push_armor_filter (afx, out);
}
for (node = keyblock; node; node = node->next)
{
if (is_deleted_kbnode (node))
continue;
if (node->pkt->pkttype == PKT_RING_TRUST)
continue; /* Skip - they should not be here anyway. */
if (!pk && (node->pkt->pkttype == PKT_PUBLIC_KEY
|| node->pkt->pkttype == PKT_SECRET_KEY))
pk = node->pkt->pkt.public_key;
if ((options & EXPORT_BACKUP))
err = build_packet_and_meta (out_help? out_help : out, node->pkt);
else
err = build_packet (out_help? out_help : out, node->pkt);
if (err)
{
log_error ("build_packet(%d) failed: %s\n",
node->pkt->pkttype, gpg_strerror (err) );
goto leave;
}
}
err = 0;
if (out_help && pk)
{
const void *data;
size_t datalen;
iobuf_flush_temp (out_help);
data = iobuf_get_temp_buffer (out_help);
datalen = iobuf_get_temp_length (out_help);
err = print_pka_or_dane_records (out,
keyblock, pk, data, datalen,
(options & EXPORT_PKA_FORMAT),
(options & EXPORT_DANE_FORMAT));
}
leave:
if (err)
iobuf_cancel (out);
else
iobuf_close (out);
iobuf_cancel (out_help);
release_armor_context (afx);
return err;
}
/*
* Apply the keep-uid filter to the keyblock. The deleted nodes are
* marked and thus the caller should call commit_kbnode afterwards.
* KEYBLOCK must not have any blocks marked as deleted.
*/
static void
apply_keep_uid_filter (ctrl_t ctrl, kbnode_t keyblock, recsel_expr_t selector)
{
kbnode_t node;
struct impex_filter_parm_s parm;
parm.ctrl = ctrl;
for (node = keyblock->next; node; node = node->next )
{
if (node->pkt->pkttype == PKT_USER_ID)
{
parm.node = node;
if (!recsel_select (selector, impex_filter_getval, &parm))
{
/* log_debug ("keep-uid: deleting '%s'\n", */
/* node->pkt->pkt.user_id->name); */
/* The UID packet and all following packets up to the
* next UID or a subkey. */
delete_kbnode (node);
for (; node->next
&& node->next->pkt->pkttype != PKT_USER_ID
&& node->next->pkt->pkttype != PKT_PUBLIC_SUBKEY
&& node->next->pkt->pkttype != PKT_SECRET_SUBKEY ;
node = node->next)
delete_kbnode (node->next);
}
/* else */
/* log_debug ("keep-uid: keeping '%s'\n", */
/* node->pkt->pkt.user_id->name); */
}
}
}
/*
* Apply the drop-subkey filter to the keyblock. The deleted nodes are
* marked and thus the caller should call commit_kbnode afterwards.
* KEYBLOCK must not have any blocks marked as deleted.
*/
static void
apply_drop_subkey_filter (ctrl_t ctrl, kbnode_t keyblock,
recsel_expr_t selector)
{
kbnode_t node;
struct impex_filter_parm_s parm;
parm.ctrl = ctrl;
for (node = keyblock->next; node; node = node->next )
{
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| node->pkt->pkttype == PKT_SECRET_SUBKEY)
{
parm.node = node;
if (recsel_select (selector, impex_filter_getval, &parm))
{
/*log_debug ("drop-subkey: deleting a key\n");*/
/* The subkey packet and all following packets up to the
* next subkey. */
delete_kbnode (node);
for (; node->next
&& node->next->pkt->pkttype != PKT_PUBLIC_SUBKEY
&& node->next->pkt->pkttype != PKT_SECRET_SUBKEY ;
node = node->next)
delete_kbnode (node->next);
}
}
}
}
/* Print DANE or PKA records for all user IDs in KEYBLOCK to OUT. The
* data for the record is taken from (DATA,DATELEN). PK is the public
* key packet with the primary key. */
static gpg_error_t
print_pka_or_dane_records (iobuf_t out, kbnode_t keyblock, PKT_public_key *pk,
const void *data, size_t datalen,
int print_pka, int print_dane)
{
gpg_error_t err = 0;
kbnode_t kbctx, node;
PKT_user_id *uid;
char *mbox = NULL;
char hashbuf[32];
char *hash = NULL;
char *domain;
const char *s;
unsigned int len;
estream_t fp = NULL;
char *hexdata = NULL;
char *hexfpr;
hexfpr = hexfingerprint (pk, NULL, 0);
if (!hexfpr)
{
err = gpg_error_from_syserror ();
goto leave;
}
hexdata = bin2hex (data, datalen, NULL);
if (!hexdata)
{
err = gpg_error_from_syserror ();
goto leave;
}
ascii_strlwr (hexdata);
fp = es_fopenmem (0, "rw,samethread");
if (!fp)
{
err = gpg_error_from_syserror ();
goto leave;
}
for (kbctx = NULL; (node = walk_kbnode (keyblock, &kbctx, 0));)
{
if (node->pkt->pkttype != PKT_USER_ID)
continue;
uid = node->pkt->pkt.user_id;
if (uid->flags.expired || uid->flags.revoked)
continue;
xfree (mbox);
mbox = mailbox_from_userid (uid->name, 0);
if (!mbox)
continue;
domain = strchr (mbox, '@');
*domain++ = 0;
if (print_pka)
{
es_fprintf (fp, "$ORIGIN _pka.%s.\n; %s\n; ", domain, hexfpr);
print_utf8_buffer (fp, uid->name, uid->len);
es_putc ('\n', fp);
gcry_md_hash_buffer (GCRY_MD_SHA1, hashbuf, mbox, strlen (mbox));
xfree (hash);
hash = zb32_encode (hashbuf, 8*20);
if (!hash)
{
err = gpg_error_from_syserror ();
goto leave;
}
len = strlen (hexfpr)/2;
es_fprintf (fp, "%s TYPE37 \\# %u 0006 0000 00 %02X %s\n\n",
hash, 6 + len, len, hexfpr);
}
if (print_dane && hexdata)
{
es_fprintf (fp, "$ORIGIN _openpgpkey.%s.\n; %s\n; ", domain, hexfpr);
print_utf8_buffer (fp, uid->name, uid->len);
es_putc ('\n', fp);
gcry_md_hash_buffer (GCRY_MD_SHA256, hashbuf, mbox, strlen (mbox));
xfree (hash);
hash = bin2hex (hashbuf, 28, NULL);
if (!hash)
{
err = gpg_error_from_syserror ();
goto leave;
}
ascii_strlwr (hash);
len = strlen (hexdata)/2;
es_fprintf (fp, "%s TYPE61 \\# %u (\n", hash, len);
for (s = hexdata; ;)
{
es_fprintf (fp, "\t%.64s\n", s);
if (strlen (s) < 64)
break;
s += 64;
}
es_fputs ("\t)\n\n", fp);
}
}
/* Make sure it is a string and write it. */
es_fputc (0, fp);
{
void *vp;
if (es_fclose_snatch (fp, &vp, NULL))
{
err = gpg_error_from_syserror ();
goto leave;
}
fp = NULL;
iobuf_writestr (out, vp);
es_free (vp);
}
err = 0;
leave:
xfree (hash);
xfree (mbox);
es_fclose (fp);
xfree (hexdata);
xfree (hexfpr);
return err;
}
/* Helper for do_export_stream which writes one keyblock to OUT. */
static gpg_error_t
do_export_one_keyblock (ctrl_t ctrl, kbnode_t keyblock, u32 *keyid,
iobuf_t out, int secret, unsigned int options,
export_stats_t stats, int *any,
KEYDB_SEARCH_DESC *desc, size_t ndesc,
size_t descindex, gcry_cipher_hd_t cipherhd)
{
gpg_error_t err = gpg_error (GPG_ERR_NOT_FOUND);
char *cache_nonce = NULL;
subkey_list_t subkey_list = NULL; /* Track already processed subkeys. */
int skip_until_subkey = 0;
int cleartext = 0;
char *hexgrip = NULL;
char *serialno = NULL;
PKT_public_key *pk;
u32 subkidbuf[2], *subkid;
kbnode_t kbctx, node;
/* NB: walk_kbnode skips packets marked as deleted. */
for (kbctx=NULL; (node = walk_kbnode (keyblock, &kbctx, 0)); )
{
if (skip_until_subkey)
{
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
skip_until_subkey = 0;
else
continue;
}
/* We used to use comment packets, but not any longer. In
* case we still have comments on a key, strip them here
* before we call build_packet(). */
if (node->pkt->pkttype == PKT_COMMENT)
continue;
/* Skip ring trust packets - they should not be here anyway. */
if (node->pkt->pkttype == PKT_RING_TRUST)
continue;
/* If exact is set, then we only export what was requested
* (plus the primary key, if the user didn't specifically
* request it). */
if (desc[descindex].exact && node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
if (!exact_subkey_match_p (desc+descindex, node))
{
/* Before skipping this subkey, check whether any
* other description wants an exact match on a
* subkey and include that subkey into the output
* too. Need to add this subkey to a list so that
* it won't get processed a second time.
*
* So the first step here is to check that list and
* skip in any case if the key is in that list.
*
* We need this whole mess because the import
* function of GnuPG < 2.1 is not able to merge
* secret keys and thus it is useless to output them
* as two separate keys and have import merge them.
*/
if (subkey_in_list_p (subkey_list, node))
skip_until_subkey = 1; /* Already processed this one. */
else
{
size_t j;
for (j=0; j < ndesc; j++)
if (j != descindex && desc[j].exact
&& exact_subkey_match_p (desc+j, node))
break;
if (!(j < ndesc))
skip_until_subkey = 1; /* No other one matching. */
}
}
if (skip_until_subkey)
continue;
/* Mark this one as processed. */
{
subkey_list_t tmp = new_subkey_list_item (node);
tmp->next = subkey_list;
subkey_list = tmp;
}
}
if (node->pkt->pkttype == PKT_SIGNATURE)
{
/* Do not export packets which are marked as not
* exportable. */
if (!(options & EXPORT_LOCAL_SIGS)
&& !node->pkt->pkt.signature->flags.exportable)
continue; /* not exportable */
/* Do not export packets with a "sensitive" revocation key
* unless the user wants us to. Note that we do export
* these when issuing the actual revocation (see revoke.c). */
if (!(options & EXPORT_SENSITIVE_REVKEYS)
&& node->pkt->pkt.signature->revkey)
{
int i;
for (i = 0; i < node->pkt->pkt.signature->numrevkeys; i++)
if ((node->pkt->pkt.signature->revkey[i].class & 0x40))
break;
if (i < node->pkt->pkt.signature->numrevkeys)
continue;
}
}
/* Don't export user ids (and attributes)? This is not RFC-4880
* compliant but we allow it anyway. */
if ((options & EXPORT_DROP_UIDS)
&& node->pkt->pkttype == PKT_USER_ID)
{
/* Skip until we get to something that is not a user id (or
* attrib) or a signature on it. */
while (kbctx->next && kbctx->next->pkt->pkttype == PKT_SIGNATURE)
kbctx = kbctx->next;
continue;
}
/* Don't export attribs? */
if (!(options & EXPORT_ATTRIBUTES)
&& node->pkt->pkttype == PKT_USER_ID
&& node->pkt->pkt.user_id->attrib_data)
{
/* Skip until we get to something that is not an attrib or a
* signature on an attrib. */
while (kbctx->next && kbctx->next->pkt->pkttype == PKT_SIGNATURE)
kbctx = kbctx->next;
continue;
}
if (secret && (node->pkt->pkttype == PKT_PUBLIC_KEY
|| node->pkt->pkttype == PKT_PUBLIC_SUBKEY))
{
pk = node->pkt->pkt.public_key;
if (node->pkt->pkttype == PKT_PUBLIC_KEY)
subkid = NULL;
else
{
keyid_from_pk (pk, subkidbuf);
subkid = subkidbuf;
}
if (pk->seckey_info)
{
log_error ("key %s: oops: seckey_info already set"
" - skipped\n", keystr_with_sub (keyid, subkid));
skip_until_subkey = 1;
continue;
}
xfree (hexgrip);
err = hexkeygrip_from_pk (pk, &hexgrip);
if (err)
{
log_error ("key %s: error computing keygrip: %s"
" - skipped\n", keystr_with_sub (keyid, subkid),
gpg_strerror (err));
skip_until_subkey = 1;
err = 0;
continue;
}
xfree (serialno);
serialno = NULL;
if (secret == 2 && node->pkt->pkttype == PKT_PUBLIC_KEY)
{
/* We are asked not to export the secret parts of the
* primary key. Make up an error code to create the
* stub. */
err = GPG_ERR_NOT_FOUND;
}
else
err = agent_get_keyinfo (ctrl, hexgrip, &serialno, &cleartext);
if ((!err && serialno)
&& secret == 2 && node->pkt->pkttype == PKT_PUBLIC_KEY)
{
/* It does not make sense to export a key with its
* primary key on card using a non-key stub. Thus we
* skip those keys when used with --export-secret-subkeys. */
log_info (_("key %s: key material on-card - skipped\n"),
keystr_with_sub (keyid, subkid));
skip_until_subkey = 1;
}
else if (gpg_err_code (err) == GPG_ERR_NOT_FOUND
|| (!err && serialno))
{
/* Create a key stub. */
struct seckey_info *ski;
const char *s;
pk->seckey_info = ski = xtrycalloc (1, sizeof *ski);
if (!ski)
{
err = gpg_error_from_syserror ();
goto leave;
}
ski->is_protected = 1;
if (err)
ski->s2k.mode = 1001; /* GNU dummy (no secret key). */
else
{
ski->s2k.mode = 1002; /* GNU-divert-to-card. */
for (s=serialno; sizeof (ski->ivlen) && *s && s[1];
ski->ivlen++, s += 2)
ski->iv[ski->ivlen] = xtoi_2 (s);
}
if ((options & EXPORT_BACKUP))
err = build_packet_and_meta (out, node->pkt);
else
err = build_packet (out, node->pkt);
if (!err && node->pkt->pkttype == PKT_PUBLIC_KEY)
{
stats->exported++;
print_status_exported (node->pkt->pkt.public_key);
}
}
else if (!err)
{
err = receive_seckey_from_agent (ctrl, cipherhd,
cleartext, &cache_nonce,
hexgrip, pk);
if (err)
{
if (gpg_err_code (err) == GPG_ERR_FULLY_CANCELED)
goto leave;
skip_until_subkey = 1;
err = 0;
}
else
{
if ((options & EXPORT_BACKUP))
err = build_packet_and_meta (out, node->pkt);
else
err = build_packet (out, node->pkt);
if (node->pkt->pkttype == PKT_PUBLIC_KEY)
{
stats->exported++;
print_status_exported (node->pkt->pkt.public_key);
}
}
}
else
{
log_error ("key %s: error getting keyinfo from agent: %s"
" - skipped\n", keystr_with_sub (keyid, subkid),
gpg_strerror (err));
skip_until_subkey = 1;
err = 0;
}
xfree (pk->seckey_info);
pk->seckey_info = NULL;
{
int i;
for (i = pubkey_get_npkey (pk->pubkey_algo);
i < pubkey_get_nskey (pk->pubkey_algo); i++)
{
gcry_mpi_release (pk->pkey[i]);
pk->pkey[i] = NULL;
}
}
}
else /* Not secret or common packets. */
{
if ((options & EXPORT_BACKUP))
err = build_packet_and_meta (out, node->pkt);
else
err = build_packet (out, node->pkt);
if (!err && node->pkt->pkttype == PKT_PUBLIC_KEY)
{
stats->exported++;
print_status_exported (node->pkt->pkt.public_key);
}
}
if (err)
{
log_error ("build_packet(%d) failed: %s\n",
node->pkt->pkttype, gpg_strerror (err));
goto leave;
}
if (!skip_until_subkey)
*any = 1;
}
leave:
release_subkey_list (subkey_list);
xfree (serialno);
xfree (hexgrip);
xfree (cache_nonce);
return err;
}
/* Export the keys identified by the list of strings in USERS to the
stream OUT. If SECRET is false public keys will be exported. With
secret true secret keys will be exported; in this case 1 means the
entire secret keyblock and 2 only the subkeys. OPTIONS are the
export options to apply. If KEYBLOCK_OUT is not NULL, AND the exit
code is zero, a pointer to the first keyblock found and exported
will be stored at this address; no other keyblocks are exported in
this case. The caller must free the returned keyblock. If any
key has been exported true is stored at ANY. */
static int
do_export_stream (ctrl_t ctrl, iobuf_t out, strlist_t users, int secret,
kbnode_t *keyblock_out, unsigned int options,
export_stats_t stats, int *any)
{
gpg_error_t err = 0;
PACKET pkt;
kbnode_t keyblock = NULL;
kbnode_t node;
size_t ndesc, descindex;
KEYDB_SEARCH_DESC *desc = NULL;
KEYDB_HANDLE kdbhd;
strlist_t sl;
gcry_cipher_hd_t cipherhd = NULL;
struct export_stats_s dummystats;
iobuf_t out_help = NULL;
if (!stats)
stats = &dummystats;
*any = 0;
init_packet (&pkt);
kdbhd = keydb_new ();
if (!kdbhd)
return gpg_error_from_syserror ();
/* For the PKA and DANE format open a helper iobuf and for DANE
* enforce some options. */
if ((options & (EXPORT_PKA_FORMAT | EXPORT_DANE_FORMAT)))
{
out_help = iobuf_temp ();
if ((options & EXPORT_DANE_FORMAT))
options |= EXPORT_MINIMAL | EXPORT_CLEAN;
}
if (!users)
{
ndesc = 1;
desc = xcalloc (ndesc, sizeof *desc);
desc[0].mode = KEYDB_SEARCH_MODE_FIRST;
}
else
{
for (ndesc=0, sl=users; sl; sl = sl->next, ndesc++)
;
desc = xmalloc ( ndesc * sizeof *desc);
for (ndesc=0, sl=users; sl; sl = sl->next)
{
if (!(err=classify_user_id (sl->d, desc+ndesc, 1)))
ndesc++;
else
log_error (_("key \"%s\" not found: %s\n"),
sl->d, gpg_strerror (err));
}
keydb_disable_caching (kdbhd); /* We are looping the search. */
/* 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. To set this flag 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. */
}
#ifdef ENABLE_SELINUX_HACKS
if (secret)
{
log_error (_("exporting secret keys not allowed\n"));
err = gpg_error (GPG_ERR_NOT_SUPPORTED);
goto leave;
}
#endif
/* For secret key export we need to setup a decryption context. */
if (secret)
{
void *kek = NULL;
size_t keklen;
err = agent_keywrap_key (ctrl, 1, &kek, &keklen);
if (err)
{
log_error ("error getting the KEK: %s\n", gpg_strerror (err));
goto leave;
}
/* Prepare a cipher context. */
err = gcry_cipher_open (&cipherhd, GCRY_CIPHER_AES128,
GCRY_CIPHER_MODE_AESWRAP, 0);
if (!err)
err = gcry_cipher_setkey (cipherhd, kek, keklen);
if (err)
{
log_error ("error setting up an encryption context: %s\n",
gpg_strerror (err));
goto leave;
}
xfree (kek);
kek = NULL;
}
for (;;)
{
u32 keyid[2];
PKT_public_key *pk;
err = keydb_search (kdbhd, desc, ndesc, &descindex);
if (!users)
desc[0].mode = KEYDB_SEARCH_MODE_NEXT;
if (err)
break;
/* Read the keyblock. */
release_kbnode (keyblock);
keyblock = NULL;
err = keydb_get_keyblock (kdbhd, &keyblock);
if (err)
{
log_error (_("error reading keyblock: %s\n"), gpg_strerror (err));
goto leave;
}
node = find_kbnode (keyblock, PKT_PUBLIC_KEY);
if (!node)
{
log_error ("public key packet not found in keyblock - skipped\n");
continue;
}
stats->count++;
setup_main_keyids (keyblock); /* gpg_format_keydesc needs it. */
pk = node->pkt->pkt.public_key;
keyid_from_pk (pk, keyid);
/* If a secret key export is required we need to check whether
we have a secret key at all and if so create the seckey_info
structure. */
if (secret)
{
if (agent_probe_any_secret_key (ctrl, keyblock))
continue; /* No secret key (neither primary nor subkey). */
/* No v3 keys with GNU mode 1001. */
if (secret == 2 && pk->version == 3)
{
log_info (_("key %s: PGP 2.x style key - skipped\n"),
keystr (keyid));
continue;
}
/* The agent does not yet allow export of v3 packets. It is
actually questionable whether we should allow them at
all. */
if (pk->version == 3)
{
log_info ("key %s: PGP 2.x style key (v3) export "
"not yet supported - skipped\n", keystr (keyid));
continue;
}
stats->secret_count++;
}
/* Always do the cleaning on the public key part if requested.
* A designated revocation is never stripped, even with
* export-minimal set. */
if ((options & EXPORT_CLEAN))
{
merge_keys_and_selfsig (ctrl, keyblock);
clean_all_uids (ctrl, keyblock, opt.verbose,
(options&EXPORT_MINIMAL), NULL, NULL);
clean_all_subkeys (ctrl, keyblock, opt.verbose,
(options&EXPORT_MINIMAL)? KEY_CLEAN_ALL
/**/ : KEY_CLEAN_AUTHENCR,
NULL, NULL);
commit_kbnode (&keyblock);
}
if (export_keep_uid)
{
commit_kbnode (&keyblock);
apply_keep_uid_filter (ctrl, keyblock, export_keep_uid);
commit_kbnode (&keyblock);
}
if (export_drop_subkey)
{
commit_kbnode (&keyblock);
apply_drop_subkey_filter (ctrl, keyblock, export_drop_subkey);
commit_kbnode (&keyblock);
}
/* And write it. */
err = do_export_one_keyblock (ctrl, keyblock, keyid,
out_help? out_help : out,
secret, options, stats, any,
desc, ndesc, descindex, cipherhd);
if (err)
break;
if (keyblock_out)
{
*keyblock_out = keyblock;
break;
}
if (out_help)
{
/* We want to write PKA or DANE records. OUT_HELP has the
* keyblock and we print a record for each uid to OUT. */
const void *data;
size_t datalen;
iobuf_flush_temp (out_help);
data = iobuf_get_temp_buffer (out_help);
datalen = iobuf_get_temp_length (out_help);
err = print_pka_or_dane_records (out,
keyblock, pk, data, datalen,
(options & EXPORT_PKA_FORMAT),
(options & EXPORT_DANE_FORMAT));
if (err)
goto leave;
iobuf_close (out_help);
out_help = iobuf_temp ();
}
}
if (gpg_err_code (err) == GPG_ERR_NOT_FOUND)
err = 0;
leave:
iobuf_cancel (out_help);
gcry_cipher_close (cipherhd);
xfree(desc);
keydb_release (kdbhd);
if (err || !keyblock_out)
release_kbnode( keyblock );
if( !*any )
log_info(_("WARNING: nothing exported\n"));
return err;
}
static gpg_error_t
key_to_sshblob (membuf_t *mb, const char *identifier, ...)
{
va_list arg_ptr;
gpg_error_t err = 0;
unsigned char nbuf[4];
unsigned char *buf;
size_t buflen;
gcry_mpi_t a;
ulongtobuf (nbuf, (ulong)strlen (identifier));
put_membuf (mb, nbuf, 4);
put_membuf_str (mb, identifier);
if (!strncmp (identifier, "ecdsa-sha2-", 11))
{
ulongtobuf (nbuf, (ulong)strlen (identifier+11));
put_membuf (mb, nbuf, 4);
put_membuf_str (mb, identifier+11);
}
va_start (arg_ptr, identifier);
while ((a = va_arg (arg_ptr, gcry_mpi_t)))
{
err = gcry_mpi_aprint (GCRYMPI_FMT_SSH, &buf, &buflen, a);
if (err)
break;
if (!strcmp (identifier, "ssh-ed25519")
&& buflen > 5 && buf[4] == 0x40)
{
/* We need to strip our 0x40 prefix. */
put_membuf (mb, "\x00\x00\x00\x20", 4);
put_membuf (mb, buf+5, buflen-5);
}
else
put_membuf (mb, buf, buflen);
gcry_free (buf);
}
va_end (arg_ptr);
return err;
}
/* Export the key identified by USERID in the SSH public key format.
The function exports the latest subkey with Authentication
capability unless the '!' suffix is used to export a specific
key. */
gpg_error_t
export_ssh_key (ctrl_t ctrl, const char *userid)
{
gpg_error_t err;
kbnode_t keyblock = NULL;
KEYDB_SEARCH_DESC desc;
u32 latest_date;
u32 curtime = make_timestamp ();
kbnode_t latest_key, node;
PKT_public_key *pk;
const char *identifier = NULL;
membuf_t mb;
estream_t fp = NULL;
struct b64state b64_state;
const char *fname = "-";
init_membuf (&mb, 4096);
/* We need to know whether the key has been specified using the
exact syntax ('!' suffix). Thus we need to run a
classify_user_id on our own. */
err = classify_user_id (userid, &desc, 1);
/* Get the public key. */
if (!err)
{
getkey_ctx_t getkeyctx;
- err = get_pubkey_byname (ctrl, &getkeyctx, NULL, userid, &keyblock,
+ err = get_pubkey_byname (ctrl, GET_PUBKEY_NO_AKL,
+ &getkeyctx, NULL, userid, &keyblock,
NULL,
- 0 /* Only usable keys or given exact. */,
- 1 /* No AKL lookup. */);
+ 0 /* Only usable keys or given exact. */);
if (!err)
{
err = getkey_next (ctrl, getkeyctx, NULL, NULL);
if (!err)
err = gpg_error (GPG_ERR_AMBIGUOUS_NAME);
else if (gpg_err_code (err) == GPG_ERR_NO_PUBKEY)
err = 0;
}
getkey_end (ctrl, getkeyctx);
}
if (err)
{
log_error (_("key \"%s\" not found: %s\n"), userid, gpg_strerror (err));
return err;
}
/* The finish_lookup code in getkey.c does not handle auth keys,
thus we have to duplicate the code here to find the latest
subkey. However, if the key has been found using an exact match
('!' notation) we use that key without any further checks and
even allow the use of the primary key. */
latest_date = 0;
latest_key = NULL;
for (node = keyblock; node; node = node->next)
{
if ((node->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| node->pkt->pkttype == PKT_PUBLIC_KEY)
&& node->pkt->pkt.public_key->flags.exact)
{
latest_key = node;
break;
}
}
if (!latest_key)
{
for (node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype != PKT_PUBLIC_SUBKEY)
continue;
pk = node->pkt->pkt.public_key;
if (DBG_LOOKUP)
log_debug ("\tchecking subkey %08lX\n",
(ulong) keyid_from_pk (pk, NULL));
if (!(pk->pubkey_usage & PUBKEY_USAGE_AUTH))
{
if (DBG_LOOKUP)
log_debug ("\tsubkey not usable for authentication\n");
continue;
}
if (!pk->flags.valid)
{
if (DBG_LOOKUP)
log_debug ("\tsubkey not valid\n");
continue;
}
if (pk->flags.revoked)
{
if (DBG_LOOKUP)
log_debug ("\tsubkey has been revoked\n");
continue;
}
if (pk->has_expired)
{
if (DBG_LOOKUP)
log_debug ("\tsubkey has expired\n");
continue;
}
if (pk->timestamp > curtime && !opt.ignore_valid_from)
{
if (DBG_LOOKUP)
log_debug ("\tsubkey not yet valid\n");
continue;
}
if (DBG_LOOKUP)
log_debug ("\tsubkey might be fine\n");
/* In case a key has a timestamp of 0 set, we make sure that it
is used. A better change would be to compare ">=" but that
might also change the selected keys and is as such a more
intrusive change. */
if (pk->timestamp > latest_date || (!pk->timestamp && !latest_date))
{
latest_date = pk->timestamp;
latest_key = node;
}
}
/* If no subkey was suitable check the primary key. */
if (!latest_key
&& (node = keyblock) && node->pkt->pkttype == PKT_PUBLIC_KEY)
{
pk = node->pkt->pkt.public_key;
if (DBG_LOOKUP)
log_debug ("\tchecking primary key %08lX\n",
(ulong) keyid_from_pk (pk, NULL));
if (!(pk->pubkey_usage & PUBKEY_USAGE_AUTH))
{
if (DBG_LOOKUP)
log_debug ("\tprimary key not usable for authentication\n");
}
else if (!pk->flags.valid)
{
if (DBG_LOOKUP)
log_debug ("\tprimary key not valid\n");
}
else if (pk->flags.revoked)
{
if (DBG_LOOKUP)
log_debug ("\tprimary key has been revoked\n");
}
else if (pk->has_expired)
{
if (DBG_LOOKUP)
log_debug ("\tprimary key has expired\n");
}
else if (pk->timestamp > curtime && !opt.ignore_valid_from)
{
if (DBG_LOOKUP)
log_debug ("\tprimary key not yet valid\n");
}
else
{
if (DBG_LOOKUP)
log_debug ("\tprimary key is fine\n");
latest_date = pk->timestamp;
latest_key = node;
}
}
}
if (!latest_key)
{
err = gpg_error (GPG_ERR_UNUSABLE_PUBKEY);
log_error (_("key \"%s\" not found: %s\n"), userid, gpg_strerror (err));
goto leave;
}
pk = latest_key->pkt->pkt.public_key;
if (DBG_LOOKUP)
log_debug ("\tusing key %08lX\n", (ulong) keyid_from_pk (pk, NULL));
switch (pk->pubkey_algo)
{
case PUBKEY_ALGO_DSA:
identifier = "ssh-dss";
err = key_to_sshblob (&mb, identifier,
pk->pkey[0], pk->pkey[1], pk->pkey[2], pk->pkey[3],
NULL);
break;
case PUBKEY_ALGO_RSA:
case PUBKEY_ALGO_RSA_S:
identifier = "ssh-rsa";
err = key_to_sshblob (&mb, identifier, pk->pkey[1], pk->pkey[0], NULL);
break;
case PUBKEY_ALGO_ECDSA:
{
char *curveoid;
const char *curve;
curveoid = openpgp_oid_to_str (pk->pkey[0]);
if (!curveoid)
err = gpg_error_from_syserror ();
else if (!(curve = openpgp_oid_to_curve (curveoid, 0)))
err = gpg_error (GPG_ERR_UNKNOWN_CURVE);
else
{
if (!strcmp (curve, "nistp256"))
identifier = "ecdsa-sha2-nistp256";
else if (!strcmp (curve, "nistp384"))
identifier = "ecdsa-sha2-nistp384";
else if (!strcmp (curve, "nistp521"))
identifier = "ecdsa-sha2-nistp521";
if (!identifier)
err = gpg_error (GPG_ERR_UNKNOWN_CURVE);
else
err = key_to_sshblob (&mb, identifier, pk->pkey[1], NULL);
}
xfree (curveoid);
}
break;
case PUBKEY_ALGO_EDDSA:
if (!openpgp_oid_is_ed25519 (pk->pkey[0]))
err = gpg_error (GPG_ERR_UNKNOWN_CURVE);
else
{
identifier = "ssh-ed25519";
err = key_to_sshblob (&mb, identifier, pk->pkey[1], NULL);
}
break;
case PUBKEY_ALGO_ELGAMAL_E:
case PUBKEY_ALGO_ELGAMAL:
err = gpg_error (GPG_ERR_UNUSABLE_PUBKEY);
break;
default:
err = GPG_ERR_PUBKEY_ALGO;
break;
}
if (!identifier)
goto leave;
if (opt.outfile && *opt.outfile && strcmp (opt.outfile, "-"))
fp = es_fopen ((fname = opt.outfile), "w");
else
fp = es_stdout;
if (!fp)
{
err = gpg_error_from_syserror ();
log_error (_("error creating '%s': %s\n"), fname, gpg_strerror (err));
goto leave;
}
es_fprintf (fp, "%s ", identifier);
err = b64enc_start_es (&b64_state, fp, "");
if (!err)
{
void *blob;
size_t bloblen;
blob = get_membuf (&mb, &bloblen);
if (blob)
{
err = b64enc_write (&b64_state, blob, bloblen);
xfree (blob);
if (err)
goto leave;
}
err = b64enc_finish (&b64_state);
}
if (err)
goto leave;
es_fprintf (fp, " openpgp:0x%08lX\n", (ulong)keyid_from_pk (pk, NULL));
if (es_ferror (fp))
err = gpg_error_from_syserror ();
else
{
if (es_fclose (fp))
err = gpg_error_from_syserror ();
fp = NULL;
}
if (err)
log_error (_("error writing '%s': %s\n"), fname, gpg_strerror (err));
leave:
es_fclose (fp);
xfree (get_membuf (&mb, NULL));
release_kbnode (keyblock);
return err;
}
diff --git a/g10/getkey.c b/g10/getkey.c
index 9dae879d2..57617a0a9 100644
--- a/g10/getkey.c
+++ b/g10/getkey.c
@@ -1,4234 +1,4133 @@
/* getkey.c - Get a key from the database
* Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006,
* 2007, 2008, 2010 Free Software Foundation, Inc.
* Copyright (C) 2015, 2016 g10 Code GmbH
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "gpg.h"
#include "../common/util.h"
#include "packet.h"
#include "../common/iobuf.h"
#include "keydb.h"
#include "options.h"
#include "main.h"
#include "trustdb.h"
#include "../common/i18n.h"
#include "keyserver-internal.h"
#include "call-agent.h"
+#include "objcache.h"
#include "../common/host2net.h"
#include "../common/mbox-util.h"
#include "../common/status.h"
#define MAX_PK_CACHE_ENTRIES PK_UID_CACHE_SIZE
#define MAX_UID_CACHE_ENTRIES PK_UID_CACHE_SIZE
#if MAX_PK_CACHE_ENTRIES < 2
#error We need the cache for key creation
#endif
/* Flags values returned by the lookup code. Note that the values are
* directly used by the KEY_CONSIDERED status line. */
#define LOOKUP_NOT_SELECTED (1<<0)
#define LOOKUP_ALL_SUBKEYS_EXPIRED (1<<1) /* or revoked */
/* A context object used by the lookup functions. */
struct getkey_ctx_s
{
/* Part of the search criteria: whether the search is an exact
search or not. A search that is exact requires that a key or
subkey meet all of the specified criteria. A search that is not
exact allows selecting a different key or subkey from the
keyblock that matched the criteria. Further, an exact search
returns the key or subkey that matched whereas a non-exact search
typically returns the primary key. See finish_lookup for
details. */
int exact;
/* Part of the search criteria: Whether the caller only wants keys
with an available secret key. This is used by getkey_next to get
the next result with the same initial criteria. */
int want_secret;
/* Part of the search criteria: The type of the requested key. A
mask of PUBKEY_USAGE_SIG, PUBKEY_USAGE_ENC and PUBKEY_USAGE_CERT.
If non-zero, then for a key to match, it must implement one of
the required uses. */
int req_usage;
/* The database handle. */
KEYDB_HANDLE kr_handle;
/* Whether we should call xfree() on the context when the context is
released using getkey_end()). */
int not_allocated;
/* This variable is used as backing store for strings which have
their address used in ITEMS. */
strlist_t extra_list;
/* Hack to return the mechanism (AKL_foo) used to find the key. */
int found_via_akl;
/* Part of the search criteria: The low-level search specification
as passed to keydb_search. */
int nitems;
/* This must be the last element in the structure. When we allocate
the structure, we allocate it so that ITEMS can hold NITEMS. */
KEYDB_SEARCH_DESC items[1];
};
#if 0
static struct
{
int any;
int okay_count;
int nokey_count;
int error_count;
} lkup_stats[21];
#endif
typedef struct keyid_list
{
struct keyid_list *next;
+ byte fprlen;
char fpr[MAX_FINGERPRINT_LEN];
u32 keyid[2];
} *keyid_list_t;
#if MAX_PK_CACHE_ENTRIES
typedef struct pk_cache_entry
{
struct pk_cache_entry *next;
u32 keyid[2];
PKT_public_key *pk;
} *pk_cache_entry_t;
static pk_cache_entry_t pk_cache;
static int pk_cache_entries; /* Number of entries in pk cache. */
static int pk_cache_disabled;
#endif
#if MAX_UID_CACHE_ENTRIES < 5
#error we really need the userid cache
#endif
-typedef struct user_id_db
-{
- struct user_id_db *next;
- keyid_list_t keyids;
- int len;
- char name[1];
-} *user_id_db_t;
-static user_id_db_t user_id_db;
-static int uid_cache_entries; /* Number of entries in uid cache. */
static void merge_selfsigs (ctrl_t ctrl, kbnode_t keyblock);
static int lookup (ctrl_t ctrl, getkey_ctx_t ctx, int want_secret,
kbnode_t *ret_keyblock, kbnode_t *ret_found_key);
static kbnode_t finish_lookup (kbnode_t keyblock,
unsigned int req_usage, int want_exact,
int want_secret, unsigned int *r_flags);
static void print_status_key_considered (kbnode_t keyblock, unsigned int flags);
#if 0
static void
print_stats ()
{
int i;
for (i = 0; i < DIM (lkup_stats); i++)
{
if (lkup_stats[i].any)
es_fprintf (es_stderr,
"lookup stats: mode=%-2d ok=%-6d nokey=%-6d err=%-6d\n",
i,
lkup_stats[i].okay_count,
lkup_stats[i].nokey_count, lkup_stats[i].error_count);
}
}
#endif
/* Cache a copy of a public key in the public key cache. PK is not
* cached if caching is disabled (via getkey_disable_caches), if
* PK->FLAGS.DONT_CACHE is set, we don't know how to derive a key id
* from the public key (e.g., unsupported algorithm), or a key with
* the key id is already in the cache.
*
* The public key packet is copied into the cache using
* copy_public_key. Thus, any secret parts are not copied, for
* instance.
*
* This cache is filled by get_pubkey and is read by get_pubkey and
* get_pubkey_fast. */
void
cache_public_key (PKT_public_key * pk)
{
#if MAX_PK_CACHE_ENTRIES
pk_cache_entry_t ce, ce2;
u32 keyid[2];
if (pk_cache_disabled)
return;
if (pk->flags.dont_cache)
return;
if (is_ELGAMAL (pk->pubkey_algo)
|| pk->pubkey_algo == PUBKEY_ALGO_DSA
|| pk->pubkey_algo == PUBKEY_ALGO_ECDSA
|| pk->pubkey_algo == PUBKEY_ALGO_EDDSA
|| pk->pubkey_algo == PUBKEY_ALGO_ECDH
|| is_RSA (pk->pubkey_algo))
{
keyid_from_pk (pk, keyid);
}
else
return; /* Don't know how to get the keyid. */
for (ce = pk_cache; ce; ce = ce->next)
if (ce->keyid[0] == keyid[0] && ce->keyid[1] == keyid[1])
{
if (DBG_CACHE)
log_debug ("cache_public_key: already in cache\n");
return;
}
if (pk_cache_entries >= MAX_PK_CACHE_ENTRIES)
{
int n;
/* Remove the last 50% of the entries. */
for (ce = pk_cache, n = 0; ce && n < pk_cache_entries/2; n++)
ce = ce->next;
if (ce && ce != pk_cache && ce->next)
{
ce2 = ce->next;
ce->next = NULL;
ce = ce2;
for (; ce; ce = ce2)
{
ce2 = ce->next;
free_public_key (ce->pk);
xfree (ce);
pk_cache_entries--;
}
}
log_assert (pk_cache_entries < MAX_PK_CACHE_ENTRIES);
}
pk_cache_entries++;
ce = xmalloc (sizeof *ce);
ce->next = pk_cache;
pk_cache = ce;
ce->pk = copy_public_key (NULL, pk);
ce->keyid[0] = keyid[0];
ce->keyid[1] = keyid[1];
#endif
}
/* Return a const utf-8 string with the text "[User ID not found]".
This function is required so that we don't need to switch gettext's
encoding temporary. */
static const char *
user_id_not_found_utf8 (void)
{
static char *text;
if (!text)
text = native_to_utf8 (_("[User ID not found]"));
return text;
}
-/* Return the user ID from the given keyblock.
- * We use the primary uid flag which has been set by the merge_selfsigs
- * function. The returned value is only valid as long as the given
- * keyblock is not changed. */
-static const char *
-get_primary_uid (KBNODE keyblock, size_t * uidlen)
-{
- KBNODE k;
- const char *s;
-
- for (k = keyblock; k; k = k->next)
- {
- if (k->pkt->pkttype == PKT_USER_ID
- && !k->pkt->pkt.user_id->attrib_data
- && k->pkt->pkt.user_id->flags.primary)
- {
- *uidlen = k->pkt->pkt.user_id->len;
- return k->pkt->pkt.user_id->name;
- }
- }
- s = user_id_not_found_utf8 ();
- *uidlen = strlen (s);
- return s;
-}
-
-
-static void
-release_keyid_list (keyid_list_t k)
-{
- while (k)
- {
- keyid_list_t k2 = k->next;
- xfree (k);
- k = k2;
- }
-}
-
-/****************
- * Store the association of keyid and userid
- * Feed only public keys to this function.
- */
-static void
-cache_user_id (KBNODE keyblock)
-{
- user_id_db_t r;
- const char *uid;
- size_t uidlen;
- keyid_list_t keyids = NULL;
- KBNODE k;
-
- for (k = keyblock; k; k = k->next)
- {
- if (k->pkt->pkttype == PKT_PUBLIC_KEY
- || k->pkt->pkttype == PKT_PUBLIC_SUBKEY)
- {
- keyid_list_t a = xmalloc_clear (sizeof *a);
- /* Hmmm: For a long list of keyids it might be an advantage
- * to append the keys. */
- fingerprint_from_pk (k->pkt->pkt.public_key, a->fpr, NULL);
- keyid_from_pk (k->pkt->pkt.public_key, a->keyid);
- /* First check for duplicates. */
- for (r = user_id_db; r; r = r->next)
- {
- keyid_list_t b;
-
- for (b = r->keyids; b; b = b->next)
- {
- if (!memcmp (b->fpr, a->fpr, MAX_FINGERPRINT_LEN))
- {
- if (DBG_CACHE)
- log_debug ("cache_user_id: already in cache\n");
- release_keyid_list (keyids);
- xfree (a);
- return;
- }
- }
- }
- /* Now put it into the cache. */
- a->next = keyids;
- keyids = a;
- }
- }
- if (!keyids)
- BUG (); /* No key no fun. */
-
-
- uid = get_primary_uid (keyblock, &uidlen);
-
- if (uid_cache_entries >= MAX_UID_CACHE_ENTRIES)
- {
- /* fixme: use another algorithm to free some cache slots */
- r = user_id_db;
- user_id_db = r->next;
- release_keyid_list (r->keyids);
- xfree (r);
- uid_cache_entries--;
- }
- r = xmalloc (sizeof *r + uidlen - 1);
- r->keyids = keyids;
- r->len = uidlen;
- memcpy (r->name, uid, r->len);
- r->next = user_id_db;
- user_id_db = r;
- uid_cache_entries++;
-}
-
/* Disable and drop the public key cache (which is filled by
cache_public_key and get_pubkey). Note: there is currently no way
to re-enable this cache. */
void
getkey_disable_caches ()
{
#if MAX_PK_CACHE_ENTRIES
{
pk_cache_entry_t ce, ce2;
for (ce = pk_cache; ce; ce = ce2)
{
ce2 = ce->next;
free_public_key (ce->pk);
xfree (ce);
}
pk_cache_disabled = 1;
pk_cache_entries = 0;
pk_cache = NULL;
}
#endif
/* fixme: disable user id cache ? */
}
/* Free a list of pubkey_t objects. */
void
pubkeys_free (pubkey_t keys)
{
while (keys)
{
pubkey_t next = keys->next;
xfree (keys->pk);
release_kbnode (keys->keyblock);
xfree (keys);
keys = next;
}
}
static void
pk_from_block (PKT_public_key *pk, kbnode_t keyblock, kbnode_t found_key)
{
kbnode_t a = found_key ? found_key : keyblock;
log_assert (a->pkt->pkttype == PKT_PUBLIC_KEY
|| a->pkt->pkttype == PKT_PUBLIC_SUBKEY);
copy_public_key (pk, a->pkt->pkt.public_key);
}
/* Specialized version of get_pubkey which retrieves the key based on
* information in SIG. In contrast to get_pubkey PK is required. */
gpg_error_t
get_pubkey_for_sig (ctrl_t ctrl, PKT_public_key *pk, PKT_signature *sig)
{
const byte *fpr;
size_t fprlen;
/* First try the new ISSUER_FPR info. */
fpr = issuer_fpr_raw (sig, &fprlen);
if (fpr && !get_pubkey_byfprint (ctrl, pk, NULL, fpr, fprlen))
return 0;
/* Fallback to use the ISSUER_KEYID. */
return get_pubkey (ctrl, pk, sig->keyid);
}
/* Return the public key with the key id KEYID and store it at PK.
* The resources in *PK should be released using
* release_public_key_parts(). This function also stores a copy of
* the public key in the user id cache (see cache_public_key).
*
* If PK is NULL, this function just stores the public key in the
* cache and returns the usual return code.
*
* PK->REQ_USAGE (which is a mask of PUBKEY_USAGE_SIG,
* PUBKEY_USAGE_ENC and PUBKEY_USAGE_CERT) is passed through to the
* lookup function. If this is non-zero, only keys with the specified
* usage will be returned. As such, it is essential that
* PK->REQ_USAGE be correctly initialized!
*
* Returns 0 on success, GPG_ERR_NO_PUBKEY if there is no public key
* with the specified key id, or another error code if an error
* occurs.
*
* If the data was not read from the cache, then the self-signed data
* has definitely been merged into the public key using
* merge_selfsigs. */
int
get_pubkey (ctrl_t ctrl, PKT_public_key * pk, u32 * keyid)
{
int internal = 0;
int rc = 0;
#if MAX_PK_CACHE_ENTRIES
if (pk)
{
/* Try to get it from the cache. We don't do this when pk is
NULL as it does not guarantee that the user IDs are
cached. */
pk_cache_entry_t ce;
for (ce = pk_cache; ce; ce = ce->next)
{
if (ce->keyid[0] == keyid[0] && ce->keyid[1] == keyid[1])
/* XXX: We don't check PK->REQ_USAGE here, but if we don't
read from the cache, we do check it! */
{
copy_public_key (pk, ce->pk);
return 0;
}
}
}
#endif
/* More init stuff. */
if (!pk)
{
internal++;
pk = xtrycalloc (1, sizeof *pk);
if (!pk)
{
rc = gpg_error_from_syserror ();
goto leave;
}
}
/* Do a lookup. */
{
struct getkey_ctx_s ctx;
kbnode_t kb = NULL;
kbnode_t found_key = NULL;
memset (&ctx, 0, sizeof ctx);
ctx.exact = 1; /* Use the key ID exactly as given. */
ctx.not_allocated = 1;
if (ctrl && ctrl->cached_getkey_kdb)
{
ctx.kr_handle = ctrl->cached_getkey_kdb;
ctrl->cached_getkey_kdb = NULL;
keydb_search_reset (ctx.kr_handle);
}
else
{
ctx.kr_handle = keydb_new ();
if (!ctx.kr_handle)
{
rc = gpg_error_from_syserror ();
goto leave;
}
}
ctx.nitems = 1;
ctx.items[0].mode = KEYDB_SEARCH_MODE_LONG_KID;
ctx.items[0].u.kid[0] = keyid[0];
ctx.items[0].u.kid[1] = keyid[1];
ctx.req_usage = pk->req_usage;
rc = lookup (ctrl, &ctx, 0, &kb, &found_key);
if (!rc)
{
pk_from_block (pk, kb, found_key);
}
getkey_end (ctrl, &ctx);
release_kbnode (kb);
}
if (!rc)
goto leave;
rc = GPG_ERR_NO_PUBKEY;
leave:
if (!rc)
cache_public_key (pk);
if (internal)
free_public_key (pk);
return rc;
}
/* Similar to get_pubkey, but it does not take PK->REQ_USAGE into
* account nor does it merge in the self-signed data. This function
* also only considers primary keys. It is intended to be used as a
* quick check of the key to avoid recursion. It should only be used
* in very certain cases. Like get_pubkey and unlike any of the other
* lookup functions, this function also consults the user id cache
* (see cache_public_key).
*
* Return the public key in *PK. The resources in *PK should be
* released using release_public_key_parts(). */
int
get_pubkey_fast (PKT_public_key * pk, u32 * keyid)
{
int rc = 0;
KEYDB_HANDLE hd;
KBNODE keyblock;
u32 pkid[2];
log_assert (pk);
#if MAX_PK_CACHE_ENTRIES
{
/* Try to get it from the cache */
pk_cache_entry_t ce;
for (ce = pk_cache; ce; ce = ce->next)
{
if (ce->keyid[0] == keyid[0] && ce->keyid[1] == keyid[1]
/* Only consider primary keys. */
&& ce->pk->keyid[0] == ce->pk->main_keyid[0]
&& ce->pk->keyid[1] == ce->pk->main_keyid[1])
{
if (pk)
copy_public_key (pk, ce->pk);
return 0;
}
}
}
#endif
hd = keydb_new ();
if (!hd)
return gpg_error_from_syserror ();
rc = keydb_search_kid (hd, keyid);
if (gpg_err_code (rc) == GPG_ERR_NOT_FOUND)
{
keydb_release (hd);
return GPG_ERR_NO_PUBKEY;
}
rc = keydb_get_keyblock (hd, &keyblock);
keydb_release (hd);
if (rc)
{
log_error ("keydb_get_keyblock failed: %s\n", gpg_strerror (rc));
return GPG_ERR_NO_PUBKEY;
}
log_assert (keyblock && keyblock->pkt
&& keyblock->pkt->pkttype == PKT_PUBLIC_KEY);
/* We return the primary key. If KEYID matched a subkey, then we
return an error. */
keyid_from_pk (keyblock->pkt->pkt.public_key, pkid);
if (keyid[0] == pkid[0] && keyid[1] == pkid[1])
copy_public_key (pk, keyblock->pkt->pkt.public_key);
else
rc = GPG_ERR_NO_PUBKEY;
release_kbnode (keyblock);
/* Not caching key here since it won't have all of the fields
properly set. */
return rc;
}
/* Return the entire keyblock used to create SIG. This is a
* specialized version of get_pubkeyblock.
*
* FIXME: This is a hack because get_pubkey_for_sig was already called
* and it could have used a cache to hold the key. */
kbnode_t
get_pubkeyblock_for_sig (ctrl_t ctrl, PKT_signature *sig)
{
const byte *fpr;
size_t fprlen;
kbnode_t keyblock;
/* First try the new ISSUER_FPR info. */
fpr = issuer_fpr_raw (sig, &fprlen);
if (fpr && !get_pubkey_byfprint (ctrl, NULL, &keyblock, fpr, fprlen))
return keyblock;
/* Fallback to use the ISSUER_KEYID. */
return get_pubkeyblock (ctrl, sig->keyid);
}
/* Return the key block for the key with key id KEYID or NULL, if an
* error occurs. Use release_kbnode() to release the key block.
*
* The self-signed data has already been merged into the public key
* using merge_selfsigs. */
kbnode_t
get_pubkeyblock (ctrl_t ctrl, u32 * keyid)
{
struct getkey_ctx_s ctx;
int rc = 0;
KBNODE keyblock = NULL;
memset (&ctx, 0, sizeof ctx);
/* No need to set exact here because we want the entire block. */
ctx.not_allocated = 1;
ctx.kr_handle = keydb_new ();
if (!ctx.kr_handle)
return NULL;
ctx.nitems = 1;
ctx.items[0].mode = KEYDB_SEARCH_MODE_LONG_KID;
ctx.items[0].u.kid[0] = keyid[0];
ctx.items[0].u.kid[1] = keyid[1];
rc = lookup (ctrl, &ctx, 0, &keyblock, NULL);
getkey_end (ctrl, &ctx);
return rc ? NULL : keyblock;
}
/* Return the public key with the key id KEYID iff the secret key is
* available and store it at PK. The resources should be released
* using release_public_key_parts().
*
* Unlike other lookup functions, PK may not be NULL. PK->REQ_USAGE
* is passed through to the lookup function and is a mask of
* PUBKEY_USAGE_SIG, PUBKEY_USAGE_ENC and PUBKEY_USAGE_CERT. Thus, it
* must be valid! If this is non-zero, only keys with the specified
* usage will be returned.
*
* Returns 0 on success. If a public key with the specified key id is
* not found or a secret key is not available for that public key, an
* error code is returned. Note: this function ignores legacy keys.
* An error code is also return if an error occurs.
*
* The self-signed data has already been merged into the public key
* using merge_selfsigs. */
gpg_error_t
get_seckey (ctrl_t ctrl, PKT_public_key *pk, u32 *keyid)
{
gpg_error_t err;
struct getkey_ctx_s ctx;
kbnode_t keyblock = NULL;
kbnode_t found_key = NULL;
memset (&ctx, 0, sizeof ctx);
ctx.exact = 1; /* Use the key ID exactly as given. */
ctx.not_allocated = 1;
ctx.kr_handle = keydb_new ();
if (!ctx.kr_handle)
return gpg_error_from_syserror ();
ctx.nitems = 1;
ctx.items[0].mode = KEYDB_SEARCH_MODE_LONG_KID;
ctx.items[0].u.kid[0] = keyid[0];
ctx.items[0].u.kid[1] = keyid[1];
ctx.req_usage = pk->req_usage;
err = lookup (ctrl, &ctx, 1, &keyblock, &found_key);
if (!err)
{
pk_from_block (pk, keyblock, found_key);
}
getkey_end (ctrl, &ctx);
release_kbnode (keyblock);
if (!err)
{
err = agent_probe_secret_key (/*ctrl*/NULL, pk);
if (err)
release_public_key_parts (pk);
}
return err;
}
/* Skip unusable keys. A key is unusable if it is revoked, expired or
disabled or if the selected user id is revoked or expired. */
static int
skip_unusable (void *opaque, u32 * keyid, int uid_no)
{
ctrl_t ctrl = opaque;
int unusable = 0;
KBNODE keyblock;
PKT_public_key *pk;
keyblock = get_pubkeyblock (ctrl, keyid);
if (!keyblock)
{
log_error ("error checking usability status of %s\n", keystr (keyid));
goto leave;
}
pk = keyblock->pkt->pkt.public_key;
/* Is the key revoked or expired? */
if (pk->flags.revoked || pk->has_expired)
unusable = 1;
/* Is the user ID in question revoked or expired? */
if (!unusable && uid_no)
{
KBNODE node;
int uids_seen = 0;
for (node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_USER_ID)
{
PKT_user_id *user_id = node->pkt->pkt.user_id;
uids_seen ++;
if (uids_seen != uid_no)
continue;
if (user_id->flags.revoked || user_id->flags.expired)
unusable = 1;
break;
}
}
/* If UID_NO is non-zero, then the keyblock better have at least
that many UIDs. */
log_assert (uids_seen == uid_no);
}
if (!unusable)
unusable = pk_is_disabled (pk);
leave:
release_kbnode (keyblock);
return unusable;
}
/* Search for keys matching some criteria.
If RETCTX is not NULL, then the constructed context is returned in
*RETCTX so that getpubkey_next can be used to get subsequent
results. In this case, getkey_end() must be used to free the
search context. If RETCTX is not NULL, then RET_KDBHD must be
NULL.
If NAMELIST is not NULL, then a search query is constructed using
classify_user_id on each of the strings in the list. (Recall: the
database does an OR of the terms, not an AND.) If NAMELIST is
NULL, then all results are returned.
If PK is not NULL, the public key of the first result is returned
in *PK. Note: PK->REQ_USAGE must be valid!!! If PK->REQ_USAGE is
set, it is used to filter the search results. See the
documentation for finish_lookup to understand exactly how this is
used. Note: The self-signed data has already been merged into the
public key using merge_selfsigs. Free *PK by calling
release_public_key_parts (or, if PK was allocated using xfree, you
can use free_public_key, which calls release_public_key_parts(PK)
and then xfree(PK)).
If WANT_SECRET is set, then only keys with an available secret key
(either locally or via key registered on a smartcard) are returned.
If INCLUDE_UNUSABLE is set, then unusable keys (see the
documentation for skip_unusable for an exact definition) are
skipped unless they are looked up by key id or by fingerprint.
If RET_KB is not NULL, the keyblock is returned in *RET_KB. This
should be freed using release_kbnode().
If RET_KDBHD is not NULL, then the new database handle used to
conduct the search is returned in *RET_KDBHD. This can be used to
get subsequent results using keydb_search_next. Note: in this
case, no advanced filtering is done for subsequent results (e.g.,
WANT_SECRET and PK->REQ_USAGE are not respected).
This function returns 0 on success. Otherwise, an error code is
returned. In particular, GPG_ERR_NO_PUBKEY or GPG_ERR_NO_SECKEY
(if want_secret is set) is returned if the key is not found. */
static int
key_byname (ctrl_t ctrl, GETKEY_CTX *retctx, strlist_t namelist,
PKT_public_key *pk,
int want_secret, int include_unusable,
KBNODE * ret_kb, KEYDB_HANDLE * ret_kdbhd)
{
int rc = 0;
int n;
strlist_t r;
GETKEY_CTX ctx;
KBNODE help_kb = NULL;
KBNODE found_key = NULL;
if (retctx)
{
/* Reset the returned context in case of error. */
log_assert (!ret_kdbhd); /* Not allowed because the handle is stored
in the context. */
*retctx = NULL;
}
if (ret_kdbhd)
*ret_kdbhd = NULL;
if (!namelist)
/* No search terms: iterate over the whole DB. */
{
ctx = xmalloc_clear (sizeof *ctx);
ctx->nitems = 1;
ctx->items[0].mode = KEYDB_SEARCH_MODE_FIRST;
if (!include_unusable)
{
ctx->items[0].skipfnc = skip_unusable;
ctx->items[0].skipfncvalue = ctrl;
}
}
else
{
/* Build the search context. */
for (n = 0, r = namelist; r; r = r->next)
n++;
/* CTX has space for a single search term at the end. Thus, we
need to allocate sizeof *CTX plus (n - 1) sizeof
CTX->ITEMS. */
ctx = xmalloc_clear (sizeof *ctx + (n - 1) * sizeof ctx->items);
ctx->nitems = n;
for (n = 0, r = namelist; r; r = r->next, n++)
{
gpg_error_t err;
err = classify_user_id (r->d, &ctx->items[n], 1);
if (ctx->items[n].exact)
ctx->exact = 1;
if (err)
{
xfree (ctx);
return gpg_err_code (err); /* FIXME: remove gpg_err_code. */
}
if (!include_unusable
&& ctx->items[n].mode != KEYDB_SEARCH_MODE_SHORT_KID
&& ctx->items[n].mode != KEYDB_SEARCH_MODE_LONG_KID
&& ctx->items[n].mode != KEYDB_SEARCH_MODE_FPR)
{
ctx->items[n].skipfnc = skip_unusable;
ctx->items[n].skipfncvalue = ctrl;
}
}
}
ctx->want_secret = want_secret;
ctx->kr_handle = keydb_new ();
if (!ctx->kr_handle)
{
rc = gpg_error_from_syserror ();
getkey_end (ctrl, ctx);
return rc;
}
if (!ret_kb)
ret_kb = &help_kb;
if (pk)
{
ctx->req_usage = pk->req_usage;
}
rc = lookup (ctrl, ctx, want_secret, ret_kb, &found_key);
if (!rc && pk)
{
pk_from_block (pk, *ret_kb, found_key);
}
release_kbnode (help_kb);
if (retctx) /* Caller wants the context. */
*retctx = ctx;
else
{
if (ret_kdbhd)
{
*ret_kdbhd = ctx->kr_handle;
ctx->kr_handle = NULL;
}
getkey_end (ctrl, ctx);
}
return rc;
}
/* Find a public key identified by NAME.
*
- * If name appears to be a valid RFC822 mailbox (i.e., email
- * address) and auto key lookup is enabled (no_akl == 0), then the
- * specified auto key lookup methods (--auto-key-lookup) are used to
- * import the key into the local keyring. Otherwise, just the local
- * keyring is consulted.
+ * If name appears to be a valid RFC822 mailbox (i.e., email address)
+ * and auto key lookup is enabled (mode != GET_PUBKEY_NO_AKL), then
+ * the specified auto key lookup methods (--auto-key-lookup) are used
+ * to import the key into the local keyring. Otherwise, just the
+ * local keyring is consulted.
+ *
+ * MODE can be one of:
+ * GET_PUBKEY_NORMAL - The standard mode
+ * GET_PUBKEY_NO_AKL - The auto key locate functionality is
+ * disabled and only the local key ring is
+ * considered. Note: the local key ring is
+ * consulted even if local is not in the
+ * auto-key-locate option list!
+ * GET_PUBKEY_NO_LOCAL - Only the auto key locate functionaly is
+ * used and no local search is done.
*
* If RETCTX is not NULL, then the constructed context is returned in
* *RETCTX so that getpubkey_next can be used to get subsequent
* results. In this case, getkey_end() must be used to free the
* search context. If RETCTX is not NULL, then RET_KDBHD must be
* NULL.
*
* If PK is not NULL, the public key of the first result is returned
* in *PK. Note: PK->REQ_USAGE must be valid!!! PK->REQ_USAGE is
* passed through to the lookup function and is a mask of
* PUBKEY_USAGE_SIG, PUBKEY_USAGE_ENC and PUBKEY_USAGE_CERT. If this
* is non-zero, only keys with the specified usage will be returned.
* Note: The self-signed data has already been merged into the public
* key using merge_selfsigs. Free *PK by calling
* release_public_key_parts (or, if PK was allocated using xfree, you
* can use free_public_key, which calls release_public_key_parts(PK)
* and then xfree(PK)).
*
* NAME is a string, which is turned into a search query using
* classify_user_id.
*
* If RET_KEYBLOCK is not NULL, the keyblock is returned in
* *RET_KEYBLOCK. This should be freed using release_kbnode().
*
* If RET_KDBHD is not NULL, then the new database handle used to
* conduct the search is returned in *RET_KDBHD. This can be used to
* get subsequent results using keydb_search_next or to modify the
* returned record. Note: in this case, no advanced filtering is done
* for subsequent results (e.g., PK->REQ_USAGE is not respected).
* Unlike RETCTX, this is always returned.
*
* If INCLUDE_UNUSABLE is set, then unusable keys (see the
* documentation for skip_unusable for an exact definition) are
* skipped unless they are looked up by key id or by fingerprint.
*
- * If NO_AKL is set, then the auto key locate functionality is
- * disabled and only the local key ring is considered. Note: the
- * local key ring is consulted even if local is not in the
- * --auto-key-locate option list!
- *
* This function returns 0 on success. Otherwise, an error code is
* returned. In particular, GPG_ERR_NO_PUBKEY or GPG_ERR_NO_SECKEY
* (if want_secret is set) is returned if the key is not found. */
int
-get_pubkey_byname (ctrl_t ctrl, GETKEY_CTX * retctx, PKT_public_key * pk,
+get_pubkey_byname (ctrl_t ctrl, enum get_pubkey_modes mode,
+ GETKEY_CTX * retctx, PKT_public_key * pk,
const char *name, KBNODE * ret_keyblock,
- KEYDB_HANDLE * ret_kdbhd, int include_unusable, int no_akl)
+ KEYDB_HANDLE * ret_kdbhd, int include_unusable)
{
int rc;
strlist_t namelist = NULL;
struct akl *akl;
int is_mbox;
int nodefault = 0;
int anylocalfirst = 0;
int mechanism_type = AKL_NODEFAULT;
/* If RETCTX is not NULL, then RET_KDBHD must be NULL. */
log_assert (retctx == NULL || ret_kdbhd == NULL);
if (retctx)
*retctx = NULL;
/* Does NAME appear to be a mailbox (mail address)? */
is_mbox = is_valid_mailbox (name);
/* The auto-key-locate feature works as follows: there are a number
* of methods to look up keys. By default, the local keyring is
* tried first. Then, each method listed in the --auto-key-locate is
* tried in the order it appears.
*
* This can be changed as follows:
*
* - if nodefault appears anywhere in the list of options, then
* the local keyring is not tried first, or,
*
* - if local appears anywhere in the list of options, then the
* local keyring is not tried first, but in the order in which
* it was listed in the --auto-key-locate option.
*
* Note: we only save the search context in RETCTX if the local
* method is the first method tried (either explicitly or
* implicitly). */
- if (!no_akl)
+ if (mode == GET_PUBKEY_NO_LOCAL)
+ nodefault = 1; /* Auto-key-locate but ignore "local". */
+ else if (mode != GET_PUBKEY_NO_AKL)
{
/* auto-key-locate is enabled. */
/* nodefault is true if "nodefault" or "local" appear. */
for (akl = opt.auto_key_locate; akl; akl = akl->next)
if (akl->type == AKL_NODEFAULT || akl->type == AKL_LOCAL)
{
nodefault = 1;
break;
}
/* anylocalfirst is true if "local" appears before any other
search methods (except "nodefault"). */
for (akl = opt.auto_key_locate; akl; akl = akl->next)
if (akl->type != AKL_NODEFAULT)
{
if (akl->type == AKL_LOCAL)
anylocalfirst = 1;
break;
}
}
if (!nodefault)
{
/* "nodefault" didn't occur. Thus, "local" is implicitly the
* first method to try. */
anylocalfirst = 1;
}
- if (nodefault && is_mbox)
+ if (mode == GET_PUBKEY_NO_LOCAL)
+ {
+ /* Force using the AKL. If IS_MBOX is not set this is the final
+ * error code. */
+ rc = GPG_ERR_NO_PUBKEY;
+ }
+ else if (nodefault && is_mbox)
{
/* Either "nodefault" or "local" (explicitly) appeared in the
* auto key locate list and NAME appears to be an email address.
* Don't try the local keyring. */
rc = GPG_ERR_NO_PUBKEY;
}
else
{
/* Either "nodefault" and "local" don't appear in the auto key
* locate list (in which case we try the local keyring first) or
* NAME does not appear to be an email address (in which case we
* only try the local keyring). In this case, lookup NAME in
* the local keyring. */
add_to_strlist (&namelist, name);
rc = key_byname (ctrl, retctx, namelist, pk, 0,
include_unusable, ret_keyblock, ret_kdbhd);
}
/* If the requested name resembles a valid mailbox and automatic
retrieval has been enabled, we try to import the key. */
- if (gpg_err_code (rc) == GPG_ERR_NO_PUBKEY && !no_akl && is_mbox)
+ if (gpg_err_code (rc) == GPG_ERR_NO_PUBKEY
+ && mode != GET_PUBKEY_NO_AKL
+ && is_mbox)
{
/* NAME wasn't present in the local keyring (or we didn't try
* the local keyring). Since the auto key locate feature is
* enabled and NAME appears to be an email address, try the auto
* locate feature. */
for (akl = opt.auto_key_locate; akl; akl = akl->next)
{
unsigned char *fpr = NULL;
size_t fpr_len;
int did_akl_local = 0;
int no_fingerprint = 0;
const char *mechanism_string = "?";
mechanism_type = akl->type;
switch (mechanism_type)
{
case AKL_NODEFAULT:
/* This is a dummy mechanism. */
- mechanism_string = "None";
+ mechanism_string = "";
rc = GPG_ERR_NO_PUBKEY;
break;
case AKL_LOCAL:
- mechanism_string = "Local";
- did_akl_local = 1;
- if (retctx)
- {
- getkey_end (ctrl, *retctx);
- *retctx = NULL;
- }
- add_to_strlist (&namelist, name);
- rc = key_byname (ctrl, anylocalfirst ? retctx : NULL,
- namelist, pk, 0,
- include_unusable, ret_keyblock, ret_kdbhd);
+ if (mode == GET_PUBKEY_NO_LOCAL)
+ {
+ mechanism_string = "";
+ rc = GPG_ERR_NO_PUBKEY;
+ }
+ else
+ {
+ mechanism_string = "Local";
+ did_akl_local = 1;
+ if (retctx)
+ {
+ getkey_end (ctrl, *retctx);
+ *retctx = NULL;
+ }
+ add_to_strlist (&namelist, name);
+ rc = key_byname (ctrl, anylocalfirst ? retctx : NULL,
+ namelist, pk, 0,
+ include_unusable, ret_keyblock, ret_kdbhd);
+ }
break;
case AKL_CERT:
mechanism_string = "DNS CERT";
glo_ctrl.in_auto_key_retrieve++;
rc = keyserver_import_cert (ctrl, name, 0, &fpr, &fpr_len);
glo_ctrl.in_auto_key_retrieve--;
break;
case AKL_PKA:
mechanism_string = "PKA";
glo_ctrl.in_auto_key_retrieve++;
rc = keyserver_import_pka (ctrl, name, &fpr, &fpr_len);
glo_ctrl.in_auto_key_retrieve--;
break;
case AKL_DANE:
mechanism_string = "DANE";
glo_ctrl.in_auto_key_retrieve++;
rc = keyserver_import_cert (ctrl, name, 1, &fpr, &fpr_len);
glo_ctrl.in_auto_key_retrieve--;
break;
case AKL_WKD:
mechanism_string = "WKD";
glo_ctrl.in_auto_key_retrieve++;
rc = keyserver_import_wkd (ctrl, name, 0, &fpr, &fpr_len);
glo_ctrl.in_auto_key_retrieve--;
break;
case AKL_LDAP:
mechanism_string = "LDAP";
glo_ctrl.in_auto_key_retrieve++;
rc = keyserver_import_ldap (ctrl, name, &fpr, &fpr_len);
glo_ctrl.in_auto_key_retrieve--;
break;
case AKL_KEYSERVER:
/* Strictly speaking, we don't need to only use a valid
* mailbox for the getname search, but it helps cut down
* on the problem of searching for something like "john"
* and getting a whole lot of keys back. */
if (keyserver_any_configured (ctrl))
{
mechanism_string = "keyserver";
glo_ctrl.in_auto_key_retrieve++;
rc = keyserver_import_name (ctrl, name, &fpr, &fpr_len,
opt.keyserver);
glo_ctrl.in_auto_key_retrieve--;
}
else
{
mechanism_string = "Unconfigured keyserver";
rc = GPG_ERR_NO_PUBKEY;
}
break;
case AKL_SPEC:
{
struct keyserver_spec *keyserver;
mechanism_string = akl->spec->uri;
keyserver = keyserver_match (akl->spec);
glo_ctrl.in_auto_key_retrieve++;
rc = keyserver_import_name (ctrl,
name, &fpr, &fpr_len, keyserver);
glo_ctrl.in_auto_key_retrieve--;
}
break;
}
/* Use the fingerprint of the key that we actually fetched.
* This helps prevent problems where the key that we fetched
* doesn't have the same name that we used to fetch it. In
* the case of CERT and PKA, this is an actual security
* requirement as the URL might point to a key put in by an
* attacker. By forcing the use of the fingerprint, we
* won't use the attacker's key here. */
if (!rc && fpr)
{
char fpr_string[MAX_FINGERPRINT_LEN * 2 + 1];
log_assert (fpr_len <= MAX_FINGERPRINT_LEN);
free_strlist (namelist);
namelist = NULL;
bin2hex (fpr, fpr_len, fpr_string);
if (opt.verbose)
log_info ("auto-key-locate found fingerprint %s\n",
fpr_string);
add_to_strlist (&namelist, fpr_string);
}
else if (!rc && !fpr && !did_akl_local)
{ /* The acquisition method said no failure occurred, but
* it didn't return a fingerprint. That's a failure. */
no_fingerprint = 1;
rc = GPG_ERR_NO_PUBKEY;
}
xfree (fpr);
fpr = NULL;
if (!rc && !did_akl_local)
{ /* There was no error and we didn't do a local lookup.
* This means that we imported a key into the local
* keyring. Try to read the imported key from the
* keyring. */
if (retctx)
{
getkey_end (ctrl, *retctx);
*retctx = NULL;
}
rc = key_byname (ctrl, anylocalfirst ? retctx : NULL,
namelist, pk, 0,
include_unusable, ret_keyblock, ret_kdbhd);
}
if (!rc)
{
/* Key found. */
if (opt.verbose)
log_info (_("automatically retrieved '%s' via %s\n"),
name, mechanism_string);
break;
}
- if (gpg_err_code (rc) != GPG_ERR_NO_PUBKEY
- || opt.verbose || no_fingerprint)
+ if ((gpg_err_code (rc) != GPG_ERR_NO_PUBKEY
+ || opt.verbose || no_fingerprint) && *mechanism_string)
log_info (_("error retrieving '%s' via %s: %s\n"),
name, mechanism_string,
no_fingerprint ? _("No fingerprint") : gpg_strerror (rc));
}
}
-
if (rc && retctx)
{
getkey_end (ctrl, *retctx);
*retctx = NULL;
}
if (retctx && *retctx)
{
log_assert (!(*retctx)->extra_list);
(*retctx)->extra_list = namelist;
(*retctx)->found_via_akl = mechanism_type;
}
else
free_strlist (namelist);
return rc;
}
/* Comparison machinery for get_best_pubkey_byname. */
/* First we have a struct to cache computed information about the key
* in question. */
struct pubkey_cmp_cookie
{
int valid; /* Is this cookie valid? */
PKT_public_key key; /* The key. */
PKT_user_id *uid; /* The matching UID packet. */
unsigned int validity; /* Computed validity of (KEY, UID). */
u32 creation_time; /* Creation time of the newest subkey
capable of encryption. */
};
/* Then we have a series of helper functions. */
static int
key_is_ok (const PKT_public_key *key)
{
return (! key->has_expired && ! key->flags.revoked
&& key->flags.valid && ! key->flags.disabled);
}
static int
uid_is_ok (const PKT_public_key *key, const PKT_user_id *uid)
{
return key_is_ok (key) && ! uid->flags.revoked;
}
static int
subkey_is_ok (const PKT_public_key *sub)
{
return ! sub->flags.revoked && sub->flags.valid && ! sub->flags.disabled;
}
/* Return true if KEYBLOCK has only expired encryption subkyes. Note
* that the function returns false if the key has no encryption
- * subkeys at all or the subkecys are revoked. */
+ * subkeys at all or the subkeys are revoked. */
static int
only_expired_enc_subkeys (kbnode_t keyblock)
{
kbnode_t node;
PKT_public_key *sub;
int any = 0;
for (node = find_next_kbnode (keyblock, PKT_PUBLIC_SUBKEY);
node; node = find_next_kbnode (node, PKT_PUBLIC_SUBKEY))
{
sub = node->pkt->pkt.public_key;
if (!(sub->pubkey_usage & PUBKEY_USAGE_ENC))
continue;
if (!subkey_is_ok (sub))
continue;
any = 1;
if (!sub->has_expired)
return 0;
}
return any? 1 : 0;
}
/* Finally this function compares a NEW key to the former candidate
* OLD. Returns < 0 if the old key is worse, > 0 if the old key is
* better, == 0 if it is a tie. */
static int
pubkey_cmp (ctrl_t ctrl, const char *name, struct pubkey_cmp_cookie *old,
struct pubkey_cmp_cookie *new, KBNODE new_keyblock)
{
kbnode_t n;
new->creation_time = 0;
for (n = find_next_kbnode (new_keyblock, PKT_PUBLIC_SUBKEY);
n; n = find_next_kbnode (n, PKT_PUBLIC_SUBKEY))
{
PKT_public_key *sub = n->pkt->pkt.public_key;
if ((sub->pubkey_usage & PUBKEY_USAGE_ENC) == 0)
continue;
if (! subkey_is_ok (sub))
continue;
if (sub->timestamp > new->creation_time)
new->creation_time = sub->timestamp;
}
for (n = find_next_kbnode (new_keyblock, PKT_USER_ID);
n; n = find_next_kbnode (n, PKT_USER_ID))
{
PKT_user_id *uid = n->pkt->pkt.user_id;
char *mbox = mailbox_from_userid (uid->name, 0);
int match = mbox ? strcasecmp (name, mbox) == 0 : 0;
xfree (mbox);
if (! match)
continue;
new->uid = scopy_user_id (uid);
new->validity =
get_validity (ctrl, new_keyblock, &new->key, uid, NULL, 0) & TRUST_MASK;
new->valid = 1;
if (! old->valid)
return -1; /* No OLD key. */
if (! uid_is_ok (&old->key, old->uid) && uid_is_ok (&new->key, uid))
return -1; /* Validity of the NEW key is better. */
if (old->validity < new->validity)
return -1; /* Validity of the NEW key is better. */
if (old->validity == new->validity && uid_is_ok (&new->key, uid)
&& old->creation_time < new->creation_time)
return -1; /* Both keys are of the same validity, but the
NEW key is newer. */
}
/* Stick with the OLD key. */
return 1;
}
/* This function works like get_pubkey_byname, but if the name
* resembles a mail address, the results are ranked and only the best
* result is returned. */
gpg_error_t
-get_best_pubkey_byname (ctrl_t ctrl, GETKEY_CTX *retctx, PKT_public_key *pk,
+get_best_pubkey_byname (ctrl_t ctrl, enum get_pubkey_modes mode,
+ GETKEY_CTX *retctx, PKT_public_key *pk,
const char *name, KBNODE *ret_keyblock,
int include_unusable)
{
gpg_error_t err;
struct getkey_ctx_s *ctx = NULL;
int is_mbox = is_valid_mailbox (name);
int wkd_tried = 0;
if (retctx)
*retctx = NULL;
start_over:
if (ctx) /* Clear in case of a start over. */
{
if (ret_keyblock)
{
release_kbnode (*ret_keyblock);
*ret_keyblock = NULL;
}
getkey_end (ctrl, ctx);
ctx = NULL;
}
- err = get_pubkey_byname (ctrl, &ctx, pk, name, ret_keyblock,
- NULL, include_unusable, 0);
+ err = get_pubkey_byname (ctrl, mode,
+ &ctx, pk, name, ret_keyblock,
+ NULL, include_unusable);
if (err)
{
getkey_end (ctrl, ctx);
return err;
}
/* If the keyblock was retrieved from the local database and the key
* has expired, do further checks. However, we can do this only if
* the caller requested a keyblock. */
if (is_mbox && ctx && ctx->found_via_akl == AKL_LOCAL && ret_keyblock)
{
u32 now = make_timestamp ();
PKT_public_key *pk2 = (*ret_keyblock)->pkt->pkt.public_key;
int found;
/* If the key has expired and its origin was the WKD then try to
* get a fresh key from the WKD. We also try this if the key
* has any only expired encryption subkeys. In case we checked
* for a fresh copy in the last 3 hours we won't do that again.
* Unfortunately that does not yet work because KEYUPDATE is
* only updated during import iff the key has actually changed
* (see import.c:import_one). */
if (!wkd_tried && pk2->keyorg == KEYORG_WKD
&& (pk2->keyupdate + 3*3600) < now
&& (pk2->has_expired || only_expired_enc_subkeys (*ret_keyblock)))
{
if (opt.verbose)
log_info (_("checking for a fresh copy of an expired key via %s\n"),
"WKD");
wkd_tried = 1;
glo_ctrl.in_auto_key_retrieve++;
found = !keyserver_import_wkd (ctrl, name, 0, NULL, NULL);
glo_ctrl.in_auto_key_retrieve--;
if (found)
goto start_over;
}
}
if (is_mbox && ctx)
{
/* Rank results and return only the most relevant key. */
struct pubkey_cmp_cookie best = { 0 };
struct pubkey_cmp_cookie new = { 0 };
kbnode_t new_keyblock;
while (getkey_next (ctrl, ctx, &new.key, &new_keyblock) == 0)
{
int diff = pubkey_cmp (ctrl, name, &best, &new, new_keyblock);
release_kbnode (new_keyblock);
if (diff < 0)
{
/* New key is better. */
release_public_key_parts (&best.key);
free_user_id (best.uid);
best = new;
}
else if (diff > 0)
{
/* Old key is better. */
release_public_key_parts (&new.key);
free_user_id (new.uid);
- new.uid = NULL;
}
else
{
/* A tie. Keep the old key. */
release_public_key_parts (&new.key);
free_user_id (new.uid);
- new.uid = NULL;
}
+ new.uid = NULL;
}
getkey_end (ctrl, ctx);
ctx = NULL;
free_user_id (best.uid);
best.uid = NULL;
if (best.valid)
{
if (retctx || ret_keyblock)
{
ctx = xtrycalloc (1, sizeof **retctx);
if (! ctx)
err = gpg_error_from_syserror ();
else
{
ctx->kr_handle = keydb_new ();
if (! ctx->kr_handle)
{
err = gpg_error_from_syserror ();
xfree (ctx);
ctx = NULL;
if (retctx)
*retctx = NULL;
}
else
{
u32 *keyid = pk_keyid (&best.key);
ctx->exact = 1;
ctx->nitems = 1;
ctx->items[0].mode = KEYDB_SEARCH_MODE_LONG_KID;
ctx->items[0].u.kid[0] = keyid[0];
ctx->items[0].u.kid[1] = keyid[1];
if (ret_keyblock)
{
release_kbnode (*ret_keyblock);
*ret_keyblock = NULL;
err = getkey_next (ctrl, ctx, NULL, ret_keyblock);
}
}
}
}
if (pk)
*pk = best.key;
else
release_public_key_parts (&best.key);
}
}
if (err && ctx)
{
getkey_end (ctrl, ctx);
ctx = NULL;
}
if (retctx && ctx)
*retctx = ctx;
else
getkey_end (ctrl, ctx);
return err;
}
/* Get a public key from a file.
*
* PK is the buffer to store the key. The caller needs to make sure
* that PK->REQ_USAGE is valid. PK->REQ_USAGE is passed through to
* the lookup function and is a mask of PUBKEY_USAGE_SIG,
* PUBKEY_USAGE_ENC and PUBKEY_USAGE_CERT. If this is non-zero, only
* keys with the specified usage will be returned.
*
* FNAME is the file name. That file should contain exactly one
* keyblock.
*
* This function returns 0 on success. Otherwise, an error code is
* returned. In particular, GPG_ERR_NO_PUBKEY is returned if the key
* is not found.
*
* The self-signed data has already been merged into the public key
* using merge_selfsigs. The caller must release the content of PK by
* calling release_public_key_parts (or, if PK was malloced, using
* free_public_key).
*/
gpg_error_t
get_pubkey_fromfile (ctrl_t ctrl, PKT_public_key *pk, const char *fname)
{
gpg_error_t err;
kbnode_t keyblock;
kbnode_t found_key;
unsigned int infoflags;
err = read_key_from_file (ctrl, fname, &keyblock);
if (!err)
{
/* Warning: node flag bits 0 and 1 should be preserved by
* merge_selfsigs. FIXME: Check whether this still holds. */
merge_selfsigs (ctrl, keyblock);
found_key = finish_lookup (keyblock, pk->req_usage, 0, 0, &infoflags);
print_status_key_considered (keyblock, infoflags);
if (found_key)
pk_from_block (pk, keyblock, found_key);
else
err = gpg_error (GPG_ERR_UNUSABLE_PUBKEY);
}
release_kbnode (keyblock);
return err;
}
/* Lookup a key with the specified fingerprint.
*
* If PK is not NULL, the public key of the first result is returned
* in *PK. Note: this function does an exact search and thus the
* returned public key may be a subkey rather than the primary key.
* Note: The self-signed data has already been merged into the public
* key using merge_selfsigs. Free *PK by calling
* release_public_key_parts (or, if PK was allocated using xfree, you
* can use free_public_key, which calls release_public_key_parts(PK)
* and then xfree(PK)).
*
* If PK->REQ_USAGE is set, it is used to filter the search results.
* (Thus, if PK is not NULL, PK->REQ_USAGE must be valid!!!) See the
* documentation for finish_lookup to understand exactly how this is
* used.
*
* If R_KEYBLOCK is not NULL, then the first result's keyblock is
* returned in *R_KEYBLOCK. This should be freed using
* release_kbnode().
*
* FPRINT is a byte array whose contents is the fingerprint to use as
* the search term. FPRINT_LEN specifies the length of the
- * fingerprint (in bytes). Currently, only 16 and 20-byte
+ * fingerprint (in bytes). Currently, only 16, 20, and 32-byte
* fingerprints are supported.
*
* FIXME: We should replace this with the _byname function. This can
* be done by creating a userID conforming to the unified fingerprint
* style. */
int
get_pubkey_byfprint (ctrl_t ctrl, PKT_public_key *pk, kbnode_t *r_keyblock,
const byte * fprint, size_t fprint_len)
{
int rc;
if (r_keyblock)
*r_keyblock = NULL;
if (fprint_len == 32 || fprint_len == 20 || fprint_len == 16)
{
struct getkey_ctx_s ctx;
KBNODE kb = NULL;
KBNODE found_key = NULL;
memset (&ctx, 0, sizeof ctx);
ctx.exact = 1;
ctx.not_allocated = 1;
/* FIXME: We should get the handle from the cache like we do in
* get_pubkey. */
ctx.kr_handle = keydb_new ();
if (!ctx.kr_handle)
return gpg_error_from_syserror ();
ctx.nitems = 1;
ctx.items[0].mode = KEYDB_SEARCH_MODE_FPR;
memcpy (ctx.items[0].u.fpr, fprint, fprint_len);
ctx.items[0].fprlen = fprint_len;
if (pk)
ctx.req_usage = pk->req_usage;
rc = lookup (ctrl, &ctx, 0, &kb, &found_key);
if (!rc && pk)
pk_from_block (pk, kb, found_key);
if (!rc && r_keyblock)
{
*r_keyblock = kb;
kb = NULL;
}
release_kbnode (kb);
getkey_end (ctrl, &ctx);
}
else
rc = GPG_ERR_GENERAL; /* Oops */
return rc;
}
/* This function is similar to get_pubkey_byfprint, but it doesn't
* merge the self-signed data into the public key and subkeys or into
* the user ids. It also doesn't add the key to the user id cache.
* Further, this function ignores PK->REQ_USAGE.
*
* This function is intended to avoid recursion and, as such, should
* only be used in very specific situations.
*
* Like get_pubkey_byfprint, PK may be NULL. In that case, this
* function effectively just checks for the existence of the key. */
gpg_error_t
get_pubkey_byfprint_fast (PKT_public_key * pk,
const byte * fprint, size_t fprint_len)
{
gpg_error_t err;
KBNODE keyblock;
err = get_keyblock_byfprint_fast (&keyblock, NULL, fprint, fprint_len, 0);
if (!err)
{
if (pk)
copy_public_key (pk, keyblock->pkt->pkt.public_key);
release_kbnode (keyblock);
}
return err;
}
/* This function is similar to get_pubkey_byfprint_fast but returns a
* keydb handle at R_HD and the keyblock at R_KEYBLOCK. R_KEYBLOCK or
* R_HD may be NULL. If LOCK is set the handle has been opend in
* locked mode and keydb_disable_caching () has been called. On error
* R_KEYBLOCK is set to NULL but R_HD must be released by the caller;
* it may have a value of NULL, though. This allows to do an insert
* operation on a locked keydb handle. */
gpg_error_t
get_keyblock_byfprint_fast (kbnode_t *r_keyblock, KEYDB_HANDLE *r_hd,
const byte *fprint, size_t fprint_len, int lock)
{
gpg_error_t err;
KEYDB_HANDLE hd;
kbnode_t keyblock;
byte fprbuf[MAX_FINGERPRINT_LEN];
int i;
if (r_keyblock)
*r_keyblock = NULL;
if (r_hd)
*r_hd = NULL;
for (i = 0; i < MAX_FINGERPRINT_LEN && i < fprint_len; i++)
fprbuf[i] = fprint[i];
hd = keydb_new ();
if (!hd)
return gpg_error_from_syserror ();
if (lock)
{
err = keydb_lock (hd);
if (err)
{
/* If locking did not work, we better don't return a handle
* at all - there was a reason that locking has been
* requested. */
keydb_release (hd);
return err;
}
keydb_disable_caching (hd);
}
/* Fo all other errors we return the handle. */
if (r_hd)
*r_hd = hd;
err = keydb_search_fpr (hd, fprbuf, fprint_len);
if (gpg_err_code (err) == GPG_ERR_NOT_FOUND)
{
if (!r_hd)
keydb_release (hd);
return gpg_error (GPG_ERR_NO_PUBKEY);
}
err = keydb_get_keyblock (hd, &keyblock);
if (err)
{
log_error ("keydb_get_keyblock failed: %s\n", gpg_strerror (err));
if (!r_hd)
keydb_release (hd);
return gpg_error (GPG_ERR_NO_PUBKEY);
}
log_assert (keyblock->pkt->pkttype == PKT_PUBLIC_KEY
|| keyblock->pkt->pkttype == PKT_PUBLIC_SUBKEY);
/* Not caching key here since it won't have all of the fields
properly set. */
if (r_keyblock)
*r_keyblock = keyblock;
else
release_kbnode (keyblock);
if (!r_hd)
keydb_release (hd);
return 0;
}
const char *
parse_def_secret_key (ctrl_t ctrl)
{
KEYDB_HANDLE hd = NULL;
strlist_t t;
static int warned;
for (t = opt.def_secret_key; t; t = t->next)
{
gpg_error_t err;
KEYDB_SEARCH_DESC desc;
KBNODE kb;
KBNODE node;
err = classify_user_id (t->d, &desc, 1);
if (err)
{
log_error (_("secret key \"%s\" not found: %s\n"),
t->d, gpg_strerror (err));
if (!opt.quiet)
log_info (_("(check argument of option '%s')\n"), "--default-key");
continue;
}
if (! hd)
{
hd = keydb_new ();
if (!hd)
return NULL;
}
else
keydb_search_reset (hd);
err = keydb_search (hd, &desc, 1, NULL);
if (gpg_err_code (err) == GPG_ERR_NOT_FOUND)
continue;
if (err)
{
log_error (_("key \"%s\" not found: %s\n"), t->d, gpg_strerror (err));
t = NULL;
break;
}
err = keydb_get_keyblock (hd, &kb);
if (err)
{
log_error (_("error reading keyblock: %s\n"),
gpg_strerror (err));
continue;
}
merge_selfsigs (ctrl, kb);
err = gpg_error (GPG_ERR_NO_SECKEY);
node = kb;
do
{
PKT_public_key *pk = node->pkt->pkt.public_key;
/* Check that the key has the signing capability. */
if (! (pk->pubkey_usage & PUBKEY_USAGE_SIG))
continue;
/* Check if the key is valid. */
if (pk->flags.revoked)
{
if (DBG_LOOKUP)
log_debug ("not using %s as default key, %s",
keystr_from_pk (pk), "revoked");
continue;
}
if (pk->has_expired)
{
if (DBG_LOOKUP)
log_debug ("not using %s as default key, %s",
keystr_from_pk (pk), "expired");
continue;
}
if (pk_is_disabled (pk))
{
if (DBG_LOOKUP)
log_debug ("not using %s as default key, %s",
keystr_from_pk (pk), "disabled");
continue;
}
err = agent_probe_secret_key (ctrl, pk);
if (! err)
/* This is a valid key. */
break;
}
while ((node = find_next_kbnode (node, PKT_PUBLIC_SUBKEY)));
release_kbnode (kb);
if (err)
{
if (! warned && ! opt.quiet)
{
log_info (_("Warning: not using '%s' as default key: %s\n"),
t->d, gpg_strerror (GPG_ERR_NO_SECKEY));
print_reported_error (err, GPG_ERR_NO_SECKEY);
}
}
else
{
if (! warned && ! opt.quiet)
log_info (_("using \"%s\" as default secret key for signing\n"),
t->d);
break;
}
}
if (! warned && opt.def_secret_key && ! t)
log_info (_("all values passed to '%s' ignored\n"),
"--default-key");
warned = 1;
if (hd)
keydb_release (hd);
if (t)
return t->d;
return NULL;
}
/* Look up a secret key.
*
* If PK is not NULL, the public key of the first result is returned
* in *PK. Note: PK->REQ_USAGE must be valid!!! If PK->REQ_USAGE is
* set, it is used to filter the search results. See the
* documentation for finish_lookup to understand exactly how this is
* used. Note: The self-signed data has already been merged into the
* public key using merge_selfsigs. Free *PK by calling
* release_public_key_parts (or, if PK was allocated using xfree, you
* can use free_public_key, which calls release_public_key_parts(PK)
* and then xfree(PK)).
*
* If --default-key was set, then the specified key is looked up. (In
* this case, the default key is returned even if it is considered
* unusable. See the documentation for skip_unusable for exactly what
* this means.)
*
* Otherwise, this initiates a DB scan that returns all keys that are
* usable (see previous paragraph for exactly what usable means) and
* for which a secret key is available.
*
* This function returns the first match. Additional results can be
* returned using getkey_next. */
gpg_error_t
get_seckey_default (ctrl_t ctrl, PKT_public_key *pk)
{
gpg_error_t err;
strlist_t namelist = NULL;
int include_unusable = 1;
const char *def_secret_key = parse_def_secret_key (ctrl);
if (def_secret_key)
add_to_strlist (&namelist, def_secret_key);
else
include_unusable = 0;
err = key_byname (ctrl, NULL, namelist, pk, 1, include_unusable, NULL, NULL);
free_strlist (namelist);
return err;
}
/* Search for keys matching some criteria.
*
* If RETCTX is not NULL, then the constructed context is returned in
* *RETCTX so that getpubkey_next can be used to get subsequent
* results. In this case, getkey_end() must be used to free the
* search context. If RETCTX is not NULL, then RET_KDBHD must be
* NULL.
*
* If PK is not NULL, the public key of the first result is returned
* in *PK. Note: PK->REQ_USAGE must be valid!!! If PK->REQ_USAGE is
* set, it is used to filter the search results. See the
* documentation for finish_lookup to understand exactly how this is
* used. Note: The self-signed data has already been merged into the
* public key using merge_selfsigs. Free *PK by calling
* release_public_key_parts (or, if PK was allocated using xfree, you
* can use free_public_key, which calls release_public_key_parts(PK)
* and then xfree(PK)).
*
* If NAMES is not NULL, then a search query is constructed using
* classify_user_id on each of the strings in the list. (Recall: the
* database does an OR of the terms, not an AND.) If NAMES is
* NULL, then all results are returned.
*
* If WANT_SECRET is set, then only keys with an available secret key
* (either locally or via key registered on a smartcard) are returned.
*
* This function does not skip unusable keys (see the documentation
* for skip_unusable for an exact definition).
*
* If RET_KEYBLOCK is not NULL, the keyblock is returned in
* *RET_KEYBLOCK. This should be freed using release_kbnode().
*
* This function returns 0 on success. Otherwise, an error code is
* returned. In particular, GPG_ERR_NO_PUBKEY or GPG_ERR_NO_SECKEY
* (if want_secret is set) is returned if the key is not found. */
gpg_error_t
getkey_bynames (ctrl_t ctrl, getkey_ctx_t *retctx, PKT_public_key *pk,
strlist_t names, int want_secret, kbnode_t *ret_keyblock)
{
return key_byname (ctrl, retctx, names, pk, want_secret, 1,
ret_keyblock, NULL);
}
/* Search for one key matching some criteria.
*
* If RETCTX is not NULL, then the constructed context is returned in
* *RETCTX so that getpubkey_next can be used to get subsequent
* results. In this case, getkey_end() must be used to free the
* search context. If RETCTX is not NULL, then RET_KDBHD must be
* NULL.
*
* If PK is not NULL, the public key of the first result is returned
* in *PK. Note: PK->REQ_USAGE must be valid!!! If PK->REQ_USAGE is
* set, it is used to filter the search results. See the
* documentation for finish_lookup to understand exactly how this is
* used. Note: The self-signed data has already been merged into the
* public key using merge_selfsigs. Free *PK by calling
* release_public_key_parts (or, if PK was allocated using xfree, you
* can use free_public_key, which calls release_public_key_parts(PK)
* and then xfree(PK)).
*
* If NAME is not NULL, then a search query is constructed using
* classify_user_id on the string. In this case, even unusable keys
* (see the documentation for skip_unusable for an exact definition of
* unusable) are returned. Otherwise, if --default-key was set, then
* that key is returned (even if it is unusable). If neither of these
* conditions holds, then the first usable key is returned.
*
* If WANT_SECRET is set, then only keys with an available secret key
* (either locally or via key registered on a smartcard) are returned.
*
* This function does not skip unusable keys (see the documentation
* for skip_unusable for an exact definition).
*
* If RET_KEYBLOCK is not NULL, the keyblock is returned in
* *RET_KEYBLOCK. This should be freed using release_kbnode().
*
* This function returns 0 on success. Otherwise, an error code is
* returned. In particular, GPG_ERR_NO_PUBKEY or GPG_ERR_NO_SECKEY
* (if want_secret is set) is returned if the key is not found.
*
* FIXME: We also have the get_pubkey_byname function which has a
* different semantic. Should be merged with this one. */
gpg_error_t
getkey_byname (ctrl_t ctrl, getkey_ctx_t *retctx, PKT_public_key *pk,
const char *name, int want_secret, kbnode_t *ret_keyblock)
{
gpg_error_t err;
strlist_t namelist = NULL;
int with_unusable = 1;
const char *def_secret_key = NULL;
if (want_secret && !name)
def_secret_key = parse_def_secret_key (ctrl);
if (want_secret && !name && def_secret_key)
add_to_strlist (&namelist, def_secret_key);
else if (name)
add_to_strlist (&namelist, name);
else
with_unusable = 0;
err = key_byname (ctrl, retctx, namelist, pk, want_secret, with_unusable,
ret_keyblock, NULL);
/* FIXME: Check that we really return GPG_ERR_NO_SECKEY if
WANT_SECRET has been used. */
free_strlist (namelist);
return err;
}
/* Return the next search result.
*
* If PK is not NULL, the public key of the next result is returned in
* *PK. Note: The self-signed data has already been merged into the
* public key using merge_selfsigs. Free *PK by calling
* release_public_key_parts (or, if PK was allocated using xmalloc, you
* can use free_public_key, which calls release_public_key_parts(PK)
* and then xfree(PK)).
*
* RET_KEYBLOCK can be given as NULL; if it is not NULL it the entire
* found keyblock is returned which must be released with
* release_kbnode. If the function returns an error NULL is stored at
* RET_KEYBLOCK.
*
* The self-signed data has already been merged into the public key
* using merge_selfsigs. */
gpg_error_t
getkey_next (ctrl_t ctrl, getkey_ctx_t ctx,
PKT_public_key *pk, kbnode_t *ret_keyblock)
{
int rc; /* Fixme: Make sure this is proper gpg_error */
KBNODE keyblock = NULL;
KBNODE found_key = NULL;
/* We need to disable the caching so that for an exact key search we
won't get the result back from the cache and thus end up in an
endless loop. The endless loop can occur, because the cache is
used without respecting the current file pointer! */
keydb_disable_caching (ctx->kr_handle);
/* FOUND_KEY is only valid as long as RET_KEYBLOCK is. If the
* caller wants PK, but not RET_KEYBLOCK, we need hand in our own
* keyblock. */
if (pk && ret_keyblock == NULL)
ret_keyblock = &keyblock;
rc = lookup (ctrl, ctx, ctx->want_secret,
ret_keyblock, pk ? &found_key : NULL);
if (!rc && pk)
{
log_assert (found_key);
pk_from_block (pk, NULL, found_key);
release_kbnode (keyblock);
}
return rc;
}
/* Release any resources used by a key listing context. This must be
* called on the context returned by, e.g., getkey_byname. */
void
getkey_end (ctrl_t ctrl, getkey_ctx_t ctx)
{
if (ctx)
{
#ifdef HAVE_W32_SYSTEM
/* FIXME: This creates a big regression for Windows because the
* keyring is only released after the global ctrl is released.
* So if an operation does a getkey and then tries to modify the
* keyring it will fail on Windows with a sharing violation. We
* need to modify all keyring write operations to also take the
* ctrl and close the cached_getkey_kdb handle to make writing
* work. See: GnuPG-bug-id: 3097 */
(void)ctrl;
keydb_release (ctx->kr_handle);
#else /*!HAVE_W32_SYSTEM*/
if (ctrl && !ctrl->cached_getkey_kdb)
ctrl->cached_getkey_kdb = ctx->kr_handle;
else
keydb_release (ctx->kr_handle);
#endif /*!HAVE_W32_SYSTEM*/
free_strlist (ctx->extra_list);
if (!ctx->not_allocated)
xfree (ctx);
}
}
/************************************************
************* Merging stuff ********************
************************************************/
/* Set the mainkey_id fields for all keys in KEYBLOCK. This is
* usually done by merge_selfsigs but at some places we only need the
* main_kid not a full merge. The function also guarantees that all
* pk->keyids are computed. */
void
setup_main_keyids (kbnode_t keyblock)
{
u32 kid[2], mainkid[2];
kbnode_t kbctx, node;
PKT_public_key *pk;
if (keyblock->pkt->pkttype != PKT_PUBLIC_KEY)
BUG ();
pk = keyblock->pkt->pkt.public_key;
keyid_from_pk (pk, mainkid);
for (kbctx=NULL; (node = walk_kbnode (keyblock, &kbctx, 0)); )
{
if (!(node->pkt->pkttype == PKT_PUBLIC_KEY
|| node->pkt->pkttype == PKT_PUBLIC_SUBKEY))
continue;
pk = node->pkt->pkt.public_key;
keyid_from_pk (pk, kid); /* Make sure pk->keyid is set. */
if (!pk->main_keyid[0] && !pk->main_keyid[1])
{
pk->main_keyid[0] = mainkid[0];
pk->main_keyid[1] = mainkid[1];
}
}
}
/* KEYBLOCK corresponds to a public key block. This function merges
* much of the information from the self-signed data into the public
* key, public subkey and user id data structures. If you use the
* high-level search API (e.g., get_pubkey) for looking up key blocks,
* then you don't need to call this function. This function is
* useful, however, if you change the keyblock, e.g., by adding or
* removing a self-signed data packet. */
void
merge_keys_and_selfsig (ctrl_t ctrl, kbnode_t keyblock)
{
if (!keyblock)
;
else if (keyblock->pkt->pkttype == PKT_PUBLIC_KEY)
merge_selfsigs (ctrl, keyblock);
else
log_debug ("FIXME: merging secret key blocks is not anymore available\n");
}
static int
parse_key_usage (PKT_signature * sig)
{
int key_usage = 0;
const byte *p;
size_t n;
byte flags;
p = parse_sig_subpkt (sig->hashed, SIGSUBPKT_KEY_FLAGS, &n);
if (p && n)
{
/* First octet of the keyflags. */
flags = *p;
if (flags & 1)
{
key_usage |= PUBKEY_USAGE_CERT;
flags &= ~1;
}
if (flags & 2)
{
key_usage |= PUBKEY_USAGE_SIG;
flags &= ~2;
}
/* We do not distinguish between encrypting communications and
encrypting storage. */
if (flags & (0x04 | 0x08))
{
key_usage |= PUBKEY_USAGE_ENC;
flags &= ~(0x04 | 0x08);
}
if (flags & 0x20)
{
key_usage |= PUBKEY_USAGE_AUTH;
flags &= ~0x20;
}
if (flags)
key_usage |= PUBKEY_USAGE_UNKNOWN;
if (!key_usage)
key_usage |= PUBKEY_USAGE_NONE;
}
else if (p) /* Key flags of length zero. */
key_usage |= PUBKEY_USAGE_NONE;
/* We set PUBKEY_USAGE_UNKNOWN to indicate that this key has a
capability that we do not handle. This serves to distinguish
between a zero key usage which we handle as the default
capabilities for that algorithm, and a usage that we do not
handle. Likewise we use PUBKEY_USAGE_NONE to indicate that
key_flags have been given but they do not specify any usage. */
return key_usage;
}
/* Apply information from SIGNODE (which is the valid self-signature
* associated with that UID) to the UIDNODE:
* - weather the UID has been revoked
* - assumed creation date of the UID
* - temporary store the keyflags here
* - temporary store the key expiration time here
* - mark whether the primary user ID flag hat been set.
* - store the preferences
*/
static void
fixup_uidnode (KBNODE uidnode, KBNODE signode, u32 keycreated)
{
PKT_user_id *uid = uidnode->pkt->pkt.user_id;
PKT_signature *sig = signode->pkt->pkt.signature;
const byte *p, *sym, *aead, *hash, *zip;
size_t n, nsym, naead, nhash, nzip;
sig->flags.chosen_selfsig = 1;/* We chose this one. */
uid->created = 0; /* Not created == invalid. */
if (IS_UID_REV (sig))
{
uid->flags.revoked = 1;
return; /* Has been revoked. */
}
else
uid->flags.revoked = 0;
uid->expiredate = sig->expiredate;
if (sig->flags.expired)
{
uid->flags.expired = 1;
return; /* Has expired. */
}
else
uid->flags.expired = 0;
uid->created = sig->timestamp; /* This one is okay. */
uid->selfsigversion = sig->version;
/* If we got this far, it's not expired :) */
uid->flags.expired = 0;
/* Store the key flags in the helper variable for later processing. */
uid->help_key_usage = parse_key_usage (sig);
/* Ditto for the key expiration. */
p = parse_sig_subpkt (sig->hashed, SIGSUBPKT_KEY_EXPIRE, NULL);
if (p && buf32_to_u32 (p))
uid->help_key_expire = keycreated + buf32_to_u32 (p);
else
uid->help_key_expire = 0;
/* Set the primary user ID flag - we will later wipe out some
* of them to only have one in our keyblock. */
uid->flags.primary = 0;
p = parse_sig_subpkt (sig->hashed, SIGSUBPKT_PRIMARY_UID, NULL);
if (p && *p)
uid->flags.primary = 2;
/* We could also query this from the unhashed area if it is not in
* the hased area and then later try to decide which is the better
* there should be no security problem with this.
* For now we only look at the hashed one. */
/* Now build the preferences list. These must come from the
hashed section so nobody can modify the ciphers a key is
willing to accept. */
p = parse_sig_subpkt (sig->hashed, SIGSUBPKT_PREF_SYM, &n);
sym = p;
nsym = p ? n : 0;
p = parse_sig_subpkt (sig->hashed, SIGSUBPKT_PREF_AEAD, &n);
aead = p;
naead = p ? n : 0;
p = parse_sig_subpkt (sig->hashed, SIGSUBPKT_PREF_HASH, &n);
hash = p;
nhash = p ? n : 0;
p = parse_sig_subpkt (sig->hashed, SIGSUBPKT_PREF_COMPR, &n);
zip = p;
nzip = p ? n : 0;
if (uid->prefs)
xfree (uid->prefs);
n = nsym + naead + nhash + nzip;
if (!n)
uid->prefs = NULL;
else
{
uid->prefs = xmalloc (sizeof (*uid->prefs) * (n + 1));
n = 0;
for (; nsym; nsym--, n++)
{
uid->prefs[n].type = PREFTYPE_SYM;
uid->prefs[n].value = *sym++;
}
for (; naead; naead--, n++)
{
uid->prefs[n].type = PREFTYPE_AEAD;
uid->prefs[n].value = *aead++;
}
for (; nhash; nhash--, n++)
{
uid->prefs[n].type = PREFTYPE_HASH;
uid->prefs[n].value = *hash++;
}
for (; nzip; nzip--, n++)
{
uid->prefs[n].type = PREFTYPE_ZIP;
uid->prefs[n].value = *zip++;
}
uid->prefs[n].type = PREFTYPE_NONE; /* End of list marker */
uid->prefs[n].value = 0;
}
/* See whether we have the MDC feature. */
uid->flags.mdc = 0;
p = parse_sig_subpkt (sig->hashed, SIGSUBPKT_FEATURES, &n);
if (p && n && (p[0] & 0x01))
uid->flags.mdc = 1;
/* See whether we have the AEAD feature. */
uid->flags.aead = 0;
p = parse_sig_subpkt (sig->hashed, SIGSUBPKT_FEATURES, &n);
if (p && n && (p[0] & 0x02))
uid->flags.aead = 1;
/* And the keyserver modify flag. */
uid->flags.ks_modify = 1;
p = parse_sig_subpkt (sig->hashed, SIGSUBPKT_KS_FLAGS, &n);
if (p && n && (p[0] & 0x80))
uid->flags.ks_modify = 0;
}
static void
sig_to_revoke_info (PKT_signature * sig, struct revoke_info *rinfo)
{
rinfo->date = sig->timestamp;
rinfo->algo = sig->pubkey_algo;
rinfo->keyid[0] = sig->keyid[0];
rinfo->keyid[1] = sig->keyid[1];
}
/* Given a keyblock, parse the key block and extract various pieces of
* information and save them with the primary key packet and the user
* id packets. For instance, some information is stored in signature
* packets. We find the latest such valid packet (since the user can
* change that information) and copy its contents into the
* PKT_public_key.
*
* Note that R_REVOKED may be set to 0, 1 or 2.
*
* This function fills in the following fields in the primary key's
* keyblock:
*
* main_keyid (computed)
* revkey / numrevkeys (derived from self signed key data)
* flags.valid (whether we have at least 1 self-sig)
* flags.maybe_revoked (whether a designed revoked the key, but
* we are missing the key to check the sig)
* selfsigversion (highest version of any valid self-sig)
* pubkey_usage (derived from most recent self-sig or most
* recent user id)
* has_expired (various sources)
* expiredate (various sources)
*
* See the documentation for fixup_uidnode for how the user id packets
* are modified. In addition to that the primary user id's is_primary
* field is set to 1 and the other user id's is_primary are set to 0.
*/
static void
merge_selfsigs_main (ctrl_t ctrl, kbnode_t keyblock, int *r_revoked,
struct revoke_info *rinfo)
{
PKT_public_key *pk = NULL;
KBNODE k;
u32 kid[2];
u32 sigdate, uiddate, uiddate2;
KBNODE signode, uidnode, uidnode2;
u32 curtime = make_timestamp ();
unsigned int key_usage = 0;
u32 keytimestamp = 0;
u32 key_expire = 0;
int key_expire_seen = 0;
byte sigversion = 0;
*r_revoked = 0;
memset (rinfo, 0, sizeof (*rinfo));
/* Section 11.1 of RFC 4880 determines the order of packets within a
* message. There are three sections, which must occur in the
* following order: the public key, the user ids and user attributes
* and the subkeys. Within each section, each primary packet (e.g.,
* a user id packet) is followed by one or more signature packets,
* which modify that packet. */
/* According to Section 11.1 of RFC 4880, the public key must be the
first packet. Note that parse_keyblock_image ensures that the
first packet is the public key. */
if (keyblock->pkt->pkttype != PKT_PUBLIC_KEY)
BUG ();
pk = keyblock->pkt->pkt.public_key;
keytimestamp = pk->timestamp;
keyid_from_pk (pk, kid);
pk->main_keyid[0] = kid[0];
pk->main_keyid[1] = kid[1];
if (pk->version < 4)
{
/* Before v4 the key packet itself contains the expiration date
* and there was no way to change it, so we start with the one
* from the key packet. */
key_expire = pk->max_expiredate;
key_expire_seen = 1;
}
/* First pass:
*
* - Find the latest direct key self-signature. We assume that the
* newest one overrides all others.
*
* - Determine whether the key has been revoked.
*
* - Gather all revocation keys (unlike other data, we don't just
* take them from the latest self-signed packet).
*
* - Determine max (sig[...]->version).
*/
/* Reset this in case this key was already merged. */
xfree (pk->revkey);
pk->revkey = NULL;
pk->numrevkeys = 0;
signode = NULL;
sigdate = 0; /* Helper variable to find the latest signature. */
/* According to Section 11.1 of RFC 4880, the public key comes first
* and is immediately followed by any signature packets that modify
* it. */
for (k = keyblock;
k && k->pkt->pkttype != PKT_USER_ID
&& k->pkt->pkttype != PKT_ATTRIBUTE
&& k->pkt->pkttype != PKT_PUBLIC_SUBKEY;
k = k->next)
{
if (k->pkt->pkttype == PKT_SIGNATURE)
{
PKT_signature *sig = k->pkt->pkt.signature;
if (sig->keyid[0] == kid[0] && sig->keyid[1] == kid[1])
{ /* Self sig. */
if (check_key_signature (ctrl, keyblock, k, NULL))
; /* Signature did not verify. */
else if (IS_KEY_REV (sig))
{
/* Key has been revoked - there is no way to
* override such a revocation, so we theoretically
* can stop now. We should not cope with expiration
* times for revocations here because we have to
* assume that an attacker can generate all kinds of
* signatures. However due to the fact that the key
* has been revoked it does not harm either and by
* continuing we gather some more info on that
* key. */
*r_revoked = 1;
sig_to_revoke_info (sig, rinfo);
}
else if (IS_KEY_SIG (sig))
{
/* Add the indicated revocations keys from all
* signatures not just the latest. We do this
* because you need multiple 1F sigs to properly
* handle revocation keys (PGP does it this way, and
* a revocation key could be sensitive and hence in
* a different signature). */
if (sig->revkey)
{
int i;
pk->revkey =
xrealloc (pk->revkey, sizeof (struct revocation_key) *
(pk->numrevkeys + sig->numrevkeys));
for (i = 0; i < sig->numrevkeys; i++, pk->numrevkeys++)
{
pk->revkey[pk->numrevkeys].class
= sig->revkey[i].class;
pk->revkey[pk->numrevkeys].algid
= sig->revkey[i].algid;
pk->revkey[pk->numrevkeys].fprlen
= sig->revkey[i].fprlen;
memcpy (pk->revkey[pk->numrevkeys].fpr,
sig->revkey[i].fpr, sig->revkey[i].fprlen);
memset (pk->revkey[pk->numrevkeys].fpr
+ sig->revkey[i].fprlen,
0,
sizeof (sig->revkey[i].fpr)
- sig->revkey[i].fprlen);
}
}
if (sig->timestamp >= sigdate)
{ /* This is the latest signature so far. */
if (sig->flags.expired)
; /* Signature has expired - ignore it. */
else
{
sigdate = sig->timestamp;
signode = k;
if (sig->version > sigversion)
sigversion = sig->version;
}
}
}
}
}
}
/* Remove dupes from the revocation keys. */
if (pk->revkey)
{
int i, j, x, changed = 0;
for (i = 0; i < pk->numrevkeys; i++)
{
for (j = i + 1; j < pk->numrevkeys; j++)
{
if (memcmp (&pk->revkey[i], &pk->revkey[j],
sizeof (struct revocation_key)) == 0)
{
/* remove j */
for (x = j; x < pk->numrevkeys - 1; x++)
pk->revkey[x] = pk->revkey[x + 1];
pk->numrevkeys--;
j--;
changed = 1;
}
}
}
if (changed)
pk->revkey = xrealloc (pk->revkey,
pk->numrevkeys *
sizeof (struct revocation_key));
}
/* SIGNODE is the 1F signature packet with the latest creation time.
* Extract some information from it. */
if (signode)
{
/* Some information from a direct key signature take precedence
* over the same information given in UID sigs. */
PKT_signature *sig = signode->pkt->pkt.signature;
const byte *p;
key_usage = parse_key_usage (sig);
p = parse_sig_subpkt (sig->hashed, SIGSUBPKT_KEY_EXPIRE, NULL);
if (p && buf32_to_u32 (p))
{
key_expire = keytimestamp + buf32_to_u32 (p);
key_expire_seen = 1;
}
/* Mark that key as valid: One direct key signature should
* render a key as valid. */
pk->flags.valid = 1;
}
/* Pass 1.5: Look for key revocation signatures that were not made
* by the key (i.e. did a revocation key issue a revocation for
* us?). Only bother to do this if there is a revocation key in the
* first place and we're not revoked already. */
if (!*r_revoked && pk->revkey)
for (k = keyblock; k && k->pkt->pkttype != PKT_USER_ID; k = k->next)
{
if (k->pkt->pkttype == PKT_SIGNATURE)
{
PKT_signature *sig = k->pkt->pkt.signature;
if (IS_KEY_REV (sig) &&
(sig->keyid[0] != kid[0] || sig->keyid[1] != kid[1]))
{
int rc = check_revocation_keys (ctrl, pk, sig);
if (rc == 0)
{
*r_revoked = 2;
sig_to_revoke_info (sig, rinfo);
/* Don't continue checking since we can't be any
* more revoked than this. */
break;
}
else if (gpg_err_code (rc) == GPG_ERR_NO_PUBKEY)
pk->flags.maybe_revoked = 1;
/* A failure here means the sig did not verify, was
* not issued by a revocation key, or a revocation
* key loop was broken. If a revocation key isn't
* findable, however, the key might be revoked and
* we don't know it. */
/* Fixme: In the future handle subkey and cert
* revocations? PGP doesn't, but it's in 2440. */
}
}
}
/* Second pass: Look at the self-signature of all user IDs. */
/* According to RFC 4880 section 11.1, user id and attribute packets
* are in the second section, after the public key packet and before
* the subkey packets. */
signode = uidnode = NULL;
sigdate = 0; /* Helper variable to find the latest signature in one UID. */
for (k = keyblock; k && k->pkt->pkttype != PKT_PUBLIC_SUBKEY; k = k->next)
{
if (k->pkt->pkttype == PKT_USER_ID || k->pkt->pkttype == PKT_ATTRIBUTE)
{ /* New user id packet. */
/* Apply the data from the most recent self-signed packet to
* the preceding user id packet. */
if (uidnode && signode)
{
fixup_uidnode (uidnode, signode, keytimestamp);
pk->flags.valid = 1;
}
/* Clear SIGNODE. The only relevant self-signed data for
* UIDNODE follows it. */
if (k->pkt->pkttype == PKT_USER_ID)
uidnode = k;
else
uidnode = NULL;
signode = NULL;
sigdate = 0;
}
else if (k->pkt->pkttype == PKT_SIGNATURE && uidnode)
{
PKT_signature *sig = k->pkt->pkt.signature;
if (sig->keyid[0] == kid[0] && sig->keyid[1] == kid[1])
{
if (check_key_signature (ctrl, keyblock, k, NULL))
; /* signature did not verify */
else if ((IS_UID_SIG (sig) || IS_UID_REV (sig))
&& sig->timestamp >= sigdate)
{
/* Note: we allow invalidation of cert revocations
* by a newer signature. An attacker can't use this
* because a key should be revoked with a key revocation.
* The reason why we have to allow for that is that at
* one time an email address may become invalid but later
* the same email address may become valid again (hired,
* fired, hired again). */
sigdate = sig->timestamp;
signode = k;
signode->pkt->pkt.signature->flags.chosen_selfsig = 0;
if (sig->version > sigversion)
sigversion = sig->version;
}
}
}
}
if (uidnode && signode)
{
fixup_uidnode (uidnode, signode, keytimestamp);
pk->flags.valid = 1;
}
/* If the key isn't valid yet, and we have
* --allow-non-selfsigned-uid set, then force it valid. */
if (!pk->flags.valid && opt.allow_non_selfsigned_uid)
{
if (opt.verbose)
log_info (_("Invalid key %s made valid by"
" --allow-non-selfsigned-uid\n"), keystr_from_pk (pk));
pk->flags.valid = 1;
}
/* The key STILL isn't valid, so try and find an ultimately
* trusted signature. */
if (!pk->flags.valid)
{
uidnode = NULL;
for (k = keyblock; k && k->pkt->pkttype != PKT_PUBLIC_SUBKEY;
k = k->next)
{
if (k->pkt->pkttype == PKT_USER_ID)
uidnode = k;
else if (k->pkt->pkttype == PKT_SIGNATURE && uidnode)
{
PKT_signature *sig = k->pkt->pkt.signature;
if (sig->keyid[0] != kid[0] || sig->keyid[1] != kid[1])
{
PKT_public_key *ultimate_pk;
ultimate_pk = xmalloc_clear (sizeof (*ultimate_pk));
/* We don't want to use the full get_pubkey to avoid
* infinite recursion in certain cases. There is no
* reason to check that an ultimately trusted key is
* still valid - if it has been revoked the user
* should also remove the ultimate trust flag. */
if (get_pubkey_fast (ultimate_pk, sig->keyid) == 0
&& check_key_signature2 (ctrl,
keyblock, k, ultimate_pk,
NULL, NULL, NULL, NULL) == 0
&& get_ownertrust (ctrl, ultimate_pk) == TRUST_ULTIMATE)
{
free_public_key (ultimate_pk);
pk->flags.valid = 1;
break;
}
free_public_key (ultimate_pk);
}
}
}
}
/* Record the highest selfsig version so we know if this is a v3 key
* through and through, or a v3 key with a v4 selfsig somewhere.
* This is useful in a few places to know if the key must be treated
* as PGP2-style or OpenPGP-style. Note that a selfsig revocation
* with a higher version number will also raise this value. This is
* okay since such a revocation must be issued by the user (i.e. it
* cannot be issued by someone else to modify the key behavior.) */
pk->selfsigversion = sigversion;
/* Now that we had a look at all user IDs we can now get some
* information from those user IDs. */
if (!key_usage)
{
/* Find the latest user ID with key flags set. */
uiddate = 0; /* Helper to find the latest user ID. */
for (k = keyblock; k && k->pkt->pkttype != PKT_PUBLIC_SUBKEY;
k = k->next)
{
if (k->pkt->pkttype == PKT_USER_ID)
{
PKT_user_id *uid = k->pkt->pkt.user_id;
if (uid->help_key_usage && uid->created > uiddate)
{
key_usage = uid->help_key_usage;
uiddate = uid->created;
}
}
}
}
if (!key_usage)
{
/* No key flags at all: get it from the algo. */
key_usage = openpgp_pk_algo_usage (pk->pubkey_algo);
}
else
{
/* Check that the usage matches the usage as given by the algo. */
int x = openpgp_pk_algo_usage (pk->pubkey_algo);
if (x) /* Mask it down to the actual allowed usage. */
key_usage &= x;
}
/* Whatever happens, it's a primary key, so it can certify. */
pk->pubkey_usage = key_usage | PUBKEY_USAGE_CERT;
if (!key_expire_seen)
{
/* Find the latest valid user ID with a key expiration set
* Note, that this may be a different one from the above because
* some user IDs may have no expiration date set. */
uiddate = 0;
for (k = keyblock; k && k->pkt->pkttype != PKT_PUBLIC_SUBKEY;
k = k->next)
{
if (k->pkt->pkttype == PKT_USER_ID)
{
PKT_user_id *uid = k->pkt->pkt.user_id;
if (uid->help_key_expire && uid->created > uiddate)
{
key_expire = uid->help_key_expire;
uiddate = uid->created;
}
}
}
}
/* Currently only v3 keys have a maximum expiration date, but I'll
* bet v5 keys get this feature again. */
if (key_expire == 0
|| (pk->max_expiredate && key_expire > pk->max_expiredate))
key_expire = pk->max_expiredate;
pk->has_expired = key_expire >= curtime ? 0 : key_expire;
pk->expiredate = key_expire;
/* Fixme: we should see how to get rid of the expiretime fields but
* this needs changes at other places too. */
/* And now find the real primary user ID and delete all others. */
uiddate = uiddate2 = 0;
uidnode = uidnode2 = NULL;
for (k = keyblock; k && k->pkt->pkttype != PKT_PUBLIC_SUBKEY; k = k->next)
{
if (k->pkt->pkttype == PKT_USER_ID && !k->pkt->pkt.user_id->attrib_data)
{
PKT_user_id *uid = k->pkt->pkt.user_id;
if (uid->flags.primary)
{
if (uid->created > uiddate)
{
uiddate = uid->created;
uidnode = k;
}
else if (uid->created == uiddate && uidnode)
{
/* The dates are equal, so we need to do a different
* (and arbitrary) comparison. This should rarely,
* if ever, happen. It's good to try and guarantee
* that two different GnuPG users with two different
* keyrings at least pick the same primary. */
if (cmp_user_ids (uid, uidnode->pkt->pkt.user_id) > 0)
uidnode = k;
}
}
else
{
if (uid->created > uiddate2)
{
uiddate2 = uid->created;
uidnode2 = k;
}
else if (uid->created == uiddate2 && uidnode2)
{
if (cmp_user_ids (uid, uidnode2->pkt->pkt.user_id) > 0)
uidnode2 = k;
}
}
}
}
if (uidnode)
{
for (k = keyblock; k && k->pkt->pkttype != PKT_PUBLIC_SUBKEY;
k = k->next)
{
if (k->pkt->pkttype == PKT_USER_ID &&
!k->pkt->pkt.user_id->attrib_data)
{
PKT_user_id *uid = k->pkt->pkt.user_id;
if (k != uidnode)
uid->flags.primary = 0;
}
}
}
else if (uidnode2)
{
/* None is flagged primary - use the latest user ID we have,
* and disambiguate with the arbitrary packet comparison. */
uidnode2->pkt->pkt.user_id->flags.primary = 1;
}
else
{
/* None of our uids were self-signed, so pick the one that
* sorts first to be the primary. This is the best we can do
* here since there are no self sigs to date the uids. */
uidnode = NULL;
for (k = keyblock; k && k->pkt->pkttype != PKT_PUBLIC_SUBKEY;
k = k->next)
{
if (k->pkt->pkttype == PKT_USER_ID
&& !k->pkt->pkt.user_id->attrib_data)
{
if (!uidnode)
{
uidnode = k;
uidnode->pkt->pkt.user_id->flags.primary = 1;
continue;
}
else
{
if (cmp_user_ids (k->pkt->pkt.user_id,
uidnode->pkt->pkt.user_id) > 0)
{
uidnode->pkt->pkt.user_id->flags.primary = 0;
uidnode = k;
uidnode->pkt->pkt.user_id->flags.primary = 1;
}
else
{
/* just to be safe: */
k->pkt->pkt.user_id->flags.primary = 0;
}
}
}
}
}
}
/* Convert a buffer to a signature. Useful for 0x19 embedded sigs.
* Caller must free the signature when they are done. */
static PKT_signature *
buf_to_sig (const byte * buf, size_t len)
{
PKT_signature *sig = xmalloc_clear (sizeof (PKT_signature));
IOBUF iobuf = iobuf_temp_with_content (buf, len);
int save_mode = set_packet_list_mode (0);
if (parse_signature (iobuf, PKT_SIGNATURE, len, sig) != 0)
{
free_seckey_enc (sig);
sig = NULL;
}
set_packet_list_mode (save_mode);
iobuf_close (iobuf);
return sig;
}
/* Use the self-signed data to fill in various fields in subkeys.
*
* KEYBLOCK is the whole keyblock. SUBNODE is the subkey to fill in.
*
* Sets the following fields on the subkey:
*
* main_keyid
* flags.valid if the subkey has a valid self-sig binding
* flags.revoked
* flags.backsig
* pubkey_usage
* has_expired
* expired_date
*
* On this subkey's most revent valid self-signed packet, the
* following field is set:
*
* flags.chosen_selfsig
*/
static void
merge_selfsigs_subkey (ctrl_t ctrl, kbnode_t keyblock, kbnode_t subnode)
{
PKT_public_key *mainpk = NULL, *subpk = NULL;
PKT_signature *sig;
KBNODE k;
u32 mainkid[2];
u32 sigdate = 0;
KBNODE signode;
u32 curtime = make_timestamp ();
unsigned int key_usage = 0;
u32 keytimestamp = 0;
u32 key_expire = 0;
const byte *p;
if (subnode->pkt->pkttype != PKT_PUBLIC_SUBKEY)
BUG ();
mainpk = keyblock->pkt->pkt.public_key;
if (mainpk->version < 4)
return;/* (actually this should never happen) */
keyid_from_pk (mainpk, mainkid);
subpk = subnode->pkt->pkt.public_key;
keytimestamp = subpk->timestamp;
subpk->flags.valid = 0;
subpk->flags.exact = 0;
subpk->main_keyid[0] = mainpk->main_keyid[0];
subpk->main_keyid[1] = mainpk->main_keyid[1];
/* Find the latest key binding self-signature. */
signode = NULL;
sigdate = 0; /* Helper to find the latest signature. */
for (k = subnode->next; k && k->pkt->pkttype != PKT_PUBLIC_SUBKEY;
k = k->next)
{
if (k->pkt->pkttype == PKT_SIGNATURE)
{
sig = k->pkt->pkt.signature;
if (sig->keyid[0] == mainkid[0] && sig->keyid[1] == mainkid[1])
{
if (check_key_signature (ctrl, keyblock, k, NULL))
; /* Signature did not verify. */
else if (IS_SUBKEY_REV (sig))
{
/* Note that this means that the date on a
* revocation sig does not matter - even if the
* binding sig is dated after the revocation sig,
* the subkey is still marked as revoked. This
* seems ok, as it is just as easy to make new
* subkeys rather than re-sign old ones as the
* problem is in the distribution. Plus, PGP (7)
* does this the same way. */
subpk->flags.revoked = 1;
sig_to_revoke_info (sig, &subpk->revoked);
/* Although we could stop now, we continue to
* figure out other information like the old expiration
* time. */
}
else if (IS_SUBKEY_SIG (sig) && sig->timestamp >= sigdate)
{
if (sig->flags.expired)
; /* Signature has expired - ignore it. */
else
{
sigdate = sig->timestamp;
signode = k;
signode->pkt->pkt.signature->flags.chosen_selfsig = 0;
}
}
}
}
}
/* No valid key binding. */
if (!signode)
return;
sig = signode->pkt->pkt.signature;
sig->flags.chosen_selfsig = 1; /* So we know which selfsig we chose later. */
key_usage = parse_key_usage (sig);
if (!key_usage)
{
/* No key flags at all: get it from the algo. */
key_usage = openpgp_pk_algo_usage (subpk->pubkey_algo);
}
else
{
/* Check that the usage matches the usage as given by the algo. */
int x = openpgp_pk_algo_usage (subpk->pubkey_algo);
if (x) /* Mask it down to the actual allowed usage. */
key_usage &= x;
}
subpk->pubkey_usage = key_usage;
p = parse_sig_subpkt (sig->hashed, SIGSUBPKT_KEY_EXPIRE, NULL);
if (p && buf32_to_u32 (p))
key_expire = keytimestamp + buf32_to_u32 (p);
else
key_expire = 0;
subpk->has_expired = key_expire >= curtime ? 0 : key_expire;
subpk->expiredate = key_expire;
/* Algo doesn't exist. */
if (openpgp_pk_test_algo (subpk->pubkey_algo))
return;
subpk->flags.valid = 1;
/* Find the most recent 0x19 embedded signature on our self-sig. */
if (!subpk->flags.backsig)
{
int seq = 0;
size_t n;
PKT_signature *backsig = NULL;
sigdate = 0;
/* We do this while() since there may be other embedded
* signatures in the future. We only want 0x19 here. */
while ((p = enum_sig_subpkt (sig->hashed,
SIGSUBPKT_SIGNATURE, &n, &seq, NULL)))
if (n > 3
&& ((p[0] == 3 && p[2] == 0x19) || (p[0] == 4 && p[1] == 0x19)))
{
PKT_signature *tempsig = buf_to_sig (p, n);
if (tempsig)
{
if (tempsig->timestamp > sigdate)
{
if (backsig)
free_seckey_enc (backsig);
backsig = tempsig;
sigdate = backsig->timestamp;
}
else
free_seckey_enc (tempsig);
}
}
seq = 0;
/* It is safe to have this in the unhashed area since the 0x19
* is located on the selfsig for convenience, not security. */
while ((p = enum_sig_subpkt (sig->unhashed, SIGSUBPKT_SIGNATURE,
&n, &seq, NULL)))
if (n > 3
&& ((p[0] == 3 && p[2] == 0x19) || (p[0] == 4 && p[1] == 0x19)))
{
PKT_signature *tempsig = buf_to_sig (p, n);
if (tempsig)
{
if (tempsig->timestamp > sigdate)
{
if (backsig)
free_seckey_enc (backsig);
backsig = tempsig;
sigdate = backsig->timestamp;
}
else
free_seckey_enc (tempsig);
}
}
if (backsig)
{
/* At this point, backsig contains the most recent 0x19 sig.
* Let's see if it is good. */
/* 2==valid, 1==invalid, 0==didn't check */
if (check_backsig (mainpk, subpk, backsig) == 0)
subpk->flags.backsig = 2;
else
subpk->flags.backsig = 1;
free_seckey_enc (backsig);
}
}
}
/* Merge information from the self-signatures with the public key,
* subkeys and user ids to make using them more easy.
*
* See documentation for merge_selfsigs_main, merge_selfsigs_subkey
* and fixup_uidnode for exactly which fields are updated. */
static void
merge_selfsigs (ctrl_t ctrl, kbnode_t keyblock)
{
KBNODE k;
int revoked;
struct revoke_info rinfo;
PKT_public_key *main_pk;
prefitem_t *prefs;
unsigned int mdc_feature;
unsigned int aead_feature;
if (keyblock->pkt->pkttype != PKT_PUBLIC_KEY)
{
if (keyblock->pkt->pkttype == PKT_SECRET_KEY)
{
log_error ("expected public key but found secret key "
"- must stop\n");
/* We better exit here because a public key is expected at
* other places too. FIXME: Figure this out earlier and
* don't get to here at all */
g10_exit (1);
}
BUG ();
}
merge_selfsigs_main (ctrl, keyblock, &revoked, &rinfo);
/* Now merge in the data from each of the subkeys. */
for (k = keyblock; k; k = k->next)
{
if (k->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
merge_selfsigs_subkey (ctrl, keyblock, k);
}
}
main_pk = keyblock->pkt->pkt.public_key;
if (revoked || main_pk->has_expired || !main_pk->flags.valid)
{
/* If the primary key is revoked, expired, or invalid we
* better set the appropriate flags on that key and all
* subkeys. */
for (k = keyblock; k; k = k->next)
{
if (k->pkt->pkttype == PKT_PUBLIC_KEY
|| k->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
PKT_public_key *pk = k->pkt->pkt.public_key;
if (!main_pk->flags.valid)
pk->flags.valid = 0;
if (revoked && !pk->flags.revoked)
{
pk->flags.revoked = revoked;
memcpy (&pk->revoked, &rinfo, sizeof (rinfo));
}
if (main_pk->has_expired)
- pk->has_expired = main_pk->has_expired;
+ {
+ pk->has_expired = main_pk->has_expired;
+ if (!pk->expiredate || pk->expiredate > main_pk->expiredate)
+ pk->expiredate = main_pk->expiredate;
+ }
}
}
return;
}
/* Set the preference list of all keys to those of the primary real
* user ID. Note: we use these preferences when we don't know by
* which user ID the key has been selected.
* fixme: we should keep atoms of commonly used preferences or
* use reference counting to optimize the preference lists storage.
* FIXME: it might be better to use the intersection of
* all preferences.
* Do a similar thing for the MDC feature flag. */
prefs = NULL;
mdc_feature = aead_feature = 0;
for (k = keyblock; k && k->pkt->pkttype != PKT_PUBLIC_SUBKEY; k = k->next)
{
if (k->pkt->pkttype == PKT_USER_ID
&& !k->pkt->pkt.user_id->attrib_data
&& k->pkt->pkt.user_id->flags.primary)
{
prefs = k->pkt->pkt.user_id->prefs;
mdc_feature = k->pkt->pkt.user_id->flags.mdc;
aead_feature = k->pkt->pkt.user_id->flags.aead;
break;
}
}
for (k = keyblock; k; k = k->next)
{
if (k->pkt->pkttype == PKT_PUBLIC_KEY
|| k->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
PKT_public_key *pk = k->pkt->pkt.public_key;
if (pk->prefs)
xfree (pk->prefs);
pk->prefs = copy_prefs (prefs);
pk->flags.mdc = mdc_feature;
pk->flags.aead = aead_feature;
}
}
}
/* See whether the key satisfies any additional requirements specified
* in CTX. If so, return the node of an appropriate key or subkey.
* Otherwise, return NULL if there was no appropriate key.
*
* Note that we do not return a reference, i.e. the result must not be
* freed using 'release_kbnode'.
*
* In case the primary key is not required, select a suitable subkey.
* We need the primary key if PUBKEY_USAGE_CERT is set in REQ_USAGE or
* we are in PGP7 mode and PUBKEY_USAGE_SIG is set in
* REQ_USAGE.
*
* If any of PUBKEY_USAGE_SIG, PUBKEY_USAGE_ENC and PUBKEY_USAGE_CERT
* are set in REQ_USAGE, we filter by the key's function. Concretely,
* if PUBKEY_USAGE_SIG and PUBKEY_USAGE_CERT are set, then we only
* return a key if it is (at least) either a signing or a
* certification key.
*
* If REQ_USAGE is set, then we reject any keys that are not good
* (i.e., valid, not revoked, not expired, etc.). This allows the
* getkey functions to be used for plain key listings.
*
* Sets the matched key's user id field (pk->user_id) to the user id
* that matched the low-level search criteria or NULL.
*
* If R_FLAGS is not NULL set certain flags for more detailed error
* reporting. Used flags are:
*
* - LOOKUP_ALL_SUBKEYS_EXPIRED :: All Subkeys are expired or have
* been revoked.
* - LOOKUP_NOT_SELECTED :: No suitable key found
*
* This function needs to handle several different cases:
*
* 1. No requested usage and no primary key requested
* Examples for this case are that we have a keyID to be used
* for decryption or verification.
* 2. No usage but primary key requested
* This is the case for all functions which work on an
* entire keyblock, e.g. for editing or listing
* 3. Usage and primary key requested
* FIXME
* 4. Usage but no primary key requested
* FIXME
*
*/
static kbnode_t
finish_lookup (kbnode_t keyblock, unsigned int req_usage, int want_exact,
int want_secret, unsigned int *r_flags)
{
kbnode_t k;
/* If WANT_EXACT is set, the key or subkey that actually matched the
low-level search criteria. */
kbnode_t foundk = NULL;
/* The user id (if any) that matched the low-level search criteria. */
PKT_user_id *foundu = NULL;
u32 latest_date;
kbnode_t latest_key;
PKT_public_key *pk;
int req_prim;
u32 curtime = make_timestamp ();
if (r_flags)
*r_flags = 0;
#define USAGE_MASK (PUBKEY_USAGE_SIG|PUBKEY_USAGE_ENC|PUBKEY_USAGE_CERT)
req_usage &= USAGE_MASK;
/* Request the primary if we're certifying another key, and also if
* signing data while --pgp7 is on since pgp 7 do
* not understand signatures made by a signing subkey. PGP 8 does. */
req_prim = ((req_usage & PUBKEY_USAGE_CERT)
|| (PGP7 && (req_usage & PUBKEY_USAGE_SIG)));
log_assert (keyblock->pkt->pkttype == PKT_PUBLIC_KEY);
/* For an exact match mark the primary or subkey that matched the
low-level search criteria. */
if (want_exact)
{
for (k = keyblock; k; k = k->next)
{
if ((k->flag & 1))
{
log_assert (k->pkt->pkttype == PKT_PUBLIC_KEY
|| k->pkt->pkttype == PKT_PUBLIC_SUBKEY);
foundk = k;
pk = k->pkt->pkt.public_key;
pk->flags.exact = 1;
break;
}
}
}
/* Get the user id that matched that low-level search criteria. */
for (k = keyblock; k; k = k->next)
{
if ((k->flag & 2))
{
log_assert (k->pkt->pkttype == PKT_USER_ID);
foundu = k->pkt->pkt.user_id;
break;
}
}
if (DBG_LOOKUP)
log_debug ("finish_lookup: checking key %08lX (%s)(req_usage=%x)\n",
(ulong) keyid_from_pk (keyblock->pkt->pkt.public_key, NULL),
foundk ? "one" : "all", req_usage);
if (!req_usage)
{
latest_key = foundk ? foundk : keyblock;
goto found;
}
latest_date = 0;
latest_key = NULL;
/* Set LATEST_KEY to the latest (the one with the most recent
* timestamp) good (valid, not revoked, not expired, etc.) subkey.
*
* Don't bother if we are only looking for a primary key or we need
* an exact match and the exact match is not a subkey. */
if (req_prim || (foundk && foundk->pkt->pkttype != PKT_PUBLIC_SUBKEY))
;
else
{
kbnode_t nextk;
int n_subkeys = 0;
int n_revoked_or_expired = 0;
/* Either start a loop or check just this one subkey. */
for (k = foundk ? foundk : keyblock; k; k = nextk)
{
if (foundk)
{
/* If FOUNDK is not NULL, then only consider that exact
key, i.e., don't iterate. */
nextk = NULL;
}
else
nextk = k->next;
if (k->pkt->pkttype != PKT_PUBLIC_SUBKEY)
continue;
pk = k->pkt->pkt.public_key;
if (DBG_LOOKUP)
log_debug ("\tchecking subkey %08lX\n",
(ulong) keyid_from_pk (pk, NULL));
if (!pk->flags.valid)
{
if (DBG_LOOKUP)
log_debug ("\tsubkey not valid\n");
continue;
}
if (!((pk->pubkey_usage & USAGE_MASK) & req_usage))
{
if (DBG_LOOKUP)
log_debug ("\tusage does not match: want=%x have=%x\n",
req_usage, pk->pubkey_usage);
continue;
}
n_subkeys++;
if (pk->flags.revoked)
{
if (DBG_LOOKUP)
log_debug ("\tsubkey has been revoked\n");
n_revoked_or_expired++;
continue;
}
if (pk->has_expired)
{
if (DBG_LOOKUP)
log_debug ("\tsubkey has expired\n");
n_revoked_or_expired++;
continue;
}
if (pk->timestamp > curtime && !opt.ignore_valid_from)
{
if (DBG_LOOKUP)
log_debug ("\tsubkey not yet valid\n");
continue;
}
if (want_secret && agent_probe_secret_key (NULL, pk))
{
if (DBG_LOOKUP)
log_debug ("\tno secret key\n");
continue;
}
if (DBG_LOOKUP)
log_debug ("\tsubkey might be fine\n");
/* In case a key has a timestamp of 0 set, we make sure
that it is used. A better change would be to compare
">=" but that might also change the selected keys and
is as such a more intrusive change. */
if (pk->timestamp > latest_date || (!pk->timestamp && !latest_date))
{
latest_date = pk->timestamp;
latest_key = k;
}
}
if (n_subkeys == n_revoked_or_expired && r_flags)
*r_flags |= LOOKUP_ALL_SUBKEYS_EXPIRED;
}
/* Check if the primary key is ok (valid, not revoke, not expire,
* matches requested usage) if:
*
* - we didn't find an appropriate subkey and we're not doing an
* exact search,
*
* - we're doing an exact match and the exact match was the
* primary key, or,
*
* - we're just considering the primary key. */
if ((!latest_key && !want_exact) || foundk == keyblock || req_prim)
{
if (DBG_LOOKUP && !foundk && !req_prim)
log_debug ("\tno suitable subkeys found - trying primary\n");
pk = keyblock->pkt->pkt.public_key;
if (!pk->flags.valid)
{
if (DBG_LOOKUP)
log_debug ("\tprimary key not valid\n");
}
else if (!((pk->pubkey_usage & USAGE_MASK) & req_usage))
{
if (DBG_LOOKUP)
log_debug ("\tprimary key usage does not match: "
"want=%x have=%x\n", req_usage, pk->pubkey_usage);
}
else if (pk->flags.revoked)
{
if (DBG_LOOKUP)
log_debug ("\tprimary key has been revoked\n");
}
else if (pk->has_expired)
{
if (DBG_LOOKUP)
log_debug ("\tprimary key has expired\n");
}
else /* Okay. */
{
if (DBG_LOOKUP)
log_debug ("\tprimary key may be used\n");
latest_key = keyblock;
}
}
if (!latest_key)
{
if (DBG_LOOKUP)
log_debug ("\tno suitable key found - giving up\n");
if (r_flags)
*r_flags |= LOOKUP_NOT_SELECTED;
return NULL; /* Not found. */
}
found:
if (DBG_LOOKUP)
log_debug ("\tusing key %08lX\n",
(ulong) keyid_from_pk (latest_key->pkt->pkt.public_key, NULL));
if (latest_key)
{
pk = latest_key->pkt->pkt.public_key;
free_user_id (pk->user_id);
pk->user_id = scopy_user_id (foundu);
}
if (latest_key != keyblock && opt.verbose)
{
char *tempkeystr =
xstrdup (keystr_from_pk (latest_key->pkt->pkt.public_key));
log_info (_("using subkey %s instead of primary key %s\n"),
tempkeystr, keystr_from_pk (keyblock->pkt->pkt.public_key));
xfree (tempkeystr);
}
- cache_user_id (keyblock);
+ cache_put_keyblock (keyblock);
return latest_key ? latest_key : keyblock; /* Found. */
}
/* Print a KEY_CONSIDERED status line. */
static void
print_status_key_considered (kbnode_t keyblock, unsigned int flags)
{
char hexfpr[2*MAX_FINGERPRINT_LEN + 1];
kbnode_t node;
char flagbuf[20];
if (!is_status_enabled ())
return;
for (node=keyblock; node; node = node->next)
if (node->pkt->pkttype == PKT_PUBLIC_KEY
|| node->pkt->pkttype == PKT_SECRET_KEY)
break;
if (!node)
{
log_error ("%s: keyblock w/o primary key\n", __func__);
return;
}
hexfingerprint (node->pkt->pkt.public_key, hexfpr, sizeof hexfpr);
snprintf (flagbuf, sizeof flagbuf, " %u", flags);
write_status_strings (STATUS_KEY_CONSIDERED, hexfpr, flagbuf, NULL);
}
/* A high-level function to lookup keys.
*
* This function builds on top of the low-level keydb API. It first
* searches the database using the description stored in CTX->ITEMS,
* then it filters the results using CTX and, finally, if WANT_SECRET
* is set, it ignores any keys for which no secret key is available.
*
* Unlike the low-level search functions, this function also merges
* all of the self-signed data into the keys, subkeys and user id
* packets (see the merge_selfsigs for details).
*
* On success the key's keyblock is stored at *RET_KEYBLOCK, and the
* specific subkey is stored at *RET_FOUND_KEY. Note that we do not
* return a reference in *RET_FOUND_KEY, i.e. the result must not be
* freed using 'release_kbnode', and it is only valid until
* *RET_KEYBLOCK is deallocated. Therefore, if RET_FOUND_KEY is not
* NULL, then RET_KEYBLOCK must not be NULL. */
static int
lookup (ctrl_t ctrl, getkey_ctx_t ctx, int want_secret,
kbnode_t *ret_keyblock, kbnode_t *ret_found_key)
{
int rc;
int no_suitable_key = 0;
KBNODE keyblock = NULL;
KBNODE found_key = NULL;
unsigned int infoflags;
log_assert (ret_found_key == NULL || ret_keyblock != NULL);
if (ret_keyblock)
*ret_keyblock = NULL;
for (;;)
{
rc = keydb_search (ctx->kr_handle, ctx->items, ctx->nitems, NULL);
if (rc)
break;
/* If we are iterating over the entire database, then we need to
* change from KEYDB_SEARCH_MODE_FIRST, which does an implicit
* reset, to KEYDB_SEARCH_MODE_NEXT, which gets the next record. */
if (ctx->nitems && ctx->items->mode == KEYDB_SEARCH_MODE_FIRST)
ctx->items->mode = KEYDB_SEARCH_MODE_NEXT;
rc = keydb_get_keyblock (ctx->kr_handle, &keyblock);
if (rc)
{
log_error ("keydb_get_keyblock failed: %s\n", gpg_strerror (rc));
goto skip;
}
if (want_secret)
{
rc = agent_probe_any_secret_key (NULL, keyblock);
if (gpg_err_code(rc) == GPG_ERR_NO_SECKEY)
goto skip; /* No secret key available. */
if (rc)
goto found; /* Unexpected error. */
}
/* Warning: node flag bits 0 and 1 should be preserved by
* merge_selfsigs. */
merge_selfsigs (ctrl, keyblock);
found_key = finish_lookup (keyblock, ctx->req_usage, ctx->exact,
want_secret, &infoflags);
print_status_key_considered (keyblock, infoflags);
if (found_key)
{
no_suitable_key = 0;
goto found;
}
else
{
no_suitable_key = 1;
}
skip:
/* Release resources and continue search. */
release_kbnode (keyblock);
keyblock = NULL;
/* The keyblock cache ignores the current "file position".
* Thus, if we request the next result and the cache matches
* (and it will since it is what we just looked for), we'll get
* the same entry back! We can avoid this infinite loop by
* disabling the cache. */
keydb_disable_caching (ctx->kr_handle);
}
found:
if (rc && gpg_err_code (rc) != GPG_ERR_NOT_FOUND)
log_error ("keydb_search failed: %s\n", gpg_strerror (rc));
if (!rc)
{
if (ret_keyblock)
{
*ret_keyblock = keyblock; /* Return the keyblock. */
keyblock = NULL;
}
}
else if (gpg_err_code (rc) == GPG_ERR_NOT_FOUND && no_suitable_key)
rc = want_secret? GPG_ERR_UNUSABLE_SECKEY : GPG_ERR_UNUSABLE_PUBKEY;
else if (gpg_err_code (rc) == GPG_ERR_NOT_FOUND)
rc = want_secret? GPG_ERR_NO_SECKEY : GPG_ERR_NO_PUBKEY;
release_kbnode (keyblock);
if (ret_found_key)
{
if (! rc)
*ret_found_key = found_key;
else
*ret_found_key = NULL;
}
return rc;
}
gpg_error_t
get_seckey_default_or_card (ctrl_t ctrl, PKT_public_key *pk,
const byte *fpr_card, size_t fpr_len)
{
gpg_error_t err;
strlist_t namelist = NULL;
const char *def_secret_key = parse_def_secret_key (ctrl);
if (def_secret_key)
add_to_strlist (&namelist, def_secret_key);
else if (fpr_card)
return get_pubkey_byfprint (ctrl, pk, NULL, fpr_card, fpr_len);
if (!fpr_card
|| (def_secret_key && def_secret_key[strlen (def_secret_key)-1] == '!'))
err = key_byname (ctrl, NULL, namelist, pk, 1, 0, NULL, NULL);
else
{ /* Default key is specified and card key is also available. */
kbnode_t k, keyblock = NULL;
err = key_byname (ctrl, NULL, namelist, pk, 1, 0, &keyblock, NULL);
if (!err)
for (k = keyblock; k; k = k->next)
{
PKT_public_key *pk_candidate;
char fpr[MAX_FINGERPRINT_LEN];
if (k->pkt->pkttype != PKT_PUBLIC_KEY
&&k->pkt->pkttype != PKT_PUBLIC_SUBKEY)
continue;
pk_candidate = k->pkt->pkt.public_key;
if (!pk_candidate->flags.valid)
continue;
if (!((pk_candidate->pubkey_usage & USAGE_MASK) & pk->req_usage))
continue;
fingerprint_from_pk (pk_candidate, fpr, NULL);
if (!memcmp (fpr_card, fpr, fpr_len))
{
release_public_key_parts (pk);
copy_public_key (pk, pk_candidate);
break;
}
}
release_kbnode (keyblock);
}
free_strlist (namelist);
return err;
}
/*********************************************
*********** User ID printing helpers *******
*********************************************/
/* Return a string with a printable representation of the user_id.
* this string must be freed by xfree. If R_NOUID is not NULL it is
* set to true if a user id was not found; otherwise to false. */
static char *
-get_user_id_string (ctrl_t ctrl, u32 * keyid, int mode, size_t *r_len,
- int *r_nouid)
+get_user_id_string (ctrl_t ctrl, u32 * keyid, int mode)
{
- user_id_db_t r;
- keyid_list_t a;
- int pass = 0;
+ char *name;
+ unsigned int namelen;
char *p;
- if (r_nouid)
- *r_nouid = 0;
+ log_assert (mode != 2);
- /* Try it two times; second pass reads from the database. */
- do
+ name = cache_get_uid_bykid (keyid, &namelen);
+ if (!name)
{
- for (r = user_id_db; r; r = r->next)
- {
- for (a = r->keyids; a; a = a->next)
- {
- if (a->keyid[0] == keyid[0] && a->keyid[1] == keyid[1])
- {
- if (mode == 2)
- {
- /* An empty string as user id is possible. Make
- sure that the malloc allocates one byte and
- does not bail out. */
- p = xmalloc (r->len? r->len : 1);
- memcpy (p, r->name, r->len);
- if (r_len)
- *r_len = r->len;
- }
- else
- {
- if (mode)
- p = xasprintf ("%08lX%08lX %.*s",
- (ulong) keyid[0], (ulong) keyid[1],
- r->len, r->name);
- else
- p = xasprintf ("%s %.*s", keystr (keyid),
- r->len, r->name);
- if (r_len)
- *r_len = strlen (p);
- }
-
- return p;
- }
- }
- }
+ /* Get it so that the cache will be filled. */
+ if (!get_pubkey (ctrl, NULL, keyid))
+ name = cache_get_uid_bykid (keyid, &namelen);
}
- while (++pass < 2 && !get_pubkey (ctrl, NULL, keyid));
- if (mode == 2)
- p = xstrdup (user_id_not_found_utf8 ());
- else if (mode)
- p = xasprintf ("%08lX%08lX [?]", (ulong) keyid[0], (ulong) keyid[1]);
+ if (name)
+ {
+ if (mode)
+ p = xasprintf ("%08lX%08lX %.*s",
+ (ulong) keyid[0], (ulong) keyid[1], namelen, name);
+ else
+ p = xasprintf ("%s %.*s", keystr (keyid), namelen, name);
+
+ xfree (name);
+ }
else
- p = xasprintf ("%s [?]", keystr (keyid));
+ {
+ if (mode)
+ p = xasprintf ("%08lX%08lX [?]", (ulong) keyid[0], (ulong) keyid[1]);
+ else
+ p = xasprintf ("%s [?]", keystr (keyid));
+ }
- if (r_nouid)
- *r_nouid = 1;
- if (r_len)
- *r_len = strlen (p);
return p;
}
char *
get_user_id_string_native (ctrl_t ctrl, u32 * keyid)
{
- char *p = get_user_id_string (ctrl, keyid, 0, NULL, NULL);
+ char *p = get_user_id_string (ctrl, keyid, 0);
char *p2 = utf8_to_native (p, strlen (p), 0);
xfree (p);
return p2;
}
char *
get_long_user_id_string (ctrl_t ctrl, u32 * keyid)
{
- return get_user_id_string (ctrl, keyid, 1, NULL, NULL);
+ return get_user_id_string (ctrl, keyid, 1);
}
/* Please try to use get_user_byfpr instead of this one. */
char *
get_user_id (ctrl_t ctrl, u32 *keyid, size_t *rn, int *r_nouid)
{
- return get_user_id_string (ctrl, keyid, 2, rn, r_nouid);
+ char *name;
+ unsigned int namelen;
+
+ if (r_nouid)
+ *r_nouid = 0;
+
+ name = cache_get_uid_bykid (keyid, &namelen);
+ if (!name)
+ {
+ /* Get it so that the cache will be filled. */
+ if (!get_pubkey (ctrl, NULL, keyid))
+ name = cache_get_uid_bykid (keyid, &namelen);
+ }
+
+ if (!name)
+ {
+ name = xstrdup (user_id_not_found_utf8 ());
+ namelen = strlen (name);
+ if (r_nouid)
+ *r_nouid = 1;
+ }
+
+ if (rn && name)
+ *rn = namelen;
+ return name;
}
/* Please try to use get_user_id_byfpr_native instead of this one. */
char *
get_user_id_native (ctrl_t ctrl, u32 *keyid)
{
size_t rn;
char *p = get_user_id (ctrl, keyid, &rn, NULL);
char *p2 = utf8_to_native (p, rn, 0);
xfree (p);
return p2;
}
/* Return the user id for a key designated by its fingerprint, FPR,
which must be MAX_FINGERPRINT_LEN bytes in size. Note: the
returned string, which must be freed using xfree, may not be NUL
terminated. To determine the length of the string, you must use
*RN. */
-char *
-get_user_id_byfpr (ctrl_t ctrl, const byte *fpr, size_t *rn)
+static char *
+get_user_id_byfpr (ctrl_t ctrl, const byte *fpr, size_t fprlen, size_t *rn)
{
- user_id_db_t r;
- char *p;
- int pass = 0;
+ char *name;
- /* Try it two times; second pass reads from the database. */
- do
+ name = cache_get_uid_byfpr (fpr, fprlen, rn);
+ if (!name)
{
- for (r = user_id_db; r; r = r->next)
- {
- keyid_list_t a;
- for (a = r->keyids; a; a = a->next)
- {
- if (!memcmp (a->fpr, fpr, MAX_FINGERPRINT_LEN))
- {
- /* An empty string as user id is possible. Make
- sure that the malloc allocates one byte and does
- not bail out. */
- p = xmalloc (r->len? r->len : 1);
- memcpy (p, r->name, r->len);
- *rn = r->len;
- return p;
- }
- }
- }
+ /* Get it so that the cache will be filled. */
+ if (!get_pubkey_byfprint (ctrl, NULL, NULL, fpr, fprlen))
+ name = cache_get_uid_byfpr (fpr, fprlen, rn);
}
- while (++pass < 2
- && !get_pubkey_byfprint (ctrl, NULL, NULL, fpr, MAX_FINGERPRINT_LEN));
- p = xstrdup (user_id_not_found_utf8 ());
- *rn = strlen (p);
- return p;
+
+ if (!name)
+ {
+ name = xstrdup (user_id_not_found_utf8 ());
+ *rn = strlen (name);
+ }
+
+ return name;
}
/* Like get_user_id_byfpr, but convert the string to the native
encoding. The returned string needs to be freed. Unlike
get_user_id_byfpr, the returned string is NUL terminated. */
char *
-get_user_id_byfpr_native (ctrl_t ctrl, const byte *fpr)
+get_user_id_byfpr_native (ctrl_t ctrl, const byte *fpr, size_t fprlen)
{
size_t rn;
- char *p = get_user_id_byfpr (ctrl, fpr, &rn);
+ char *p = get_user_id_byfpr (ctrl, fpr, fprlen, &rn);
char *p2 = utf8_to_native (p, rn, 0);
xfree (p);
return p2;
}
/* Return the database handle used by this context. The context still
owns the handle. */
KEYDB_HANDLE
get_ctx_handle (GETKEY_CTX ctx)
{
return ctx->kr_handle;
}
static void
free_akl (struct akl *akl)
{
if (! akl)
return;
if (akl->spec)
free_keyserver_spec (akl->spec);
xfree (akl);
}
void
release_akl (void)
{
while (opt.auto_key_locate)
{
struct akl *akl2 = opt.auto_key_locate;
opt.auto_key_locate = opt.auto_key_locate->next;
free_akl (akl2);
}
}
/* Returns false on error. */
int
parse_auto_key_locate (const char *options_arg)
{
char *tok;
char *options, *options_buf;
options = options_buf = xstrdup (options_arg);
while ((tok = optsep (&options)))
{
struct akl *akl, *check, *last = NULL;
int dupe = 0;
if (tok[0] == '\0')
continue;
akl = xmalloc_clear (sizeof (*akl));
if (ascii_strcasecmp (tok, "clear") == 0)
{
xfree (akl);
free_akl (opt.auto_key_locate);
opt.auto_key_locate = NULL;
continue;
}
else if (ascii_strcasecmp (tok, "nodefault") == 0)
akl->type = AKL_NODEFAULT;
else if (ascii_strcasecmp (tok, "local") == 0)
akl->type = AKL_LOCAL;
else if (ascii_strcasecmp (tok, "ldap") == 0)
akl->type = AKL_LDAP;
else if (ascii_strcasecmp (tok, "keyserver") == 0)
akl->type = AKL_KEYSERVER;
else if (ascii_strcasecmp (tok, "cert") == 0)
akl->type = AKL_CERT;
else if (ascii_strcasecmp (tok, "pka") == 0)
akl->type = AKL_PKA;
else if (ascii_strcasecmp (tok, "dane") == 0)
akl->type = AKL_DANE;
else if (ascii_strcasecmp (tok, "wkd") == 0)
akl->type = AKL_WKD;
else if ((akl->spec = parse_keyserver_uri (tok, 1)))
akl->type = AKL_SPEC;
else
{
free_akl (akl);
xfree (options_buf);
return 0;
}
/* We must maintain the order the user gave us */
for (check = opt.auto_key_locate; check;
last = check, check = check->next)
{
/* Check for duplicates */
if (check->type == akl->type
&& (akl->type != AKL_SPEC
|| (akl->type == AKL_SPEC
&& strcmp (check->spec->uri, akl->spec->uri) == 0)))
{
dupe = 1;
free_akl (akl);
break;
}
}
if (!dupe)
{
if (last)
last->next = akl;
else
opt.auto_key_locate = akl;
}
}
xfree (options_buf);
return 1;
}
/* The list of key origins. */
static struct {
const char *name;
int origin;
} key_origin_list[] =
{
{ "self", KEYORG_SELF },
{ "file", KEYORG_FILE },
{ "url", KEYORG_URL },
{ "wkd", KEYORG_WKD },
{ "dane", KEYORG_DANE },
{ "ks-pref", KEYORG_KS_PREF },
{ "ks", KEYORG_KS },
{ "unknown", KEYORG_UNKNOWN }
};
/* Parse the argument for --key-origin. Return false on error. */
int
parse_key_origin (char *string)
{
int i;
char *comma;
comma = strchr (string, ',');
if (comma)
*comma = 0;
if (!ascii_strcasecmp (string, "help"))
{
log_info (_("valid values for option '%s':\n"), "--key-origin");
for (i=0; i < DIM (key_origin_list); i++)
log_info (" %s\n", key_origin_list[i].name);
g10_exit (1);
}
for (i=0; i < DIM (key_origin_list); i++)
if (!ascii_strcasecmp (string, key_origin_list[i].name))
{
opt.key_origin = key_origin_list[i].origin;
xfree (opt.key_origin_url);
opt.key_origin_url = NULL;
if (comma && comma[1])
{
opt.key_origin_url = xstrdup (comma+1);
trim_spaces (opt.key_origin_url);
}
return 1;
}
if (comma)
*comma = ',';
return 0;
}
/* Return a string or "?" for the key ORIGIN. */
const char *
key_origin_string (int origin)
{
int i;
for (i=0; i < DIM (key_origin_list); i++)
if (key_origin_list[i].origin == origin)
return key_origin_list[i].name;
return "?";
}
/* Returns true if a secret key is available for the public key with
key id KEYID; returns false if not. This function ignores legacy
keys. Note: this is just a fast check and does not tell us whether
the secret key is valid; this check merely indicates whether there
is some secret key with the specified key id. */
int
have_secret_key_with_kid (u32 *keyid)
{
gpg_error_t err;
KEYDB_HANDLE kdbhd;
KEYDB_SEARCH_DESC desc;
kbnode_t keyblock;
kbnode_t node;
int result = 0;
kdbhd = keydb_new ();
if (!kdbhd)
return 0;
memset (&desc, 0, sizeof desc);
desc.mode = KEYDB_SEARCH_MODE_LONG_KID;
desc.u.kid[0] = keyid[0];
desc.u.kid[1] = keyid[1];
while (!result)
{
err = keydb_search (kdbhd, &desc, 1, NULL);
if (err)
break;
err = keydb_get_keyblock (kdbhd, &keyblock);
if (err)
{
log_error (_("error reading keyblock: %s\n"), gpg_strerror (err));
break;
}
for (node = keyblock; node; node = node->next)
{
/* Bit 0 of the flags is set if the search found the key
using that key or subkey. Note: a search will only ever
match a single key or subkey. */
if ((node->flag & 1))
{
log_assert (node->pkt->pkttype == PKT_PUBLIC_KEY
|| node->pkt->pkttype == PKT_PUBLIC_SUBKEY);
if (!agent_probe_secret_key (NULL, node->pkt->pkt.public_key))
result = 1; /* Secret key available. */
else
result = 0;
break;
}
}
release_kbnode (keyblock);
}
keydb_release (kdbhd);
return result;
}
diff --git a/g10/gpg.c b/g10/gpg.c
index 1ab7b0497..0bbe72394 100644
--- a/g10/gpg.c
+++ b/g10/gpg.c
@@ -1,5579 +1,5588 @@
/* gpg.c - The GnuPG utility (main for gpg)
* Copyright (C) 1998-2011 Free Software Foundation, Inc.
* Copyright (C) 1997-2017 Werner Koch
* Copyright (C) 2015-2017 g10 Code GmbH
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#ifdef HAVE_STAT
#include <sys/stat.h> /* for stat() */
#endif
#include <fcntl.h>
#ifdef HAVE_W32_SYSTEM
# ifdef HAVE_WINSOCK2_H
# include <winsock2.h>
# endif
# include <windows.h>
#endif
#define INCLUDED_BY_MAIN_MODULE 1
#include "gpg.h"
#include <assuan.h>
#include "../common/iobuf.h"
#include "../common/util.h"
#include "packet.h"
#include "../common/membuf.h"
#include "main.h"
#include "options.h"
#include "keydb.h"
#include "trustdb.h"
#include "filter.h"
#include "../common/ttyio.h"
#include "../common/i18n.h"
#include "../common/sysutils.h"
#include "../common/status.h"
#include "keyserver-internal.h"
#include "exec.h"
#include "../common/gc-opt-flags.h"
#include "../common/asshelp.h"
#include "call-dirmngr.h"
#include "tofu.h"
+#include "objcache.h"
#include "../common/init.h"
#include "../common/mbox-util.h"
#include "../common/shareddefs.h"
#include "../common/compliance.h"
#if defined(HAVE_DOSISH_SYSTEM) || defined(__CYGWIN__)
#define MY_O_BINARY O_BINARY
#ifndef S_IRGRP
# define S_IRGRP 0
# define S_IWGRP 0
#endif
#else
#define MY_O_BINARY 0
#endif
#ifdef __MINGW32__
int _dowildcard = -1;
#endif
enum cmd_and_opt_values
{
aNull = 0,
oArmor = 'a',
aDetachedSign = 'b',
aSym = 'c',
aDecrypt = 'd',
aEncr = 'e',
oRecipientFile = 'f',
oHiddenRecipientFile = 'F',
oInteractive = 'i',
aListKeys = 'k',
oDryRun = 'n',
oOutput = 'o',
oQuiet = 'q',
oRecipient = 'r',
oHiddenRecipient = 'R',
aSign = 's',
oTextmodeShort= 't',
oLocalUser = 'u',
oVerbose = 'v',
oCompress = 'z',
oSetNotation = 'N',
aListSecretKeys = 'K',
oBatch = 500,
oMaxOutput,
oInputSizeHint,
oChunkSize,
oSigNotation,
oCertNotation,
oShowNotation,
oNoShowNotation,
oKnownNotation,
aEncrFiles,
aEncrSym,
aDecryptFiles,
aClearsign,
aStore,
aQuickKeygen,
aFullKeygen,
aKeygen,
aSignEncr,
aSignEncrSym,
aSignSym,
aSignKey,
aLSignKey,
aQuickSignKey,
aQuickLSignKey,
aQuickAddUid,
aQuickAddKey,
aQuickRevUid,
aQuickSetExpire,
aQuickSetPrimaryUid,
aListConfig,
aListGcryptConfig,
aGPGConfList,
aGPGConfTest,
aListPackets,
aEditKey,
aDeleteKeys,
aDeleteSecretKeys,
aDeleteSecretAndPublicKeys,
aImport,
aFastImport,
aVerify,
aVerifyFiles,
aListSigs,
aSendKeys,
aRecvKeys,
aLocateKeys,
+ aLocateExtKeys,
aSearchKeys,
aRefreshKeys,
aFetchKeys,
aShowKeys,
aExport,
aExportSecret,
aExportSecretSub,
aExportSshKey,
aCheckKeys,
aGenRevoke,
aDesigRevoke,
aPrimegen,
aPrintMD,
aPrintMDs,
aCheckTrustDB,
aUpdateTrustDB,
aFixTrustDB,
aListTrustDB,
aListTrustPath,
aExportOwnerTrust,
aImportOwnerTrust,
aDeArmor,
aEnArmor,
aGenRandom,
aRebuildKeydbCaches,
aCardStatus,
aCardEdit,
aChangePIN,
aPasswd,
aServer,
aTOFUPolicy,
oMimemode,
oTextmode,
oNoTextmode,
oExpert,
oNoExpert,
oDefSigExpire,
oAskSigExpire,
oNoAskSigExpire,
oDefCertExpire,
oAskCertExpire,
oNoAskCertExpire,
oDefCertLevel,
oMinCertLevel,
oAskCertLevel,
oNoAskCertLevel,
oFingerprint,
oWithFingerprint,
oWithSubkeyFingerprint,
oWithICAOSpelling,
oWithKeygrip,
oWithKeyScreening,
oWithSecret,
oWithWKDHash,
oWithColons,
oWithKeyData,
oWithKeyOrigin,
oWithTofuInfo,
oWithSigList,
oWithSigCheck,
oAnswerYes,
oAnswerNo,
oKeyring,
oPrimaryKeyring,
oSecretKeyring,
oShowKeyring,
oDefaultKey,
oDefRecipient,
oDefRecipientSelf,
oNoDefRecipient,
oTrySecretKey,
oOptions,
oDebug,
oDebugLevel,
oDebugAll,
oDebugIOLBF,
oDebugSetIobufSize,
oDebugAllowLargeChunks,
oStatusFD,
oStatusFile,
oAttributeFD,
oAttributeFile,
oEmitVersion,
oNoEmitVersion,
oCompletesNeeded,
oMarginalsNeeded,
oMaxCertDepth,
oLoadExtension,
oCompliance,
oGnuPG,
oRFC2440,
oRFC4880,
oRFC4880bis,
oOpenPGP,
oPGP7,
oPGP8,
oDE_VS,
oRFC2440Text,
oNoRFC2440Text,
oCipherAlgo,
oAEADAlgo,
oDigestAlgo,
oCertDigestAlgo,
oCompressAlgo,
oCompressLevel,
oBZ2CompressLevel,
oBZ2DecompressLowmem,
oPassphrase,
oPassphraseFD,
oPassphraseFile,
oPassphraseRepeat,
oPinentryMode,
oCommandFD,
oCommandFile,
oQuickRandom,
oNoVerbose,
oTrustDBName,
oNoSecmemWarn,
oRequireSecmem,
oNoRequireSecmem,
oNoPermissionWarn,
oNoArmor,
oNoDefKeyring,
oNoKeyring,
oNoGreeting,
oNoTTY,
oNoOptions,
oNoBatch,
oHomedir,
oSkipVerify,
oSkipHiddenRecipients,
oNoSkipHiddenRecipients,
oAlwaysTrust,
oTrustModel,
oForceOwnertrust,
oSetFilename,
oForYourEyesOnly,
oNoForYourEyesOnly,
oSetPolicyURL,
oSigPolicyURL,
oCertPolicyURL,
oShowPolicyURL,
oNoShowPolicyURL,
oSigKeyserverURL,
oUseEmbeddedFilename,
oNoUseEmbeddedFilename,
oComment,
oDefaultComment,
oNoComments,
oThrowKeyids,
oNoThrowKeyids,
oShowPhotos,
oNoShowPhotos,
oPhotoViewer,
oForceAEAD,
oS2KMode,
oS2KDigest,
oS2KCipher,
oS2KCount,
oDisplayCharset,
oNotDashEscaped,
oEscapeFrom,
oNoEscapeFrom,
oLockOnce,
oLockMultiple,
oLockNever,
oKeyServer,
oKeyServerOptions,
oImportOptions,
oImportFilter,
oExportOptions,
oExportFilter,
oListOptions,
oVerifyOptions,
oTempDir,
oExecPath,
oEncryptTo,
oHiddenEncryptTo,
oNoEncryptTo,
oEncryptToDefaultKey,
oLoggerFD,
oLoggerFile,
oUtf8Strings,
oNoUtf8Strings,
oDisableCipherAlgo,
oDisablePubkeyAlgo,
oAllowNonSelfsignedUID,
oNoAllowNonSelfsignedUID,
oAllowFreeformUID,
oNoAllowFreeformUID,
oAllowSecretKeyImport,
oEnableSpecialFilenames,
oNoLiteral,
oSetFilesize,
oHonorHttpProxy,
oFastListMode,
oListOnly,
oIgnoreTimeConflict,
oIgnoreValidFrom,
oIgnoreCrcError,
oIgnoreMDCError,
oShowSessionKey,
oOverrideSessionKey,
oOverrideSessionKeyFD,
oNoRandomSeedFile,
oAutoKeyRetrieve,
oNoAutoKeyRetrieve,
oUseAgent,
oNoUseAgent,
oGpgAgentInfo,
oMergeOnly,
oTryAllSecrets,
oTrustedKey,
oNoExpensiveTrustChecks,
oFixedListMode,
oLegacyListMode,
oNoSigCache,
oAutoCheckTrustDB,
oNoAutoCheckTrustDB,
oPreservePermissions,
oDefaultPreferenceList,
oDefaultKeyserverURL,
oPersonalCipherPreferences,
oPersonalAEADPreferences,
oPersonalDigestPreferences,
oPersonalCompressPreferences,
oAgentProgram,
oDirmngrProgram,
oDisableDirmngr,
oDisplay,
oTTYname,
oTTYtype,
oLCctype,
oLCmessages,
oXauthority,
oGroup,
oUnGroup,
oNoGroups,
oStrict,
oNoStrict,
oMangleDosFilenames,
oNoMangleDosFilenames,
oEnableProgressFilter,
oMultifile,
oKeyidFormat,
oExitOnStatusWriteError,
oLimitCardInsertTries,
oReaderPort,
octapiDriver,
opcscDriver,
oDisableCCID,
oRequireCrossCert,
oNoRequireCrossCert,
oAutoKeyLocate,
oNoAutoKeyLocate,
oEnableLargeRSA,
oDisableLargeRSA,
oEnableDSA2,
oDisableDSA2,
oAllowWeakDigestAlgos,
oFakedSystemTime,
oNoAutostart,
oPrintPKARecords,
oPrintDANERecords,
oTOFUDefaultPolicy,
oTOFUDBFormat,
oDefaultNewKeyAlgo,
oWeakDigest,
oUnwrap,
oOnlySignTextIDs,
oDisableSignerUID,
oSender,
oKeyOrigin,
oRequestOrigin,
oNoSymkeyCache,
oNoop
};
static ARGPARSE_OPTS opts[] = {
ARGPARSE_group (300, N_("@Commands:\n ")),
ARGPARSE_c (aSign, "sign", N_("make a signature")),
ARGPARSE_c (aClearsign, "clear-sign", N_("make a clear text signature")),
ARGPARSE_c (aClearsign, "clearsign", "@"),
ARGPARSE_c (aDetachedSign, "detach-sign", N_("make a detached signature")),
ARGPARSE_c (aEncr, "encrypt", N_("encrypt data")),
ARGPARSE_c (aEncrFiles, "encrypt-files", "@"),
ARGPARSE_c (aSym, "symmetric", N_("encryption only with symmetric cipher")),
ARGPARSE_c (aStore, "store", "@"),
ARGPARSE_c (aDecrypt, "decrypt", N_("decrypt data (default)")),
ARGPARSE_c (aDecryptFiles, "decrypt-files", "@"),
ARGPARSE_c (aVerify, "verify" , N_("verify a signature")),
ARGPARSE_c (aVerifyFiles, "verify-files" , "@" ),
ARGPARSE_c (aListKeys, "list-keys", N_("list keys")),
ARGPARSE_c (aListKeys, "list-public-keys", "@" ),
ARGPARSE_c (aListSigs, "list-signatures", N_("list keys and signatures")),
ARGPARSE_c (aListSigs, "list-sigs", "@"),
ARGPARSE_c (aCheckKeys, "check-signatures",
N_("list and check key signatures")),
ARGPARSE_c (aCheckKeys, "check-sigs", "@"),
ARGPARSE_c (oFingerprint, "fingerprint", N_("list keys and fingerprints")),
ARGPARSE_c (aListSecretKeys, "list-secret-keys", N_("list secret keys")),
ARGPARSE_c (aKeygen, "generate-key",
N_("generate a new key pair")),
ARGPARSE_c (aKeygen, "gen-key", "@"),
ARGPARSE_c (aQuickKeygen, "quick-generate-key" ,
N_("quickly generate a new key pair")),
ARGPARSE_c (aQuickKeygen, "quick-gen-key", "@"),
ARGPARSE_c (aQuickAddUid, "quick-add-uid",
N_("quickly add a new user-id")),
ARGPARSE_c (aQuickAddUid, "quick-adduid", "@"),
ARGPARSE_c (aQuickAddKey, "quick-add-key", "@"),
ARGPARSE_c (aQuickAddKey, "quick-addkey", "@"),
ARGPARSE_c (aQuickRevUid, "quick-revoke-uid",
N_("quickly revoke a user-id")),
ARGPARSE_c (aQuickRevUid, "quick-revuid", "@"),
ARGPARSE_c (aQuickSetExpire, "quick-set-expire",
N_("quickly set a new expiration date")),
ARGPARSE_c (aQuickSetPrimaryUid, "quick-set-primary-uid", "@"),
ARGPARSE_c (aFullKeygen, "full-generate-key" ,
N_("full featured key pair generation")),
ARGPARSE_c (aFullKeygen, "full-gen-key", "@"),
ARGPARSE_c (aGenRevoke, "generate-revocation",
N_("generate a revocation certificate")),
ARGPARSE_c (aGenRevoke, "gen-revoke", "@"),
ARGPARSE_c (aDeleteKeys,"delete-keys",
N_("remove keys from the public keyring")),
ARGPARSE_c (aDeleteSecretKeys, "delete-secret-keys",
N_("remove keys from the secret keyring")),
ARGPARSE_c (aQuickSignKey, "quick-sign-key" ,
N_("quickly sign a key")),
ARGPARSE_c (aQuickLSignKey, "quick-lsign-key",
N_("quickly sign a key locally")),
ARGPARSE_c (aSignKey, "sign-key" ,N_("sign a key")),
ARGPARSE_c (aLSignKey, "lsign-key" ,N_("sign a key locally")),
ARGPARSE_c (aEditKey, "edit-key" ,N_("sign or edit a key")),
ARGPARSE_c (aEditKey, "key-edit" ,"@"),
ARGPARSE_c (aPasswd, "change-passphrase", N_("change a passphrase")),
ARGPARSE_c (aPasswd, "passwd", "@"),
ARGPARSE_c (aDesigRevoke, "generate-designated-revocation", "@"),
ARGPARSE_c (aDesigRevoke, "desig-revoke","@" ),
ARGPARSE_c (aExport, "export" , N_("export keys") ),
ARGPARSE_c (aSendKeys, "send-keys" , N_("export keys to a keyserver") ),
ARGPARSE_c (aRecvKeys, "receive-keys" , N_("import keys from a keyserver") ),
ARGPARSE_c (aRecvKeys, "recv-keys" , "@"),
ARGPARSE_c (aSearchKeys, "search-keys" ,
N_("search for keys on a keyserver") ),
ARGPARSE_c (aRefreshKeys, "refresh-keys",
N_("update all keys from a keyserver")),
ARGPARSE_c (aLocateKeys, "locate-keys", "@"),
+ ARGPARSE_c (aLocateExtKeys, "locate-external-keys", "@"),
ARGPARSE_c (aFetchKeys, "fetch-keys" , "@" ),
ARGPARSE_c (aShowKeys, "show-keys" , "@" ),
ARGPARSE_c (aExportSecret, "export-secret-keys" , "@" ),
ARGPARSE_c (aExportSecretSub, "export-secret-subkeys" , "@" ),
ARGPARSE_c (aExportSshKey, "export-ssh-key", "@" ),
ARGPARSE_c (aImport, "import", N_("import/merge keys")),
ARGPARSE_c (aFastImport, "fast-import", "@"),
#ifdef ENABLE_CARD_SUPPORT
ARGPARSE_c (aCardStatus, "card-status", N_("print the card status")),
ARGPARSE_c (aCardEdit, "edit-card", N_("change data on a card")),
ARGPARSE_c (aCardEdit, "card-edit", "@"),
ARGPARSE_c (aChangePIN, "change-pin", N_("change a card's PIN")),
#endif
ARGPARSE_c (aListConfig, "list-config", "@"),
ARGPARSE_c (aListGcryptConfig, "list-gcrypt-config", "@"),
ARGPARSE_c (aGPGConfList, "gpgconf-list", "@" ),
ARGPARSE_c (aGPGConfTest, "gpgconf-test", "@" ),
ARGPARSE_c (aListPackets, "list-packets","@"),
#ifndef NO_TRUST_MODELS
ARGPARSE_c (aExportOwnerTrust, "export-ownertrust", "@"),
ARGPARSE_c (aImportOwnerTrust, "import-ownertrust", "@"),
ARGPARSE_c (aUpdateTrustDB,"update-trustdb",
N_("update the trust database")),
ARGPARSE_c (aCheckTrustDB, "check-trustdb", "@"),
ARGPARSE_c (aFixTrustDB, "fix-trustdb", "@"),
#endif
ARGPARSE_c (aDeArmor, "dearmor", "@"),
ARGPARSE_c (aDeArmor, "dearmour", "@"),
ARGPARSE_c (aEnArmor, "enarmor", "@"),
ARGPARSE_c (aEnArmor, "enarmour", "@"),
ARGPARSE_c (aPrintMD, "print-md", N_("print message digests")),
ARGPARSE_c (aPrimegen, "gen-prime", "@" ),
ARGPARSE_c (aGenRandom,"gen-random", "@" ),
ARGPARSE_c (aServer, "server", N_("run in server mode")),
ARGPARSE_c (aTOFUPolicy, "tofu-policy",
N_("|VALUE|set the TOFU policy for a key")),
ARGPARSE_group (301, N_("@\nOptions:\n ")),
ARGPARSE_s_n (oArmor, "armor", N_("create ascii armored output")),
ARGPARSE_s_n (oArmor, "armour", "@"),
ARGPARSE_s_s (oRecipient, "recipient", N_("|USER-ID|encrypt for USER-ID")),
ARGPARSE_s_s (oHiddenRecipient, "hidden-recipient", "@"),
ARGPARSE_s_s (oRecipientFile, "recipient-file", "@"),
ARGPARSE_s_s (oHiddenRecipientFile, "hidden-recipient-file", "@"),
ARGPARSE_s_s (oRecipient, "remote-user", "@"), /* (old option name) */
ARGPARSE_s_s (oDefRecipient, "default-recipient", "@"),
ARGPARSE_s_n (oDefRecipientSelf, "default-recipient-self", "@"),
ARGPARSE_s_n (oNoDefRecipient, "no-default-recipient", "@"),
ARGPARSE_s_s (oTempDir, "temp-directory", "@"),
ARGPARSE_s_s (oExecPath, "exec-path", "@"),
ARGPARSE_s_s (oEncryptTo, "encrypt-to", "@"),
ARGPARSE_s_n (oNoEncryptTo, "no-encrypt-to", "@"),
ARGPARSE_s_s (oHiddenEncryptTo, "hidden-encrypt-to", "@"),
ARGPARSE_s_n (oEncryptToDefaultKey, "encrypt-to-default-key", "@"),
ARGPARSE_s_s (oLocalUser, "local-user",
N_("|USER-ID|use USER-ID to sign or decrypt")),
ARGPARSE_s_s (oSender, "sender", "@"),
ARGPARSE_s_s (oTrySecretKey, "try-secret-key", "@"),
ARGPARSE_s_i (oCompress, NULL,
N_("|N|set compress level to N (0 disables)")),
ARGPARSE_s_i (oCompressLevel, "compress-level", "@"),
ARGPARSE_s_i (oBZ2CompressLevel, "bzip2-compress-level", "@"),
ARGPARSE_s_n (oBZ2DecompressLowmem, "bzip2-decompress-lowmem", "@"),
ARGPARSE_s_n (oMimemode, "mimemode", "@"),
ARGPARSE_s_n (oTextmodeShort, NULL, "@"),
ARGPARSE_s_n (oTextmode, "textmode", N_("use canonical text mode")),
ARGPARSE_s_n (oNoTextmode, "no-textmode", "@"),
ARGPARSE_s_n (oExpert, "expert", "@"),
ARGPARSE_s_n (oNoExpert, "no-expert", "@"),
ARGPARSE_s_s (oDefSigExpire, "default-sig-expire", "@"),
ARGPARSE_s_n (oAskSigExpire, "ask-sig-expire", "@"),
ARGPARSE_s_n (oNoAskSigExpire, "no-ask-sig-expire", "@"),
ARGPARSE_s_s (oDefCertExpire, "default-cert-expire", "@"),
ARGPARSE_s_n (oAskCertExpire, "ask-cert-expire", "@"),
ARGPARSE_s_n (oNoAskCertExpire, "no-ask-cert-expire", "@"),
ARGPARSE_s_i (oDefCertLevel, "default-cert-level", "@"),
ARGPARSE_s_i (oMinCertLevel, "min-cert-level", "@"),
ARGPARSE_s_n (oAskCertLevel, "ask-cert-level", "@"),
ARGPARSE_s_n (oNoAskCertLevel, "no-ask-cert-level", "@"),
ARGPARSE_s_s (oOutput, "output", N_("|FILE|write output to FILE")),
ARGPARSE_p_u (oMaxOutput, "max-output", "@"),
ARGPARSE_s_s (oInputSizeHint, "input-size-hint", "@"),
ARGPARSE_s_i (oChunkSize, "chunk-size", "@"),
ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")),
ARGPARSE_s_n (oQuiet, "quiet", "@"),
ARGPARSE_s_n (oNoTTY, "no-tty", "@"),
ARGPARSE_s_n (oForceAEAD, "force-aead", "@"),
ARGPARSE_s_n (oDisableSignerUID, "disable-signer-uid", "@"),
ARGPARSE_s_n (oDryRun, "dry-run", N_("do not make any changes")),
ARGPARSE_s_n (oInteractive, "interactive", N_("prompt before overwriting")),
ARGPARSE_s_n (oBatch, "batch", "@"),
ARGPARSE_s_n (oAnswerYes, "yes", "@"),
ARGPARSE_s_n (oAnswerNo, "no", "@"),
ARGPARSE_s_s (oKeyring, "keyring", "@"),
ARGPARSE_s_s (oPrimaryKeyring, "primary-keyring", "@"),
ARGPARSE_s_s (oSecretKeyring, "secret-keyring", "@"),
ARGPARSE_s_n (oShowKeyring, "show-keyring", "@"),
ARGPARSE_s_s (oDefaultKey, "default-key", "@"),
ARGPARSE_s_s (oKeyServer, "keyserver", "@"),
ARGPARSE_s_s (oKeyServerOptions, "keyserver-options", "@"),
ARGPARSE_s_s (oKeyOrigin, "key-origin", "@"),
ARGPARSE_s_s (oImportOptions, "import-options", "@"),
ARGPARSE_s_s (oImportFilter, "import-filter", "@"),
ARGPARSE_s_s (oExportOptions, "export-options", "@"),
ARGPARSE_s_s (oExportFilter, "export-filter", "@"),
ARGPARSE_s_s (oListOptions, "list-options", "@"),
ARGPARSE_s_s (oVerifyOptions, "verify-options", "@"),
ARGPARSE_s_s (oDisplayCharset, "display-charset", "@"),
ARGPARSE_s_s (oDisplayCharset, "charset", "@"),
ARGPARSE_s_s (oOptions, "options", "@"),
ARGPARSE_s_s (oDebug, "debug", "@"),
ARGPARSE_s_s (oDebugLevel, "debug-level", "@"),
ARGPARSE_s_n (oDebugAll, "debug-all", "@"),
ARGPARSE_s_n (oDebugIOLBF, "debug-iolbf", "@"),
ARGPARSE_s_u (oDebugSetIobufSize, "debug-set-iobuf-size", "@"),
ARGPARSE_s_u (oDebugAllowLargeChunks, "debug-allow-large-chunks", "@"),
ARGPARSE_s_i (oStatusFD, "status-fd", "@"),
ARGPARSE_s_s (oStatusFile, "status-file", "@"),
ARGPARSE_s_i (oAttributeFD, "attribute-fd", "@"),
ARGPARSE_s_s (oAttributeFile, "attribute-file", "@"),
ARGPARSE_s_i (oCompletesNeeded, "completes-needed", "@"),
ARGPARSE_s_i (oMarginalsNeeded, "marginals-needed", "@"),
ARGPARSE_s_i (oMaxCertDepth, "max-cert-depth", "@" ),
ARGPARSE_s_s (oTrustedKey, "trusted-key", "@"),
ARGPARSE_s_s (oLoadExtension, "load-extension", "@"), /* Dummy. */
ARGPARSE_s_s (oCompliance, "compliance", "@"),
ARGPARSE_s_n (oGnuPG, "gnupg", "@"),
ARGPARSE_s_n (oGnuPG, "no-pgp2", "@"),
ARGPARSE_s_n (oGnuPG, "no-pgp6", "@"),
ARGPARSE_s_n (oGnuPG, "no-pgp7", "@"),
ARGPARSE_s_n (oGnuPG, "no-pgp8", "@"),
ARGPARSE_s_n (oRFC2440, "rfc2440", "@"),
ARGPARSE_s_n (oRFC4880, "rfc4880", "@"),
ARGPARSE_s_n (oRFC4880bis, "rfc4880bis", "@"),
ARGPARSE_s_n (oOpenPGP, "openpgp", N_("use strict OpenPGP behavior")),
ARGPARSE_s_n (oPGP7, "pgp6", "@"),
ARGPARSE_s_n (oPGP7, "pgp7", "@"),
ARGPARSE_s_n (oPGP8, "pgp8", "@"),
ARGPARSE_s_n (oRFC2440Text, "rfc2440-text", "@"),
ARGPARSE_s_n (oNoRFC2440Text, "no-rfc2440-text", "@"),
ARGPARSE_s_i (oS2KMode, "s2k-mode", "@"),
ARGPARSE_s_s (oS2KDigest, "s2k-digest-algo", "@"),
ARGPARSE_s_s (oS2KCipher, "s2k-cipher-algo", "@"),
ARGPARSE_s_i (oS2KCount, "s2k-count", "@"),
ARGPARSE_s_s (oCipherAlgo, "cipher-algo", "@"),
ARGPARSE_s_s (oAEADAlgo, "aead-algo", "@"),
ARGPARSE_s_s (oDigestAlgo, "digest-algo", "@"),
ARGPARSE_s_s (oCertDigestAlgo, "cert-digest-algo", "@"),
ARGPARSE_s_s (oCompressAlgo,"compress-algo", "@"),
ARGPARSE_s_s (oCompressAlgo, "compression-algo", "@"), /* Alias */
ARGPARSE_s_n (oThrowKeyids, "throw-keyids", "@"),
ARGPARSE_s_n (oNoThrowKeyids, "no-throw-keyids", "@"),
ARGPARSE_s_n (oShowPhotos, "show-photos", "@"),
ARGPARSE_s_n (oNoShowPhotos, "no-show-photos", "@"),
ARGPARSE_s_s (oPhotoViewer, "photo-viewer", "@"),
ARGPARSE_s_s (oSetNotation, "set-notation", "@"),
ARGPARSE_s_s (oSigNotation, "sig-notation", "@"),
ARGPARSE_s_s (oCertNotation, "cert-notation", "@"),
ARGPARSE_s_s (oKnownNotation, "known-notation", "@"),
ARGPARSE_group (302, N_(
"@\n(See the man page for a complete listing of all commands and options)\n"
)),
ARGPARSE_group (303, N_("@\nExamples:\n\n"
" -se -r Bob [file] sign and encrypt for user Bob\n"
" --clear-sign [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")),
/* More hidden commands and options. */
ARGPARSE_c (aPrintMDs, "print-mds", "@"), /* old */
#ifndef NO_TRUST_MODELS
ARGPARSE_c (aListTrustDB, "list-trustdb", "@"),
#endif
/* Not yet used:
ARGPARSE_c (aListTrustPath, "list-trust-path", "@"), */
ARGPARSE_c (aDeleteSecretAndPublicKeys,
"delete-secret-and-public-keys", "@"),
ARGPARSE_c (aRebuildKeydbCaches, "rebuild-keydb-caches", "@"),
ARGPARSE_s_s (oPassphrase, "passphrase", "@"),
ARGPARSE_s_i (oPassphraseFD, "passphrase-fd", "@"),
ARGPARSE_s_s (oPassphraseFile, "passphrase-file", "@"),
ARGPARSE_s_i (oPassphraseRepeat,"passphrase-repeat", "@"),
ARGPARSE_s_s (oPinentryMode, "pinentry-mode", "@"),
ARGPARSE_s_s (oRequestOrigin, "request-origin", "@"),
ARGPARSE_s_i (oCommandFD, "command-fd", "@"),
ARGPARSE_s_s (oCommandFile, "command-file", "@"),
ARGPARSE_s_n (oQuickRandom, "debug-quick-random", "@"),
ARGPARSE_s_n (oNoVerbose, "no-verbose", "@"),
#ifndef NO_TRUST_MODELS
ARGPARSE_s_s (oTrustDBName, "trustdb-name", "@"),
ARGPARSE_s_n (oAutoCheckTrustDB, "auto-check-trustdb", "@"),
ARGPARSE_s_n (oNoAutoCheckTrustDB, "no-auto-check-trustdb", "@"),
ARGPARSE_s_s (oForceOwnertrust, "force-ownertrust", "@"),
#endif
ARGPARSE_s_n (oNoSecmemWarn, "no-secmem-warning", "@"),
ARGPARSE_s_n (oRequireSecmem, "require-secmem", "@"),
ARGPARSE_s_n (oNoRequireSecmem, "no-require-secmem", "@"),
ARGPARSE_s_n (oNoPermissionWarn, "no-permission-warning", "@"),
ARGPARSE_s_n (oNoArmor, "no-armor", "@"),
ARGPARSE_s_n (oNoArmor, "no-armour", "@"),
ARGPARSE_s_n (oNoDefKeyring, "no-default-keyring", "@"),
ARGPARSE_s_n (oNoKeyring, "no-keyring", "@"),
ARGPARSE_s_n (oNoGreeting, "no-greeting", "@"),
ARGPARSE_s_n (oNoOptions, "no-options", "@"),
ARGPARSE_s_s (oHomedir, "homedir", "@"),
ARGPARSE_s_n (oNoBatch, "no-batch", "@"),
ARGPARSE_s_n (oWithColons, "with-colons", "@"),
ARGPARSE_s_n (oWithTofuInfo,"with-tofu-info", "@"),
ARGPARSE_s_n (oWithKeyData,"with-key-data", "@"),
ARGPARSE_s_n (oWithSigList,"with-sig-list", "@"),
ARGPARSE_s_n (oWithSigCheck,"with-sig-check", "@"),
ARGPARSE_c (aListKeys, "list-key", "@"), /* alias */
ARGPARSE_c (aListSigs, "list-sig", "@"), /* alias */
ARGPARSE_c (aCheckKeys, "check-sig", "@"), /* alias */
ARGPARSE_c (aShowKeys, "show-key", "@"), /* alias */
ARGPARSE_s_n (oSkipVerify, "skip-verify", "@"),
ARGPARSE_s_n (oSkipHiddenRecipients, "skip-hidden-recipients", "@"),
ARGPARSE_s_n (oNoSkipHiddenRecipients, "no-skip-hidden-recipients", "@"),
ARGPARSE_s_i (oDefCertLevel, "default-cert-check-level", "@"), /* old */
#ifndef NO_TRUST_MODELS
ARGPARSE_s_n (oAlwaysTrust, "always-trust", "@"),
#endif
ARGPARSE_s_s (oTrustModel, "trust-model", "@"),
ARGPARSE_s_s (oTOFUDefaultPolicy, "tofu-default-policy", "@"),
ARGPARSE_s_s (oSetFilename, "set-filename", "@"),
ARGPARSE_s_n (oForYourEyesOnly, "for-your-eyes-only", "@"),
ARGPARSE_s_n (oNoForYourEyesOnly, "no-for-your-eyes-only", "@"),
ARGPARSE_s_s (oSetPolicyURL, "set-policy-url", "@"),
ARGPARSE_s_s (oSigPolicyURL, "sig-policy-url", "@"),
ARGPARSE_s_s (oCertPolicyURL, "cert-policy-url", "@"),
ARGPARSE_s_n (oShowPolicyURL, "show-policy-url", "@"),
ARGPARSE_s_n (oNoShowPolicyURL, "no-show-policy-url", "@"),
ARGPARSE_s_s (oSigKeyserverURL, "sig-keyserver-url", "@"),
ARGPARSE_s_n (oShowNotation, "show-notation", "@"),
ARGPARSE_s_n (oNoShowNotation, "no-show-notation", "@"),
ARGPARSE_s_s (oComment, "comment", "@"),
ARGPARSE_s_n (oDefaultComment, "default-comment", "@"),
ARGPARSE_s_n (oNoComments, "no-comments", "@"),
ARGPARSE_s_n (oEmitVersion, "emit-version", "@"),
ARGPARSE_s_n (oNoEmitVersion, "no-emit-version", "@"),
ARGPARSE_s_n (oNoEmitVersion, "no-version", "@"), /* alias */
ARGPARSE_s_n (oNotDashEscaped, "not-dash-escaped", "@"),
ARGPARSE_s_n (oEscapeFrom, "escape-from-lines", "@"),
ARGPARSE_s_n (oNoEscapeFrom, "no-escape-from-lines", "@"),
ARGPARSE_s_n (oLockOnce, "lock-once", "@"),
ARGPARSE_s_n (oLockMultiple, "lock-multiple", "@"),
ARGPARSE_s_n (oLockNever, "lock-never", "@"),
ARGPARSE_s_i (oLoggerFD, "logger-fd", "@"),
ARGPARSE_s_s (oLoggerFile, "log-file", "@"),
ARGPARSE_s_s (oLoggerFile, "logger-file", "@"), /* 1.4 compatibility. */
ARGPARSE_s_n (oUseEmbeddedFilename, "use-embedded-filename", "@"),
ARGPARSE_s_n (oNoUseEmbeddedFilename, "no-use-embedded-filename", "@"),
ARGPARSE_s_n (oUtf8Strings, "utf8-strings", "@"),
ARGPARSE_s_n (oNoUtf8Strings, "no-utf8-strings", "@"),
ARGPARSE_s_n (oWithFingerprint, "with-fingerprint", "@"),
ARGPARSE_s_n (oWithSubkeyFingerprint, "with-subkey-fingerprint", "@"),
ARGPARSE_s_n (oWithSubkeyFingerprint, "with-subkey-fingerprints", "@"),
ARGPARSE_s_n (oWithICAOSpelling, "with-icao-spelling", "@"),
ARGPARSE_s_n (oWithKeygrip, "with-keygrip", "@"),
ARGPARSE_s_n (oWithKeyScreening,"with-key-screening", "@"),
ARGPARSE_s_n (oWithSecret, "with-secret", "@"),
ARGPARSE_s_n (oWithWKDHash, "with-wkd-hash", "@"),
ARGPARSE_s_n (oWithKeyOrigin, "with-key-origin", "@"),
ARGPARSE_s_s (oDisableCipherAlgo, "disable-cipher-algo", "@"),
ARGPARSE_s_s (oDisablePubkeyAlgo, "disable-pubkey-algo", "@"),
ARGPARSE_s_n (oAllowNonSelfsignedUID, "allow-non-selfsigned-uid", "@"),
ARGPARSE_s_n (oNoAllowNonSelfsignedUID, "no-allow-non-selfsigned-uid", "@"),
ARGPARSE_s_n (oAllowFreeformUID, "allow-freeform-uid", "@"),
ARGPARSE_s_n (oNoAllowFreeformUID, "no-allow-freeform-uid", "@"),
ARGPARSE_s_n (oNoLiteral, "no-literal", "@"),
ARGPARSE_p_u (oSetFilesize, "set-filesize", "@"),
ARGPARSE_s_n (oFastListMode, "fast-list-mode", "@"),
ARGPARSE_s_n (oFixedListMode, "fixed-list-mode", "@"),
ARGPARSE_s_n (oLegacyListMode, "legacy-list-mode", "@"),
ARGPARSE_s_n (oListOnly, "list-only", "@"),
ARGPARSE_s_n (oPrintPKARecords, "print-pka-records", "@"),
ARGPARSE_s_n (oPrintDANERecords, "print-dane-records", "@"),
ARGPARSE_s_n (oIgnoreTimeConflict, "ignore-time-conflict", "@"),
ARGPARSE_s_n (oIgnoreValidFrom, "ignore-valid-from", "@"),
ARGPARSE_s_n (oIgnoreCrcError, "ignore-crc-error", "@"),
ARGPARSE_s_n (oIgnoreMDCError, "ignore-mdc-error", "@"),
ARGPARSE_s_n (oShowSessionKey, "show-session-key", "@"),
ARGPARSE_s_s (oOverrideSessionKey, "override-session-key", "@"),
ARGPARSE_s_i (oOverrideSessionKeyFD, "override-session-key-fd", "@"),
ARGPARSE_s_n (oNoRandomSeedFile, "no-random-seed-file", "@"),
ARGPARSE_s_n (oAutoKeyRetrieve, "auto-key-retrieve", "@"),
ARGPARSE_s_n (oNoAutoKeyRetrieve, "no-auto-key-retrieve", "@"),
ARGPARSE_s_n (oNoSigCache, "no-sig-cache", "@"),
ARGPARSE_s_n (oMergeOnly, "merge-only", "@" ),
ARGPARSE_s_n (oAllowSecretKeyImport, "allow-secret-key-import", "@"),
ARGPARSE_s_n (oTryAllSecrets, "try-all-secrets", "@"),
ARGPARSE_s_n (oEnableSpecialFilenames, "enable-special-filenames", "@"),
ARGPARSE_s_n (oNoExpensiveTrustChecks, "no-expensive-trust-checks", "@"),
ARGPARSE_s_n (oPreservePermissions, "preserve-permissions", "@"),
ARGPARSE_s_s (oDefaultPreferenceList, "default-preference-list", "@"),
ARGPARSE_s_s (oDefaultKeyserverURL, "default-keyserver-url", "@"),
ARGPARSE_s_s (oPersonalCipherPreferences, "personal-cipher-preferences","@"),
ARGPARSE_s_s (oPersonalAEADPreferences, "personal-aead-preferences","@"),
ARGPARSE_s_s (oPersonalDigestPreferences, "personal-digest-preferences","@"),
ARGPARSE_s_s (oPersonalCompressPreferences,
"personal-compress-preferences", "@"),
ARGPARSE_s_s (oFakedSystemTime, "faked-system-time", "@"),
ARGPARSE_s_s (oWeakDigest, "weak-digest","@"),
ARGPARSE_s_n (oUnwrap, "unwrap", "@"),
ARGPARSE_s_n (oOnlySignTextIDs, "only-sign-text-ids", "@"),
/* Aliases. I constantly mistype these, and assume other people do
as well. */
ARGPARSE_s_s (oPersonalCipherPreferences, "personal-cipher-prefs", "@"),
ARGPARSE_s_s (oPersonalAEADPreferences, "personal-aead-prefs", "@"),
ARGPARSE_s_s (oPersonalDigestPreferences, "personal-digest-prefs", "@"),
ARGPARSE_s_s (oPersonalCompressPreferences, "personal-compress-prefs", "@"),
ARGPARSE_s_s (oAgentProgram, "agent-program", "@"),
ARGPARSE_s_s (oDirmngrProgram, "dirmngr-program", "@"),
ARGPARSE_s_n (oDisableDirmngr, "disable-dirmngr", "@"),
ARGPARSE_s_s (oDisplay, "display", "@"),
ARGPARSE_s_s (oTTYname, "ttyname", "@"),
ARGPARSE_s_s (oTTYtype, "ttytype", "@"),
ARGPARSE_s_s (oLCctype, "lc-ctype", "@"),
ARGPARSE_s_s (oLCmessages, "lc-messages","@"),
ARGPARSE_s_s (oXauthority, "xauthority", "@"),
ARGPARSE_s_s (oGroup, "group", "@"),
ARGPARSE_s_s (oUnGroup, "ungroup", "@"),
ARGPARSE_s_n (oNoGroups, "no-groups", "@"),
ARGPARSE_s_n (oStrict, "strict", "@"),
ARGPARSE_s_n (oNoStrict, "no-strict", "@"),
ARGPARSE_s_n (oMangleDosFilenames, "mangle-dos-filenames", "@"),
ARGPARSE_s_n (oNoMangleDosFilenames, "no-mangle-dos-filenames", "@"),
ARGPARSE_s_n (oEnableProgressFilter, "enable-progress-filter", "@"),
ARGPARSE_s_n (oMultifile, "multifile", "@"),
ARGPARSE_s_s (oKeyidFormat, "keyid-format", "@"),
ARGPARSE_s_n (oExitOnStatusWriteError, "exit-on-status-write-error", "@"),
ARGPARSE_s_i (oLimitCardInsertTries, "limit-card-insert-tries", "@"),
ARGPARSE_s_n (oEnableLargeRSA, "enable-large-rsa", "@"),
ARGPARSE_s_n (oDisableLargeRSA, "disable-large-rsa", "@"),
ARGPARSE_s_n (oEnableDSA2, "enable-dsa2", "@"),
ARGPARSE_s_n (oDisableDSA2, "disable-dsa2", "@"),
ARGPARSE_s_n (oAllowWeakDigestAlgos, "allow-weak-digest-algos", "@"),
ARGPARSE_s_s (oDefaultNewKeyAlgo, "default-new-key-algo", "@"),
/* These two are aliases to help users of the PGP command line
product use gpg with minimal pain. Many commands are common
already as they seem to have borrowed commands from us. Now I'm
returning the favor. */
ARGPARSE_s_s (oLocalUser, "sign-with", "@"),
ARGPARSE_s_s (oRecipient, "user", "@"),
ARGPARSE_s_n (oRequireCrossCert, "require-backsigs", "@"),
ARGPARSE_s_n (oRequireCrossCert, "require-cross-certification", "@"),
ARGPARSE_s_n (oNoRequireCrossCert, "no-require-backsigs", "@"),
ARGPARSE_s_n (oNoRequireCrossCert, "no-require-cross-certification", "@"),
/* New options. Fixme: Should go more to the top. */
ARGPARSE_s_s (oAutoKeyLocate, "auto-key-locate", "@"),
ARGPARSE_s_n (oNoAutoKeyLocate, "no-auto-key-locate", "@"),
ARGPARSE_s_n (oNoAutostart, "no-autostart", "@"),
ARGPARSE_s_n (oNoSymkeyCache, "no-symkey-cache", "@"),
/* Dummy options with warnings. */
ARGPARSE_s_n (oUseAgent, "use-agent", "@"),
ARGPARSE_s_n (oNoUseAgent, "no-use-agent", "@"),
ARGPARSE_s_s (oGpgAgentInfo, "gpg-agent-info", "@"),
ARGPARSE_s_s (oReaderPort, "reader-port", "@"),
ARGPARSE_s_s (octapiDriver, "ctapi-driver", "@"),
ARGPARSE_s_s (opcscDriver, "pcsc-driver", "@"),
ARGPARSE_s_n (oDisableCCID, "disable-ccid", "@"),
ARGPARSE_s_n (oHonorHttpProxy, "honor-http-proxy", "@"),
ARGPARSE_s_s (oTOFUDBFormat, "tofu-db-format", "@"),
/* Dummy options. */
ARGPARSE_s_n (oNoop, "sk-comments", "@"),
ARGPARSE_s_n (oNoop, "no-sk-comments", "@"),
ARGPARSE_s_n (oNoop, "compress-keys", "@"),
ARGPARSE_s_n (oNoop, "compress-sigs", "@"),
ARGPARSE_s_n (oNoop, "force-v3-sigs", "@"),
ARGPARSE_s_n (oNoop, "no-force-v3-sigs", "@"),
ARGPARSE_s_n (oNoop, "force-v4-certs", "@"),
ARGPARSE_s_n (oNoop, "no-force-v4-certs", "@"),
ARGPARSE_s_n (oNoop, "no-mdc-warning", "@"),
ARGPARSE_s_n (oNoop, "force-mdc", "@"),
ARGPARSE_s_n (oNoop, "no-force-mdc", "@"),
ARGPARSE_s_n (oNoop, "disable-mdc", "@"),
ARGPARSE_s_n (oNoop, "no-disable-mdc", "@"),
ARGPARSE_s_n (oNoop, "allow-multisig-verification", "@"),
ARGPARSE_s_n (oNoop, "allow-multiple-messages", "@"),
ARGPARSE_s_n (oNoop, "no-allow-multiple-messages", "@"),
ARGPARSE_end ()
};
/* The list of supported debug flags. */
static struct debug_flags_s debug_flags [] =
{
{ DBG_PACKET_VALUE , "packet" },
{ DBG_MPI_VALUE , "mpi" },
{ DBG_CRYPTO_VALUE , "crypto" },
{ DBG_FILTER_VALUE , "filter" },
{ DBG_IOBUF_VALUE , "iobuf" },
{ DBG_MEMORY_VALUE , "memory" },
{ DBG_CACHE_VALUE , "cache" },
{ DBG_MEMSTAT_VALUE, "memstat" },
{ DBG_TRUST_VALUE , "trust" },
{ DBG_HASHING_VALUE, "hashing" },
{ DBG_IPC_VALUE , "ipc" },
{ DBG_CLOCK_VALUE , "clock" },
{ DBG_LOOKUP_VALUE , "lookup" },
{ DBG_EXTPROG_VALUE, "extprog" },
{ 0, NULL }
};
#ifdef ENABLE_SELINUX_HACKS
#define ALWAYS_ADD_KEYRINGS 1
#else
#define ALWAYS_ADD_KEYRINGS 0
#endif
int g10_errors_seen = 0;
static int utf8_strings = 0;
static int maybe_setuid = 1;
static unsigned int opt_set_iobuf_size;
static unsigned int opt_set_iobuf_size_used;
static char *build_list( const char *text, char letter,
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 print_mds( const char *fname, int algo );
static void add_notation_data( const char *string, int which );
static void add_policy_url( const char *string, int which );
static void add_keyserver_url( const char *string, int which );
static void emergency_cleanup (void);
static void read_sessionkey_from_fd (int fd);
static char *
make_libversion (const char *libname, const char *(*getfnc)(const char*))
{
const char *s;
char *result;
if (maybe_setuid)
{
gcry_control (GCRYCTL_INIT_SECMEM, 0, 0); /* Drop setuid. */
maybe_setuid = 0;
}
s = getfnc (NULL);
result = xmalloc (strlen (libname) + 1 + strlen (s) + 1);
strcpy (stpcpy (stpcpy (result, libname), " "), s);
return result;
}
static int
build_list_pk_test_algo (int algo)
{
/* Show only one "RSA" string. If RSA_E or RSA_S is available RSA
is also available. */
if (algo == PUBKEY_ALGO_RSA_E
|| algo == PUBKEY_ALGO_RSA_S)
return GPG_ERR_DIGEST_ALGO;
return openpgp_pk_test_algo (algo);
}
static const char *
build_list_pk_algo_name (int algo)
{
return openpgp_pk_algo_name (algo);
}
static int
build_list_cipher_test_algo (int algo)
{
return openpgp_cipher_test_algo (algo);
}
static const char *
build_list_cipher_algo_name (int algo)
{
return openpgp_cipher_algo_name (algo);
}
static int
build_list_aead_test_algo (int algo)
{
return openpgp_aead_test_algo (algo);
}
static const char *
build_list_aead_algo_name (int algo)
{
return openpgp_aead_algo_name (algo);
}
static int
build_list_md_test_algo (int algo)
{
/* By default we do not accept MD5 based signatures. To avoid
confusion we do not announce support for it either. */
if (algo == DIGEST_ALGO_MD5)
return GPG_ERR_DIGEST_ALGO;
return openpgp_md_test_algo (algo);
}
static const char *
build_list_md_algo_name (int algo)
{
return openpgp_md_algo_name (algo);
}
static const char *
my_strusage( int level )
{
static char *digests, *pubkeys, *ciphers, *zips, *aeads, *ver_gcry;
const char *p;
switch( level ) {
case 11: p = "@GPG@ (@GNUPG@)";
break;
case 13: p = VERSION; break;
case 17: p = PRINTABLE_OS_NAME; break;
case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break;
case 20:
if (!ver_gcry)
ver_gcry = make_libversion ("libgcrypt", gcry_check_version);
p = ver_gcry;
break;
#ifdef IS_DEVELOPMENT_VERSION
case 25:
p="NOTE: THIS IS A DEVELOPMENT VERSION!";
break;
case 26:
p="It is only intended for test purposes and should NOT be";
break;
case 27:
p="used in a production environment or with production keys!";
break;
#endif
case 1:
case 40: p =
_("Usage: @GPG@ [options] [files] (-h for help)");
break;
case 41: p =
_("Syntax: @GPG@ [options] [files]\n"
"Sign, check, encrypt or decrypt\n"
"Default operation depends on the input data\n");
break;
case 31: p = "\nHome: "; break;
#ifndef __riscos__
case 32: p = gnupg_homedir (); break;
#else /* __riscos__ */
case 32: p = make_filename(gnupg_homedir (), NULL); break;
#endif /* __riscos__ */
case 33: p = _("\nSupported algorithms:\n"); break;
case 34:
if (!pubkeys)
pubkeys = build_list (_("Pubkey: "), 1,
build_list_pk_algo_name,
build_list_pk_test_algo );
p = pubkeys;
break;
case 35:
if( !ciphers )
ciphers = build_list(_("Cipher: "), 'S',
build_list_cipher_algo_name,
build_list_cipher_test_algo );
p = ciphers;
break;
case 36:
if (!aeads)
aeads = build_list ("AEAD: ", 'A',
build_list_aead_algo_name,
build_list_aead_test_algo);
p = aeads;
break;
case 37:
if( !digests )
digests = build_list(_("Hash: "), 'H',
build_list_md_algo_name,
build_list_md_test_algo );
p = digests;
break;
case 38:
if( !zips )
zips = build_list(_("Compression: "),'Z',
compress_algo_to_string,
check_compress_algo);
p = zips;
break;
default: p = NULL;
}
return p;
}
static char *
build_list (const char *text, char letter,
const char * (*mapf)(int), int (*chkf)(int))
{
membuf_t mb;
int indent;
int i, j, len;
int limit;
const char *s;
char *string;
if (maybe_setuid)
gcry_control (GCRYCTL_INIT_SECMEM, 0, 0); /* Drop setuid. */
indent = utf8_charcount (text, -1);
len = 0;
init_membuf (&mb, 512);
limit = (letter == 'A')? 4 : 110;
for (i=0; i <= limit; i++ )
{
if (!chkf (i) && (s = mapf (i)))
{
if (mb.len - len > 60)
{
put_membuf_str (&mb, ",\n");
len = mb.len;
for (j=0; j < indent; j++)
put_membuf_str (&mb, " ");
}
else if (mb.len)
put_membuf_str (&mb, ", ");
else
put_membuf_str (&mb, text);
put_membuf_str (&mb, s);
if (opt.verbose && letter)
{
char num[20];
if (letter == 1)
snprintf (num, sizeof num, " (%d)", i);
else
snprintf (num, sizeof num, " (%c%d)", letter, i);
put_membuf_str (&mb, num);
}
}
}
if (mb.len)
put_membuf_str (&mb, "\n");
put_membuf (&mb, "", 1);
string = get_membuf (&mb, NULL);
return xrealloc (string, strlen (string)+1);
}
static void
wrong_args( const char *text)
{
es_fprintf (es_stderr, _("usage: %s [options] %s\n"), GPG_NAME, text);
log_inc_errorcount ();
g10_exit(2);
}
static char *
make_username( const char *string )
{
char *p;
if( utf8_strings )
p = xstrdup(string);
else
p = native_to_utf8( string );
return p;
}
static void
set_opt_session_env (const char *name, const char *value)
{
gpg_error_t err;
err = session_env_setenv (opt.session_env, name, value);
if (err)
log_fatal ("error setting session environment: %s\n",
gpg_strerror (err));
}
/* Setup the debugging. With a LEVEL of NULL only the active debug
flags are propagated to the subsystems. With LEVEL set, a specific
set of debug flags is set; thus overriding all flags already
set. */
static void
set_debug (const char *level)
{
int numok = (level && digitp (level));
int numlvl = numok? atoi (level) : 0;
if (!level)
;
else if (!strcmp (level, "none") || (numok && numlvl < 1))
opt.debug = 0;
else if (!strcmp (level, "basic") || (numok && numlvl <= 2))
opt.debug = DBG_MEMSTAT_VALUE;
else if (!strcmp (level, "advanced") || (numok && numlvl <= 5))
opt.debug = DBG_MEMSTAT_VALUE|DBG_TRUST_VALUE|DBG_EXTPROG_VALUE;
else if (!strcmp (level, "expert") || (numok && numlvl <= 8))
opt.debug = (DBG_MEMSTAT_VALUE|DBG_TRUST_VALUE|DBG_EXTPROG_VALUE
|DBG_CACHE_VALUE|DBG_LOOKUP|DBG_FILTER_VALUE|DBG_PACKET_VALUE);
else if (!strcmp (level, "guru") || numok)
{
opt.debug = ~0;
/* Unless the "guru" string has been used we don't want to allow
hashing debugging. The rationale is that people tend to
select the highest debug value and would then clutter their
disk with debug files which may reveal confidential data. */
if (numok)
opt.debug &= ~(DBG_HASHING_VALUE);
}
else
{
log_error (_("invalid debug-level '%s' given\n"), level);
g10_exit (2);
}
if ((opt.debug & DBG_MEMORY_VALUE))
memory_debug_mode = 1;
if ((opt.debug & DBG_MEMSTAT_VALUE))
memory_stat_debug_mode = 1;
if (DBG_MPI)
gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 2);
if (DBG_CRYPTO)
gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 1);
if ((opt.debug & DBG_IOBUF_VALUE))
iobuf_debug_mode = 1;
gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose);
if (opt.debug)
parse_debug_flag (NULL, &opt.debug, debug_flags);
if (opt_set_iobuf_size || opt_set_iobuf_size_used)
log_debug ("iobuf buffer size is %uk\n",
iobuf_set_buffer_size (opt_set_iobuf_size));
}
/* We set the screen dimensions for UI purposes. Do not allow screens
smaller than 80x24 for the sake of simplicity. */
static void
set_screen_dimensions(void)
{
#ifndef HAVE_W32_SYSTEM
char *str;
str=getenv("COLUMNS");
if(str)
opt.screen_columns=atoi(str);
str=getenv("LINES");
if(str)
opt.screen_lines=atoi(str);
#endif
if(opt.screen_columns<80 || opt.screen_columns>255)
opt.screen_columns=80;
if(opt.screen_lines<24 || opt.screen_lines>255)
opt.screen_lines=24;
}
/* Helper to open a file FNAME either for reading or writing to be
used with --status-file etc functions. Not generally useful but it
avoids the riscos specific functions and well some Windows people
might like it too. Prints an error message and returns -1 on
error. On success the file descriptor is returned. */
static int
open_info_file (const char *fname, int for_write, int binary)
{
#ifdef __riscos__
return riscos_fdopenfile (fname, for_write);
#elif defined (ENABLE_SELINUX_HACKS)
/* We can't allow these even when testing for a secured filename
because files to be secured might not yet been secured. This is
similar to the option file but in that case it is unlikely that
sensitive information may be retrieved by means of error
messages. */
(void)fname;
(void)for_write;
(void)binary;
return -1;
#else
int fd;
if (binary)
binary = MY_O_BINARY;
/* if (is_secured_filename (fname)) */
/* { */
/* fd = -1; */
/* gpg_err_set_errno (EPERM); */
/* } */
/* else */
/* { */
do
{
if (for_write)
fd = open (fname, O_CREAT | O_TRUNC | O_WRONLY | binary,
S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
else
fd = open (fname, O_RDONLY | binary);
}
while (fd == -1 && errno == EINTR);
/* } */
if ( fd == -1)
log_error ( for_write? _("can't create '%s': %s\n")
: _("can't open '%s': %s\n"), fname, strerror(errno));
return fd;
#endif
}
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 == aSym )
cmd = aSignSym;
else if( cmd == aSym && new_cmd == aSign )
cmd = aSignSym;
else if( cmd == aSym && new_cmd == aEncr )
cmd = aEncrSym;
else if( cmd == aEncr && new_cmd == aSym )
cmd = aEncrSym;
else if (cmd == aSignEncr && new_cmd == aSym)
cmd = aSignEncrSym;
else if (cmd == aSignSym && new_cmd == aEncr)
cmd = aSignEncrSym;
else if (cmd == aEncrSym && new_cmd == aSign)
cmd = aSignEncrSym;
else if( ( cmd == aSign && new_cmd == aClearsign )
|| ( cmd == aClearsign && new_cmd == aSign ) )
cmd = aClearsign;
else {
log_error(_("conflicting commands\n"));
g10_exit(2);
}
*ret_cmd = cmd;
}
static void
add_group(char *string)
{
char *name,*value;
struct groupitem *item;
/* Break off the group name */
name=strsep(&string,"=");
if(string==NULL)
{
log_error(_("no = sign found in group definition '%s'\n"),name);
return;
}
trim_trailing_ws(name,strlen(name));
/* Does this group already exist? */
for(item=opt.grouplist;item;item=item->next)
if(strcasecmp(item->name,name)==0)
break;
if(!item)
{
item=xmalloc(sizeof(struct groupitem));
item->name=name;
item->next=opt.grouplist;
item->values=NULL;
opt.grouplist=item;
}
/* Break apart the values */
while ((value= strsep(&string," \t")))
{
if (*value)
add_to_strlist2(&item->values,value,utf8_strings);
}
}
static void
rm_group(char *name)
{
struct groupitem *item,*last=NULL;
trim_trailing_ws(name,strlen(name));
for(item=opt.grouplist;item;last=item,item=item->next)
{
if(strcasecmp(item->name,name)==0)
{
if(last)
last->next=item->next;
else
opt.grouplist=item->next;
free_strlist(item->values);
xfree(item);
break;
}
}
}
/* We need to check three things.
0) The homedir. It must be x00, a directory, and owned by the
user.
1) The options/gpg.conf file. Okay unless it or its containing
directory is group or other writable or not owned by us. Disable
exec in this case.
2) Extensions. Same as #1.
Returns true if the item is unsafe. */
static int
check_permissions (const char *path, int item)
{
#if defined(HAVE_STAT) && !defined(HAVE_DOSISH_SYSTEM)
static int homedir_cache=-1;
char *tmppath,*dir;
struct stat statbuf,dirbuf;
int homedir=0,ret=0,checkonly=0;
int perm=0,own=0,enc_dir_perm=0,enc_dir_own=0;
if(opt.no_perm_warn)
return 0;
log_assert(item==0 || item==1 || item==2);
/* extensions may attach a path */
if(item==2 && path[0]!=DIRSEP_C)
{
if(strchr(path,DIRSEP_C))
tmppath=make_filename(path,NULL);
else
tmppath=make_filename(gnupg_libdir (),path,NULL);
}
else
tmppath=xstrdup(path);
/* If the item is located in the homedir, but isn't the homedir,
don't continue if we already checked the homedir itself. This is
to avoid user confusion with an extra options file warning which
could be rectified if the homedir itself had proper
permissions. */
if(item!=0 && homedir_cache>-1
&& !ascii_strncasecmp (gnupg_homedir (), tmppath,
strlen (gnupg_homedir ())))
{
ret=homedir_cache;
goto end;
}
/* It's okay if the file or directory doesn't exist */
if(stat(tmppath,&statbuf)!=0)
{
ret=0;
goto end;
}
/* Now check the enclosing directory. Theoretically, we could walk
this test up to the root directory /, but for the sake of sanity,
I'm stopping at one level down. */
dir=make_dirname(tmppath);
if(stat(dir,&dirbuf)!=0 || !S_ISDIR(dirbuf.st_mode))
{
/* Weird error */
ret=1;
goto end;
}
xfree(dir);
/* Assume failure */
ret=1;
if(item==0)
{
/* The homedir must be x00, a directory, and owned by the user. */
if(S_ISDIR(statbuf.st_mode))
{
if(statbuf.st_uid==getuid())
{
if((statbuf.st_mode & (S_IRWXG|S_IRWXO))==0)
ret=0;
else
perm=1;
}
else
own=1;
homedir_cache=ret;
}
}
else if(item==1 || item==2)
{
/* The options or extension file. Okay unless it or its
containing directory is group or other writable or not owned
by us or root. */
if(S_ISREG(statbuf.st_mode))
{
if(statbuf.st_uid==getuid() || statbuf.st_uid==0)
{
if((statbuf.st_mode & (S_IWGRP|S_IWOTH))==0)
{
/* it's not writable, so make sure the enclosing
directory is also not writable */
if(dirbuf.st_uid==getuid() || dirbuf.st_uid==0)
{
if((dirbuf.st_mode & (S_IWGRP|S_IWOTH))==0)
ret=0;
else
enc_dir_perm=1;
}
else
enc_dir_own=1;
}
else
{
/* it's writable, so the enclosing directory had
better not let people get to it. */
if(dirbuf.st_uid==getuid() || dirbuf.st_uid==0)
{
if((dirbuf.st_mode & (S_IRWXG|S_IRWXO))==0)
ret=0;
else
perm=enc_dir_perm=1; /* unclear which one to fix! */
}
else
enc_dir_own=1;
}
}
else
own=1;
}
}
else
BUG();
if(!checkonly)
{
if(own)
{
if(item==0)
log_info(_("WARNING: unsafe ownership on"
" homedir '%s'\n"),tmppath);
else if(item==1)
log_info(_("WARNING: unsafe ownership on"
" configuration file '%s'\n"),tmppath);
else
log_info(_("WARNING: unsafe ownership on"
" extension '%s'\n"),tmppath);
}
if(perm)
{
if(item==0)
log_info(_("WARNING: unsafe permissions on"
" homedir '%s'\n"),tmppath);
else if(item==1)
log_info(_("WARNING: unsafe permissions on"
" configuration file '%s'\n"),tmppath);
else
log_info(_("WARNING: unsafe permissions on"
" extension '%s'\n"),tmppath);
}
if(enc_dir_own)
{
if(item==0)
log_info(_("WARNING: unsafe enclosing directory ownership on"
" homedir '%s'\n"),tmppath);
else if(item==1)
log_info(_("WARNING: unsafe enclosing directory ownership on"
" configuration file '%s'\n"),tmppath);
else
log_info(_("WARNING: unsafe enclosing directory ownership on"
" extension '%s'\n"),tmppath);
}
if(enc_dir_perm)
{
if(item==0)
log_info(_("WARNING: unsafe enclosing directory permissions on"
" homedir '%s'\n"),tmppath);
else if(item==1)
log_info(_("WARNING: unsafe enclosing directory permissions on"
" configuration file '%s'\n"),tmppath);
else
log_info(_("WARNING: unsafe enclosing directory permissions on"
" extension '%s'\n"),tmppath);
}
}
end:
xfree(tmppath);
if(homedir)
homedir_cache=ret;
return ret;
#else /*!(HAVE_STAT && !HAVE_DOSISH_SYSTEM)*/
(void)path;
(void)item;
return 0;
#endif /*!(HAVE_STAT && !HAVE_DOSISH_SYSTEM)*/
}
/* Print the OpenPGP defined algo numbers. */
static void
print_algo_numbers(int (*checker)(int))
{
int i,first=1;
for(i=0;i<=110;i++)
{
if(!checker(i))
{
if(first)
first=0;
else
es_printf (";");
es_printf ("%d",i);
}
}
}
static void
print_algo_names(int (*checker)(int),const char *(*mapper)(int))
{
int i,first=1;
for(i=0;i<=110;i++)
{
if(!checker(i))
{
if(first)
first=0;
else
es_printf (";");
es_printf ("%s",mapper(i));
}
}
}
/* In the future, we can do all sorts of interesting configuration
output here. For now, just give "group" as the Enigmail folks need
it, and pubkey, cipher, hash, and compress as they may be useful
for frontends. */
static void
list_config(char *items)
{
int show_all = !items;
char *name = NULL;
const char *s;
struct groupitem *giter;
int first, iter;
if(!opt.with_colons)
return;
while(show_all || (name=strsep(&items," ")))
{
int any=0;
if(show_all || ascii_strcasecmp(name,"group")==0)
{
for (giter = opt.grouplist; giter; giter = giter->next)
{
strlist_t sl;
es_fprintf (es_stdout, "cfg:group:");
es_write_sanitized (es_stdout, giter->name, strlen(giter->name),
":", NULL);
es_putc (':', es_stdout);
for(sl=giter->values; sl; sl=sl->next)
{
es_write_sanitized (es_stdout, sl->d, strlen (sl->d),
":;", NULL);
if(sl->next)
es_printf(";");
}
es_printf("\n");
}
any=1;
}
if(show_all || ascii_strcasecmp(name,"version")==0)
{
es_printf("cfg:version:");
es_write_sanitized (es_stdout, VERSION, strlen(VERSION), ":", NULL);
es_printf ("\n");
any=1;
}
if(show_all || ascii_strcasecmp(name,"pubkey")==0)
{
es_printf ("cfg:pubkey:");
print_algo_numbers (build_list_pk_test_algo);
es_printf ("\n");
any=1;
}
if(show_all || ascii_strcasecmp(name,"pubkeyname")==0)
{
es_printf ("cfg:pubkeyname:");
print_algo_names (build_list_pk_test_algo,
build_list_pk_algo_name);
es_printf ("\n");
any=1;
}
if(show_all || ascii_strcasecmp(name,"cipher")==0)
{
es_printf ("cfg:cipher:");
print_algo_numbers (build_list_cipher_test_algo);
es_printf ("\n");
any=1;
}
if (show_all || !ascii_strcasecmp (name,"ciphername"))
{
es_printf ("cfg:ciphername:");
print_algo_names (build_list_cipher_test_algo,
build_list_cipher_algo_name);
es_printf ("\n");
any = 1;
}
if(show_all
|| ascii_strcasecmp(name,"digest")==0
|| ascii_strcasecmp(name,"hash")==0)
{
es_printf ("cfg:digest:");
print_algo_numbers (build_list_md_test_algo);
es_printf ("\n");
any=1;
}
if (show_all
|| !ascii_strcasecmp(name,"digestname")
|| !ascii_strcasecmp(name,"hashname"))
{
es_printf ("cfg:digestname:");
print_algo_names (build_list_md_test_algo,
build_list_md_algo_name);
es_printf ("\n");
any=1;
}
if(show_all || ascii_strcasecmp(name,"compress")==0)
{
es_printf ("cfg:compress:");
print_algo_numbers(check_compress_algo);
es_printf ("\n");
any=1;
}
if(show_all || ascii_strcasecmp (name, "compressname") == 0)
{
es_printf ("cfg:compressname:");
print_algo_names (check_compress_algo,
compress_algo_to_string);
es_printf ("\n");
any=1;
}
if (show_all || !ascii_strcasecmp(name,"ccid-reader-id"))
{
/* We ignore this for GnuPG 1.4 backward compatibility. */
any=1;
}
if (show_all || !ascii_strcasecmp (name,"curve"))
{
es_printf ("cfg:curve:");
for (iter=0, first=1; (s = openpgp_enum_curves (&iter)); first=0)
es_printf ("%s%s", first?"":";", s);
es_printf ("\n");
any=1;
}
/* Curve OIDs are rarely useful and thus only printed if requested. */
if (name && !ascii_strcasecmp (name,"curveoid"))
{
es_printf ("cfg:curveoid:");
for (iter=0, first=1; (s = openpgp_enum_curves (&iter)); first = 0)
{
s = openpgp_curve_to_oid (s, NULL);
es_printf ("%s%s", first?"":";", s? s:"[?]");
}
es_printf ("\n");
any=1;
}
if(show_all)
break;
if(!any)
log_error(_("unknown configuration item '%s'\n"),name);
}
}
/* List options and default values in the GPG Conf format. This is a
new tool distributed with gnupg 1.9.x but we also want some limited
support in older gpg versions. The output is the name of the
configuration file and a list of options available for editing by
gpgconf. */
static void
gpgconf_list (const char *configfile)
{
char *configfile_esc = percent_escape (configfile, NULL);
es_printf ("%s-%s.conf:%lu:\"%s\n",
GPGCONF_NAME, GPG_NAME,
GC_OPT_FLAG_DEFAULT,
configfile_esc ? configfile_esc : "/dev/null");
es_printf ("verbose:%lu:\n", GC_OPT_FLAG_NONE);
es_printf ("quiet:%lu:\n", GC_OPT_FLAG_NONE);
es_printf ("keyserver:%lu:\n", GC_OPT_FLAG_NONE);
es_printf ("reader-port:%lu:\n", GC_OPT_FLAG_NONE);
es_printf ("default-key:%lu:\n", GC_OPT_FLAG_NONE);
es_printf ("encrypt-to:%lu:\n", GC_OPT_FLAG_NONE);
es_printf ("try-secret-key:%lu:\n", GC_OPT_FLAG_NONE);
es_printf ("auto-key-locate:%lu:\n", GC_OPT_FLAG_NONE);
es_printf ("auto-key-retrieve:%lu:\n", GC_OPT_FLAG_NONE);
es_printf ("log-file:%lu:\n", GC_OPT_FLAG_NONE);
es_printf ("debug-level:%lu:\"none:\n", GC_OPT_FLAG_DEFAULT);
es_printf ("group:%lu:\n", GC_OPT_FLAG_NONE);
es_printf ("compliance:%lu:\"%s:\n", GC_OPT_FLAG_DEFAULT, "gnupg");
es_printf ("default-new-key-algo:%lu:\n", GC_OPT_FLAG_NONE);
es_printf ("trust-model:%lu:\n", GC_OPT_FLAG_NONE);
es_printf ("disable-dirmngr:%lu:\n", GC_OPT_FLAG_NONE);
es_printf ("max-cert-depth:%lu:\n", GC_OPT_FLAG_NONE);
es_printf ("completes-needed:%lu:\n", GC_OPT_FLAG_NONE);
es_printf ("marginals-needed:%lu:\n", GC_OPT_FLAG_NONE);
/* The next one is an info only item and should match the macros at
the top of keygen.c */
es_printf ("default_pubkey_algo:%lu:\"%s:\n", GC_OPT_FLAG_DEFAULT,
get_default_pubkey_algo ());
xfree (configfile_esc);
}
static int
parse_subpacket_list(char *list)
{
char *tok;
byte subpackets[128],i;
int count=0;
if(!list)
{
/* No arguments means all subpackets */
memset(subpackets+1,1,sizeof(subpackets)-1);
count=127;
}
else
{
memset(subpackets,0,sizeof(subpackets));
/* Merge with earlier copy */
if(opt.show_subpackets)
{
byte *in;
for(in=opt.show_subpackets;*in;in++)
{
if(*in>127 || *in<1)
BUG();
if(!subpackets[*in])
count++;
subpackets[*in]=1;
}
}
while((tok=strsep(&list," ,")))
{
if(!*tok)
continue;
i=atoi(tok);
if(i>127 || i<1)
return 0;
if(!subpackets[i])
count++;
subpackets[i]=1;
}
}
xfree(opt.show_subpackets);
opt.show_subpackets=xmalloc(count+1);
opt.show_subpackets[count--]=0;
for(i=1;i<128 && count>=0;i++)
if(subpackets[i])
opt.show_subpackets[count--]=i;
return 1;
}
static int
parse_list_options(char *str)
{
char *subpackets=""; /* something that isn't NULL */
struct parse_options lopts[]=
{
{"show-photos",LIST_SHOW_PHOTOS,NULL,
N_("display photo IDs during key listings")},
{"show-usage",LIST_SHOW_USAGE,NULL,
N_("show key usage information during key listings")},
{"show-policy-urls",LIST_SHOW_POLICY_URLS,NULL,
N_("show policy URLs during signature listings")},
{"show-notations",LIST_SHOW_NOTATIONS,NULL,
N_("show all notations during signature listings")},
{"show-std-notations",LIST_SHOW_STD_NOTATIONS,NULL,
N_("show IETF standard notations during signature listings")},
{"show-standard-notations",LIST_SHOW_STD_NOTATIONS,NULL,
NULL},
{"show-user-notations",LIST_SHOW_USER_NOTATIONS,NULL,
N_("show user-supplied notations during signature listings")},
{"show-keyserver-urls",LIST_SHOW_KEYSERVER_URLS,NULL,
N_("show preferred keyserver URLs during signature listings")},
{"show-uid-validity",LIST_SHOW_UID_VALIDITY,NULL,
N_("show user ID validity during key listings")},
{"show-unusable-uids",LIST_SHOW_UNUSABLE_UIDS,NULL,
N_("show revoked and expired user IDs in key listings")},
{"show-unusable-subkeys",LIST_SHOW_UNUSABLE_SUBKEYS,NULL,
N_("show revoked and expired subkeys in key listings")},
{"show-keyring",LIST_SHOW_KEYRING,NULL,
N_("show the keyring name in key listings")},
{"show-sig-expire",LIST_SHOW_SIG_EXPIRE,NULL,
N_("show expiration dates during signature listings")},
{"show-sig-subpackets",LIST_SHOW_SIG_SUBPACKETS,NULL,
NULL},
{"show-only-fpr-mbox",LIST_SHOW_ONLY_FPR_MBOX, NULL,
NULL},
{NULL,0,NULL,NULL}
};
/* C99 allows for non-constant initializers, but we'd like to
compile everywhere, so fill in the show-sig-subpackets argument
here. Note that if the parse_options array changes, we'll have
to change the subscript here. */
lopts[13].value=&subpackets;
if(parse_options(str,&opt.list_options,lopts,1))
{
if(opt.list_options&LIST_SHOW_SIG_SUBPACKETS)
{
/* Unset so users can pass multiple lists in. */
opt.list_options&=~LIST_SHOW_SIG_SUBPACKETS;
if(!parse_subpacket_list(subpackets))
return 0;
}
else if(subpackets==NULL && opt.show_subpackets)
{
/* User did 'no-show-subpackets' */
xfree(opt.show_subpackets);
opt.show_subpackets=NULL;
}
return 1;
}
else
return 0;
}
/* Collapses argc/argv into a single string that must be freed */
static char *
collapse_args(int argc,char *argv[])
{
char *str=NULL;
int i,first=1,len=0;
for(i=0;i<argc;i++)
{
len+=strlen(argv[i])+2;
str=xrealloc(str,len);
if(first)
{
str[0]='\0';
first=0;
}
else
strcat(str," ");
strcat(str,argv[i]);
}
return str;
}
#ifndef NO_TRUST_MODELS
static void
parse_trust_model(const char *model)
{
if(ascii_strcasecmp(model,"pgp")==0)
opt.trust_model=TM_PGP;
else if(ascii_strcasecmp(model,"classic")==0)
opt.trust_model=TM_CLASSIC;
else if(ascii_strcasecmp(model,"always")==0)
opt.trust_model=TM_ALWAYS;
else if(ascii_strcasecmp(model,"direct")==0)
opt.trust_model=TM_DIRECT;
#ifdef USE_TOFU
else if(ascii_strcasecmp(model,"tofu")==0)
opt.trust_model=TM_TOFU;
else if(ascii_strcasecmp(model,"tofu+pgp")==0)
opt.trust_model=TM_TOFU_PGP;
#endif /*USE_TOFU*/
else if(ascii_strcasecmp(model,"auto")==0)
opt.trust_model=TM_AUTO;
else
log_error("unknown trust model '%s'\n",model);
}
#endif /*NO_TRUST_MODELS*/
static int
parse_tofu_policy (const char *policystr)
{
#ifdef USE_TOFU
struct { const char *keyword; int policy; } list[] = {
{ "auto", TOFU_POLICY_AUTO },
{ "good", TOFU_POLICY_GOOD },
{ "unknown", TOFU_POLICY_UNKNOWN },
{ "bad", TOFU_POLICY_BAD },
{ "ask", TOFU_POLICY_ASK }
};
int i;
if (!ascii_strcasecmp (policystr, "help"))
{
log_info (_("valid values for option '%s':\n"), "--tofu-policy");
for (i=0; i < DIM (list); i++)
log_info (" %s\n", list[i].keyword);
g10_exit (1);
}
for (i=0; i < DIM (list); i++)
if (!ascii_strcasecmp (policystr, list[i].keyword))
return list[i].policy;
#endif /*USE_TOFU*/
log_error (_("unknown TOFU policy '%s'\n"), policystr);
if (!opt.quiet)
log_info (_("(use \"help\" to list choices)\n"));
g10_exit (1);
}
static struct gnupg_compliance_option compliance_options[] =
{
{ "gnupg", oGnuPG },
{ "openpgp", oOpenPGP },
{ "rfc4880bis", oRFC4880bis },
{ "rfc4880", oRFC4880 },
{ "rfc2440", oRFC2440 },
{ "pgp6", oPGP7 },
{ "pgp7", oPGP7 },
{ "pgp8", oPGP8 },
{ "de-vs", oDE_VS }
};
/* Helper to set compliance related options. This is a separate
* function so that it can also be used by the --compliance option
* parser. */
static void
set_compliance_option (enum cmd_and_opt_values option)
{
opt.flags.rfc4880bis = 0; /* Clear becuase it is initially set. */
switch (option)
{
case oRFC4880bis:
opt.flags.rfc4880bis = 1;
/* fall through. */
case oOpenPGP:
case oRFC4880:
/* This is effectively the same as RFC2440, but with
"--enable-dsa2 --no-rfc2440-text --escape-from-lines
--require-cross-certification". */
opt.compliance = CO_RFC4880;
opt.flags.dsa2 = 1;
opt.flags.require_cross_cert = 1;
opt.rfc2440_text = 0;
opt.allow_non_selfsigned_uid = 1;
opt.allow_freeform_uid = 1;
opt.escape_from = 1;
opt.not_dash_escaped = 0;
opt.def_cipher_algo = 0;
opt.def_aead_algo = 0;
opt.def_digest_algo = 0;
opt.cert_digest_algo = 0;
opt.compress_algo = -1;
opt.s2k_mode = 3; /* iterated+salted */
opt.s2k_digest_algo = DIGEST_ALGO_SHA1;
opt.s2k_cipher_algo = CIPHER_ALGO_3DES;
break;
case oRFC2440:
opt.compliance = CO_RFC2440;
opt.flags.dsa2 = 0;
opt.rfc2440_text = 1;
opt.allow_non_selfsigned_uid = 1;
opt.allow_freeform_uid = 1;
opt.escape_from = 0;
opt.not_dash_escaped = 0;
opt.def_cipher_algo = 0;
opt.def_aead_algo = 0;
opt.def_digest_algo = 0;
opt.cert_digest_algo = 0;
opt.compress_algo = -1;
opt.s2k_mode = 3; /* iterated+salted */
opt.s2k_digest_algo = DIGEST_ALGO_SHA1;
opt.s2k_cipher_algo = CIPHER_ALGO_3DES;
break;
case oPGP7: opt.compliance = CO_PGP7; break;
case oPGP8: opt.compliance = CO_PGP8; break;
case oGnuPG:
opt.compliance = CO_GNUPG;
opt.flags.rfc4880bis = 1;
break;
case oDE_VS:
set_compliance_option (oOpenPGP);
opt.compliance = CO_DE_VS;
opt.def_aead_algo = 0;
/* Fixme: Change other options. */
break;
default:
BUG ();
}
}
/* This function called to initialized a new control object. It is
assumed that this object has been zeroed out before calling this
function. */
static void
gpg_init_default_ctrl (ctrl_t ctrl)
{
ctrl->magic = SERVER_CONTROL_MAGIC;
}
/* This function is called to deinitialize a control object. It is
not deallocated. */
static void
gpg_deinit_default_ctrl (ctrl_t ctrl)
{
#ifdef USE_TOFU
tofu_closedbs (ctrl);
#endif
gpg_dirmngr_deinit_session_data (ctrl);
keydb_release (ctrl->cached_getkey_kdb);
}
char *
get_default_configname (void)
{
char *configname = NULL;
char *name = xstrdup (GPG_NAME EXTSEP_S "conf-" SAFE_VERSION);
char *ver = &name[strlen (GPG_NAME EXTSEP_S "conf-")];
do
{
if (configname)
{
char *tok;
xfree (configname);
configname = NULL;
if ((tok = strrchr (ver, SAFE_VERSION_DASH)))
*tok='\0';
else if ((tok = strrchr (ver, SAFE_VERSION_DOT)))
*tok='\0';
else
break;
}
configname = make_filename (gnupg_homedir (), name, NULL);
}
while (access (configname, R_OK));
xfree(name);
if (! configname)
configname = make_filename (gnupg_homedir (),
GPG_NAME EXTSEP_S "conf", NULL);
if (! access (configname, R_OK))
{
/* Print a warning when both config files are present. */
char *p = make_filename (gnupg_homedir (), "options", NULL);
if (! access (p, R_OK))
log_info (_("Note: old default options file '%s' ignored\n"), p);
xfree (p);
}
else
{
/* Use the old default only if it exists. */
char *p = make_filename (gnupg_homedir (), "options", NULL);
if (!access (p, R_OK))
{
xfree (configname);
configname = p;
}
else
xfree (p);
}
return configname;
}
int
main (int argc, char **argv)
{
ARGPARSE_ARGS pargs;
IOBUF a;
int rc=0;
int orig_argc;
char **orig_argv;
const char *fname;
char *username;
int may_coredump;
strlist_t sl;
strlist_t remusr = NULL;
strlist_t locusr = NULL;
strlist_t nrings = NULL;
armor_filter_context_t *afx = NULL;
int detached_sig = 0;
FILE *configfp = NULL;
char *configname = NULL;
char *save_configname = NULL;
char *default_configname = NULL;
unsigned configlineno;
int parse_debug = 0;
int default_config = 1;
int default_keyring = 1;
int greeting = 0;
int nogreeting = 0;
char *logfile = NULL;
int use_random_seed = 1;
enum cmd_and_opt_values cmd = 0;
const char *debug_level = NULL;
#ifndef NO_TRUST_MODELS
const char *trustdb_name = NULL;
#endif /*!NO_TRUST_MODELS*/
char *def_cipher_string = NULL;
char *def_aead_string = NULL;
char *def_digest_string = NULL;
char *compress_algo_string = NULL;
char *cert_digest_string = NULL;
char *s2k_cipher_string = NULL;
char *s2k_digest_string = NULL;
char *pers_cipher_list = NULL;
char *pers_aead_list = NULL;
char *pers_digest_list = NULL;
char *pers_compress_list = NULL;
int eyes_only=0;
int multifile=0;
int pwfd = -1;
int ovrseskeyfd = -1;
int fpr_maybe_cmd = 0; /* --fingerprint maybe a command. */
int any_explicit_recipient = 0;
int default_akl = 1;
int require_secmem = 0;
int got_secmem = 0;
struct assuan_malloc_hooks malloc_hooks;
ctrl_t ctrl;
static int print_dane_records;
static int print_pka_records;
static int allow_large_chunks;
#ifdef __riscos__
opt.lock_once = 1;
#endif /* __riscos__ */
/* 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. */
early_system_init ();
gnupg_reopen_std (GPG_NAME);
trap_unaligned ();
gnupg_rl_initialize ();
set_strusage (my_strusage);
gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN);
log_set_prefix (GPG_NAME, GPGRT_LOG_WITH_PREFIX);
/* Make sure that our subsystems are ready. */
i18n_init();
init_common_subsystems (&argc, &argv);
/* Use our own logging handler for Libcgrypt. */
setup_libgcrypt_logging ();
/* Put random number into secure memory */
gcry_control (GCRYCTL_USE_SECURE_RNDPOOL);
may_coredump = disable_core_dumps();
gnupg_init_signals (0, emergency_cleanup);
dotlock_create (NULL, 0); /* Register lock file cleanup. */
/* Tell the compliance module who we are. */
gnupg_initialize_compliance (GNUPG_MODULE_NAME_GPG);
opt.autostart = 1;
opt.session_env = session_env_new ();
if (!opt.session_env)
log_fatal ("error allocating session environment block: %s\n",
strerror (errno));
opt.command_fd = -1; /* no command fd */
opt.compress_level = -1; /* defaults to standard compress level */
opt.bz2_compress_level = -1; /* defaults to standard compress level */
/* note: if you change these lines, look at oOpenPGP */
opt.def_cipher_algo = 0;
opt.def_aead_algo = 0;
opt.def_digest_algo = 0;
opt.cert_digest_algo = 0;
opt.compress_algo = -1; /* defaults to DEFAULT_COMPRESS_ALGO */
opt.s2k_mode = 3; /* iterated+salted */
opt.s2k_count = 0; /* Auto-calibrate when needed. */
opt.s2k_cipher_algo = DEFAULT_CIPHER_ALGO;
opt.completes_needed = 1;
opt.marginals_needed = 3;
opt.max_cert_depth = 5;
opt.escape_from = 1;
opt.flags.require_cross_cert = 1;
opt.import_options = IMPORT_REPAIR_KEYS;
opt.export_options = EXPORT_ATTRIBUTES;
opt.keyserver_options.import_options = (IMPORT_REPAIR_KEYS
- | IMPORT_REPAIR_PKS_SUBKEY_BUG);
+ | IMPORT_REPAIR_PKS_SUBKEY_BUG
+ | IMPORT_SELF_SIGS_ONLY
+ | IMPORT_CLEAN);
opt.keyserver_options.export_options = EXPORT_ATTRIBUTES;
opt.keyserver_options.options = KEYSERVER_HONOR_PKA_RECORD;
opt.verify_options = (LIST_SHOW_UID_VALIDITY
| VERIFY_SHOW_POLICY_URLS
| VERIFY_SHOW_STD_NOTATIONS
| VERIFY_SHOW_KEYSERVER_URLS);
opt.list_options = (LIST_SHOW_UID_VALIDITY
| LIST_SHOW_USAGE);
#ifdef NO_TRUST_MODELS
opt.trust_model = TM_ALWAYS;
#else
opt.trust_model = TM_AUTO;
#endif
opt.tofu_default_policy = TOFU_POLICY_AUTO;
opt.mangle_dos_filenames = 0;
opt.min_cert_level = 2;
set_screen_dimensions ();
opt.keyid_format = KF_NONE;
opt.def_sig_expire = "0";
opt.def_cert_expire = "0";
gnupg_set_homedir (NULL);
opt.passphrase_repeat = 1;
opt.emit_version = 0;
opt.weak_digests = NULL;
opt.compliance = CO_GNUPG;
opt.flags.rfc4880bis = 1;
/* Check whether we have a config file on the command line. */
orig_argc = argc;
orig_argv = argv;
pargs.argc = &argc;
pargs.argv = &argv;
pargs.flags= (ARGPARSE_FLAG_KEEP | ARGPARSE_FLAG_NOVERSION);
while( arg_parse( &pargs, opts) ) {
if( pargs.r_opt == oDebug || pargs.r_opt == oDebugAll )
parse_debug++;
else if (pargs.r_opt == oDebugIOLBF)
es_setvbuf (es_stdout, NULL, _IOLBF, 0);
else if( pargs.r_opt == oOptions ) {
/* yes there is one, so we do not try the default one, but
* read the option file when it is encountered at the commandline
*/
default_config = 0;
}
else if( pargs.r_opt == oNoOptions )
{
default_config = 0; /* --no-options */
opt.no_homedir_creation = 1;
}
else if( pargs.r_opt == oHomedir )
gnupg_set_homedir (pargs.r.ret_str);
else if( pargs.r_opt == oNoPermissionWarn )
opt.no_perm_warn=1;
else if (pargs.r_opt == oStrict )
{
/* Not used */
}
else if (pargs.r_opt == oNoStrict )
{
/* Not used */
}
}
#ifdef HAVE_DOSISH_SYSTEM
if ( strchr (gnupg_homedir (), '\\') ) {
char *d, *buf = xmalloc (strlen (gnupg_homedir ())+1);
const char *s;
for (d=buf, s = gnupg_homedir (); *s; s++)
{
*d++ = *s == '\\'? '/': *s;
#ifdef HAVE_W32_SYSTEM
if (s[1] && IsDBCSLeadByte (*s))
*d++ = *++s;
#endif
}
*d = 0;
gnupg_set_homedir (buf);
}
#endif
/* Initialize the secure memory. */
if (!gcry_control (GCRYCTL_INIT_SECMEM, SECMEM_BUFFER_SIZE, 0))
got_secmem = 1;
#if defined(HAVE_GETUID) && defined(HAVE_GETEUID)
/* There should be no way to get to this spot while still carrying
setuid privs. Just in case, bomb out if we are. */
if ( getuid () != geteuid () )
BUG ();
#endif
maybe_setuid = 0;
/* Okay, we are now working under our real uid */
/* malloc hooks go here ... */
malloc_hooks.malloc = gcry_malloc;
malloc_hooks.realloc = gcry_realloc;
malloc_hooks.free = gcry_free;
assuan_set_malloc_hooks (&malloc_hooks);
assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT);
setup_libassuan_logging (&opt.debug, NULL);
/* Set default options which require that malloc stuff is ready. */
additional_weak_digest ("MD5");
parse_auto_key_locate ("local,wkd");
/* Try for a version specific config file first */
default_configname = get_default_configname ();
if (default_config)
configname = xstrdup (default_configname);
argc = orig_argc;
argv = orig_argv;
pargs.argc = &argc;
pargs.argv = &argv;
pargs.flags= ARGPARSE_FLAG_KEEP;
/* By this point we have a homedir, and cannot change it. */
check_permissions (gnupg_homedir (), 0);
next_pass:
if( configname ) {
if(check_permissions(configname,1))
{
/* If any options file is unsafe, then disable any external
programs for keyserver calls or photo IDs. Since the
external program to call is set in the options file, a
unsafe options file can lead to an arbitrary program
being run. */
opt.exec_disable=1;
}
configlineno = 0;
configfp = fopen( configname, "r" );
if (configfp && is_secured_file (fileno (configfp)))
{
fclose (configfp);
configfp = NULL;
gpg_err_set_errno (EPERM);
}
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) );
g10_exit(2);
}
xfree(configname); configname = NULL;
}
if( parse_debug && configname )
log_info(_("reading options from '%s'\n"), configname );
default_config = 0;
}
while( optfile_parse( configfp, configname, &configlineno,
&pargs, opts) )
{
switch( pargs.r_opt )
{
case aListConfig:
case aListGcryptConfig:
case aGPGConfList:
case aGPGConfTest:
set_cmd (&cmd, pargs.r_opt);
/* Do not register a keyring for these commands. */
default_keyring = -1;
break;
case aCheckKeys:
case aListPackets:
case aImport:
case aFastImport:
case aSendKeys:
case aRecvKeys:
case aSearchKeys:
case aRefreshKeys:
case aFetchKeys:
case aExport:
#ifdef ENABLE_CARD_SUPPORT
case aCardStatus:
case aCardEdit:
case aChangePIN:
#endif /* ENABLE_CARD_SUPPORT*/
case aListKeys:
case aLocateKeys:
+ case aLocateExtKeys:
case aListSigs:
case aExportSecret:
case aExportSecretSub:
case aExportSshKey:
case aSym:
case aClearsign:
case aGenRevoke:
case aDesigRevoke:
case aPrimegen:
case aGenRandom:
case aPrintMD:
case aPrintMDs:
case aListTrustDB:
case aCheckTrustDB:
case aUpdateTrustDB:
case aFixTrustDB:
case aListTrustPath:
case aDeArmor:
case aEnArmor:
case aSign:
case aQuickSignKey:
case aQuickLSignKey:
case aSignKey:
case aLSignKey:
case aStore:
case aQuickKeygen:
case aQuickAddUid:
case aQuickAddKey:
case aQuickRevUid:
case aQuickSetExpire:
case aQuickSetPrimaryUid:
case aExportOwnerTrust:
case aImportOwnerTrust:
case aRebuildKeydbCaches:
set_cmd (&cmd, pargs.r_opt);
break;
case aKeygen:
case aFullKeygen:
case aEditKey:
case aDeleteSecretKeys:
case aDeleteSecretAndPublicKeys:
case aDeleteKeys:
case aPasswd:
set_cmd (&cmd, pargs.r_opt);
greeting=1;
break;
case aShowKeys:
set_cmd (&cmd, pargs.r_opt);
opt.import_options |= IMPORT_SHOW;
opt.import_options |= IMPORT_DRY_RUN;
opt.import_options &= ~IMPORT_REPAIR_KEYS;
opt.list_options |= LIST_SHOW_UNUSABLE_UIDS;
opt.list_options |= LIST_SHOW_UNUSABLE_SUBKEYS;
opt.list_options |= LIST_SHOW_NOTATIONS;
opt.list_options |= LIST_SHOW_POLICY_URLS;
break;
case aDetachedSign: detached_sig = 1; set_cmd( &cmd, aSign ); break;
case aDecryptFiles: multifile=1; /* fall through */
case aDecrypt: set_cmd( &cmd, aDecrypt); break;
case aEncrFiles: multifile=1; /* fall through */
case aEncr: set_cmd( &cmd, aEncr); break;
case aVerifyFiles: multifile=1; /* fall through */
case aVerify: set_cmd( &cmd, aVerify); break;
case aServer:
set_cmd (&cmd, pargs.r_opt);
opt.batch = 1;
break;
case aTOFUPolicy:
set_cmd (&cmd, pargs.r_opt);
break;
case oArmor: opt.armor = 1; opt.no_armor=0; break;
case oOutput: opt.outfile = pargs.r.ret_str; break;
case oMaxOutput: opt.max_output = pargs.r.ret_ulong; break;
case oInputSizeHint:
opt.input_size_hint = string_to_u64 (pargs.r.ret_str);
break;
case oChunkSize:
opt.chunk_size = pargs.r.ret_int;
break;
case oQuiet: opt.quiet = 1; break;
case oNoTTY: tty_no_terminal(1); break;
case oDryRun: opt.dry_run = 1; break;
case oInteractive: opt.interactive = 1; break;
case oVerbose:
opt.verbose++;
gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose);
opt.list_options|=LIST_SHOW_UNUSABLE_UIDS;
opt.list_options|=LIST_SHOW_UNUSABLE_SUBKEYS;
break;
case oBatch:
opt.batch = 1;
nogreeting = 1;
break;
case oUseAgent: /* Dummy. */
break;
case oNoUseAgent:
obsolete_option (configname, configlineno, "no-use-agent");
break;
case oGpgAgentInfo:
obsolete_option (configname, configlineno, "gpg-agent-info");
break;
case oReaderPort:
obsolete_scdaemon_option (configname, configlineno, "reader-port");
break;
case octapiDriver:
obsolete_scdaemon_option (configname, configlineno, "ctapi-driver");
break;
case opcscDriver:
obsolete_scdaemon_option (configname, configlineno, "pcsc-driver");
break;
case oDisableCCID:
obsolete_scdaemon_option (configname, configlineno, "disable-ccid");
break;
case oHonorHttpProxy:
obsolete_option (configname, configlineno, "honor-http-proxy");
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 oPrimaryKeyring:
sl = append_to_strlist (&nrings, pargs.r.ret_str);
sl->flags = KEYDB_RESOURCE_FLAG_PRIMARY;
break;
case oShowKeyring:
deprecated_warning(configname,configlineno,"--show-keyring",
"--list-options ","show-keyring");
opt.list_options|=LIST_SHOW_KEYRING;
break;
case oDebug:
if (parse_debug_flag (pargs.r.ret_str, &opt.debug, debug_flags))
{
pargs.r_opt = ARGPARSE_INVALID_ARG;
pargs.err = ARGPARSE_PRINT_ERROR;
}
break;
case oDebugAll: opt.debug = ~0; break;
case oDebugLevel: debug_level = pargs.r.ret_str; break;
case oDebugIOLBF: break; /* Already set in pre-parse step. */
case oDebugSetIobufSize:
opt_set_iobuf_size = pargs.r.ret_ulong;
opt_set_iobuf_size_used = 1;
break;
case oDebugAllowLargeChunks:
allow_large_chunks = 1;
break;
case oStatusFD:
set_status_fd ( translate_sys2libc_fd_int (pargs.r.ret_int, 1) );
break;
case oStatusFile:
set_status_fd ( open_info_file (pargs.r.ret_str, 1, 0) );
break;
case oAttributeFD:
set_attrib_fd ( translate_sys2libc_fd_int (pargs.r.ret_int, 1) );
break;
case oAttributeFile:
set_attrib_fd ( open_info_file (pargs.r.ret_str, 1, 1) );
break;
case oLoggerFD:
log_set_fd (translate_sys2libc_fd_int (pargs.r.ret_int, 1));
break;
case oLoggerFile:
logfile = pargs.r.ret_str;
break;
case oWithFingerprint:
opt.with_fingerprint = 1;
opt.fingerprint++;
break;
case oWithSubkeyFingerprint:
opt.with_subkey_fingerprint = 1;
break;
case oWithICAOSpelling:
opt.with_icao_spelling = 1;
break;
case oFingerprint:
opt.fingerprint++;
fpr_maybe_cmd = 1;
break;
case oWithKeygrip:
opt.with_keygrip = 1;
break;
case oWithKeyScreening:
opt.with_key_screening = 1;
break;
case oWithSecret:
opt.with_secret = 1;
break;
case oWithWKDHash:
opt.with_wkd_hash = 1;
break;
case oWithKeyOrigin:
opt.with_key_origin = 1;
break;
case oSecretKeyring:
/* Ignore this old option. */
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 oNoArmor: opt.no_armor=1; opt.armor=0; break;
case oNoDefKeyring:
if (default_keyring > 0)
default_keyring = 0;
break;
case oNoKeyring:
default_keyring = -1;
break;
case oNoGreeting: nogreeting = 1; break;
case oNoVerbose:
opt.verbose = 0;
gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose);
opt.list_sigs=0;
break;
case oQuickRandom:
gcry_control (GCRYCTL_ENABLE_QUICK_RANDOM, 0);
break;
case oEmitVersion: opt.emit_version++; break;
case oNoEmitVersion: opt.emit_version=0; break;
case oCompletesNeeded: opt.completes_needed = pargs.r.ret_int; break;
case oMarginalsNeeded: opt.marginals_needed = pargs.r.ret_int; break;
case oMaxCertDepth: opt.max_cert_depth = pargs.r.ret_int; break;
#ifndef NO_TRUST_MODELS
case oTrustDBName: trustdb_name = pargs.r.ret_str; break;
#endif /*!NO_TRUST_MODELS*/
case oDefaultKey:
sl = add_to_strlist (&opt.def_secret_key, pargs.r.ret_str);
sl->flags = (pargs.r_opt << PK_LIST_SHIFT);
if (configfp)
sl->flags |= PK_LIST_CONFIG;
break;
case oDefRecipient:
if( *pargs.r.ret_str )
{
xfree (opt.def_recipient);
opt.def_recipient = make_username(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 oNoOptions: opt.no_homedir_creation = 1; break; /* no-options */
case oHomedir: break;
case oNoBatch: opt.batch = 0; break;
case oWithTofuInfo: opt.with_tofu_info = 1; break;
case oWithKeyData: opt.with_key_data=1; /*FALLTHRU*/
case oWithColons: opt.with_colons=':'; break;
case oWithSigCheck: opt.check_sigs = 1; /*FALLTHRU*/
case oWithSigList: opt.list_sigs = 1; break;
case oSkipVerify: opt.skip_verify=1; break;
case oSkipHiddenRecipients: opt.skip_hidden_recipients = 1; break;
case oNoSkipHiddenRecipients: opt.skip_hidden_recipients = 0; break;
case aListSecretKeys: set_cmd( &cmd, aListSecretKeys); break;
#ifndef NO_TRUST_MODELS
/* There are many programs (like mutt) that call gpg with
--always-trust so keep this option around for a long
time. */
case oAlwaysTrust: opt.trust_model=TM_ALWAYS; break;
case oTrustModel:
parse_trust_model(pargs.r.ret_str);
break;
#endif /*!NO_TRUST_MODELS*/
case oTOFUDefaultPolicy:
opt.tofu_default_policy = parse_tofu_policy (pargs.r.ret_str);
break;
case oTOFUDBFormat:
obsolete_option (configname, configlineno, "tofu-db-format");
break;
case oForceOwnertrust:
log_info(_("Note: %s is not for normal use!\n"),
"--force-ownertrust");
opt.force_ownertrust=string_to_trust_value(pargs.r.ret_str);
if(opt.force_ownertrust==-1)
{
log_error("invalid ownertrust '%s'\n",pargs.r.ret_str);
opt.force_ownertrust=0;
}
break;
case oLoadExtension:
/* Dummy so that gpg 1.4 conf files can work. Should
eventually be removed. */
break;
case oCompliance:
{
int compliance = gnupg_parse_compliance_option
(pargs.r.ret_str,
compliance_options, DIM (compliance_options),
opt.quiet);
if (compliance < 0)
g10_exit (1);
set_compliance_option (compliance);
}
break;
case oOpenPGP:
case oRFC2440:
case oRFC4880:
case oRFC4880bis:
case oPGP7:
case oPGP8:
case oGnuPG:
set_compliance_option (pargs.r_opt);
break;
case oRFC2440Text: opt.rfc2440_text=1; break;
case oNoRFC2440Text: opt.rfc2440_text=0; break;
case oSetFilename:
if(utf8_strings)
opt.set_filename = pargs.r.ret_str;
else
opt.set_filename = native_to_utf8(pargs.r.ret_str);
break;
case oForYourEyesOnly: eyes_only = 1; break;
case oNoForYourEyesOnly: eyes_only = 0; break;
case oSetPolicyURL:
add_policy_url(pargs.r.ret_str,0);
add_policy_url(pargs.r.ret_str,1);
break;
case oSigPolicyURL: add_policy_url(pargs.r.ret_str,0); break;
case oCertPolicyURL: add_policy_url(pargs.r.ret_str,1); break;
case oShowPolicyURL:
deprecated_warning(configname,configlineno,"--show-policy-url",
"--list-options ","show-policy-urls");
deprecated_warning(configname,configlineno,"--show-policy-url",
"--verify-options ","show-policy-urls");
opt.list_options|=LIST_SHOW_POLICY_URLS;
opt.verify_options|=VERIFY_SHOW_POLICY_URLS;
break;
case oNoShowPolicyURL:
deprecated_warning(configname,configlineno,"--no-show-policy-url",
"--list-options ","no-show-policy-urls");
deprecated_warning(configname,configlineno,"--no-show-policy-url",
"--verify-options ","no-show-policy-urls");
opt.list_options&=~LIST_SHOW_POLICY_URLS;
opt.verify_options&=~VERIFY_SHOW_POLICY_URLS;
break;
case oSigKeyserverURL: add_keyserver_url(pargs.r.ret_str,0); break;
case oUseEmbeddedFilename:
opt.flags.use_embedded_filename=1;
break;
case oNoUseEmbeddedFilename:
opt.flags.use_embedded_filename=0;
break;
case oComment:
if(pargs.r.ret_str[0])
append_to_strlist(&opt.comments,pargs.r.ret_str);
break;
case oDefaultComment:
deprecated_warning(configname,configlineno,
"--default-comment","--no-comments","");
/* fall through */
case oNoComments:
free_strlist(opt.comments);
opt.comments=NULL;
break;
case oThrowKeyids: opt.throw_keyids = 1; break;
case oNoThrowKeyids: opt.throw_keyids = 0; break;
case oShowPhotos:
deprecated_warning(configname,configlineno,"--show-photos",
"--list-options ","show-photos");
deprecated_warning(configname,configlineno,"--show-photos",
"--verify-options ","show-photos");
opt.list_options|=LIST_SHOW_PHOTOS;
opt.verify_options|=VERIFY_SHOW_PHOTOS;
break;
case oNoShowPhotos:
deprecated_warning(configname,configlineno,"--no-show-photos",
"--list-options ","no-show-photos");
deprecated_warning(configname,configlineno,"--no-show-photos",
"--verify-options ","no-show-photos");
opt.list_options&=~LIST_SHOW_PHOTOS;
opt.verify_options&=~VERIFY_SHOW_PHOTOS;
break;
case oPhotoViewer: opt.photo_viewer = pargs.r.ret_str; break;
case oForceAEAD: opt.force_aead = 1; break;
case oDisableSignerUID: opt.flags.disable_signer_uid = 1; break;
case oS2KMode: opt.s2k_mode = pargs.r.ret_int; break;
case oS2KDigest: s2k_digest_string = xstrdup(pargs.r.ret_str); break;
case oS2KCipher: s2k_cipher_string = xstrdup(pargs.r.ret_str); break;
case oS2KCount:
if (pargs.r.ret_int)
opt.s2k_count = encode_s2k_iterations (pargs.r.ret_int);
else
opt.s2k_count = 0; /* Auto-calibrate when needed. */
break;
case oRecipient:
case oHiddenRecipient:
case oRecipientFile:
case oHiddenRecipientFile:
/* Store the recipient. Note that we also store the
* option as private data in the flags. This is achieved
* by shifting the option value to the left so to keep
* enough space for the flags. */
sl = add_to_strlist2( &remusr, pargs.r.ret_str, utf8_strings );
sl->flags = (pargs.r_opt << PK_LIST_SHIFT);
if (configfp)
sl->flags |= PK_LIST_CONFIG;
if (pargs.r_opt == oHiddenRecipient
|| pargs.r_opt == oHiddenRecipientFile)
sl->flags |= PK_LIST_HIDDEN;
if (pargs.r_opt == oRecipientFile
|| pargs.r_opt == oHiddenRecipientFile)
sl->flags |= PK_LIST_FROM_FILE;
any_explicit_recipient = 1;
break;
case oEncryptTo:
case oHiddenEncryptTo:
/* Store an additional recipient. */
sl = add_to_strlist2( &remusr, pargs.r.ret_str, utf8_strings );
sl->flags = ((pargs.r_opt << PK_LIST_SHIFT) | PK_LIST_ENCRYPT_TO);
if (configfp)
sl->flags |= PK_LIST_CONFIG;
if (pargs.r_opt == oHiddenEncryptTo)
sl->flags |= PK_LIST_HIDDEN;
break;
case oNoEncryptTo:
opt.no_encrypt_to = 1;
break;
case oEncryptToDefaultKey:
opt.encrypt_to_default_key = configfp ? 2 : 1;
break;
case oTrySecretKey:
add_to_strlist2 (&opt.secret_keys_to_try,
pargs.r.ret_str, utf8_strings);
break;
case oMimemode: opt.mimemode = opt.textmode = 1; break;
case oTextmodeShort: opt.textmode = 2; break;
case oTextmode: opt.textmode=1; break;
case oNoTextmode: opt.textmode=opt.mimemode=0; break;
case oExpert: opt.expert = 1; break;
case oNoExpert: opt.expert = 0; break;
case oDefSigExpire:
if(*pargs.r.ret_str!='\0')
{
if(parse_expire_string(pargs.r.ret_str)==(u32)-1)
log_error(_("'%s' is not a valid signature expiration\n"),
pargs.r.ret_str);
else
opt.def_sig_expire=pargs.r.ret_str;
}
break;
case oAskSigExpire: opt.ask_sig_expire = 1; break;
case oNoAskSigExpire: opt.ask_sig_expire = 0; break;
case oDefCertExpire:
if(*pargs.r.ret_str!='\0')
{
if(parse_expire_string(pargs.r.ret_str)==(u32)-1)
log_error(_("'%s' is not a valid signature expiration\n"),
pargs.r.ret_str);
else
opt.def_cert_expire=pargs.r.ret_str;
}
break;
case oAskCertExpire: opt.ask_cert_expire = 1; break;
case oNoAskCertExpire: opt.ask_cert_expire = 0; break;
case oDefCertLevel: opt.def_cert_level=pargs.r.ret_int; break;
case oMinCertLevel: opt.min_cert_level=pargs.r.ret_int; break;
case oAskCertLevel: opt.ask_cert_level = 1; break;
case oNoAskCertLevel: opt.ask_cert_level = 0; break;
case oLocalUser: /* store the local users */
sl = add_to_strlist2( &locusr, pargs.r.ret_str, utf8_strings );
sl->flags = (pargs.r_opt << PK_LIST_SHIFT);
if (configfp)
sl->flags |= PK_LIST_CONFIG;
break;
case oSender:
{
char *mbox = mailbox_from_userid (pargs.r.ret_str, 0);
if (!mbox)
log_error (_("\"%s\" is not a proper mail address\n"),
pargs.r.ret_str);
else
{
add_to_strlist (&opt.sender_list, mbox);
xfree (mbox);
}
}
break;
case oCompress:
/* this is the -z command line option */
opt.compress_level = opt.bz2_compress_level = pargs.r.ret_int;
break;
case oCompressLevel: opt.compress_level = pargs.r.ret_int; break;
case oBZ2CompressLevel: opt.bz2_compress_level = pargs.r.ret_int; break;
case oBZ2DecompressLowmem: opt.bz2_decompress_lowmem=1; break;
case oPassphrase:
set_passphrase_from_string(pargs.r.ret_str);
break;
case oPassphraseFD:
pwfd = translate_sys2libc_fd_int (pargs.r.ret_int, 0);
break;
case oPassphraseFile:
pwfd = open_info_file (pargs.r.ret_str, 0, 1);
break;
case oPassphraseRepeat:
opt.passphrase_repeat = pargs.r.ret_int;
break;
case oPinentryMode:
opt.pinentry_mode = parse_pinentry_mode (pargs.r.ret_str);
if (opt.pinentry_mode == -1)
log_error (_("invalid pinentry mode '%s'\n"), pargs.r.ret_str);
break;
case oRequestOrigin:
opt.request_origin = parse_request_origin (pargs.r.ret_str);
if (opt.request_origin == -1)
log_error (_("invalid request origin '%s'\n"), pargs.r.ret_str);
break;
case oCommandFD:
opt.command_fd = translate_sys2libc_fd_int (pargs.r.ret_int, 0);
if (! gnupg_fd_valid (opt.command_fd))
log_error ("command-fd is invalid: %s\n", strerror (errno));
break;
case oCommandFile:
opt.command_fd = open_info_file (pargs.r.ret_str, 0, 1);
break;
case oCipherAlgo:
def_cipher_string = xstrdup(pargs.r.ret_str);
break;
case oAEADAlgo:
def_aead_string = xstrdup (pargs.r.ret_str);
break;
case oDigestAlgo:
def_digest_string = xstrdup(pargs.r.ret_str);
break;
case oCompressAlgo:
/* If it is all digits, stick a Z in front of it for
later. This is for backwards compatibility with
versions that took the compress algorithm number. */
{
char *pt=pargs.r.ret_str;
while(*pt)
{
if (!isascii (*pt) || !isdigit (*pt))
break;
pt++;
}
if(*pt=='\0')
{
compress_algo_string=xmalloc(strlen(pargs.r.ret_str)+2);
strcpy(compress_algo_string,"Z");
strcat(compress_algo_string,pargs.r.ret_str);
}
else
compress_algo_string = xstrdup(pargs.r.ret_str);
}
break;
case oCertDigestAlgo:
cert_digest_string = xstrdup(pargs.r.ret_str);
break;
case oNoSecmemWarn:
gcry_control (GCRYCTL_DISABLE_SECMEM_WARN);
break;
case oRequireSecmem: require_secmem=1; break;
case oNoRequireSecmem: require_secmem=0; break;
case oNoPermissionWarn: opt.no_perm_warn=1; break;
case oDisplayCharset:
if( set_native_charset( pargs.r.ret_str ) )
log_error(_("'%s' is not a valid character set\n"),
pargs.r.ret_str);
break;
case oNotDashEscaped: opt.not_dash_escaped = 1; break;
case oEscapeFrom: opt.escape_from = 1; break;
case oNoEscapeFrom: opt.escape_from = 0; break;
case oLockOnce: opt.lock_once = 1; break;
case oLockNever:
dotlock_disable ();
break;
case oLockMultiple:
#ifndef __riscos__
opt.lock_once = 0;
#else /* __riscos__ */
riscos_not_implemented("lock-multiple");
#endif /* __riscos__ */
break;
case oKeyServer:
{
keyserver_spec_t keyserver;
keyserver = parse_keyserver_uri (pargs.r.ret_str, 0);
if (!keyserver)
log_error (_("could not parse keyserver URL\n"));
else
{
/* We only support a single keyserver. Later ones
override earlier ones. (Since we parse the
config file first and then the command line
arguments, the command line takes
precedence.) */
if (opt.keyserver)
free_keyserver_spec (opt.keyserver);
opt.keyserver = keyserver;
}
}
break;
case oKeyServerOptions:
if(!parse_keyserver_options(pargs.r.ret_str))
{
if(configname)
log_error(_("%s:%d: invalid keyserver options\n"),
configname,configlineno);
else
log_error(_("invalid keyserver options\n"));
}
break;
case oImportOptions:
if(!parse_import_options(pargs.r.ret_str,&opt.import_options,1))
{
if(configname)
log_error(_("%s:%d: invalid import options\n"),
configname,configlineno);
else
log_error(_("invalid import options\n"));
}
break;
case oImportFilter:
rc = parse_and_set_import_filter (pargs.r.ret_str);
if (rc)
log_error (_("invalid filter option: %s\n"), gpg_strerror (rc));
break;
case oExportOptions:
if(!parse_export_options(pargs.r.ret_str,&opt.export_options,1))
{
if(configname)
log_error(_("%s:%d: invalid export options\n"),
configname,configlineno);
else
log_error(_("invalid export options\n"));
}
break;
case oExportFilter:
rc = parse_and_set_export_filter (pargs.r.ret_str);
if (rc)
log_error (_("invalid filter option: %s\n"), gpg_strerror (rc));
break;
case oListOptions:
if(!parse_list_options(pargs.r.ret_str))
{
if(configname)
log_error(_("%s:%d: invalid list options\n"),
configname,configlineno);
else
log_error(_("invalid list options\n"));
}
break;
case oVerifyOptions:
{
struct parse_options vopts[]=
{
{"show-photos",VERIFY_SHOW_PHOTOS,NULL,
N_("display photo IDs during signature verification")},
{"show-policy-urls",VERIFY_SHOW_POLICY_URLS,NULL,
N_("show policy URLs during signature verification")},
{"show-notations",VERIFY_SHOW_NOTATIONS,NULL,
N_("show all notations during signature verification")},
{"show-std-notations",VERIFY_SHOW_STD_NOTATIONS,NULL,
N_("show IETF standard notations during signature verification")},
{"show-standard-notations",VERIFY_SHOW_STD_NOTATIONS,NULL,
NULL},
{"show-user-notations",VERIFY_SHOW_USER_NOTATIONS,NULL,
N_("show user-supplied notations during signature verification")},
{"show-keyserver-urls",VERIFY_SHOW_KEYSERVER_URLS,NULL,
N_("show preferred keyserver URLs during signature verification")},
{"show-uid-validity",VERIFY_SHOW_UID_VALIDITY,NULL,
N_("show user ID validity during signature verification")},
{"show-unusable-uids",VERIFY_SHOW_UNUSABLE_UIDS,NULL,
N_("show revoked and expired user IDs in signature verification")},
{"show-primary-uid-only",VERIFY_SHOW_PRIMARY_UID_ONLY,NULL,
N_("show only the primary user ID in signature verification")},
{"pka-lookups",VERIFY_PKA_LOOKUPS,NULL,
N_("validate signatures with PKA data")},
{"pka-trust-increase",VERIFY_PKA_TRUST_INCREASE,NULL,
N_("elevate the trust of signatures with valid PKA data")},
{NULL,0,NULL,NULL}
};
if(!parse_options(pargs.r.ret_str,&opt.verify_options,vopts,1))
{
if(configname)
log_error(_("%s:%d: invalid verify options\n"),
configname,configlineno);
else
log_error(_("invalid verify options\n"));
}
}
break;
case oTempDir: opt.temp_dir=pargs.r.ret_str; break;
case oExecPath:
if(set_exec_path(pargs.r.ret_str))
log_error(_("unable to set exec-path to %s\n"),pargs.r.ret_str);
else
opt.exec_path_set=1;
break;
case oSetNotation:
add_notation_data( pargs.r.ret_str, 0 );
add_notation_data( pargs.r.ret_str, 1 );
break;
case oSigNotation: add_notation_data( pargs.r.ret_str, 0 ); break;
case oCertNotation: add_notation_data( pargs.r.ret_str, 1 ); break;
case oKnownNotation: register_known_notation (pargs.r.ret_str); break;
case oShowNotation:
deprecated_warning(configname,configlineno,"--show-notation",
"--list-options ","show-notations");
deprecated_warning(configname,configlineno,"--show-notation",
"--verify-options ","show-notations");
opt.list_options|=LIST_SHOW_NOTATIONS;
opt.verify_options|=VERIFY_SHOW_NOTATIONS;
break;
case oNoShowNotation:
deprecated_warning(configname,configlineno,"--no-show-notation",
"--list-options ","no-show-notations");
deprecated_warning(configname,configlineno,"--no-show-notation",
"--verify-options ","no-show-notations");
opt.list_options&=~LIST_SHOW_NOTATIONS;
opt.verify_options&=~VERIFY_SHOW_NOTATIONS;
break;
case oUtf8Strings: utf8_strings = 1; break;
case oNoUtf8Strings: utf8_strings = 0; break;
case oDisableCipherAlgo:
{
int algo = string_to_cipher_algo (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 oNoSigCache: opt.no_sig_cache = 1; break;
case oAllowNonSelfsignedUID: opt.allow_non_selfsigned_uid = 1; break;
case oNoAllowNonSelfsignedUID: opt.allow_non_selfsigned_uid=0; break;
case oAllowFreeformUID: opt.allow_freeform_uid = 1; break;
case oNoAllowFreeformUID: opt.allow_freeform_uid = 0; break;
case oNoLiteral: opt.no_literal = 1; break;
case oSetFilesize: opt.set_filesize = pargs.r.ret_ulong; break;
case oFastListMode: opt.fast_list_mode = 1; break;
case oFixedListMode: /* Dummy */ break;
case oLegacyListMode: opt.legacy_list_mode = 1; break;
case oPrintPKARecords: print_pka_records = 1; break;
case oPrintDANERecords: print_dane_records = 1; break;
case oListOnly: opt.list_only=1; break;
case oIgnoreTimeConflict: opt.ignore_time_conflict = 1; break;
case oIgnoreValidFrom: opt.ignore_valid_from = 1; break;
case oIgnoreCrcError: opt.ignore_crc_error = 1; break;
case oIgnoreMDCError: opt.ignore_mdc_error = 1; break;
case oNoRandomSeedFile: use_random_seed = 0; break;
case oAutoKeyRetrieve:
opt.keyserver_options.options |= KEYSERVER_AUTO_KEY_RETRIEVE;
break;
case oNoAutoKeyRetrieve:
opt.keyserver_options.options &= ~KEYSERVER_AUTO_KEY_RETRIEVE;
break;
case oShowSessionKey: opt.show_session_key = 1; break;
case oOverrideSessionKey:
opt.override_session_key = pargs.r.ret_str;
break;
case oOverrideSessionKeyFD:
ovrseskeyfd = translate_sys2libc_fd_int (pargs.r.ret_int, 0);
break;
case oMergeOnly:
deprecated_warning(configname,configlineno,"--merge-only",
"--import-options ","merge-only");
opt.import_options|=IMPORT_MERGE_ONLY;
break;
case oAllowSecretKeyImport: /* obsolete */ break;
case oTryAllSecrets: opt.try_all_secrets = 1; break;
case oTrustedKey: register_trusted_key( pargs.r.ret_str ); break;
case oEnableSpecialFilenames:
enable_special_filenames ();
break;
case oNoExpensiveTrustChecks: opt.no_expensive_trust_checks=1; break;
case oAutoCheckTrustDB: opt.no_auto_check_trustdb=0; break;
case oNoAutoCheckTrustDB: opt.no_auto_check_trustdb=1; break;
case oPreservePermissions: opt.preserve_permissions=1; break;
case oDefaultPreferenceList:
opt.def_preference_list = pargs.r.ret_str;
break;
case oDefaultKeyserverURL:
{
keyserver_spec_t keyserver;
keyserver = parse_keyserver_uri (pargs.r.ret_str,1 );
if (!keyserver)
log_error (_("could not parse keyserver URL\n"));
else
free_keyserver_spec (keyserver);
opt.def_keyserver_url = pargs.r.ret_str;
}
break;
case oPersonalCipherPreferences:
pers_cipher_list=pargs.r.ret_str;
break;
case oPersonalAEADPreferences:
pers_aead_list = pargs.r.ret_str;
break;
case oPersonalDigestPreferences:
pers_digest_list=pargs.r.ret_str;
break;
case oPersonalCompressPreferences:
pers_compress_list=pargs.r.ret_str;
break;
case oAgentProgram: opt.agent_program = pargs.r.ret_str; break;
case oDirmngrProgram: opt.dirmngr_program = pargs.r.ret_str; break;
case oDisableDirmngr: opt.disable_dirmngr = 1; break;
case oWeakDigest:
additional_weak_digest(pargs.r.ret_str);
break;
case oUnwrap:
opt.unwrap_encryption = 1;
break;
case oOnlySignTextIDs:
opt.only_sign_text_ids = 1;
break;
case oDisplay:
set_opt_session_env ("DISPLAY", pargs.r.ret_str);
break;
case oTTYname:
set_opt_session_env ("GPG_TTY", pargs.r.ret_str);
break;
case oTTYtype:
set_opt_session_env ("TERM", pargs.r.ret_str);
break;
case oXauthority:
set_opt_session_env ("XAUTHORITY", pargs.r.ret_str);
break;
case oLCctype: opt.lc_ctype = pargs.r.ret_str; break;
case oLCmessages: opt.lc_messages = pargs.r.ret_str; break;
case oGroup: add_group(pargs.r.ret_str); break;
case oUnGroup: rm_group(pargs.r.ret_str); break;
case oNoGroups:
while(opt.grouplist)
{
struct groupitem *iter=opt.grouplist;
free_strlist(iter->values);
opt.grouplist=opt.grouplist->next;
xfree(iter);
}
break;
case oStrict:
case oNoStrict:
/* Not used */
break;
case oMangleDosFilenames: opt.mangle_dos_filenames = 1; break;
case oNoMangleDosFilenames: opt.mangle_dos_filenames = 0; break;
case oEnableProgressFilter: opt.enable_progress_filter = 1; break;
case oMultifile: multifile=1; break;
case oKeyidFormat:
if(ascii_strcasecmp(pargs.r.ret_str,"short")==0)
opt.keyid_format=KF_SHORT;
else if(ascii_strcasecmp(pargs.r.ret_str,"long")==0)
opt.keyid_format=KF_LONG;
else if(ascii_strcasecmp(pargs.r.ret_str,"0xshort")==0)
opt.keyid_format=KF_0xSHORT;
else if(ascii_strcasecmp(pargs.r.ret_str,"0xlong")==0)
opt.keyid_format=KF_0xLONG;
else if(ascii_strcasecmp(pargs.r.ret_str,"none")==0)
opt.keyid_format = KF_NONE;
else
log_error("unknown keyid-format '%s'\n",pargs.r.ret_str);
break;
case oExitOnStatusWriteError:
opt.exit_on_status_write_error = 1;
break;
case oLimitCardInsertTries:
opt.limit_card_insert_tries = pargs.r.ret_int;
break;
case oRequireCrossCert: opt.flags.require_cross_cert=1; break;
case oNoRequireCrossCert: opt.flags.require_cross_cert=0; break;
case oAutoKeyLocate:
if (default_akl)
{
/* This is the first time --auto-key-locate is seen.
* We need to reset the default akl. */
default_akl = 0;
release_akl();
}
if(!parse_auto_key_locate(pargs.r.ret_str))
{
if(configname)
log_error(_("%s:%d: invalid auto-key-locate list\n"),
configname,configlineno);
else
log_error(_("invalid auto-key-locate list\n"));
}
break;
case oNoAutoKeyLocate:
release_akl();
break;
case oKeyOrigin:
if(!parse_key_origin (pargs.r.ret_str))
log_error (_("invalid argument for option \"%.50s\"\n"),
"--key-origin");
break;
case oEnableLargeRSA:
#if SECMEM_BUFFER_SIZE >= 65536
opt.flags.large_rsa=1;
#else
if (configname)
log_info("%s:%d: WARNING: gpg not built with large secure "
"memory buffer. Ignoring enable-large-rsa\n",
configname,configlineno);
else
log_info("WARNING: gpg not built with large secure "
"memory buffer. Ignoring --enable-large-rsa\n");
#endif /* SECMEM_BUFFER_SIZE >= 65536 */
break;
case oDisableLargeRSA: opt.flags.large_rsa=0;
break;
case oEnableDSA2: opt.flags.dsa2=1; break;
case oDisableDSA2: opt.flags.dsa2=0; break;
case oAllowWeakDigestAlgos:
opt.flags.allow_weak_digest_algos = 1;
break;
case oFakedSystemTime:
{
size_t len = strlen (pargs.r.ret_str);
int freeze = 0;
time_t faked_time;
if (len > 0 && pargs.r.ret_str[len-1] == '!')
{
freeze = 1;
pargs.r.ret_str[len-1] = '\0';
}
faked_time = isotime2epoch (pargs.r.ret_str);
if (faked_time == (time_t)(-1))
faked_time = (time_t)strtoul (pargs.r.ret_str, NULL, 10);
gnupg_set_time (faked_time, freeze);
}
break;
case oNoAutostart: opt.autostart = 0; break;
case oNoSymkeyCache: opt.no_symkey_cache = 1; break;
case oDefaultNewKeyAlgo:
opt.def_new_key_algo = pargs.r.ret_str;
break;
case oNoop: break;
default:
if (configfp)
pargs.err = ARGPARSE_PRINT_WARNING;
else
{
pargs.err = ARGPARSE_PRINT_ERROR;
/* The argparse function calls a plain exit and thus
* we need to print a status here. */
write_status_failure ("option-parser",
gpg_error(GPG_ERR_GENERAL));
}
break;
}
}
if (configfp)
{
fclose( configfp );
configfp = NULL;
/* Remember the first config file name. */
if (!save_configname)
save_configname = configname;
else
xfree(configname);
configname = NULL;
goto next_pass;
}
xfree(configname); configname = NULL;
if (log_get_errorcount (0))
{
write_status_failure ("option-parser", gpg_error(GPG_ERR_GENERAL));
g10_exit(2);
}
/* The command --gpgconf-list is pretty simple and may be called
directly after the option parsing. */
if (cmd == aGPGConfList)
{
gpgconf_list (save_configname ? save_configname : default_configname);
g10_exit (0);
}
xfree (save_configname);
xfree (default_configname);
if (print_dane_records)
log_error ("invalid option \"%s\"; use \"%s\" instead\n",
"--print-dane-records",
"--export-options export-dane");
if (print_pka_records)
log_error ("invalid option \"%s\"; use \"%s\" instead\n",
"--print-pks-records",
"--export-options export-pka");
if (log_get_errorcount (0))
{
write_status_failure ("option-checking", gpg_error(GPG_ERR_GENERAL));
g10_exit(2);
}
if( nogreeting )
greeting = 0;
if( greeting )
{
es_fprintf (es_stderr, "%s %s; %s\n",
strusage(11), strusage(13), strusage(14) );
es_fprintf (es_stderr, "%s\n", strusage(15) );
}
#ifdef IS_DEVELOPMENT_VERSION
if (!opt.batch)
{
const char *s;
if((s=strusage(25)))
log_info("%s\n",s);
if((s=strusage(26)))
log_info("%s\n",s);
if((s=strusage(27)))
log_info("%s\n",s);
}
#endif
/* FIXME: We should use logging to a file only in server mode;
however we have not yet implemetyed that. Thus we try to get
away with --batch as indication for logging to file
required. */
if (logfile && opt.batch)
{
log_set_file (logfile);
log_set_prefix (NULL, GPGRT_LOG_WITH_PREFIX | GPGRT_LOG_WITH_TIME | GPGRT_LOG_WITH_PID);
}
if (opt.verbose > 2)
log_info ("using character set '%s'\n", get_native_charset ());
if( may_coredump && !opt.quiet )
log_info(_("WARNING: program may create a core file!\n"));
if (opt.flags.rfc4880bis)
log_info ("Note: RFC4880bis features are enabled.\n");
else
{
opt.mimemode = 0; /* This will use text mode instead. */
}
if (eyes_only) {
if (opt.set_filename)
log_info(_("WARNING: %s overrides %s\n"),
"--for-your-eyes-only","--set-filename");
opt.set_filename="_CONSOLE";
}
if (opt.no_literal) {
log_info(_("Note: %s is not for normal use!\n"), "--no-literal");
if (opt.textmode)
log_error(_("%s not allowed with %s!\n"),
"--textmode", "--no-literal" );
if (opt.set_filename)
log_error(_("%s makes no sense with %s!\n"),
eyes_only?"--for-your-eyes-only":"--set-filename",
"--no-literal" );
}
if (opt.set_filesize)
log_info(_("Note: %s is not for normal use!\n"), "--set-filesize");
if( opt.batch )
tty_batchmode( 1 );
if (gnupg_faked_time_p ())
{
gnupg_isotime_t tbuf;
log_info (_("WARNING: running with faked system time: "));
gnupg_get_isotime (tbuf);
dump_isotime (tbuf);
log_printf ("\n");
}
/* Print a warning if an argument looks like an option. */
if (!opt.quiet && !(pargs.flags & ARGPARSE_FLAG_STOP_SEEN))
{
int i;
for (i=0; i < argc; i++)
if (argv[i][0] == '-' && argv[i][1] == '-')
log_info (_("Note: '%s' is not considered an option\n"), argv[i]);
}
gcry_control (GCRYCTL_RESUME_SECMEM_WARN);
if(require_secmem && !got_secmem)
{
log_info(_("will not run with insecure memory due to %s\n"),
"--require-secmem");
write_status_failure ("option-checking", gpg_error(GPG_ERR_GENERAL));
g10_exit(2);
}
set_debug (debug_level);
if (DBG_CLOCK)
log_clock ("start");
/* Do these after the switch(), so they can override settings. */
if (PGP7)
{
/* That does not anymore work because we have no more support
for v3 signatures. */
opt.escape_from=1;
opt.ask_sig_expire=0;
}
else if(PGP8)
{
opt.escape_from=1;
}
if( def_cipher_string ) {
opt.def_cipher_algo = string_to_cipher_algo (def_cipher_string);
xfree(def_cipher_string); def_cipher_string = NULL;
if ( openpgp_cipher_test_algo (opt.def_cipher_algo) )
log_error(_("selected cipher algorithm is invalid\n"));
}
if (def_aead_string)
{
opt.def_aead_algo = string_to_aead_algo (def_aead_string);
xfree (def_aead_string); def_aead_string = NULL;
if (openpgp_aead_test_algo (opt.def_aead_algo))
log_error(_("selected AEAD algorithm is invalid\n"));
}
if( def_digest_string ) {
opt.def_digest_algo = string_to_digest_algo (def_digest_string);
xfree(def_digest_string); def_digest_string = NULL;
if ( openpgp_md_test_algo (opt.def_digest_algo) )
log_error(_("selected digest algorithm is invalid\n"));
}
if( compress_algo_string ) {
opt.compress_algo = string_to_compress_algo(compress_algo_string);
xfree(compress_algo_string); compress_algo_string = NULL;
if( check_compress_algo(opt.compress_algo) )
log_error(_("selected compression algorithm is invalid\n"));
}
if( cert_digest_string ) {
opt.cert_digest_algo = string_to_digest_algo (cert_digest_string);
xfree(cert_digest_string); cert_digest_string = NULL;
if (openpgp_md_test_algo(opt.cert_digest_algo))
log_error(_("selected certification digest algorithm is invalid\n"));
}
if( s2k_cipher_string ) {
opt.s2k_cipher_algo = string_to_cipher_algo (s2k_cipher_string);
xfree(s2k_cipher_string); s2k_cipher_string = NULL;
if (openpgp_cipher_test_algo (opt.s2k_cipher_algo))
log_error(_("selected cipher algorithm is invalid\n"));
}
if( s2k_digest_string ) {
opt.s2k_digest_algo = string_to_digest_algo (s2k_digest_string);
xfree(s2k_digest_string); s2k_digest_string = NULL;
if (openpgp_md_test_algo(opt.s2k_digest_algo))
log_error(_("selected digest algorithm is invalid\n"));
}
if( opt.completes_needed < 1 )
log_error(_("completes-needed must be greater than 0\n"));
if( opt.marginals_needed < 2 )
log_error(_("marginals-needed must be greater than 1\n"));
if( opt.max_cert_depth < 1 || opt.max_cert_depth > 255 )
log_error(_("max-cert-depth must be in the range from 1 to 255\n"));
if(opt.def_cert_level<0 || opt.def_cert_level>3)
log_error(_("invalid default-cert-level; must be 0, 1, 2, or 3\n"));
if( opt.min_cert_level < 1 || opt.min_cert_level > 3 )
log_error(_("invalid min-cert-level; must be 1, 2, or 3\n"));
switch( opt.s2k_mode ) {
case 0:
log_info(_("Note: simple S2K mode (0) is strongly discouraged\n"));
break;
case 1: case 3: break;
default:
log_error(_("invalid S2K mode; must be 0, 1 or 3\n"));
}
/* This isn't actually needed, but does serve to error out if the
string is invalid. */
if(opt.def_preference_list &&
keygen_set_std_prefs(opt.def_preference_list,0))
log_error(_("invalid default preferences\n"));
if(pers_cipher_list &&
keygen_set_std_prefs(pers_cipher_list,PREFTYPE_SYM))
log_error(_("invalid personal cipher preferences\n"));
if (pers_aead_list && keygen_set_std_prefs (pers_aead_list, PREFTYPE_AEAD))
log_error(_("invalid personal AEAD preferences\n"));
if(pers_digest_list &&
keygen_set_std_prefs(pers_digest_list,PREFTYPE_HASH))
log_error(_("invalid personal digest preferences\n"));
if(pers_compress_list &&
keygen_set_std_prefs(pers_compress_list,PREFTYPE_ZIP))
log_error(_("invalid personal compress preferences\n"));
/* Check chunk size. Please fix also the man page if you change
* the default. The limits are given by the specs. */
if (!opt.chunk_size)
opt.chunk_size = 27; /* Default to the suggested max of 128 MiB. */
else if (opt.chunk_size < 6)
{
opt.chunk_size = 6;
log_info (_("chunk size invalid - using %d\n"), opt.chunk_size);
}
else if (opt.chunk_size > (allow_large_chunks? 62 : 27))
{
opt.chunk_size = (allow_large_chunks? 62 : 27);
log_info (_("chunk size invalid - using %d\n"), opt.chunk_size);
}
/* We don't support all possible commands with multifile yet */
if(multifile)
{
char *cmdname;
switch(cmd)
{
case aSign:
cmdname="--sign";
break;
case aSignEncr:
cmdname="--sign --encrypt";
break;
case aClearsign:
cmdname="--clear-sign";
break;
case aDetachedSign:
cmdname="--detach-sign";
break;
case aSym:
cmdname="--symmetric";
break;
case aEncrSym:
cmdname="--symmetric --encrypt";
break;
case aStore:
cmdname="--store";
break;
default:
cmdname=NULL;
break;
}
if(cmdname)
log_error(_("%s does not yet work with %s\n"),cmdname,"--multifile");
}
if( log_get_errorcount(0) )
{
write_status_failure ("option-postprocessing",
gpg_error(GPG_ERR_GENERAL));
g10_exit (2);
}
if(opt.compress_level==0)
opt.compress_algo=COMPRESS_ALGO_NONE;
/* Check our chosen algorithms against the list of legal
algorithms. */
if(!GNUPG && !opt.flags.rfc4880bis)
{
const char *badalg=NULL;
preftype_t badtype=PREFTYPE_NONE;
if(opt.def_cipher_algo
&& !algo_available(PREFTYPE_SYM,opt.def_cipher_algo,NULL))
{
badalg = openpgp_cipher_algo_name (opt.def_cipher_algo);
badtype = PREFTYPE_SYM;
}
else if(opt.def_aead_algo
&& !algo_available(PREFTYPE_AEAD, opt.def_aead_algo, NULL))
{
badalg = openpgp_aead_algo_name (opt.def_aead_algo);
badtype = PREFTYPE_AEAD;
}
else if(opt.def_digest_algo
&& !algo_available(PREFTYPE_HASH,opt.def_digest_algo,NULL))
{
badalg = gcry_md_algo_name (opt.def_digest_algo);
badtype = PREFTYPE_HASH;
}
else if(opt.cert_digest_algo
&& !algo_available(PREFTYPE_HASH,opt.cert_digest_algo,NULL))
{
badalg = gcry_md_algo_name (opt.cert_digest_algo);
badtype = PREFTYPE_HASH;
}
else if(opt.compress_algo!=-1
&& !algo_available(PREFTYPE_ZIP,opt.compress_algo,NULL))
{
badalg = compress_algo_to_string(opt.compress_algo);
badtype = PREFTYPE_ZIP;
}
if(badalg)
{
switch(badtype)
{
case PREFTYPE_SYM:
log_info (_("cipher algorithm '%s'"
" may not be used in %s mode\n"),
badalg,
gnupg_compliance_option_string (opt.compliance));
break;
case PREFTYPE_AEAD:
log_info (_("AEAD algorithm '%s'"
" may not be used in %s mode\n"),
badalg,
gnupg_compliance_option_string (opt.compliance));
break;
case PREFTYPE_HASH:
log_info (_("digest algorithm '%s'"
" may not be used in %s mode\n"),
badalg,
gnupg_compliance_option_string (opt.compliance));
break;
case PREFTYPE_ZIP:
log_info (_("compression algorithm '%s'"
" may not be used in %s mode\n"),
badalg,
gnupg_compliance_option_string (opt.compliance));
break;
default:
BUG();
}
compliance_failure();
}
}
/* Check our chosen algorithms against the list of allowed
* algorithms in the current compliance mode, and fail hard if it
* is not. This is us being nice to the user informing her early
* that the chosen algorithms are not available. We also check
* and enforce this right before the actual operation. */
/* FIXME: We also need to check the AEAD algo. */
if (opt.def_cipher_algo
&& ! gnupg_cipher_is_allowed (opt.compliance,
cmd == aEncr
|| cmd == aSignEncr
|| cmd == aEncrSym
|| cmd == aSym
|| cmd == aSignSym
|| cmd == aSignEncrSym,
opt.def_cipher_algo,
GCRY_CIPHER_MODE_NONE))
log_error (_("cipher algorithm '%s' may not be used in %s mode\n"),
openpgp_cipher_algo_name (opt.def_cipher_algo),
gnupg_compliance_option_string (opt.compliance));
if (opt.def_digest_algo
&& ! gnupg_digest_is_allowed (opt.compliance,
cmd == aSign
|| cmd == aSignEncr
|| cmd == aSignEncrSym
|| cmd == aSignSym
|| cmd == aClearsign,
opt.def_digest_algo))
log_error (_("digest algorithm '%s' may not be used in %s mode\n"),
gcry_md_algo_name (opt.def_digest_algo),
gnupg_compliance_option_string (opt.compliance));
/* Fail hard. */
if (log_get_errorcount (0))
{
write_status_failure ("option-checking", gpg_error(GPG_ERR_GENERAL));
g10_exit (2);
}
/* Set the random seed file. */
if( use_random_seed ) {
char *p = make_filename (gnupg_homedir (), "random_seed", NULL );
gcry_control (GCRYCTL_SET_RANDOM_SEED_FILE, p);
if (!access (p, F_OK))
register_secured_file (p);
xfree(p);
}
/* If there is no command but the --fingerprint is given, default
to the --list-keys command. */
if (!cmd && fpr_maybe_cmd)
{
set_cmd (&cmd, aListKeys);
}
if( opt.verbose > 1 )
set_packet_list_mode(1);
/* Add the keyrings, but not for some special commands. We always
* need to add the keyrings if we are running under SELinux, this
* is so that the rings are added to the list of secured files.
* We do not add any keyring if --no-keyring has been used. */
if (default_keyring >= 0
&& (ALWAYS_ADD_KEYRINGS
|| (cmd != aDeArmor && cmd != aEnArmor && cmd != aGPGConfTest)))
{
if (!nrings || default_keyring > 0) /* Add default ring. */
keydb_add_resource ("pubring" EXTSEP_S GPGEXT_GPG,
KEYDB_RESOURCE_FLAG_DEFAULT);
for (sl = nrings; sl; sl = sl->next )
keydb_add_resource (sl->d, sl->flags);
}
FREE_STRLIST(nrings);
if (opt.pinentry_mode == PINENTRY_MODE_LOOPBACK)
/* In loopback mode, never ask for the password multiple
times. */
{
opt.passphrase_repeat = 0;
}
if (cmd == aGPGConfTest)
g10_exit(0);
if (pwfd != -1) /* Read the passphrase now. */
read_passphrase_from_fd (pwfd);
if (ovrseskeyfd != -1 ) /* Read the sessionkey now. */
read_sessionkey_from_fd (ovrseskeyfd);
fname = argc? *argv : NULL;
if(fname && utf8_strings)
opt.flags.utf8_filename=1;
ctrl = xcalloc (1, sizeof *ctrl);
gpg_init_default_ctrl (ctrl);
#ifndef NO_TRUST_MODELS
switch (cmd)
{
case aPrimegen:
case aPrintMD:
case aPrintMDs:
case aGenRandom:
case aDeArmor:
case aEnArmor:
case aListConfig:
case aListGcryptConfig:
break;
case aFixTrustDB:
case aExportOwnerTrust:
rc = setup_trustdb (0, trustdb_name);
break;
case aListTrustDB:
rc = setup_trustdb (argc? 1:0, trustdb_name);
break;
case aKeygen:
case aFullKeygen:
case aQuickKeygen:
rc = setup_trustdb (1, trustdb_name);
break;
default:
/* If we are using TM_ALWAYS, we do not need to create the
trustdb. */
rc = setup_trustdb (opt.trust_model != TM_ALWAYS, trustdb_name);
break;
}
if (rc)
log_error (_("failed to initialize the TrustDB: %s\n"),
gpg_strerror (rc));
#endif /*!NO_TRUST_MODELS*/
switch (cmd)
{
case aStore:
case aSym:
case aSign:
case aSignSym:
case aClearsign:
if (!opt.quiet && any_explicit_recipient)
log_info (_("WARNING: recipients (-r) given "
"without using public key encryption\n"));
break;
default:
break;
}
/* Check for certain command whether we need to migrate a
secring.gpg to the gpg-agent. */
switch (cmd)
{
case aListSecretKeys:
case aSign:
case aSignEncr:
case aSignEncrSym:
case aSignSym:
case aClearsign:
case aDecrypt:
case aSignKey:
case aLSignKey:
case aEditKey:
case aPasswd:
case aDeleteSecretKeys:
case aDeleteSecretAndPublicKeys:
case aQuickKeygen:
case aQuickAddUid:
case aQuickAddKey:
case aQuickRevUid:
case aQuickSetPrimaryUid:
case aFullKeygen:
case aKeygen:
case aImport:
case aExportSecret:
case aExportSecretSub:
case aGenRevoke:
case aDesigRevoke:
case aCardEdit:
case aChangePIN:
migrate_secring (ctrl);
break;
case aListKeys:
if (opt.with_secret)
migrate_secring (ctrl);
break;
default:
break;
}
/* The command dispatcher. */
switch( cmd )
{
case aServer:
gpg_server (ctrl);
break;
case aStore: /* only store the file */
if( argc > 1 )
wrong_args("--store [filename]");
if( (rc = encrypt_store(fname)) )
{
write_status_failure ("store", rc);
log_error ("storing '%s' failed: %s\n",
print_fname_stdin(fname),gpg_strerror (rc) );
}
break;
case aSym: /* encrypt the given file only with the symmetric cipher */
if( argc > 1 )
wrong_args("--symmetric [filename]");
if( (rc = encrypt_symmetric(fname)) )
{
write_status_failure ("symencrypt", rc);
log_error (_("symmetric encryption of '%s' failed: %s\n"),
print_fname_stdin(fname),gpg_strerror (rc) );
}
break;
case aEncr: /* encrypt the given file */
if(multifile)
encrypt_crypt_files (ctrl, argc, argv, remusr);
else
{
if( argc > 1 )
wrong_args("--encrypt [filename]");
if( (rc = encrypt_crypt (ctrl, -1, fname, remusr, 0, NULL, -1)) )
{
write_status_failure ("encrypt", rc);
log_error("%s: encryption failed: %s\n",
print_fname_stdin(fname), gpg_strerror (rc) );
}
}
break;
case aEncrSym:
/* This works with PGP 8 in the sense that it acts just like a
symmetric message. It doesn't work at all with 2 or 6. It
might work with 7, but alas, I don't have a copy to test
with right now. */
if( argc > 1 )
wrong_args("--symmetric --encrypt [filename]");
else if(opt.s2k_mode==0)
log_error(_("you cannot use --symmetric --encrypt"
" with --s2k-mode 0\n"));
else if (PGP7)
log_error(_("you cannot use --symmetric --encrypt"
" in %s mode\n"),
gnupg_compliance_option_string (opt.compliance));
else
{
if( (rc = encrypt_crypt (ctrl, -1, fname, remusr, 1, NULL, -1)) )
{
write_status_failure ("encrypt", rc);
log_error ("%s: encryption failed: %s\n",
print_fname_stdin(fname), gpg_strerror (rc) );
}
}
break;
case aSign: /* sign the given file */
sl = NULL;
if( detached_sig ) { /* sign all files */
for( ; argc; argc--, argv++ )
add_to_strlist( &sl, *argv );
}
else {
if( argc > 1 )
wrong_args("--sign [filename]");
if( argc ) {
sl = xmalloc_clear( sizeof *sl + strlen(fname));
strcpy(sl->d, fname);
}
}
if ((rc = sign_file (ctrl, sl, detached_sig, locusr, 0, NULL, NULL)))
{
write_status_failure ("sign", rc);
log_error ("signing failed: %s\n", gpg_strerror (rc) );
}
free_strlist(sl);
break;
case aSignEncr: /* sign and encrypt the given file */
if( argc > 1 )
wrong_args("--sign --encrypt [filename]");
if( argc ) {
sl = xmalloc_clear( sizeof *sl + strlen(fname));
strcpy(sl->d, fname);
}
else
sl = NULL;
if ((rc = sign_file (ctrl, sl, detached_sig, locusr, 1, remusr, NULL)))
{
write_status_failure ("sign-encrypt", rc);
log_error("%s: sign+encrypt failed: %s\n",
print_fname_stdin(fname), gpg_strerror (rc) );
}
free_strlist(sl);
break;
case aSignEncrSym: /* sign and encrypt the given file */
if( argc > 1 )
wrong_args("--symmetric --sign --encrypt [filename]");
else if(opt.s2k_mode==0)
log_error(_("you cannot use --symmetric --sign --encrypt"
" with --s2k-mode 0\n"));
else if (PGP7)
log_error(_("you cannot use --symmetric --sign --encrypt"
" in %s mode\n"),
gnupg_compliance_option_string (opt.compliance));
else
{
if( argc )
{
sl = xmalloc_clear( sizeof *sl + strlen(fname));
strcpy(sl->d, fname);
}
else
sl = NULL;
if ((rc = sign_file (ctrl, sl, detached_sig, locusr,
2, remusr, NULL)))
{
write_status_failure ("sign-encrypt", rc);
log_error("%s: symmetric+sign+encrypt failed: %s\n",
print_fname_stdin(fname), gpg_strerror (rc) );
}
free_strlist(sl);
}
break;
case aSignSym: /* sign and conventionally encrypt the given file */
if (argc > 1)
wrong_args("--sign --symmetric [filename]");
rc = sign_symencrypt_file (ctrl, fname, locusr);
if (rc)
{
write_status_failure ("sign-symencrypt", rc);
log_error("%s: sign+symmetric failed: %s\n",
print_fname_stdin(fname), gpg_strerror (rc) );
}
break;
case aClearsign: /* make a clearsig */
if( argc > 1 )
wrong_args("--clear-sign [filename]");
if( (rc = clearsign_file (ctrl, fname, locusr, NULL)) )
{
write_status_failure ("sign", rc);
log_error("%s: clear-sign failed: %s\n",
print_fname_stdin(fname), gpg_strerror (rc) );
}
break;
case aVerify:
if (multifile)
{
if ((rc = verify_files (ctrl, argc, argv)))
log_error("verify files failed: %s\n", gpg_strerror (rc) );
}
else
{
if ((rc = verify_signatures (ctrl, argc, argv)))
log_error("verify signatures failed: %s\n", gpg_strerror (rc) );
}
if (rc)
write_status_failure ("verify", rc);
break;
case aDecrypt:
if (multifile)
decrypt_messages (ctrl, argc, argv);
else
{
if( argc > 1 )
wrong_args("--decrypt [filename]");
if( (rc = decrypt_message (ctrl, fname) ))
{
write_status_failure ("decrypt", rc);
log_error("decrypt_message failed: %s\n", gpg_strerror (rc) );
}
}
break;
case aQuickSignKey:
case aQuickLSignKey:
{
const char *fpr;
if (argc < 1)
wrong_args ("--quick-[l]sign-key fingerprint [userids]");
fpr = *argv++; argc--;
sl = NULL;
for( ; argc; argc--, argv++)
append_to_strlist2 (&sl, *argv, utf8_strings);
keyedit_quick_sign (ctrl, fpr, sl, locusr, (cmd == aQuickLSignKey));
free_strlist (sl);
}
break;
case aSignKey:
if( argc != 1 )
wrong_args("--sign-key user-id");
/* fall through */
case aLSignKey:
if( argc != 1 )
wrong_args("--lsign-key user-id");
/* fall through */
sl=NULL;
if(cmd==aSignKey)
append_to_strlist(&sl,"sign");
else if(cmd==aLSignKey)
append_to_strlist(&sl,"lsign");
else
BUG();
append_to_strlist( &sl, "save" );
username = make_username( fname );
keyedit_menu (ctrl, username, locusr, sl, 0, 0 );
xfree(username);
free_strlist(sl);
break;
case aEditKey: /* Edit a key signature */
if( !argc )
wrong_args("--edit-key user-id [commands]");
username = make_username( fname );
if( argc > 1 ) {
sl = NULL;
for( argc--, argv++ ; argc; argc--, argv++ )
append_to_strlist( &sl, *argv );
keyedit_menu (ctrl, username, locusr, sl, 0, 1 );
free_strlist(sl);
}
else
keyedit_menu (ctrl, username, locusr, NULL, 0, 1 );
xfree(username);
break;
case aPasswd:
if (argc != 1)
wrong_args("--change-passphrase <user-id>");
else
{
username = make_username (fname);
keyedit_passwd (ctrl, username);
xfree (username);
}
break;
case aDeleteKeys:
case aDeleteSecretKeys:
case aDeleteSecretAndPublicKeys:
sl = NULL;
/* I'm adding these in reverse order as add_to_strlist2
reverses them again, and it's easier to understand in the
proper order :) */
for( ; argc; argc-- )
add_to_strlist2( &sl, argv[argc-1], utf8_strings );
delete_keys (ctrl, sl,
cmd==aDeleteSecretKeys, cmd==aDeleteSecretAndPublicKeys);
free_strlist(sl);
break;
case aCheckKeys:
opt.check_sigs = 1; /* fall through */
case aListSigs:
opt.list_sigs = 1; /* fall through */
case aListKeys:
sl = NULL;
for( ; argc; argc--, argv++ )
add_to_strlist2( &sl, *argv, utf8_strings );
- public_key_list (ctrl, sl, 0);
+ public_key_list (ctrl, sl, 0, 0);
free_strlist(sl);
break;
case aListSecretKeys:
sl = NULL;
for( ; argc; argc--, argv++ )
add_to_strlist2( &sl, *argv, utf8_strings );
secret_key_list (ctrl, sl);
free_strlist(sl);
break;
case aLocateKeys:
+ case aLocateExtKeys:
sl = NULL;
for (; argc; argc--, argv++)
add_to_strlist2( &sl, *argv, utf8_strings );
- public_key_list (ctrl, sl, 1);
+ public_key_list (ctrl, sl, 1, cmd == aLocateExtKeys);
free_strlist (sl);
break;
case aQuickKeygen:
{
const char *x_algo, *x_usage, *x_expire;
if (argc < 1 || argc > 4)
wrong_args("--quick-generate-key USER-ID [ALGO [USAGE [EXPIRE]]]");
username = make_username (fname);
argv++, argc--;
x_algo = "";
x_usage = "";
x_expire = "";
if (argc)
{
x_algo = *argv++; argc--;
if (argc)
{
x_usage = *argv++; argc--;
if (argc)
{
x_expire = *argv++; argc--;
}
}
}
quick_generate_keypair (ctrl, username, x_algo, x_usage, x_expire);
xfree (username);
}
break;
case aKeygen: /* generate a key */
if( opt.batch ) {
if( argc > 1 )
wrong_args("--generate-key [parameterfile]");
generate_keypair (ctrl, 0, argc? *argv : NULL, NULL, 0);
}
else {
if (opt.command_fd != -1 && argc)
{
if( argc > 1 )
wrong_args("--generate-key [parameterfile]");
opt.batch = 1;
generate_keypair (ctrl, 0, argc? *argv : NULL, NULL, 0);
}
else if (argc)
wrong_args ("--generate-key");
else
generate_keypair (ctrl, 0, NULL, NULL, 0);
}
break;
case aFullKeygen: /* Generate a key with all options. */
if (opt.batch)
{
if (argc > 1)
wrong_args ("--full-generate-key [parameterfile]");
generate_keypair (ctrl, 1, argc? *argv : NULL, NULL, 0);
}
else
{
if (argc)
wrong_args("--full-generate-key");
generate_keypair (ctrl, 1, NULL, NULL, 0);
}
break;
case aQuickAddUid:
{
const char *uid, *newuid;
if (argc != 2)
wrong_args ("--quick-add-uid USER-ID NEW-USER-ID");
uid = *argv++; argc--;
newuid = *argv++; argc--;
keyedit_quick_adduid (ctrl, uid, newuid);
}
break;
case aQuickAddKey:
{
const char *x_fpr, *x_algo, *x_usage, *x_expire;
if (argc < 1 || argc > 4)
wrong_args ("--quick-add-key FINGERPRINT [ALGO [USAGE [EXPIRE]]]");
x_fpr = *argv++; argc--;
x_algo = "";
x_usage = "";
x_expire = "";
if (argc)
{
x_algo = *argv++; argc--;
if (argc)
{
x_usage = *argv++; argc--;
if (argc)
{
x_expire = *argv++; argc--;
}
}
}
keyedit_quick_addkey (ctrl, x_fpr, x_algo, x_usage, x_expire);
}
break;
case aQuickRevUid:
{
const char *uid, *uidtorev;
if (argc != 2)
wrong_args ("--quick-revoke-uid USER-ID USER-ID-TO-REVOKE");
uid = *argv++; argc--;
uidtorev = *argv++; argc--;
keyedit_quick_revuid (ctrl, uid, uidtorev);
}
break;
case aQuickSetExpire:
{
const char *x_fpr, *x_expire;
if (argc < 2)
wrong_args ("--quick-set-exipre FINGERPRINT EXPIRE [SUBKEY-FPRS]");
x_fpr = *argv++; argc--;
x_expire = *argv++; argc--;
keyedit_quick_set_expire (ctrl, x_fpr, x_expire, argv);
}
break;
case aQuickSetPrimaryUid:
{
const char *uid, *primaryuid;
if (argc != 2)
wrong_args ("--quick-set-primary-uid USER-ID PRIMARY-USER-ID");
uid = *argv++; argc--;
primaryuid = *argv++; argc--;
keyedit_quick_set_primary (ctrl, uid, primaryuid);
}
break;
case aFastImport:
opt.import_options |= IMPORT_FAST; /* fall through */
case aImport:
case aShowKeys:
import_keys (ctrl, argc? argv:NULL, argc, NULL,
opt.import_options, opt.key_origin, opt.key_origin_url);
break;
/* TODO: There are a number of command that use this same
"make strlist, call function, report error, free strlist"
pattern. Join them together here and avoid all that
duplicated code. */
case aExport:
case aSendKeys:
case aRecvKeys:
sl = NULL;
for( ; argc; argc--, argv++ )
append_to_strlist2( &sl, *argv, utf8_strings );
if( cmd == aSendKeys )
rc = keyserver_export (ctrl, sl );
else if( cmd == aRecvKeys )
rc = keyserver_import (ctrl, sl );
else
{
export_stats_t stats = export_new_stats ();
rc = export_pubkeys (ctrl, sl, opt.export_options, stats);
export_print_stats (stats);
export_release_stats (stats);
}
if(rc)
{
if(cmd==aSendKeys)
{
write_status_failure ("send-keys", rc);
log_error(_("keyserver send failed: %s\n"),gpg_strerror (rc));
}
else if(cmd==aRecvKeys)
{
write_status_failure ("recv-keys", rc);
log_error (_("keyserver receive failed: %s\n"),
gpg_strerror (rc));
}
else
{
write_status_failure ("export", rc);
log_error (_("key export failed: %s\n"), gpg_strerror (rc));
}
}
free_strlist(sl);
break;
case aExportSshKey:
if (argc != 1)
wrong_args ("--export-ssh-key <user-id>");
rc = export_ssh_key (ctrl, argv[0]);
if (rc)
{
write_status_failure ("export-ssh-key", rc);
log_error (_("export as ssh key failed: %s\n"), gpg_strerror (rc));
}
break;
case aSearchKeys:
sl = NULL;
for (; argc; argc--, argv++)
append_to_strlist2 (&sl, *argv, utf8_strings);
rc = keyserver_search (ctrl, sl);
if (rc)
{
write_status_failure ("search-keys", rc);
log_error (_("keyserver search failed: %s\n"), gpg_strerror (rc));
}
free_strlist (sl);
break;
case aRefreshKeys:
sl = NULL;
for( ; argc; argc--, argv++ )
append_to_strlist2( &sl, *argv, utf8_strings );
rc = keyserver_refresh (ctrl, sl);
if(rc)
{
write_status_failure ("refresh-keys", rc);
log_error (_("keyserver refresh failed: %s\n"),gpg_strerror (rc));
}
free_strlist(sl);
break;
case aFetchKeys:
sl = NULL;
for( ; argc; argc--, argv++ )
append_to_strlist2( &sl, *argv, utf8_strings );
rc = keyserver_fetch (ctrl, sl, opt.key_origin);
if(rc)
{
write_status_failure ("fetch-keys", rc);
log_error ("key fetch failed: %s\n",gpg_strerror (rc));
}
free_strlist(sl);
break;
case aExportSecret:
sl = NULL;
for( ; argc; argc--, argv++ )
add_to_strlist2( &sl, *argv, utf8_strings );
{
export_stats_t stats = export_new_stats ();
export_seckeys (ctrl, sl, opt.export_options, stats);
export_print_stats (stats);
export_release_stats (stats);
}
free_strlist(sl);
break;
case aExportSecretSub:
sl = NULL;
for( ; argc; argc--, argv++ )
add_to_strlist2( &sl, *argv, utf8_strings );
{
export_stats_t stats = export_new_stats ();
export_secsubkeys (ctrl, sl, opt.export_options, stats);
export_print_stats (stats);
export_release_stats (stats);
}
free_strlist(sl);
break;
case aGenRevoke:
if( argc != 1 )
wrong_args("--generate-revocation user-id");
username = make_username(*argv);
gen_revoke (ctrl, username );
xfree( username );
break;
case aDesigRevoke:
if (argc != 1)
wrong_args ("--generate-designated-revocation user-id");
username = make_username (*argv);
gen_desig_revoke (ctrl, username, locusr);
xfree (username);
break;
case aDeArmor:
if( argc > 1 )
wrong_args("--dearmor [file]");
rc = dearmor_file( argc? *argv: NULL );
if( rc )
{
write_status_failure ("dearmor", rc);
log_error (_("dearmoring failed: %s\n"), gpg_strerror (rc));
}
break;
case aEnArmor:
if( argc > 1 )
wrong_args("--enarmor [file]");
rc = enarmor_file( argc? *argv: NULL );
if( rc )
{
write_status_failure ("enarmor", rc);
log_error (_("enarmoring failed: %s\n"), gpg_strerror (rc));
}
break;
case aPrimegen:
#if 0 /*FIXME*/
{ int mode = argc < 2 ? 0 : atoi(*argv);
if( mode == 1 && argc == 2 ) {
mpi_print (es_stdout,
generate_public_prime( atoi(argv[1]) ), 1);
}
else if( mode == 2 && argc == 3 ) {
mpi_print (es_stdout, generate_elg_prime(
0, atoi(argv[1]),
atoi(argv[2]), NULL,NULL ), 1);
}
else if( mode == 3 && argc == 3 ) {
MPI *factors;
mpi_print (es_stdout, generate_elg_prime(
1, atoi(argv[1]),
atoi(argv[2]), NULL,&factors ), 1);
es_putc ('\n', es_stdout);
mpi_print (es_stdout, factors[0], 1 ); /* print q */
}
else if( mode == 4 && argc == 3 ) {
MPI g = mpi_alloc(1);
mpi_print (es_stdout, generate_elg_prime(
0, atoi(argv[1]),
atoi(argv[2]), g, NULL ), 1);
es_putc ('\n', es_stdout);
mpi_print (es_stdout, g, 1 );
mpi_free (g);
}
else
wrong_args("--gen-prime mode bits [qbits] ");
es_putc ('\n', es_stdout);
}
#endif
wrong_args("--gen-prime not yet supported ");
break;
case aGenRandom:
{
int level = argc ? atoi(*argv):0;
int count = argc > 1 ? atoi(argv[1]): 0;
int endless = !count;
if( argc < 1 || argc > 2 || level < 0 || level > 2 || count < 0 )
wrong_args("--gen-random 0|1|2 [count]");
while( endless || count ) {
byte *p;
/* We need a multiple of 3, so that in case of
armored output we get a correct string. No
linefolding is done, as it is best to leave this to
other tools */
size_t n = !endless && count < 99? count : 99;
p = gcry_random_bytes (n, level);
#ifdef HAVE_DOSISH_SYSTEM
setmode ( fileno(stdout), O_BINARY );
#endif
if (opt.armor) {
char *tmp = make_radix64_string (p, n);
es_fputs (tmp, es_stdout);
xfree (tmp);
if (n%3 == 1)
es_putc ('=', es_stdout);
if (n%3)
es_putc ('=', es_stdout);
} else {
es_fwrite( p, n, 1, es_stdout );
}
xfree(p);
if( !endless )
count -= n;
}
if (opt.armor)
es_putc ('\n', es_stdout);
}
break;
case aPrintMD:
if( argc < 1)
wrong_args("--print-md algo [files]");
{
int all_algos = (**argv=='*' && !(*argv)[1]);
int algo = all_algos? 0 : gcry_md_map_name (*argv);
if( !algo && !all_algos )
log_error(_("invalid hash algorithm '%s'\n"), *argv );
else {
argc--; argv++;
if( !argc )
print_mds(NULL, algo);
else {
for(; argc; argc--, argv++ )
print_mds(*argv, algo);
}
}
}
break;
case aPrintMDs: /* old option */
if( !argc )
print_mds(NULL,0);
else {
for(; argc; argc--, argv++ )
print_mds(*argv,0);
}
break;
#ifndef NO_TRUST_MODELS
case aListTrustDB:
if( !argc )
list_trustdb (ctrl, es_stdout, NULL);
else {
for( ; argc; argc--, argv++ )
list_trustdb (ctrl, es_stdout, *argv );
}
break;
case aUpdateTrustDB:
if( argc )
wrong_args("--update-trustdb");
update_trustdb (ctrl);
break;
case aCheckTrustDB:
/* Old versions allowed for arguments - ignore them */
check_trustdb (ctrl);
break;
case aFixTrustDB:
how_to_fix_the_trustdb ();
break;
case aListTrustPath:
if( !argc )
wrong_args("--list-trust-path <user-ids>");
for( ; argc; argc--, argv++ ) {
username = make_username( *argv );
list_trust_path( username );
xfree(username);
}
break;
case aExportOwnerTrust:
if( argc )
wrong_args("--export-ownertrust");
export_ownertrust (ctrl);
break;
case aImportOwnerTrust:
if( argc > 1 )
wrong_args("--import-ownertrust [file]");
import_ownertrust (ctrl, argc? *argv:NULL );
break;
#endif /*!NO_TRUST_MODELS*/
case aRebuildKeydbCaches:
if (argc)
wrong_args ("--rebuild-keydb-caches");
keydb_rebuild_caches (ctrl, 1);
break;
#ifdef ENABLE_CARD_SUPPORT
case aCardStatus:
if (argc == 0)
card_status (ctrl, es_stdout, NULL);
else if (argc == 1)
card_status (ctrl, es_stdout, *argv);
else
wrong_args ("--card-status [serialno]");
break;
case aCardEdit:
if (argc) {
sl = NULL;
for (argc--, argv++ ; argc; argc--, argv++)
append_to_strlist (&sl, *argv);
card_edit (ctrl, sl);
free_strlist (sl);
}
else
card_edit (ctrl, NULL);
break;
case aChangePIN:
if (!argc)
change_pin (0,1);
else if (argc == 1)
change_pin (atoi (*argv),1);
else
wrong_args ("--change-pin [no]");
break;
#endif /* ENABLE_CARD_SUPPORT*/
case aListConfig:
{
char *str=collapse_args(argc,argv);
list_config(str);
xfree(str);
}
break;
case aListGcryptConfig:
/* Fixme: It would be nice to integrate that with
--list-config but unfortunately there is no way yet to have
libgcrypt print it to an estream for further parsing. */
gcry_control (GCRYCTL_PRINT_CONFIG, stdout);
break;
case aTOFUPolicy:
#ifdef USE_TOFU
{
int policy;
int i;
KEYDB_HANDLE hd;
if (argc < 2)
wrong_args ("--tofu-policy POLICY KEYID [KEYID...]");
policy = parse_tofu_policy (argv[0]);
hd = keydb_new ();
if (! hd)
{
write_status_failure ("tofu-driver", gpg_error(GPG_ERR_GENERAL));
g10_exit (1);
}
tofu_begin_batch_update (ctrl);
for (i = 1; i < argc; i ++)
{
KEYDB_SEARCH_DESC desc;
kbnode_t kb;
rc = classify_user_id (argv[i], &desc, 0);
if (rc)
{
log_error (_("error parsing key specification '%s': %s\n"),
argv[i], gpg_strerror (rc));
write_status_failure ("tofu-driver", rc);
g10_exit (1);
}
if (! (desc.mode == KEYDB_SEARCH_MODE_SHORT_KID
|| desc.mode == KEYDB_SEARCH_MODE_LONG_KID
|| desc.mode == KEYDB_SEARCH_MODE_FPR
|| desc.mode == KEYDB_SEARCH_MODE_KEYGRIP))
{
log_error (_("'%s' does not appear to be a valid"
" key ID, fingerprint or keygrip\n"),
argv[i]);
write_status_failure ("tofu-driver",
gpg_error(GPG_ERR_GENERAL));
g10_exit (1);
}
rc = keydb_search_reset (hd);
if (rc)
{
/* This should not happen, thus no need to tranalate
the string. */
log_error ("keydb_search_reset failed: %s\n",
gpg_strerror (rc));
write_status_failure ("tofu-driver", rc);
g10_exit (1);
}
rc = keydb_search (hd, &desc, 1, NULL);
if (rc)
{
log_error (_("key \"%s\" not found: %s\n"), argv[i],
gpg_strerror (rc));
write_status_failure ("tofu-driver", rc);
g10_exit (1);
}
rc = keydb_get_keyblock (hd, &kb);
if (rc)
{
log_error (_("error reading keyblock: %s\n"),
gpg_strerror (rc));
write_status_failure ("tofu-driver", rc);
g10_exit (1);
}
merge_keys_and_selfsig (ctrl, kb);
if (tofu_set_policy (ctrl, kb, policy))
{
write_status_failure ("tofu-driver", rc);
g10_exit (1);
}
release_kbnode (kb);
}
tofu_end_batch_update (ctrl);
keydb_release (hd);
}
#endif /*USE_TOFU*/
break;
default:
if (!opt.quiet)
log_info (_("WARNING: no command supplied."
" Trying to guess what you mean ...\n"));
/*FALLTHRU*/
case aListPackets:
if( argc > 1 )
wrong_args("[filename]");
/* Issue some output for the unix newbie */
if (!fname && !opt.outfile
&& gnupg_isatty (fileno (stdin))
&& gnupg_isatty (fileno (stdout))
&& gnupg_isatty (fileno (stderr)))
log_info(_("Go ahead and type your message ...\n"));
a = iobuf_open(fname);
if (a && is_secured_file (iobuf_get_fd (a)))
{
iobuf_close (a);
a = NULL;
gpg_err_set_errno (EPERM);
}
if( !a )
log_error(_("can't open '%s'\n"), print_fname_stdin(fname));
else {
if( !opt.no_armor ) {
if( use_armor_filter( a ) ) {
afx = new_armor_context ();
push_armor_filter (afx, a);
}
}
if( cmd == aListPackets ) {
opt.list_packets=1;
set_packet_list_mode(1);
}
rc = proc_packets (ctrl, NULL, a );
if( rc )
{
write_status_failure ("-", rc);
log_error ("processing message failed: %s\n",
gpg_strerror (rc));
}
iobuf_close(a);
}
break;
}
/* cleanup */
gpg_deinit_default_ctrl (ctrl);
xfree (ctrl);
release_armor_context (afx);
FREE_STRLIST(remusr);
FREE_STRLIST(locusr);
g10_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
g10_exit( int rc )
{
/* If we had an error but not printed an error message, do it now.
* Note that write_status_failure will never print a second failure
* status line. */
if (rc)
write_status_failure ("gpg-exit", gpg_error (GPG_ERR_GENERAL));
gcry_control (GCRYCTL_UPDATE_RANDOM_SEED_FILE);
if (DBG_CLOCK)
log_clock ("stop");
if ( (opt.debug & DBG_MEMSTAT_VALUE) )
{
keydb_dump_stats ();
sig_check_dump_stats ();
+ objcache_dump_stats ();
gcry_control (GCRYCTL_DUMP_MEMORY_STATS);
gcry_control (GCRYCTL_DUMP_RANDOM_STATS);
}
if (opt.debug)
gcry_control (GCRYCTL_DUMP_SECMEM_STATS );
+ gnupg_block_all_signals ();
emergency_cleanup ();
rc = rc? rc : log_get_errorcount(0)? 2 : g10_errors_seen? 1 : 0;
exit (rc);
}
/* Pretty-print hex hashes. This assumes at least an 80-character
display, but there are a few other similar assumptions in the
display code. */
static void
print_hex (gcry_md_hd_t md, int algo, const char *fname)
{
int i,n,count,indent=0;
const byte *p;
if (fname)
indent = es_printf("%s: ",fname);
if (indent>40)
{
es_printf ("\n");
indent=0;
}
if (algo==DIGEST_ALGO_RMD160)
indent += es_printf("RMD160 = ");
else if (algo>0)
indent += es_printf("%6s = ", gcry_md_algo_name (algo));
else
algo = abs(algo);
count = indent;
p = gcry_md_read (md, algo);
n = gcry_md_get_algo_dlen (algo);
count += es_printf ("%02X",*p++);
for(i=1;i<n;i++,p++)
{
if(n==16)
{
if(count+2>79)
{
es_printf ("\n%*s",indent," ");
count = indent;
}
else
count += es_printf(" ");
if (!(i%8))
count += es_printf(" ");
}
else if (n==20)
{
if(!(i%2))
{
if(count+4>79)
{
es_printf ("\n%*s",indent," ");
count=indent;
}
else
count += es_printf(" ");
}
if (!(i%10))
count += es_printf(" ");
}
else
{
if(!(i%4))
{
if (count+8>79)
{
es_printf ("\n%*s",indent," ");
count=indent;
}
else
count += es_printf(" ");
}
}
count += es_printf("%02X",*p);
}
es_printf ("\n");
}
static void
print_hashline( gcry_md_hd_t md, int algo, const char *fname )
{
int i, n;
const byte *p;
if ( fname )
{
for (p = fname; *p; p++ )
{
if ( *p <= 32 || *p > 127 || *p == ':' || *p == '%' )
es_printf ("%%%02X", *p );
else
es_putc (*p, es_stdout);
}
}
es_putc (':', es_stdout);
es_printf ("%d:", algo);
p = gcry_md_read (md, algo);
n = gcry_md_get_algo_dlen (algo);
for(i=0; i < n ; i++, p++ )
es_printf ("%02X", *p);
es_fputs (":\n", es_stdout);
}
static void
print_mds( const char *fname, int algo )
{
estream_t fp;
char buf[1024];
size_t n;
gcry_md_hd_t md;
if (!fname)
{
fp = es_stdin;
es_set_binary (fp);
}
else
{
fp = es_fopen (fname, "rb" );
if (fp && is_secured_file (es_fileno (fp)))
{
es_fclose (fp);
fp = NULL;
gpg_err_set_errno (EPERM);
}
}
if (!fp)
{
log_error("%s: %s\n", fname?fname:"[stdin]", strerror(errno) );
return;
}
gcry_md_open (&md, 0, 0);
if (algo)
gcry_md_enable (md, algo);
else
{
if (!gcry_md_test_algo (GCRY_MD_MD5))
gcry_md_enable (md, GCRY_MD_MD5);
gcry_md_enable (md, GCRY_MD_SHA1);
if (!gcry_md_test_algo (GCRY_MD_RMD160))
gcry_md_enable (md, GCRY_MD_RMD160);
if (!gcry_md_test_algo (GCRY_MD_SHA224))
gcry_md_enable (md, GCRY_MD_SHA224);
if (!gcry_md_test_algo (GCRY_MD_SHA256))
gcry_md_enable (md, GCRY_MD_SHA256);
if (!gcry_md_test_algo (GCRY_MD_SHA384))
gcry_md_enable (md, GCRY_MD_SHA384);
if (!gcry_md_test_algo (GCRY_MD_SHA512))
gcry_md_enable (md, GCRY_MD_SHA512);
}
while ((n=es_fread (buf, 1, DIM(buf), fp)))
gcry_md_write (md, buf, n);
if (es_ferror(fp))
log_error ("%s: %s\n", fname?fname:"[stdin]", strerror(errno));
else
{
gcry_md_final (md);
if (opt.with_colons)
{
if ( algo )
print_hashline (md, algo, fname);
else
{
if (!gcry_md_test_algo (GCRY_MD_MD5))
print_hashline( md, GCRY_MD_MD5, fname );
print_hashline( md, GCRY_MD_SHA1, fname );
if (!gcry_md_test_algo (GCRY_MD_RMD160))
print_hashline( md, GCRY_MD_RMD160, fname );
if (!gcry_md_test_algo (GCRY_MD_SHA224))
print_hashline (md, GCRY_MD_SHA224, fname);
if (!gcry_md_test_algo (GCRY_MD_SHA256))
print_hashline( md, GCRY_MD_SHA256, fname );
if (!gcry_md_test_algo (GCRY_MD_SHA384))
print_hashline ( md, GCRY_MD_SHA384, fname );
if (!gcry_md_test_algo (GCRY_MD_SHA512))
print_hashline ( md, GCRY_MD_SHA512, fname );
}
}
else
{
if (algo)
print_hex (md, -algo, fname);
else
{
if (!gcry_md_test_algo (GCRY_MD_MD5))
print_hex (md, GCRY_MD_MD5, fname);
print_hex (md, GCRY_MD_SHA1, fname );
if (!gcry_md_test_algo (GCRY_MD_RMD160))
print_hex (md, GCRY_MD_RMD160, fname );
if (!gcry_md_test_algo (GCRY_MD_SHA224))
print_hex (md, GCRY_MD_SHA224, fname);
if (!gcry_md_test_algo (GCRY_MD_SHA256))
print_hex (md, GCRY_MD_SHA256, fname );
if (!gcry_md_test_algo (GCRY_MD_SHA384))
print_hex (md, GCRY_MD_SHA384, fname );
if (!gcry_md_test_algo (GCRY_MD_SHA512))
print_hex (md, GCRY_MD_SHA512, fname );
}
}
}
gcry_md_close (md);
if (fp != es_stdin)
es_fclose (fp);
}
/****************
* Check the supplied name,value string and add it to the notation
* data to be used for signatures. which==0 for sig notations, and 1
* for cert notations.
*/
static void
add_notation_data( const char *string, int which )
{
struct notation *notation;
notation=string_to_notation(string,utf8_strings);
if(notation)
{
if(which)
{
notation->next=opt.cert_notations;
opt.cert_notations=notation;
}
else
{
notation->next=opt.sig_notations;
opt.sig_notations=notation;
}
}
}
static void
add_policy_url( const char *string, int which )
{
unsigned int i,critical=0;
strlist_t sl;
if(*string=='!')
{
string++;
critical=1;
}
for(i=0;i<strlen(string);i++)
if( !isascii (string[i]) || iscntrl(string[i]))
break;
if(i==0 || i<strlen(string))
{
if(which)
log_error(_("the given certification policy URL is invalid\n"));
else
log_error(_("the given signature policy URL is invalid\n"));
}
if(which)
sl=add_to_strlist( &opt.cert_policy_url, string );
else
sl=add_to_strlist( &opt.sig_policy_url, string );
if(critical)
sl->flags |= 1;
}
static void
add_keyserver_url( const char *string, int which )
{
unsigned int i,critical=0;
strlist_t sl;
if(*string=='!')
{
string++;
critical=1;
}
for(i=0;i<strlen(string);i++)
if( !isascii (string[i]) || iscntrl(string[i]))
break;
if(i==0 || i<strlen(string))
{
if(which)
BUG();
else
log_error(_("the given preferred keyserver URL is invalid\n"));
}
if(which)
BUG();
else
sl=add_to_strlist( &opt.sig_keyserver_url, string );
if(critical)
sl->flags |= 1;
}
static void
read_sessionkey_from_fd (int fd)
{
int i, len;
char *line;
if (! gnupg_fd_valid (fd))
log_fatal ("override-session-key-fd is invalid: %s\n", strerror (errno));
for (line = NULL, i = len = 100; ; i++ )
{
if (i >= len-1 )
{
char *tmp = line;
len += 100;
line = xmalloc_secure (len);
if (tmp)
{
memcpy (line, tmp, i);
xfree (tmp);
}
else
i=0;
}
if (read (fd, line + i, 1) != 1 || line[i] == '\n')
break;
}
line[i] = 0;
log_debug ("seskey: %s\n", line);
gpgrt_annotate_leaked_object (line);
opt.override_session_key = line;
}
diff --git a/g10/gpgcompose.c b/g10/gpgcompose.c
index e882fa8e3..7b7e1dc9a 100644
--- a/g10/gpgcompose.c
+++ b/g10/gpgcompose.c
@@ -1,3103 +1,3112 @@
/* gpgcompose.c - Maintainer tool to create OpenPGP messages by hand.
* Copyright (C) 2016 g10 Code GmbH
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <errno.h>
#include "gpg.h"
#include "packet.h"
#include "keydb.h"
#include "main.h"
#include "options.h"
#include "call-agent.h"
static int do_debug;
#define debug(fmt, ...) \
do { if (do_debug) log_debug (fmt, ##__VA_ARGS__); } while (0)
/* --encryption, for instance, adds a filter in front of out. There
is an operator (--encryption-pop) to end this. We use the
following infrastructure to make it easy to pop the state. */
struct filter
{
void *func;
void *context;
int pkttype;
int partial_block_mode;
struct filter *next;
};
/* Hack to ass CTRL to some functions. */
static ctrl_t global_ctrl;
static struct filter *filters;
static void
filter_push (iobuf_t out, void *func, void *context,
int type, int partial_block_mode)
{
gpg_error_t err;
struct filter *f = xmalloc_clear (sizeof (*f));
f->next = filters;
f->func = func;
f->context = context;
f->pkttype = type;
f->partial_block_mode = partial_block_mode;
filters = f;
err = iobuf_push_filter (out, func, context);
if (err)
log_fatal ("Adding filter: %s\n", gpg_strerror (err));
}
static void
filter_pop (iobuf_t out, int expected_type)
{
gpg_error_t err;
struct filter *f = filters;
log_assert (f);
if (f->pkttype != expected_type)
log_fatal ("Attempted to pop a %s container, "
"but current container is a %s container.\n",
pkttype_str (f->pkttype), pkttype_str (expected_type));
if (f->pkttype == PKT_ENCRYPTED)
{
err = iobuf_pop_filter (out, f->func, f->context);
if (err)
log_fatal ("Popping encryption filter: %s\n", gpg_strerror (err));
}
else
log_fatal ("FILTERS appears to be corrupted.\n");
if (f->partial_block_mode)
iobuf_set_partial_body_length_mode (out, 0);
filters = f->next;
xfree (f);
}
/* Return if CIPHER_ID is a valid cipher. */
static int
valid_cipher (int cipher_id)
{
return (cipher_id == CIPHER_ALGO_IDEA
|| cipher_id == CIPHER_ALGO_3DES
|| cipher_id == CIPHER_ALGO_CAST5
|| cipher_id == CIPHER_ALGO_BLOWFISH
|| cipher_id == CIPHER_ALGO_AES
|| cipher_id == CIPHER_ALGO_AES192
|| cipher_id == CIPHER_ALGO_AES256
|| cipher_id == CIPHER_ALGO_TWOFISH
|| cipher_id == CIPHER_ALGO_CAMELLIA128
|| cipher_id == CIPHER_ALGO_CAMELLIA192
|| cipher_id == CIPHER_ALGO_CAMELLIA256);
}
/* Parse a session key encoded as a string of the form x:HEXDIGITS
where x is the algorithm id. (This is the format emitted by gpg
--show-session-key.) */
struct session_key
{
int algo;
int keylen;
char *key;
};
static struct session_key
parse_session_key (const char *option, char *p, int require_algo)
{
char *tail;
struct session_key sk;
memset (&sk, 0, sizeof (sk));
/* Check for the optional "cipher-id:" at the start of the
string. */
errno = 0;
sk.algo = strtol (p, &tail, 10);
if (! errno && tail && *tail == ':')
{
if (! valid_cipher (sk.algo))
log_info ("%s: %d is not a known cipher (but using anyways)\n",
option, sk.algo);
p = tail + 1;
}
else if (require_algo)
log_fatal ("%s: Session key must have the form algo:HEXCHARACTERS.\n",
option);
else
sk.algo = 0;
/* Ignore a leading 0x. */
if (p[0] == '0' && p[1] == 'x')
p += 2;
if (strlen (p) % 2 != 0)
log_fatal ("%s: session key must consist of an even number of hexadecimal characters.\n",
option);
sk.keylen = strlen (p) / 2;
sk.key = xmalloc (sk.keylen);
if (hex2bin (p, sk.key, sk.keylen) == -1)
log_fatal ("%s: Session key must only contain hexadecimal characters\n",
option);
return sk;
}
/* A callback.
OPTION_STR is the option that was matched. ARGC is the number of
arguments following the option and ARGV are those arguments.
(Thus, argv[0] is the first string following the option and
argv[-1] is the option.)
COOKIE is the opaque value passed to process_options. */
typedef int (*option_prcessor_t) (const char *option_str,
int argc, char *argv[],
void *cookie);
struct option
{
/* The option that this matches. This must start with "--" or be
the empty string. The empty string matches bare arguments. */
const char *option;
/* The function to call to process this option. */
option_prcessor_t func;
/* Documentation. */
const char *help;
};
/* Merge two lists of options. Note: this makes a shallow copy! The
caller must xfree() the result. */
static struct option *
merge_options (struct option a[], struct option b[])
{
int i, j;
struct option *c;
for (i = 0; a[i].option; i ++)
;
for (j = 0; b[j].option; j ++)
;
c = xmalloc ((i + j + 1) * sizeof (struct option));
memcpy (c, a, i * sizeof (struct option));
memcpy (&c[i], b, j * sizeof (struct option));
c[i + j].option = NULL;
if (a[i].help && b[j].help)
c[i + j].help = xasprintf ("%s\n\n%s", a[i].help, b[j].help);
else if (a[i].help)
c[i + j].help = a[i].help;
else if (b[j].help)
c[i + j].help = b[j].help;
return c;
}
/* Returns whether ARG is an option. All options start with --. */
static int
is_option (const char *arg)
{
return arg[0] == '-' && arg[1] == '-';
}
/* OPTIONS is a NULL terminated array of struct option:s. Finds the
entry that is the same as ARG. Returns -1 if no entry is found.
The empty string option matches bare arguments. */
static int
match_option (const struct option options[], const char *arg)
{
int i;
int bare_arg = ! is_option (arg);
for (i = 0; options[i].option; i ++)
if ((! bare_arg && strcmp (options[i].option, arg) == 0)
/* Non-options match the empty string. */
|| (bare_arg && options[i].option[0] == '\0'))
return i;
return -1;
}
static void
show_help (struct option options[])
{
int i;
int max_length = 0;
int space;
for (i = 0; options[i].option; i ++)
{
const char *option = options[i].option[0] ? options[i].option : "ARG";
int l = strlen (option);
if (l > max_length)
max_length = l;
}
space = 72 - (max_length + 2);
if (space < 40)
space = 40;
for (i = 0; ; i ++)
{
const char *option = options[i].option;
const char *help = options[i].help;
int l;
int j;
char *tmp;
char *formatted;
char *p;
char *newline;
if (! option && ! help)
break;
if (option)
{
const char *o = option[0] ? option : "ARG";
l = strlen (o);
fprintf (stdout, "%s", o);
}
if (! help)
{
fputc ('\n', stdout);
continue;
}
if (option)
for (j = l; j < max_length + 2; j ++)
fputc (' ', stdout);
#define BOLD_START "\033[1m"
#define NORMAL_RESTORE "\033[0m"
#define BOLD(x) BOLD_START x NORMAL_RESTORE
if (! option || options[i].func)
tmp = (char *) help;
else
tmp = xasprintf ("%s " BOLD("(Unimplemented.)"), help);
if (! option)
space = 72;
formatted = format_text (tmp, space, space + 4);
if (!formatted)
abort ();
if (tmp != help)
xfree (tmp);
if (! option)
{
printf ("\n%s\n", formatted);
break;
}
for (p = formatted;
p && *p;
p = (*newline == '\0') ? newline : newline + 1)
{
newline = strchr (p, '\n');
if (! newline)
newline = &p[strlen (p)];
l = (size_t) newline - (size_t) p;
if (p != formatted)
for (j = 0; j < max_length + 2; j ++)
fputc (' ', stdout);
fwrite (p, l, 1, stdout);
fputc ('\n', stdout);
}
xfree (formatted);
}
}
/* Return value is number of consumed argv elements. */
static int
process_options (const char *parent_option,
struct option break_options[],
struct option local_options[], void *lcookie,
struct option global_options[], void *gcookie,
int argc, char *argv[])
{
int i;
for (i = 0; i < argc; i ++)
{
int j;
struct option *option;
void *cookie;
int bare_arg;
option_prcessor_t func;
int consumed;
if (break_options)
{
j = match_option (break_options, argv[i]);
if (j != -1)
/* Match. Break out. */
return i;
}
j = match_option (local_options, argv[i]);
if (j == -1)
{
if (global_options)
j = match_option (global_options, argv[i]);
if (j == -1)
{
if (strcmp (argv[i], "--help") == 0)
{
if (! global_options)
show_help (local_options);
else
{
struct option *combined
= merge_options (local_options, global_options);
show_help (combined);
xfree (combined);
}
g10_exit (0);
}
if (parent_option)
log_fatal ("%s: Unknown option: %s\n", parent_option, argv[i]);
else
log_fatal ("Unknown option: %s\n", argv[i]);
}
option = &global_options[j];
cookie = gcookie;
}
else
{
option = &local_options[j];
cookie = lcookie;
}
bare_arg = strcmp (option->option, "") == 0;
func = option->func;
if (! func)
{
if (bare_arg)
log_fatal ("Bare arguments unimplemented.\n");
else
log_fatal ("Unimplemented option: %s\n",
option->option);
}
consumed = func (bare_arg ? parent_option : argv[i],
argc - i - !bare_arg, &argv[i + !bare_arg],
cookie);
i += consumed;
if (bare_arg)
i --;
}
return i;
}
/* The keys, subkeys, user ids and user attributes in the order that
they were added. */
PACKET components[20];
/* The number of components. */
int ncomponents;
static int
add_component (int pkttype, void *component)
{
int i = ncomponents ++;
log_assert (i < sizeof (components) / sizeof (components[0]));
log_assert (pkttype == PKT_PUBLIC_KEY
|| pkttype == PKT_PUBLIC_SUBKEY
|| pkttype == PKT_SECRET_KEY
|| pkttype == PKT_SECRET_SUBKEY
|| pkttype == PKT_USER_ID
|| pkttype == PKT_ATTRIBUTE);
components[i].pkttype = pkttype;
components[i].pkt.generic = component;
return i;
}
static void
dump_component (PACKET *pkt)
{
struct kbnode_struct kbnode;
if (! do_debug)
return;
memset (&kbnode, 0, sizeof (kbnode));
kbnode.pkt = pkt;
dump_kbnode (&kbnode);
}
/* Returns the first primary key in COMPONENTS or NULL if there is
none. */
static PKT_public_key *
primary_key (void)
{
int i;
for (i = 0; i < ncomponents; i ++)
if (components[i].pkttype == PKT_PUBLIC_KEY)
return components[i].pkt.public_key;
return NULL;
}
/* The last session key (updated when adding a SK-ESK, PK-ESK or SED
packet. */
static DEK session_key;
static int user_id (const char *option, int argc, char *argv[],
void *cookie);
static int public_key (const char *option, int argc, char *argv[],
void *cookie);
static int sk_esk (const char *option, int argc, char *argv[],
void *cookie);
static int pk_esk (const char *option, int argc, char *argv[],
void *cookie);
static int encrypted (const char *option, int argc, char *argv[],
void *cookie);
static int encrypted_pop (const char *option, int argc, char *argv[],
void *cookie);
static int literal (const char *option, int argc, char *argv[],
void *cookie);
static int signature (const char *option, int argc, char *argv[],
void *cookie);
static int copy (const char *option, int argc, char *argv[],
void *cookie);
static struct option major_options[] = {
{ "--user-id", user_id, "Create a user id packet." },
{ "--public-key", public_key, "Create a public key packet." },
{ "--private-key", NULL, "Create a private key packet." },
{ "--public-subkey", public_key, "Create a subkey packet." },
{ "--private-subkey", NULL, "Create a private subkey packet." },
{ "--sk-esk", sk_esk,
"Create a symmetric-key encrypted session key packet." },
{ "--pk-esk", pk_esk,
"Create a public-key encrypted session key packet." },
{ "--encrypted", encrypted, "Create a symmetrically encrypted data packet." },
{ "--encrypted-mdc", encrypted,
"Create a symmetrically encrypted and integrity protected data packet." },
{ "--encrypted-pop", encrypted_pop,
"Pop the most recent encryption container started by either"
" --encrypted or --encrypted-mdc." },
{ "--compressed", NULL, "Create a compressed data packet." },
{ "--literal", literal, "Create a literal (plaintext) data packet." },
{ "--signature", signature, "Create a signature packet." },
{ "--onepass-sig", NULL, "Create a one-pass signature packet." },
{ "--copy", copy, "Copy the specified file." },
{ NULL, NULL,
"To get more information about a given command, use:\n\n"
" $ gpgcompose --command --help to list a command's options."},
};
static struct option global_options[] = {
{ NULL, NULL, NULL },
};
/* Make our lives easier and use a static limit for the user name.
10k is way more than enough anyways... */
const int user_id_max_len = 10 * 1024;
static int
user_id_name (const char *option, int argc, char *argv[], void *cookie)
{
PKT_user_id *uid = cookie;
int l;
if (argc == 0)
log_fatal ("Usage: %s USER_ID\n", option);
if (uid->len)
log_fatal ("Attempt to set user id multiple times.\n");
l = strlen (argv[0]);
if (l > user_id_max_len)
log_fatal ("user id too long (max: %d)\n", user_id_max_len);
memcpy (uid->name, argv[0], l);
uid->name[l] = 0;
uid->len = l;
return 1;
}
static struct option user_id_options[] = {
{ "", user_id_name,
"Set the user id. This is usually in the format "
"\"Name (comment) <email@example.org>\"" },
{ NULL, NULL,
"Example:\n\n"
" $ gpgcompose --user-id \"USERID\" | " GPG_NAME " --list-packets" }
};
static int
user_id (const char *option, int argc, char *argv[], void *cookie)
{
iobuf_t out = cookie;
gpg_error_t err;
PKT_user_id *uid = xmalloc_clear (sizeof (*uid) + user_id_max_len);
int c = add_component (PKT_USER_ID, uid);
int processed;
processed = process_options (option,
major_options,
user_id_options, uid,
global_options, NULL,
argc, argv);
if (! uid->len)
log_fatal ("%s: user id not given", option);
err = build_packet (out, &components[c]);
if (err)
log_fatal ("Serializing user id packet: %s\n", gpg_strerror (err));
debug ("Wrote user id packet:\n");
dump_component (&components[c]);
return processed;
}
static int
pk_search_terms (const char *option, int argc, char *argv[], void *cookie)
{
gpg_error_t err;
KEYDB_HANDLE hd;
KEYDB_SEARCH_DESC desc;
kbnode_t kb;
PKT_public_key *pk = cookie;
PKT_public_key *pk_ref;
int i;
if (argc == 0)
log_fatal ("Usage: %s KEYID\n", option);
if (pk->pubkey_algo)
log_fatal ("%s: multiple keys provided\n", option);
err = classify_user_id (argv[0], &desc, 0);
if (err)
log_fatal ("search terms '%s': %s\n", argv[0], gpg_strerror (err));
hd = keydb_new ();
err = keydb_search (hd, &desc, 1, NULL);
if (err)
log_fatal ("looking up '%s': %s\n", argv[0], gpg_strerror (err));
err = keydb_get_keyblock (hd, &kb);
if (err)
log_fatal ("retrieving keyblock for '%s': %s\n",
argv[0], gpg_strerror (err));
keydb_release (hd);
pk_ref = kb->pkt->pkt.public_key;
/* Copy the timestamp (if not already set), algo and public key
parameters. */
if (! pk->timestamp)
pk->timestamp = pk_ref->timestamp;
pk->pubkey_algo = pk_ref->pubkey_algo;
for (i = 0; i < pubkey_get_npkey (pk->pubkey_algo); i ++)
pk->pkey[i] = gcry_mpi_copy (pk_ref->pkey[i]);
release_kbnode (kb);
return 1;
}
static int
pk_timestamp (const char *option, int argc, char *argv[], void *cookie)
{
PKT_public_key *pk = cookie;
char *tail = NULL;
if (argc == 0)
log_fatal ("Usage: %s TIMESTAMP\n", option);
errno = 0;
pk->timestamp = parse_timestamp (argv[0], &tail);
if (errno || (tail && *tail))
log_fatal ("Invalid value passed to %s (%s)\n", option, argv[0]);
return 1;
}
#define TIMESTAMP_HELP \
"Either as seconds since the epoch or as an ISO 8601 formatted " \
"string (yyyymmddThhmmss, where the T is a literal)."
static struct option pk_options[] = {
{ "--timestamp", pk_timestamp,
"The creation time. " TIMESTAMP_HELP },
{ "", pk_search_terms,
"The key to copy the creation time and public key parameters from." },
{ NULL, NULL,
"Example:\n\n"
" $ gpgcompose --public-key $KEYID --user-id \"USERID\" \\\n"
" | " GPG_NAME " --list-packets" }
};
static int
public_key (const char *option, int argc, char *argv[], void *cookie)
{
gpg_error_t err;
iobuf_t out = cookie;
PKT_public_key *pk;
int c;
int processed;
int t = (strcmp (option, "--public-key") == 0
? PKT_PUBLIC_KEY : PKT_PUBLIC_SUBKEY);
(void) option;
pk = xmalloc_clear (sizeof (*pk));
pk->version = 4;
c = add_component (t, pk);
processed = process_options (option,
major_options,
pk_options, pk,
global_options, NULL,
argc, argv);
if (! pk->pubkey_algo)
log_fatal ("%s: key to extract public key parameters from not given",
option);
/* Clear the keyid in case we updated one of the relevant fields
after accessing it. */
pk->keyid[0] = pk->keyid[1] = 0;
err = build_packet (out, &components[c]);
if (err)
log_fatal ("serializing %s packet: %s\n",
t == PKT_PUBLIC_KEY ? "public key" : "subkey",
gpg_strerror (err));
debug ("Wrote %s packet:\n",
t == PKT_PUBLIC_KEY ? "public key" : "subkey");
dump_component (&components[c]);
return processed;
}
struct signinfo
{
/* Key with which to sign. */
kbnode_t issuer_kb;
PKT_public_key *issuer_pk;
/* Overrides the issuer's key id. */
u32 issuer_keyid[2];
/* Sets the issuer's keyid to the primary key's key id. */
int issuer_keyid_self;
/* Key to sign. */
PKT_public_key *pk;
/* Subkey to sign. */
PKT_public_key *sk;
/* User id to sign. */
PKT_user_id *uid;
int class;
int digest_algo;
u32 timestamp;
u32 key_expiration;
byte *cipher_algorithms;
int cipher_algorithms_len;
byte *digest_algorithms;
int digest_algorithms_len;
byte *compress_algorithms;
int compress_algorithms_len;
u32 expiration;
int exportable_set;
int exportable;
int revocable_set;
int revocable;
int trust_level_set;
byte trust_args[2];
char *trust_scope;
struct revocation_key *revocation_key;
int nrevocation_keys;
struct notation *notations;
byte *key_server_preferences;
int key_server_preferences_len;
char *key_server;
int primary_user_id_set;
int primary_user_id;
char *policy_uri;
byte *key_flags;
int key_flags_len;
char *signers_user_id;
byte reason_for_revocation_code;
char *reason_for_revocation;
byte *features;
int features_len;
/* Whether to corrupt the signature. */
int corrupt;
};
static int
sig_issuer (const char *option, int argc, char *argv[], void *cookie)
{
gpg_error_t err;
KEYDB_HANDLE hd;
KEYDB_SEARCH_DESC desc;
struct signinfo *si = cookie;
if (argc == 0)
log_fatal ("Usage: %s KEYID\n", option);
if (si->issuer_pk)
log_fatal ("%s: multiple keys provided\n", option);
err = classify_user_id (argv[0], &desc, 0);
if (err)
log_fatal ("search terms '%s': %s\n", argv[0], gpg_strerror (err));
hd = keydb_new ();
err = keydb_search (hd, &desc, 1, NULL);
if (err)
log_fatal ("looking up '%s': %s\n", argv[0], gpg_strerror (err));
err = keydb_get_keyblock (hd, &si->issuer_kb);
if (err)
log_fatal ("retrieving keyblock for '%s': %s\n",
argv[0], gpg_strerror (err));
keydb_release (hd);
si->issuer_pk = si->issuer_kb->pkt->pkt.public_key;
return 1;
}
static int
sig_issuer_keyid (const char *option, int argc, char *argv[], void *cookie)
{
gpg_error_t err;
KEYDB_SEARCH_DESC desc;
struct signinfo *si = cookie;
if (argc == 0)
log_fatal ("Usage: %s KEYID|self\n", option);
if (si->issuer_keyid[0] || si->issuer_keyid[1] || si->issuer_keyid_self)
log_fatal ("%s given multiple times.\n", option);
if (strcasecmp (argv[0], "self") == 0)
{
si->issuer_keyid_self = 1;
return 1;
}
err = classify_user_id (argv[0], &desc, 0);
if (err)
log_fatal ("search terms '%s': %s\n", argv[0], gpg_strerror (err));
if (desc.mode != KEYDB_SEARCH_MODE_LONG_KID)
log_fatal ("%s is not a valid long key id.\n", argv[0]);
keyid_copy (si->issuer_keyid, desc.u.kid);
return 1;
}
static int
sig_pk (const char *option, int argc, char *argv[], void *cookie)
{
struct signinfo *si = cookie;
int i;
char *tail = NULL;
if (argc == 0)
log_fatal ("Usage: %s COMPONENT_INDEX\n", option);
errno = 0;
i = strtoul (argv[0], &tail, 10);
if (errno || (tail && *tail))
log_fatal ("Invalid value passed to %s (%s)\n", option, argv[0]);
if (i >= ncomponents)
log_fatal ("%d: No such component (have %d components so far)\n",
i, ncomponents);
if (! (components[i].pkttype == PKT_PUBLIC_KEY
|| components[i].pkttype == PKT_PUBLIC_SUBKEY))
log_fatal ("Component %d is not a public key or a subkey.", i);
if (strcmp (option, "--pk") == 0)
{
if (si->pk)
log_fatal ("%s already given.\n", option);
si->pk = components[i].pkt.public_key;
}
else if (strcmp (option, "--sk") == 0)
{
if (si->sk)
log_fatal ("%s already given.\n", option);
si->sk = components[i].pkt.public_key;
}
else
log_fatal ("Cannot handle %s\n", option);
return 1;
}
static int
sig_user_id (const char *option, int argc, char *argv[], void *cookie)
{
struct signinfo *si = cookie;
int i;
char *tail = NULL;
if (argc == 0)
log_fatal ("Usage: %s COMPONENT_INDEX\n", option);
if (si->uid)
log_fatal ("%s already given.\n", option);
errno = 0;
i = strtoul (argv[0], &tail, 10);
if (errno || (tail && *tail))
log_fatal ("Invalid value passed to %s (%s)\n", option, argv[0]);
if (i >= ncomponents)
log_fatal ("%d: No such component (have %d components so far)\n",
i, ncomponents);
if (! (components[i].pkttype != PKT_USER_ID
|| components[i].pkttype == PKT_ATTRIBUTE))
log_fatal ("Component %d is not a public key or a subkey.", i);
si->uid = components[i].pkt.user_id;
return 1;
}
static int
sig_class (const char *option, int argc, char *argv[], void *cookie)
{
struct signinfo *si = cookie;
int i;
char *tail = NULL;
if (argc == 0)
log_fatal ("Usage: %s CLASS\n", option);
errno = 0;
i = strtoul (argv[0], &tail, 0);
if (errno || (tail && *tail))
log_fatal ("Invalid value passed to %s (%s)\n", option, argv[0]);
si->class = i;
return 1;
}
static int
sig_digest (const char *option, int argc, char *argv[], void *cookie)
{
struct signinfo *si = cookie;
int i;
char *tail = NULL;
if (argc == 0)
log_fatal ("Usage: %s DIGEST_ALGO\n", option);
errno = 0;
i = strtoul (argv[0], &tail, 10);
if (errno || (tail && *tail))
log_fatal ("Invalid value passed to %s (%s)\n", option, argv[0]);
si->digest_algo = i;
return 1;
}
static int
sig_timestamp (const char *option, int argc, char *argv[], void *cookie)
{
struct signinfo *si = cookie;
char *tail = NULL;
if (argc == 0)
log_fatal ("Usage: %s TIMESTAMP\n", option);
errno = 0;
si->timestamp = parse_timestamp (argv[0], &tail);
if (errno || (tail && *tail))
log_fatal ("Invalid value passed to %s (%s)\n", option, argv[0]);
return 1;
}
static int
sig_expiration (const char *option, int argc, char *argv[], void *cookie)
{
struct signinfo *si = cookie;
int is_expiration = strcmp (option, "--expiration") == 0;
u32 *i = is_expiration ? &si->expiration : &si->key_expiration;
if (! is_expiration)
log_assert (strcmp (option, "--key-expiration") == 0);
if (argc == 0)
log_fatal ("Usage: %s DURATION\n", option);
*i = parse_expire_string (argv[0]);
if (*i == (u32)-1)
log_fatal ("Invalid value passed to %s (%s)\n", option, argv[0]);
return 1;
}
static int
sig_int_list (const char *option, int argc, char *argv[], void *cookie)
{
struct signinfo *si = cookie;
int nvalues = 1;
char *values = xmalloc (nvalues * sizeof (values[0]));
char *tail = argv[0];
int i;
byte **a;
int *n;
if (argc == 0)
log_fatal ("Usage: %s VALUE[,VALUE...]\n", option);
for (i = 0; tail && *tail; i ++)
{
int v;
char *old_tail = tail;
errno = 0;
v = strtol (tail, &tail, 0);
if (errno || old_tail == tail || (tail && !(*tail == ',' || *tail == 0)))
log_fatal ("Invalid value passed to %s (%s). "
"Expected a list of comma separated numbers\n",
option, argv[0]);
if (! (0 <= v && v <= 255))
log_fatal ("%s: %d is out of range (Expected: 0-255)\n", option, v);
if (i == nvalues)
{
nvalues *= 2;
values = xrealloc (values, nvalues * sizeof (values[0]));
}
values[i] = v;
if (*tail == ',')
tail ++;
else
log_assert (*tail == 0);
}
if (strcmp ("--cipher-algos", option) == 0)
{
a = &si->cipher_algorithms;
n = &si->cipher_algorithms_len;
}
else if (strcmp ("--digest-algos", option) == 0)
{
a = &si->digest_algorithms;
n = &si->digest_algorithms_len;
}
else if (strcmp ("--compress-algos", option) == 0)
{
a = &si->compress_algorithms;
n = &si->compress_algorithms_len;
}
else
log_fatal ("Cannot handle %s\n", option);
if (*a)
log_fatal ("Option %s given multiple times.\n", option);
*a = values;
*n = i;
return 1;
}
static int
sig_flag (const char *option, int argc, char *argv[], void *cookie)
{
struct signinfo *si = cookie;
int range[2] = {0, 255};
char *tail;
int v;
if (strcmp (option, "--primary-user-id") == 0)
range[1] = 1;
if (argc <= 1)
{
if (range[0] == 0 && range[1] == 1)
log_fatal ("Usage: %s 0|1\n", option);
else
log_fatal ("Usage: %s %d-%d\n", option, range[0], range[1]);
}
errno = 0;
v = strtol (argv[0], &tail, 0);
if (errno || (tail && *tail) || !(range[0] <= v && v <= range[1]))
log_fatal ("Invalid value passed to %s (%s). Expected %d-%d\n",
option, argv[0], range[0], range[1]);
if (strcmp (option, "--exportable") == 0)
{
si->exportable_set = 1;
si->exportable = v;
}
else if (strcmp (option, "--revocable") == 0)
{
si->revocable_set = 1;
si->revocable = v;
}
else if (strcmp (option, "--primary-user-id") == 0)
{
si->primary_user_id_set = 1;
si->primary_user_id = v;
}
else
log_fatal ("Cannot handle %s\n", option);
return 1;
}
static int
sig_trust_level (const char *option, int argc, char *argv[], void *cookie)
{
struct signinfo *si = cookie;
int i;
char *tail;
if (argc <= 1)
log_fatal ("Usage: %s DEPTH TRUST_AMOUNT\n", option);
for (i = 0; i < sizeof (si->trust_args) / sizeof (si->trust_args[0]); i ++)
{
int v;
errno = 0;
v = strtol (argv[i], &tail, 0);
if (errno || (tail && *tail) || !(0 <= v && v <= 255))
log_fatal ("Invalid value passed to %s (%s). Expected 0-255\n",
option, argv[i]);
si->trust_args[i] = v;
}
si->trust_level_set = 1;
return 2;
}
static int
sig_string_arg (const char *option, int argc, char *argv[], void *cookie)
{
struct signinfo *si = cookie;
char *p = argv[0];
char **s;
if (argc == 0)
log_fatal ("Usage: %s STRING\n", option);
if (strcmp (option, "--trust-scope") == 0)
s = &si->trust_scope;
else if (strcmp (option, "--key-server") == 0)
s = &si->key_server;
else if (strcmp (option, "--signers-user-id") == 0)
s = &si->signers_user_id;
else if (strcmp (option, "--policy-uri") == 0)
s = &si->policy_uri;
else
log_fatal ("Cannot handle %s\n", option);
if (*s)
log_fatal ("%s already given.\n", option);
*s = xstrdup (p);
return 1;
}
static int
sig_revocation_key (const char *option, int argc, char *argv[], void *cookie)
{
gpg_error_t err;
struct signinfo *si = cookie;
int v;
char *tail;
PKT_public_key pk;
struct revocation_key *revkey;
if (argc < 2)
log_fatal ("Usage: %s CLASS KEYID\n", option);
memset (&pk, 0, sizeof (pk));
errno = 0;
v = strtol (argv[0], &tail, 16);
if (errno || (tail && *tail) || !(0 <= v && v <= 255))
log_fatal ("%s: Invalid class value (%s). Expected 0-255\n",
option, argv[0]);
pk.req_usage = PUBKEY_USAGE_SIG;
- err = get_pubkey_byname (NULL, NULL, &pk, argv[1], NULL, NULL, 1, 1);
+ err = get_pubkey_byname (NULL, GET_PUBKEY_NO_AKL,
+ NULL, &pk, argv[1], NULL, NULL, 1);
if (err)
log_fatal ("looking up key %s: %s\n", argv[1], gpg_strerror (err));
si->nrevocation_keys ++;
si->revocation_key = xrealloc (si->revocation_key,
si->nrevocation_keys
* sizeof (*si->revocation_key));
revkey = &si->revocation_key[si->nrevocation_keys - 1];
revkey->class = v;
revkey->algid = pk.pubkey_algo;
fingerprint_from_pk (&pk, revkey->fpr, NULL);
release_public_key_parts (&pk);
return 2;
}
static int
sig_notation (const char *option, int argc, char *argv[], void *cookie)
{
struct signinfo *si = cookie;
int is_blob = strcmp (option, "--notation") != 0;
struct notation *notation;
char *p = argv[0];
int p_free = 0;
char *data;
int data_size;
int data_len;
if (argc == 0)
log_fatal ("Usage: %s [!<]name=value\n", option);
if ((p[0] == '!' && p[1] == '<') || p[0] == '<')
/* Read from a file. */
{
char *filename = NULL;
iobuf_t in;
int prefix;
if (p[0] == '<')
p ++;
else
{
/* Remove the '<', which string_to_notation does not
understand, and preserve the '!'. */
p = xstrdup (&p[1]);
p_free = 1;
p[0] = '!';
}
filename = strchr (p, '=');
if (! filename)
log_fatal ("No value specified. Usage: %s [!<]name=value\n",
option);
filename ++;
prefix = (size_t) filename - (size_t) p;
errno = 0;
in = iobuf_open (filename);
if (! in)
log_fatal ("Opening '%s': %s\n",
filename, errno ? strerror (errno): "unknown error");
/* A notation can be at most about a few dozen bytes short of
64k. Since this is relatively small, we just allocate that
much instead of trying to dynamically size a buffer. */
data_size = 64 * 1024;
data = xmalloc (data_size);
log_assert (prefix <= data_size);
memcpy (data, p, prefix);
data_len = iobuf_read (in, &data[prefix], data_size - prefix - 1);
if (data_len == -1)
/* EOF => 0 bytes read. */
data_len = 0;
if (data_len == data_size - prefix - 1)
/* Technically, we should do another read and check for EOF,
but what's one byte more or less? */
log_fatal ("Notation data doesn't fit in the packet.\n");
iobuf_close (in);
/* NUL terminate it. */
data[prefix + data_len] = 0;
if (p_free)
xfree (p);
p = data;
p_free = 1;
data = &p[prefix];
if (is_blob)
p[prefix - 1] = 0;
}
else if (is_blob)
{
data = strchr (p, '=');
if (! data)
{
data = p;
data_len = 0;
}
else
{
p = xstrdup (p);
p_free = 1;
data = strchr (p, '=');
log_assert (data);
/* NUL terminate the name. */
*data = 0;
data ++;
data_len = strlen (data);
}
}
if (is_blob)
notation = blob_to_notation (p, data, data_len);
else
notation = string_to_notation (p, 1);
if (! notation)
log_fatal ("creating notation: an unknown error occurred.\n");
notation->next = si->notations;
si->notations = notation;
if (p_free)
xfree (p);
return 1;
}
static int
sig_big_endian_arg (const char *option, int argc, char *argv[], void *cookie)
{
struct signinfo *si = cookie;
char *p = argv[0];
int i;
int l;
char *bytes;
if (argc == 0)
log_fatal ("Usage: %s HEXDIGITS\n", option);
/* Skip a leading "0x". */
if (p[0] == '0' && p[1] == 'x')
p += 2;
for (i = 0; i < strlen (p); i ++)
if (!hexdigitp (&p[i]))
log_fatal ("%s: argument ('%s') must consist of hex digits.\n",
option, p);
if (strlen (p) % 2 != 0)
log_fatal ("%s: argument ('%s') must contain an even number of hex digits.\n",
option, p);
l = strlen (p) / 2;
bytes = xmalloc (l);
hex2bin (p, bytes, l);
if (strcmp (option, "--key-server-preferences") == 0)
{
if (si->key_server_preferences)
log_fatal ("%s given multiple times.\n", option);
si->key_server_preferences = bytes;
si->key_server_preferences_len = l;
}
else if (strcmp (option, "--key-flags") == 0)
{
if (si->key_flags)
log_fatal ("%s given multiple times.\n", option);
si->key_flags = bytes;
si->key_flags_len = l;
}
else if (strcmp (option, "--features") == 0)
{
if (si->features)
log_fatal ("%s given multiple times.\n", option);
si->features = bytes;
si->features_len = l;
}
else
log_fatal ("Cannot handle %s\n", option);
return 1;
}
static int
sig_reason_for_revocation (const char *option, int argc, char *argv[], void *cookie)
{
struct signinfo *si = cookie;
int v;
char *tail;
if (argc < 2)
log_fatal ("Usage: %s REASON_CODE REASON_STRING\n", option);
errno = 0;
v = strtol (argv[0], &tail, 16);
if (errno || (tail && *tail) || !(0 <= v && v <= 255))
log_fatal ("%s: Invalid reason code (%s). Expected 0-255\n",
option, argv[0]);
if (si->reason_for_revocation)
log_fatal ("%s given multiple times.\n", option);
si->reason_for_revocation_code = v;
si->reason_for_revocation = xstrdup (argv[1]);
return 2;
}
static int
sig_corrupt (const char *option, int argc, char *argv[], void *cookie)
{
struct signinfo *si = cookie;
(void) option;
(void) argc;
(void) argv;
(void) cookie;
si->corrupt = 1;
return 0;
}
static struct option sig_options[] = {
{ "--issuer", sig_issuer,
"The key to use to generate the signature."},
{ "--issuer-keyid", sig_issuer_keyid,
"Set the issuer's key id. This is useful for creating a "
"self-signature. As a special case, the value \"self\" refers "
"to the primary key's key id. "
"(RFC 4880, Section 5.2.3.5)" },
{ "--pk", sig_pk,
"The primary keyas an index into the components (keys and uids) "
"created so far where the first component has the index 0." },
{ "--sk", sig_pk,
"The subkey as an index into the components (keys and uids) created "
"so far where the first component has the index 0. Only needed for "
"0x18, 0x19, and 0x28 signatures." },
{ "--user-id", sig_user_id,
"The user id as an index into the components (keys and uids) created "
"so far where the first component has the index 0. Only needed for "
"0x10-0x13 and 0x30 signatures." },
{ "--class", sig_class,
"The signature's class. Valid values are "
"0x10-0x13 (user id and primary-key certification), "
"0x18 (subkey binding), "
"0x19 (primary key binding), "
"0x1f (direct primary key signature), "
"0x20 (key revocation), "
"0x28 (subkey revocation), and "
"0x30 (certification revocation)."
},
{ "--digest", sig_digest, "The digest algorithm" },
{ "--timestamp", sig_timestamp,
"The signature's creation time. " TIMESTAMP_HELP " 0 means now. "
"(RFC 4880, Section 5.2.3.4)" },
{ "--key-expiration", sig_expiration,
"The number of days until the associated key expires. To specify "
"seconds, prefix the value with \"seconds=\". It is also possible "
"to use 'y', 'm' and 'w' as simple multipliers. For instance, 2y "
"means 2 years, etc. "
"(RFC 4880, Section 5.2.3.6)" },
{ "--cipher-algos", sig_int_list,
"A comma separated list of the preferred cipher algorithms (identified by "
"their number, see RFC 4880, Section 9). "
"(RFC 4880, Section 5.2.3.7)" },
{ "--digest-algos", sig_int_list,
"A comma separated list of the preferred algorithms (identified by "
"their number, see RFC 4880, Section 9). "
"(RFC 4880, Section 5.2.3.8)" },
{ "--compress-algos", sig_int_list,
"A comma separated list of the preferred algorithms (identified by "
"their number, see RFC 4880, Section 9)."
"(RFC 4880, Section 5.2.3.9)" },
{ "--expiration", sig_expiration,
"The number of days until the signature expires. To specify seconds, "
"prefix the value with \"seconds=\". It is also possible to use 'y', "
"'m' and 'w' as simple multipliers. For instance, 2y means 2 years, "
"etc. "
"(RFC 4880, Section 5.2.3.10)" },
{ "--exportable", sig_flag,
"Mark this signature as exportable (1) or local (0). "
"(RFC 4880, Section 5.2.3.11)" },
{ "--revocable", sig_flag,
"Mark this signature as revocable (1, revocations are ignored) "
"or non-revocable (0). "
"(RFC 4880, Section 5.2.3.12)" },
{ "--trust-level", sig_trust_level,
"Set the trust level. This takes two integer arguments (0-255): "
"the trusted-introducer level and the degree of trust. "
"(RFC 4880, Section 5.2.3.13.)" },
{ "--trust-scope", sig_string_arg,
"A regular expression that limits the scope of --trust-level. "
"(RFC 4880, Section 5.2.3.14.)" },
{ "--revocation-key", sig_revocation_key,
"Specify a designated revoker. Takes two arguments: the class "
"(normally 0x80 or 0xC0 (sensitive)) and the key id of the "
"designatured revoker. May be given multiple times. "
"(RFC 4880, Section 5.2.3.15)" },
{ "--notation", sig_notation,
"Add a human-readable notation of the form \"[!<]name=value\" where "
"\"!\" means that the critical flag should be set and \"<\" means "
"that VALUE is a file to read the data from. "
"(RFC 4880, Section 5.2.3.16)" },
{ "--notation-binary", sig_notation,
"Add a binary notation of the form \"[!<]name=value\" where "
"\"!\" means that the critical flag should be set and \"<\" means "
"that VALUE is a file to read the data from. "
"(RFC 4880, Section 5.2.3.16)" },
{ "--key-server-preferences", sig_big_endian_arg,
"Big-endian number encoding the keyserver preferences. "
"(RFC 4880, Section 5.2.3.17)" },
{ "--key-server", sig_string_arg,
"The preferred keyserver. (RFC 4880, Section 5.2.3.18)" },
{ "--primary-user-id", sig_flag,
"Sets the primary user id flag. (RFC 4880, Section 5.2.3.19)" },
{ "--policy-uri", sig_string_arg,
"URI of a document that describes the issuer's signing policy. "
"(RFC 4880, Section 5.2.3.20)" },
{ "--key-flags", sig_big_endian_arg,
"Big-endian number encoding the key flags. "
"(RFC 4880, Section 5.2.3.21)" },
{ "--signers-user-id", sig_string_arg,
"The user id (as a string) responsible for the signing. "
"(RFC 4880, Section 5.2.3.22)" },
{ "--reason-for-revocation", sig_reason_for_revocation,
"Takes two arguments: a reason for revocation code and a "
"user-provided string. "
"(RFC 4880, Section 5.2.3.23)" },
{ "--features", sig_big_endian_arg,
"Big-endian number encoding the feature flags. "
"(RFC 4880, Section 5.2.3.24)" },
{ "--signature-target", NULL,
"Takes three arguments: the target signature's public key algorithm "
" (as an integer), the hash algorithm (as an integer) and the hash "
" (as a hexadecimal string). "
"(RFC 4880, Section 5.2.3.25)" },
{ "--embedded-signature", NULL,
"An embedded signature. This must be immediately followed by a "
"signature packet (created using --signature ...) or a filename "
"containing the packet."
"(RFC 4880, Section 5.2.3.26)" },
{ "--hashed", NULL,
"The following attributes will be placed in the hashed area of "
"the signature. (This is the default and it reset at the end of"
"each signature.)" },
{ "--unhashed", NULL,
"The following attributes will be placed in the unhashed area of "
"the signature (and thus not integrity protected)." },
{ "--corrupt", sig_corrupt,
"Corrupt the signature." },
{ NULL, NULL,
"Example:\n\n"
" $ gpgcompose --public-key $KEYID --user-id USERID \\\n"
" --signature --class 0x10 --issuer $KEYID --issuer-keyid self \\\n"
" | " GPG_NAME " --list-packets"}
};
static int
mksubpkt_callback (PKT_signature *sig, void *cookie)
{
struct signinfo *si = cookie;
int i;
if (si->key_expiration)
{
char buf[4];
buf[0] = (si->key_expiration >> 24) & 0xff;
buf[1] = (si->key_expiration >> 16) & 0xff;
buf[2] = (si->key_expiration >> 8) & 0xff;
buf[3] = si->key_expiration & 0xff;
build_sig_subpkt (sig, SIGSUBPKT_KEY_EXPIRE, buf, 4);
}
if (si->cipher_algorithms)
build_sig_subpkt (sig, SIGSUBPKT_PREF_SYM,
si->cipher_algorithms,
si->cipher_algorithms_len);
if (si->digest_algorithms)
build_sig_subpkt (sig, SIGSUBPKT_PREF_HASH,
si->digest_algorithms,
si->digest_algorithms_len);
if (si->compress_algorithms)
build_sig_subpkt (sig, SIGSUBPKT_PREF_COMPR,
si->compress_algorithms,
si->compress_algorithms_len);
if (si->exportable_set)
{
char buf = si->exportable;
build_sig_subpkt (sig, SIGSUBPKT_EXPORTABLE, &buf, 1);
}
if (si->trust_level_set)
build_sig_subpkt (sig, SIGSUBPKT_TRUST,
si->trust_args, sizeof (si->trust_args));
if (si->trust_scope)
build_sig_subpkt (sig, SIGSUBPKT_REGEXP,
si->trust_scope, strlen (si->trust_scope));
for (i = 0; i < si->nrevocation_keys; i ++)
{
struct revocation_key *revkey = &si->revocation_key[i];
gpg_error_t err = keygen_add_revkey (sig, revkey);
if (err)
{
u32 keyid[2];
keyid_from_fingerprint (global_ctrl, revkey->fpr, 20, keyid);
log_fatal ("adding revocation key %s: %s\n",
keystr (keyid), gpg_strerror (err));
}
}
/* keygen_add_revkey sets revocable=0 so be sure to do this after
adding the rev keys. */
if (si->revocable_set)
{
char buf = si->revocable;
build_sig_subpkt (sig, SIGSUBPKT_REVOCABLE, &buf, 1);
}
keygen_add_notations (sig, si->notations);
if (si->key_server_preferences)
build_sig_subpkt (sig, SIGSUBPKT_KS_FLAGS,
si->key_server_preferences,
si->key_server_preferences_len);
if (si->key_server)
build_sig_subpkt (sig, SIGSUBPKT_PREF_KS,
si->key_server, strlen (si->key_server));
if (si->primary_user_id_set)
{
char buf = si->primary_user_id;
build_sig_subpkt (sig, SIGSUBPKT_PRIMARY_UID, &buf, 1);
}
if (si->policy_uri)
build_sig_subpkt (sig, SIGSUBPKT_POLICY,
si->policy_uri, strlen (si->policy_uri));
if (si->key_flags)
build_sig_subpkt (sig, SIGSUBPKT_KEY_FLAGS,
si->key_flags, si->key_flags_len);
if (si->signers_user_id)
build_sig_subpkt (sig, SIGSUBPKT_SIGNERS_UID,
si->signers_user_id, strlen (si->signers_user_id));
if (si->reason_for_revocation)
{
int len = 1 + strlen (si->reason_for_revocation);
char *buf;
buf = xmalloc (len);
buf[0] = si->reason_for_revocation_code;
memcpy (&buf[1], si->reason_for_revocation, len - 1);
build_sig_subpkt (sig, SIGSUBPKT_REVOC_REASON, buf, len);
xfree (buf);
}
if (si->features)
build_sig_subpkt (sig, SIGSUBPKT_FEATURES,
si->features, si->features_len);
return 0;
}
static int
signature (const char *option, int argc, char *argv[], void *cookie)
{
gpg_error_t err;
iobuf_t out = cookie;
struct signinfo si;
int processed;
PKT_public_key *pk;
PKT_signature *sig;
PACKET pkt;
u32 keyid_orig[2], keyid[2];
(void) option;
memset (&si, 0, sizeof (si));
memset (&pkt, 0, sizeof (pkt));
processed = process_options (option,
major_options,
sig_options, &si,
global_options, NULL,
argc, argv);
if (ncomponents)
{
int pkttype = components[ncomponents - 1].pkttype;
if (pkttype == PKT_PUBLIC_KEY)
{
if (! si.class)
/* Direct key sig. */
si.class = 0x1F;
}
else if (pkttype == PKT_PUBLIC_SUBKEY)
{
if (! si.sk)
si.sk = components[ncomponents - 1].pkt.public_key;
if (! si.class)
/* Subkey binding sig. */
si.class = 0x18;
}
else if (pkttype == PKT_USER_ID)
{
if (! si.uid)
si.uid = components[ncomponents - 1].pkt.user_id;
if (! si.class)
/* Certification of a user id and public key packet. */
si.class = 0x10;
}
}
pk = NULL;
if (! si.pk || ! si.issuer_pk)
/* No primary key specified. Default to the first one that we
find. */
{
int i;
for (i = 0; i < ncomponents; i ++)
if (components[i].pkttype == PKT_PUBLIC_KEY)
{
pk = components[i].pkt.public_key;
break;
}
}
if (! si.pk)
{
if (! pk)
log_fatal ("%s: no primary key given and no primary key available",
"--pk");
si.pk = pk;
}
if (! si.issuer_pk)
{
if (! pk)
log_fatal ("%s: no issuer key given and no primary key available",
"--issuer");
si.issuer_pk = pk;
}
if (si.class == 0x18 || si.class == 0x19 || si.class == 0x28)
/* Requires the primary key and a subkey. */
{
if (! si.sk)
log_fatal ("sig class 0x%x requires a subkey (--sk)\n", si.class);
}
else if (si.class == 0x10
|| si.class == 0x11
|| si.class == 0x12
|| si.class == 0x13
|| si.class == 0x30)
/* Requires the primary key and a user id. */
{
if (! si.uid)
log_fatal ("sig class 0x%x requires a uid (--uid)\n", si.class);
}
else if (si.class == 0x1F || si.class == 0x20)
/* Just requires the primary key. */
;
else
log_fatal ("Unsupported signature class: 0x%x\n", si.class);
sig = xmalloc_clear (sizeof (*sig));
/* Save SI.ISSUER_PK->KEYID. */
keyid_copy (keyid_orig, pk_keyid (si.issuer_pk));
if (si.issuer_keyid[0] || si.issuer_keyid[1])
keyid_copy (si.issuer_pk->keyid, si.issuer_keyid);
else if (si.issuer_keyid_self)
{
PKT_public_key *pripk = primary_key();
if (! pripk)
log_fatal ("--issuer-keyid self given, but no primary key available.\n");
keyid_copy (si.issuer_pk->keyid, pk_keyid (pripk));
}
+ /* The reuse of core gpg stuff by this tool is questionable when it
+ * requires adding extra code to the actual gpg code. It does not
+ * make sense to pass an extra parameter and in particular not given
+ * that gpg already has opt.cert_digest_algo to override it. */
+ if (si.digest_algo)
+ log_info ("note: digest algo can't be passed to make_keysig_packet\n");
+
/* Changing the issuer's key id is fragile. Check to make sure
make_keysig_packet didn't recompute the keyid. */
keyid_copy (keyid, si.issuer_pk->keyid);
err = make_keysig_packet (global_ctrl,
&sig, si.pk, si.uid, si.sk, si.issuer_pk,
- si.class, si.digest_algo,
+ si.class,
si.timestamp, si.expiration,
mksubpkt_callback, &si, NULL);
log_assert (keyid_cmp (keyid, si.issuer_pk->keyid) == 0);
if (err)
log_fatal ("Generating signature: %s\n", gpg_strerror (err));
/* Restore SI.PK->KEYID. */
keyid_copy (si.issuer_pk->keyid, keyid_orig);
if (si.corrupt)
{
/* Set the top 32-bits to 0xBAD0DEAD. */
int bits = gcry_mpi_get_nbits (sig->data[0]);
gcry_mpi_t x = gcry_mpi_new (0);
gcry_mpi_add_ui (x, x, 0xBAD0DEAD);
gcry_mpi_lshift (x, x, bits > 32 ? bits - 32 : bits);
gcry_mpi_clear_highbit (sig->data[0], bits > 32 ? bits - 32 : 0);
gcry_mpi_add (sig->data[0], sig->data[0], x);
gcry_mpi_release (x);
}
pkt.pkttype = PKT_SIGNATURE;
pkt.pkt.signature = sig;
err = build_packet (out, &pkt);
if (err)
log_fatal ("serializing public key packet: %s\n", gpg_strerror (err));
debug ("Wrote signature packet:\n");
dump_component (&pkt);
free_seckey_enc (sig);
release_kbnode (si.issuer_kb);
xfree (si.revocation_key);
return processed;
}
struct sk_esk_info
{
/* The cipher used for encrypting the session key (when a session
key is used). */
int cipher;
/* The cipher used for encryping the SED packet. */
int sed_cipher;
/* S2K related data. */
int hash;
int mode;
int mode_set;
byte salt[8];
int salt_set;
int iterations;
/* If applying the S2K function to the passphrase is the session key
or if it is the decryption key for the session key. */
int s2k_is_session_key;
/* Generate a new, random session key. */
int new_session_key;
/* The unencrypted session key. */
int session_key_len;
char *session_key;
char *password;
};
static int
sk_esk_cipher (const char *option, int argc, char *argv[], void *cookie)
{
struct sk_esk_info *si = cookie;
char *usage = "integer|IDEA|3DES|CAST5|BLOWFISH|AES|AES192|AES256|CAMELLIA128|CAMELLIA192|CAMELLIA256";
int cipher;
if (argc == 0)
log_fatal ("Usage: %s %s\n", option, usage);
if (strcasecmp (argv[0], "IDEA") == 0)
cipher = CIPHER_ALGO_IDEA;
else if (strcasecmp (argv[0], "3DES") == 0)
cipher = CIPHER_ALGO_3DES;
else if (strcasecmp (argv[0], "CAST5") == 0)
cipher = CIPHER_ALGO_CAST5;
else if (strcasecmp (argv[0], "BLOWFISH") == 0)
cipher = CIPHER_ALGO_BLOWFISH;
else if (strcasecmp (argv[0], "AES") == 0)
cipher = CIPHER_ALGO_AES;
else if (strcasecmp (argv[0], "AES192") == 0)
cipher = CIPHER_ALGO_AES192;
else if (strcasecmp (argv[0], "TWOFISH") == 0)
cipher = CIPHER_ALGO_TWOFISH;
else if (strcasecmp (argv[0], "CAMELLIA128") == 0)
cipher = CIPHER_ALGO_CAMELLIA128;
else if (strcasecmp (argv[0], "CAMELLIA192") == 0)
cipher = CIPHER_ALGO_CAMELLIA192;
else if (strcasecmp (argv[0], "CAMELLIA256") == 0)
cipher = CIPHER_ALGO_CAMELLIA256;
else
{
char *tail;
int v;
errno = 0;
v = strtol (argv[0], &tail, 0);
if (errno || (tail && *tail) || ! valid_cipher (v))
log_fatal ("Invalid or unsupported value. Usage: %s %s\n",
option, usage);
cipher = v;
}
if (strcmp (option, "--cipher") == 0)
{
if (si->cipher)
log_fatal ("%s given multiple times.", option);
si->cipher = cipher;
}
else if (strcmp (option, "--sed-cipher") == 0)
{
if (si->sed_cipher)
log_fatal ("%s given multiple times.", option);
si->sed_cipher = cipher;
}
return 1;
}
static int
sk_esk_mode (const char *option, int argc, char *argv[], void *cookie)
{
struct sk_esk_info *si = cookie;
char *usage = "integer|simple|salted|iterated";
if (argc == 0)
log_fatal ("Usage: %s %s\n", option, usage);
if (si->mode)
log_fatal ("%s given multiple times.", option);
if (strcasecmp (argv[0], "simple") == 0)
si->mode = 0;
else if (strcasecmp (argv[0], "salted") == 0)
si->mode = 1;
else if (strcasecmp (argv[0], "iterated") == 0)
si->mode = 3;
else
{
char *tail;
int v;
errno = 0;
v = strtol (argv[0], &tail, 0);
if (errno || (tail && *tail) || ! (v == 0 || v == 1 || v == 3))
log_fatal ("Invalid or unsupported value. Usage: %s %s\n",
option, usage);
si->mode = v;
}
si->mode_set = 1;
return 1;
}
static int
sk_esk_hash_algorithm (const char *option, int argc, char *argv[], void *cookie)
{
struct sk_esk_info *si = cookie;
char *usage = "integer|MD5|SHA1|RMD160|SHA256|SHA384|SHA512|SHA224";
if (argc == 0)
log_fatal ("Usage: %s %s\n", option, usage);
if (si->hash)
log_fatal ("%s given multiple times.", option);
if (strcasecmp (argv[0], "MD5") == 0)
si->hash = DIGEST_ALGO_MD5;
else if (strcasecmp (argv[0], "SHA1") == 0)
si->hash = DIGEST_ALGO_SHA1;
else if (strcasecmp (argv[0], "RMD160") == 0)
si->hash = DIGEST_ALGO_RMD160;
else if (strcasecmp (argv[0], "SHA256") == 0)
si->hash = DIGEST_ALGO_SHA256;
else if (strcasecmp (argv[0], "SHA384") == 0)
si->hash = DIGEST_ALGO_SHA384;
else if (strcasecmp (argv[0], "SHA512") == 0)
si->hash = DIGEST_ALGO_SHA512;
else if (strcasecmp (argv[0], "SHA224") == 0)
si->hash = DIGEST_ALGO_SHA224;
else
{
char *tail;
int v;
errno = 0;
v = strtol (argv[0], &tail, 0);
if (errno || (tail && *tail)
|| ! (v == DIGEST_ALGO_MD5
|| v == DIGEST_ALGO_SHA1
|| v == DIGEST_ALGO_RMD160
|| v == DIGEST_ALGO_SHA256
|| v == DIGEST_ALGO_SHA384
|| v == DIGEST_ALGO_SHA512
|| v == DIGEST_ALGO_SHA224))
log_fatal ("Invalid or unsupported value. Usage: %s %s\n",
option, usage);
si->hash = v;
}
return 1;
}
static int
sk_esk_salt (const char *option, int argc, char *argv[], void *cookie)
{
struct sk_esk_info *si = cookie;
char *usage = "16-HEX-CHARACTERS";
char *p = argv[0];
if (argc == 0)
log_fatal ("Usage: %s %s\n", option, usage);
if (si->salt_set)
log_fatal ("%s given multiple times.", option);
if (p[0] == '0' && p[1] == 'x')
p += 2;
if (strlen (p) != 16)
log_fatal ("%s: Salt must be exactly 16 hexadecimal characters (have: %zd)\n",
option, strlen (p));
if (hex2bin (p, si->salt, sizeof (si->salt)) == -1)
log_fatal ("%s: Salt must only contain hexadecimal characters\n",
option);
si->salt_set = 1;
return 1;
}
static int
sk_esk_iterations (const char *option, int argc, char *argv[], void *cookie)
{
struct sk_esk_info *si = cookie;
char *usage = "ITERATION-COUNT";
char *tail;
int v;
if (argc == 0)
log_fatal ("Usage: %s %s\n", option, usage);
errno = 0;
v = strtol (argv[0], &tail, 0);
if (errno || (tail && *tail) || v < 0)
log_fatal ("%s: Non-negative integer expected.\n", option);
si->iterations = v;
return 1;
}
static int
sk_esk_session_key (const char *option, int argc, char *argv[], void *cookie)
{
struct sk_esk_info *si = cookie;
char *usage = "HEX-CHARACTERS|auto|none";
char *p = argv[0];
struct session_key sk;
if (argc == 0)
log_fatal ("Usage: %s %s\n", option, usage);
if (si->session_key || si->s2k_is_session_key
|| si->new_session_key)
log_fatal ("%s given multiple times.", option);
if (strcasecmp (p, "none") == 0)
{
si->s2k_is_session_key = 1;
return 1;
}
if (strcasecmp (p, "new") == 0)
{
si->new_session_key = 1;
return 1;
}
if (strcasecmp (p, "auto") == 0)
return 1;
sk = parse_session_key (option, p, 0);
if (si->session_key)
log_fatal ("%s given multiple times.", option);
if (sk.algo)
si->sed_cipher = sk.algo;
si->session_key_len = sk.keylen;
si->session_key = sk.key;
return 1;
}
static int
sk_esk_password (const char *option, int argc, char *argv[], void *cookie)
{
struct sk_esk_info *si = cookie;
char *usage = "PASSWORD";
if (argc == 0)
log_fatal ("Usage: --sk-esk %s\n", usage);
if (si->password)
log_fatal ("%s given multiple times.", option);
si->password = xstrdup (argv[0]);
return 1;
}
static struct option sk_esk_options[] = {
{ "--cipher", sk_esk_cipher,
"The encryption algorithm for encrypting the session key. "
"One of IDEA, 3DES, CAST5, BLOWFISH, AES (default), AES192, "
"AES256, TWOFISH, CAMELLIA128, CAMELLIA192, or CAMELLIA256." },
{ "--sed-cipher", sk_esk_cipher,
"The encryption algorithm for encrypting the SED packet. "
"One of IDEA, 3DES, CAST5, BLOWFISH, AES, AES192, "
"AES256 (default), TWOFISH, CAMELLIA128, CAMELLIA192, or CAMELLIA256." },
{ "--mode", sk_esk_mode,
"The S2K mode. Either one of the strings \"simple\", \"salted\" "
"or \"iterated\" or an integer." },
{ "--hash", sk_esk_hash_algorithm,
"The hash algorithm to used to derive the key. One of "
"MD5, SHA1 (default), RMD160, SHA256, SHA384, SHA512, or SHA224." },
{ "--salt", sk_esk_salt,
"The S2K salt encoded as 16 hexadecimal characters. One needed "
"if the S2K function is in salted or iterated mode." },
{ "--iterations", sk_esk_iterations,
"The iteration count. If not provided, a reasonable value is chosen. "
"Note: due to the encoding scheme, not every value is valid. For "
"convenience, the provided value will be rounded appropriately. "
"Only needed if the S2K function is in iterated mode." },
{ "--session-key", sk_esk_session_key,
"The session key to be encrypted by the S2K function as a hexadecimal "
"string. If this is \"new\", then a new session key is generated."
"If this is \"auto\", then either the last session key is "
"used, if the was none, one is generated. If this is \"none\", then "
"the session key is the result of applying the S2K algorithms to the "
"password. The session key may be prefaced with an integer and a colon "
"to indicate the cipher to use for the SED packet (making --sed-cipher "
"unnecessary and allowing the direct use of the result of "
"\"" GPG_NAME " --show-session-key\")." },
{ "", sk_esk_password, "The password." },
{ NULL, NULL,
"Example:\n\n"
" $ gpgcompose --sk-esk foobar --encrypted \\\n"
" --literal --value foo | " GPG_NAME " --list-packets" }
};
static int
sk_esk (const char *option, int argc, char *argv[], void *cookie)
{
iobuf_t out = cookie;
gpg_error_t err;
int processed;
struct sk_esk_info si;
DEK sesdek;
DEK s2kdek;
PKT_symkey_enc *ske;
PACKET pkt;
memset (&si, 0, sizeof (si));
processed = process_options (option,
major_options,
sk_esk_options, &si,
global_options, NULL,
argc, argv);
if (! si.password)
log_fatal ("%s: missing password. Usage: %s PASSWORD", option, option);
/* Fill in defaults, if appropriate. */
if (! si.cipher)
si.cipher = CIPHER_ALGO_AES;
if (! si.sed_cipher)
si.sed_cipher = CIPHER_ALGO_AES256;
if (! si.hash)
si.hash = DIGEST_ALGO_SHA1;
if (! si.mode_set)
/* Salted and iterated. */
si.mode = 3;
if (si.mode != 0 && ! si.salt_set)
/* Generate a salt. */
gcry_randomize (si.salt, 8, GCRY_STRONG_RANDOM);
if (si.mode == 0)
{
if (si.iterations)
log_info ("%s: --iterations provided, but not used for mode=0\n",
option);
si.iterations = 0;
}
else if (! si.iterations)
si.iterations = 10000;
memset (&sesdek, 0, sizeof (sesdek));
/* The session key is used to encrypt the SED packet. */
sesdek.algo = si.sed_cipher;
if (si.session_key)
/* Copy the unencrypted session key into SESDEK. */
{
sesdek.keylen = openpgp_cipher_get_algo_keylen (sesdek.algo);
if (sesdek.keylen != si.session_key_len)
log_fatal ("%s: Cipher algorithm requires a %d byte session key, but provided session key is %d bytes.",
option, sesdek.keylen, si.session_key_len);
log_assert (sesdek.keylen <= sizeof (sesdek.key));
memcpy (sesdek.key, si.session_key, sesdek.keylen);
}
else if (! si.s2k_is_session_key || si.new_session_key)
/* We need a session key, but one wasn't provided. Generate it. */
make_session_key (&sesdek);
/* The encrypted session key needs 1 + SESDEK.KEYLEN bytes of
space. */
ske = xmalloc_clear (sizeof (*ske) + sesdek.keylen);
ske->version = 4;
ske->cipher_algo = si.cipher;
ske->s2k.mode = si.mode;
ske->s2k.hash_algo = si.hash;
log_assert (sizeof (si.salt) == sizeof (ske->s2k.salt));
memcpy (ske->s2k.salt, si.salt, sizeof (ske->s2k.salt));
if (! si.s2k_is_session_key)
{
if (!si.iterations)
ske->s2k.count = encode_s2k_iterations (agent_get_s2k_count ());
else
ske->s2k.count = encode_s2k_iterations (si.iterations);
}
/* Derive the symmetric key that is either the session key or the
key used to encrypt the session key. */
memset (&s2kdek, 0, sizeof (s2kdek));
s2kdek.algo = si.cipher;
s2kdek.keylen = openpgp_cipher_get_algo_keylen (s2kdek.algo);
err = gcry_kdf_derive (si.password, strlen (si.password),
ske->s2k.mode == 3 ? GCRY_KDF_ITERSALTED_S2K
: ske->s2k.mode == 1 ? GCRY_KDF_SALTED_S2K
: GCRY_KDF_SIMPLE_S2K,
ske->s2k.hash_algo, ske->s2k.salt, 8,
S2K_DECODE_COUNT (ske->s2k.count),
/* The size of the desired key and its
buffer. */
s2kdek.keylen, s2kdek.key);
if (err)
log_fatal ("gcry_kdf_derive failed: %s", gpg_strerror (err));
if (si.s2k_is_session_key)
{
ske->seskeylen = 0;
session_key = s2kdek;
}
else
/* Encrypt the session key using the s2k specifier. */
{
DEK *sesdekp = &sesdek;
void *enckey;
size_t enckeylen;
/* Now encrypt the session key (or rather, the algorithm used to
encrypt the SKESK plus the session key) using S2KDEK. */
err = encrypt_seskey (&s2kdek, 0, &sesdekp, &enckey, &enckeylen);
if (err)
log_fatal ("encrypt_seskey failed: %s\n", gpg_strerror (err));
if (enckeylen - 1 > sesdek.keylen)
log_fatal ("key size is too big: %zu\n", enckeylen);
else
{
ske->seskeylen = (byte)enckeylen;
memcpy (ske->seskey, enckey, enckeylen);
}
/* Save the session key for later. */
session_key = sesdek;
xfree (enckey);
}
pkt.pkttype = PKT_SYMKEY_ENC;
pkt.pkt.symkey_enc = ske;
err = build_packet (out, &pkt);
if (err)
log_fatal ("Serializing sym-key encrypted packet: %s\n",
gpg_strerror (err));
debug ("Wrote sym-key encrypted packet:\n");
dump_component (&pkt);
xfree (si.session_key);
xfree (si.password);
xfree (ske);
return processed;
}
struct pk_esk_info
{
int session_key_set;
int new_session_key;
int sed_cipher;
int session_key_len;
char *session_key;
int throw_keyid;
char *keyid;
};
static int
pk_esk_session_key (const char *option, int argc, char *argv[], void *cookie)
{
struct pk_esk_info *pi = cookie;
char *usage = "HEX-CHARACTERS|auto|none";
char *p = argv[0];
struct session_key sk;
if (argc == 0)
log_fatal ("Usage: %s %s\n", option, usage);
if (pi->session_key_set)
log_fatal ("%s given multiple times.", option);
pi->session_key_set = 1;
if (strcasecmp (p, "new") == 0)
{
pi->new_session_key = 1;
return 1;
}
if (strcasecmp (p, "auto") == 0)
return 1;
sk = parse_session_key (option, p, 0);
if (pi->session_key)
log_fatal ("%s given multiple times.", option);
if (sk.algo)
pi->sed_cipher = sk.algo;
pi->session_key_len = sk.keylen;
pi->session_key = sk.key;
return 1;
}
static int
pk_esk_throw_keyid (const char *option, int argc, char *argv[], void *cookie)
{
struct pk_esk_info *pi = cookie;
(void) option;
(void) argc;
(void) argv;
pi->throw_keyid = 1;
return 0;
}
static int
pk_esk_keyid (const char *option, int argc, char *argv[], void *cookie)
{
struct pk_esk_info *pi = cookie;
char *usage = "KEYID";
if (argc == 0)
log_fatal ("Usage: %s %s\n", option, usage);
if (pi->keyid)
log_fatal ("Multiple key ids given, but only one is allowed.");
pi->keyid = xstrdup (argv[0]);
return 1;
}
static struct option pk_esk_options[] = {
{ "--session-key", pk_esk_session_key,
"The session key to be encrypted by the S2K function as a hexadecimal "
"string. If this is not given or is \"auto\", then the current "
"session key is used. If there is no session key or this is \"new\", "
"then a new session key is generated. The session key may be "
"prefaced with an integer and a colon to indicate the cipher to use "
"for the SED packet (making --sed-cipher unnecessary and allowing the "
"direct use of the result of \"" GPG_NAME " --show-session-key\")." },
{ "--throw-keyid", pk_esk_throw_keyid,
"Throw the keyid." },
{ "", pk_esk_keyid, "The key id." },
{ NULL, NULL,
"Example:\n\n"
" $ gpgcompose --pk-esk $KEYID --encrypted --literal --value foo \\\n"
" | " GPG_NAME " --list-packets"}
};
static int
pk_esk (const char *option, int argc, char *argv[], void *cookie)
{
iobuf_t out = cookie;
gpg_error_t err;
int processed;
struct pk_esk_info pi;
PKT_public_key pk;
memset (&pi, 0, sizeof (pi));
processed = process_options (option,
major_options,
pk_esk_options, &pi,
global_options, NULL,
argc, argv);
if (! pi.keyid)
log_fatal ("%s: missing keyid. Usage: %s KEYID", option, option);
memset (&pk, 0, sizeof (pk));
pk.req_usage = PUBKEY_USAGE_ENC;
- err = get_pubkey_byname (NULL, NULL, &pk, pi.keyid, NULL, NULL, 1, 1);
+ err = get_pubkey_byname (NULL, GET_PUBKEY_NO_AKL,
+ NULL, &pk, pi.keyid, NULL, NULL, 1);
if (err)
log_fatal ("%s: looking up key %s: %s\n",
option, pi.keyid, gpg_strerror (err));
if (pi.sed_cipher)
/* Have a session key. */
{
session_key.algo = pi.sed_cipher;
session_key.keylen = pi.session_key_len;
log_assert (session_key.keylen <= sizeof (session_key.key));
memcpy (session_key.key, pi.session_key, session_key.keylen);
}
if (pi.new_session_key || ! session_key.algo)
{
if (! pi.new_session_key)
/* Default to AES256. */
session_key.algo = CIPHER_ALGO_AES256;
make_session_key (&session_key);
}
err = write_pubkey_enc (global_ctrl, &pk, pi.throw_keyid, &session_key, out);
if (err)
log_fatal ("%s: writing pk_esk packet for %s: %s\n",
option, pi.keyid, gpg_strerror (err));
debug ("Wrote pk_esk packet for %s\n", pi.keyid);
xfree (pi.keyid);
xfree (pi.session_key);
return processed;
}
struct encinfo
{
int saw_session_key;
};
static int
encrypted_session_key (const char *option, int argc, char *argv[], void *cookie)
{
struct encinfo *ei = cookie;
char *usage = "HEX-CHARACTERS|auto";
char *p = argv[0];
struct session_key sk;
if (argc == 0)
log_fatal ("Usage: %s %s\n", option, usage);
if (ei->saw_session_key)
log_fatal ("%s given multiple times.", option);
ei->saw_session_key = 1;
if (strcasecmp (p, "auto") == 0)
return 1;
sk = parse_session_key (option, p, 1);
session_key.algo = sk.algo;
log_assert (sk.keylen <= sizeof (session_key.key));
memcpy (session_key.key, sk.key, sk.keylen);
xfree (sk.key);
return 1;
}
static struct option encrypted_options[] = {
{ "--session-key", encrypted_session_key,
"The session key to be encrypted by the S2K function as a hexadecimal "
"string. If this is not given or is \"auto\", then the last session key "
"is used. If there was none, then an error is raised. The session key "
"must be prefaced with an integer and a colon to indicate the cipher "
"to use (this is format used by \"" GPG_NAME " --show-session-key\")." },
{ NULL, NULL,
"After creating the packet, this command clears the current "
"session key.\n\n"
"Example: nested encryption packets:\n\n"
" $ gpgcompose --sk-esk foo --encrypted-mdc \\\n"
" --sk-esk bar --encrypted-mdc \\\n"
" --literal --value 123 --encrypted-pop --encrypted-pop | " GPG_NAME" -d" }
};
static int
encrypted (const char *option, int argc, char *argv[], void *cookie)
{
iobuf_t out = cookie;
int processed;
struct encinfo ei;
PKT_encrypted e;
cipher_filter_context_t *cfx;
memset (&ei, 0, sizeof (ei));
processed = process_options (option,
major_options,
encrypted_options, &ei,
global_options, NULL,
argc, argv);
if (! session_key.algo)
log_fatal ("%s: no session key configured\n"
" (use e.g. --sk-esk PASSWORD or --pk-esk KEYID).\n",
option);
memset (&e, 0, sizeof (e));
/* We only need to set E->LEN, E->EXTRALEN (if E->LEN is not
0), and E->NEW_CTB. */
e.len = 0;
e.new_ctb = 1;
/* Register the cipher filter. */
cfx = xmalloc_clear (sizeof (*cfx));
/* Copy the session key. */
cfx->dek = xmalloc (sizeof (*cfx->dek));
*cfx->dek = session_key;
if (do_debug)
{
char *buf;
buf = xmalloc (2 * session_key.keylen + 1);
debug ("session key: algo: %d; keylen: %d; key: %s\n",
session_key.algo, session_key.keylen,
bin2hex (session_key.key, session_key.keylen, buf));
xfree (buf);
}
if (strcmp (option, "--encrypted-mdc") == 0)
cfx->dek->use_mdc = 1;
else if (strcmp (option, "--encrypted") == 0)
cfx->dek->use_mdc = 0;
else
log_fatal ("%s: option not handled by this function!\n", option);
cfx->datalen = 0;
filter_push (out, cipher_filter_cfb, cfx, PKT_ENCRYPTED, cfx->datalen == 0);
debug ("Wrote encrypted packet:\n");
/* Clear the current session key. */
memset (&session_key, 0, sizeof (session_key));
return processed;
}
static struct option encrypted_pop_options[] = {
{ NULL, NULL,
"Example:\n\n"
" $ gpgcompose --sk-esk PASSWORD \\\n"
" --encrypted-mdc \\\n"
" --literal --value foo \\\n"
" --encrypted-pop | " GPG_NAME " --list-packets" }
};
static int
encrypted_pop (const char *option, int argc, char *argv[], void *cookie)
{
iobuf_t out = cookie;
int processed;
processed = process_options (option,
major_options,
encrypted_pop_options,
NULL,
global_options, NULL,
argc, argv);
/* We only support a single option, --help, which causes the program
* to exit. */
log_assert (processed == 0);
filter_pop (out, PKT_ENCRYPTED);
debug ("Popped encryption container.\n");
return processed;
}
struct data
{
int file;
union
{
char *data;
char *filename;
};
struct data *next;
};
/* This must be the first member of the struct to be able to use
add_value! */
struct datahead
{
struct data *head;
struct data **last_next;
};
static int
add_value (const char *option, int argc, char *argv[], void *cookie)
{
struct datahead *dh = cookie;
struct data *d = xmalloc_clear (sizeof (struct data));
d->file = strcmp ("--file", option) == 0;
if (! d->file)
log_assert (strcmp ("--value", option) == 0);
if (argc == 0)
{
if (d->file)
log_fatal ("Usage: %s FILENAME\n", option);
else
log_fatal ("Usage: %s STRING\n", option);
}
if (! dh->last_next)
/* First time through. Initialize DH->LAST_NEXT. */
{
log_assert (! dh->head);
dh->last_next = &dh->head;
}
if (d->file)
d->filename = argv[0];
else
d->data = argv[0];
/* Append it. */
*dh->last_next = d;
dh->last_next = &d->next;
return 1;
}
struct litinfo
{
/* This must be the first element for add_value to work! */
struct datahead data;
int timestamp_set;
u32 timestamp;
char mode;
int partial_body_length_encoding;
char *name;
};
static int
literal_timestamp (const char *option, int argc, char *argv[], void *cookie)
{
struct litinfo *li = cookie;
char *tail = NULL;
if (argc == 0)
log_fatal ("Usage: %s TIMESTAMP\n", option);
errno = 0;
li->timestamp = parse_timestamp (argv[0], &tail);
if (errno || (tail && *tail))
log_fatal ("Invalid value passed to %s (%s)\n", option, argv[0]);
li->timestamp_set = 1;
return 1;
}
static int
literal_mode (const char *option, int argc, char *argv[], void *cookie)
{
struct litinfo *li = cookie;
if (argc == 0
|| ! (strcmp (argv[0], "b") == 0
|| strcmp (argv[0], "t") == 0
|| strcmp (argv[0], "u") == 0))
log_fatal ("Usage: %s [btu]\n", option);
li->mode = argv[0][0];
return 1;
}
static int
literal_partial_body_length (const char *option, int argc, char *argv[],
void *cookie)
{
struct litinfo *li = cookie;
char *tail;
int v;
int range[2] = {0, 1};
if (argc <= 1)
log_fatal ("Usage: %s [0|1]\n", option);
errno = 0;
v = strtol (argv[0], &tail, 0);
if (errno || (tail && *tail) || !(range[0] <= v && v <= range[1]))
log_fatal ("Invalid value passed to %s (%s). Expected %d-%d\n",
option, argv[0], range[0], range[1]);
li->partial_body_length_encoding = v;
return 1;
}
static int
literal_name (const char *option, int argc, char *argv[], void *cookie)
{
struct litinfo *li = cookie;
if (argc <= 0)
log_fatal ("Usage: %s NAME\n", option);
if (strlen (argv[0]) > 255)
log_fatal ("%s: name is too long (%zd > 255 characters).\n",
option, strlen (argv[0]));
li->name = argv[0];
return 1;
}
static struct option literal_options[] = {
{ "--value", add_value,
"A string to store in the literal packet." },
{ "--file", add_value,
"A file to copy into the literal packet." },
{ "--timestamp", literal_timestamp,
"The literal packet's time stamp. This defaults to the current time." },
{ "--mode", literal_mode,
"The content's mode (normally 'b' (default), 't' or 'u')." },
{ "--partial-body-length", literal_partial_body_length,
"Force partial body length encoding." },
{ "--name", literal_name,
"The literal's name." },
{ NULL, NULL,
"Example:\n\n"
" $ gpgcompose --literal --value foobar | " GPG_NAME " -d"}
};
static int
literal (const char *option, int argc, char *argv[], void *cookie)
{
iobuf_t out = cookie;
gpg_error_t err;
int processed;
struct litinfo li;
PKT_plaintext *pt;
PACKET pkt;
struct data *data;
memset (&li, 0, sizeof (li));
processed = process_options (option,
major_options,
literal_options, &li,
global_options, NULL,
argc, argv);
if (! li.data.head)
log_fatal ("%s: no data provided (use --value or --file)", option);
pt = xmalloc_clear (sizeof (*pt) + (li.name ? strlen (li.name) : 0));
pt->new_ctb = 1;
if (li.timestamp_set)
pt->timestamp = li.timestamp;
else
/* Default to the current time. */
pt->timestamp = make_timestamp ();
pt->mode = li.mode;
if (! pt->mode)
/* Default to binary. */
pt->mode = 'b';
if (li.name)
{
strcpy (pt->name, li.name);
pt->namelen = strlen (pt->name);
}
pkt.pkttype = PKT_PLAINTEXT;
pkt.pkt.plaintext = pt;
if (! li.partial_body_length_encoding)
/* Compute the amount of data. */
{
pt->len = 0;
for (data = li.data.head; data; data = data->next)
{
if (data->file)
{
iobuf_t in;
int overflow;
off_t off;
in = iobuf_open (data->filename);
if (! in)
/* An error opening the file. We do error handling
below so just break here. */
{
pt->len = 0;
break;
}
off = iobuf_get_filelength (in, &overflow);
iobuf_close (in);
if (overflow || off == 0)
/* Length is unknown or there was an error
(unfortunately, iobuf_get_filelength doesn't
distinguish between 0 length files and an error!).
Fall back to partial body mode. */
{
pt->len = 0;
break;
}
pt->len += off;
}
else
pt->len += strlen (data->data);
}
}
err = build_packet (out, &pkt);
if (err)
log_fatal ("Serializing literal packet: %s\n", gpg_strerror (err));
/* Write out the data. */
for (data = li.data.head; data; data = data->next)
{
if (data->file)
{
iobuf_t in;
errno = 0;
in = iobuf_open (data->filename);
if (! in)
log_fatal ("Opening '%s': %s\n",
data->filename,
errno ? strerror (errno): "unknown error");
iobuf_copy (out, in);
if (iobuf_error (in))
log_fatal ("Reading from %s: %s\n",
data->filename,
gpg_strerror (iobuf_error (in)));
if (iobuf_error (out))
log_fatal ("Writing literal data from %s: %s\n",
data->filename,
gpg_strerror (iobuf_error (out)));
iobuf_close (in);
}
else
{
err = iobuf_write (out, data->data, strlen (data->data));
if (err)
log_fatal ("Writing literal data: %s\n", gpg_strerror (err));
}
}
if (! pt->len)
{
/* Disable partial body length mode. */
log_assert (pt->new_ctb == 1);
iobuf_set_partial_body_length_mode (out, 0);
}
debug ("Wrote literal packet:\n");
dump_component (&pkt);
while (li.data.head)
{
data = li.data.head->next;
xfree (li.data.head);
li.data.head = data;
}
xfree (pt);
return processed;
}
static int
copy_file (const char *option, int argc, char *argv[], void *cookie)
{
char **filep = cookie;
if (argc == 0)
log_fatal ("Usage: %s FILENAME\n", option);
*filep = argv[0];
return 1;
}
static struct option copy_options[] = {
{ "", copy_file, "Copy the specified file to stdout." },
{ NULL, NULL,
"Example:\n\n"
" $ gpgcompose --copy /etc/hostname\n\n"
"This is particularly useful when combined with gpgsplit." }
};
static int
copy (const char *option, int argc, char *argv[], void *cookie)
{
iobuf_t out = cookie;
char *file = NULL;
iobuf_t in;
int processed;
processed = process_options (option,
major_options,
copy_options, &file,
global_options, NULL,
argc, argv);
if (! file)
log_fatal ("Usage: %s FILE\n", option);
errno = 0;
in = iobuf_open (file);
if (! in)
log_fatal ("Error opening %s: %s.\n",
file, errno ? strerror (errno): "unknown error");
iobuf_copy (out, in);
if (iobuf_error (out))
log_fatal ("Copying data to destination: %s\n",
gpg_strerror (iobuf_error (out)));
if (iobuf_error (in))
log_fatal ("Reading data from %s: %s\n",
argv[0], gpg_strerror (iobuf_error (in)));
iobuf_close (in);
return processed;
}
int
main (int argc, char *argv[])
{
const char *filename = "-";
iobuf_t out;
int preprocessed = 1;
int processed;
ctrl_t ctrl;
opt.ignore_time_conflict = 1;
/* Allow notations in the IETF space, for instance. */
opt.expert = 1;
global_ctrl = ctrl = xcalloc (1, sizeof *ctrl);
keydb_add_resource ("pubring" EXTSEP_S GPGEXT_GPG,
KEYDB_RESOURCE_FLAG_DEFAULT);
if (argc == 1)
/* Nothing to do. */
return 0;
if (strcmp (argv[1], "--output") == 0
|| strcmp (argv[1], "-o") == 0)
{
filename = argv[2];
log_info ("Writing to %s\n", filename);
preprocessed += 2;
}
out = iobuf_create (filename, 0);
if (! out)
log_fatal ("Failed to open stdout for writing\n");
processed = process_options (NULL, NULL,
major_options, out,
global_options, NULL,
argc - preprocessed, &argv[preprocessed]);
if (processed != argc - preprocessed)
log_fatal ("Didn't process %d options.\n", argc - preprocessed - processed);
iobuf_close (out);
return 0;
}
/* Stubs duplicated from gpg.c. */
int g10_errors_seen = 0;
/* Note: This function is used by signal handlers!. */
static void
emergency_cleanup (void)
{
gcry_control (GCRYCTL_TERM_SECMEM );
}
void
g10_exit( int rc )
{
gcry_control (GCRYCTL_UPDATE_RANDOM_SEED_FILE);
emergency_cleanup ();
rc = rc? rc : log_get_errorcount(0)? 2 : g10_errors_seen? 1 : 0;
exit (rc);
}
void
keyedit_menu (ctrl_t ctrl, const char *username, strlist_t locusr,
strlist_t commands, int quiet, int seckey_check)
{
(void) ctrl;
(void) username;
(void) locusr;
(void) commands;
(void) quiet;
(void) seckey_check;
}
void
show_basic_key_info (ctrl_t ctrl, KBNODE keyblock, int made_from_sec)
{
(void)ctrl;
(void)keyblock;
(void)made_from_sec;
}
int
keyedit_print_one_sig (ctrl_t ctrl, estream_t fp,
int rc, kbnode_t keyblock, kbnode_t node,
int *inv_sigs, int *no_key, int *oth_err,
int is_selfsig, int print_without_key, int extended)
{
(void) ctrl;
(void) fp;
(void) rc;
(void) keyblock;
(void) node;
(void) inv_sigs;
(void) no_key;
(void) oth_err;
(void) is_selfsig;
(void) print_without_key;
(void) extended;
return 0;
}
diff --git a/g10/import.c b/g10/import.c
index c2a1dd033..c32dbf059 100644
--- a/g10/import.c
+++ b/g10/import.c
@@ -1,4374 +1,4534 @@
/* import.c - import a key into our key storage.
* Copyright (C) 1998-2007, 2010-2011 Free Software Foundation, Inc.
* Copyright (C) 2014, 2016, 2017, 2019 Werner Koch
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include "gpg.h"
#include "options.h"
#include "packet.h"
#include "../common/status.h"
#include "keydb.h"
#include "../common/util.h"
#include "trustdb.h"
#include "main.h"
#include "../common/i18n.h"
#include "../common/ttyio.h"
#include "../common/recsel.h"
#include "keyserver-internal.h"
#include "call-agent.h"
#include "../common/membuf.h"
#include "../common/init.h"
#include "../common/mbox-util.h"
#include "key-check.h"
#include "key-clean.h"
struct import_stats_s
{
ulong count;
ulong no_user_id;
ulong imported;
ulong n_uids;
ulong n_sigs;
ulong n_subk;
ulong unchanged;
ulong n_revoc;
ulong secret_read;
ulong secret_imported;
ulong secret_dups;
ulong skipped_new_keys;
ulong not_imported;
ulong n_sigs_cleaned;
ulong n_uids_cleaned;
ulong v3keys; /* Number of V3 keys seen. */
};
/* Node flag to indicate that a user ID or a subkey has a
* valid self-signature. */
#define NODE_GOOD_SELFSIG 1
/* Node flag to indicate that a user ID or subkey has
* an invalid self-signature. */
#define NODE_BAD_SELFSIG 2
/* Node flag to indicate that the node shall be deleted. */
#define NODE_DELETION_MARK 4
/* A node flag used to temporary mark a node. */
#define NODE_FLAG_A 8
/* A flag used by transfer_secret_keys. */
#define NODE_TRANSFER_SECKEY 16
/* An object and a global instance to store selectors created from
* --import-filter keep-uid=EXPR.
* --import-filter drop-sig=EXPR.
*
* FIXME: We should put this into the CTRL object but that requires a
* lot more changes right now. For now we use save and restore
* function to temporary change them.
*/
/* Definition of the import filters. */
struct import_filter_s
{
recsel_expr_t keep_uid;
recsel_expr_t drop_sig;
};
/* The current instance. */
struct import_filter_s import_filter;
static int import (ctrl_t ctrl,
IOBUF inp, const char* fname, struct import_stats_s *stats,
unsigned char **fpr, size_t *fpr_len, unsigned int options,
import_screener_t screener, void *screener_arg,
int origin, const char *url);
-static int read_block (IOBUF a, int with_meta,
+static int read_block (IOBUF a, unsigned int options,
PACKET **pending_pkt, kbnode_t *ret_root, int *r_v3keys);
static void revocation_present (ctrl_t ctrl, kbnode_t keyblock);
static gpg_error_t import_one (ctrl_t ctrl,
kbnode_t keyblock,
struct import_stats_s *stats,
unsigned char **fpr, size_t *fpr_len,
unsigned int options, int from_sk, int silent,
import_screener_t screener, void *screener_arg,
int origin, const char *url, int *r_valid);
static gpg_error_t import_matching_seckeys (
ctrl_t ctrl, kbnode_t seckeys,
const byte *mainfpr, size_t mainfprlen,
struct import_stats_s *stats, int batch);
static gpg_error_t import_secret_one (ctrl_t ctrl, kbnode_t keyblock,
struct import_stats_s *stats, int batch,
unsigned int options, int for_migration,
import_screener_t screener, void *screener_arg,
kbnode_t *r_secattic);
static int import_revoke_cert (ctrl_t ctrl, kbnode_t node, unsigned int options,
struct import_stats_s *stats);
static int chk_self_sigs (ctrl_t ctrl, kbnode_t keyblock, u32 *keyid,
int *non_self);
static int delete_inv_parts (ctrl_t ctrl, kbnode_t keyblock,
u32 *keyid, unsigned int options);
static int any_uid_left (kbnode_t keyblock);
static int remove_all_uids (kbnode_t *keyblock);
+static void remove_all_non_self_sigs (kbnode_t *keyblock, u32 *keyid);
static int merge_blocks (ctrl_t ctrl, unsigned int options,
kbnode_t keyblock_orig,
kbnode_t keyblock, u32 *keyid,
u32 curtime, int origin, const char *url,
int *n_uids, int *n_sigs, int *n_subk );
static gpg_error_t append_new_uid (unsigned int options,
kbnode_t keyblock, kbnode_t node,
u32 curtime, int origin, const char *url,
int *n_sigs);
static int append_key (kbnode_t keyblock, kbnode_t node, int *n_sigs);
static int merge_sigs (kbnode_t dst, kbnode_t src, int *n_sigs);
static int merge_keysigs (kbnode_t dst, kbnode_t src, int *n_sigs);
static void
release_import_filter (import_filter_t filt)
{
recsel_release (filt->keep_uid);
filt->keep_uid = NULL;
recsel_release (filt->drop_sig);
filt->drop_sig = NULL;
}
static void
cleanup_import_globals (void)
{
release_import_filter (&import_filter);
}
int
parse_import_options(char *str,unsigned int *options,int noisy)
{
struct parse_options import_opts[]=
{
{"import-local-sigs",IMPORT_LOCAL_SIGS,NULL,
N_("import signatures that are marked as local-only")},
{"repair-pks-subkey-bug",IMPORT_REPAIR_PKS_SUBKEY_BUG,NULL,
N_("repair damage from the pks keyserver during import")},
{"keep-ownertrust", IMPORT_KEEP_OWNERTTRUST, NULL,
N_("do not clear the ownertrust values during import")},
{"fast-import",IMPORT_FAST,NULL,
N_("do not update the trustdb after import")},
{"import-show",IMPORT_SHOW,NULL,
N_("show key during import")},
{"merge-only",IMPORT_MERGE_ONLY,NULL,
N_("only accept updates to existing keys")},
{"import-clean",IMPORT_CLEAN,NULL,
N_("remove unusable parts from key after import")},
{"import-minimal",IMPORT_MINIMAL|IMPORT_CLEAN,NULL,
N_("remove as much as possible from key after import")},
{"import-drop-uids", IMPORT_DROP_UIDS, NULL,
- N_("Do not import user id or attribute packets")},
+ N_("do not import user id or attribute packets")},
+
+ {"self-sigs-only", IMPORT_SELF_SIGS_ONLY, NULL,
+ N_("ignore key-signatures which are not self-signatures")},
{"import-export", IMPORT_EXPORT, NULL,
N_("run import filters and export key immediately")},
{"restore", IMPORT_RESTORE, NULL,
N_("assume the GnuPG key backup format")},
{"import-restore", IMPORT_RESTORE, NULL, NULL},
{"repair-keys", IMPORT_REPAIR_KEYS, NULL,
N_("repair keys on import")},
/* No description to avoid string change: Fixme for 2.3 */
{"show-only", (IMPORT_SHOW | IMPORT_DRY_RUN), NULL,
NULL},
/* Aliases for backward compatibility */
{"allow-local-sigs",IMPORT_LOCAL_SIGS,NULL,NULL},
{"repair-hkp-subkey-bug",IMPORT_REPAIR_PKS_SUBKEY_BUG,NULL,NULL},
/* dummy */
{"import-unusable-sigs",0,NULL,NULL},
{"import-clean-sigs",0,NULL,NULL},
{"import-clean-uids",0,NULL,NULL},
{"convert-sk-to-pk",0, NULL,NULL}, /* Not anymore needed due to
the new design. */
{NULL,0,NULL,NULL}
};
int rc;
rc = parse_options (str, options, import_opts, noisy);
if (rc && (*options & IMPORT_RESTORE))
{
/* Alter other options we want or don't want for restore. */
*options |= (IMPORT_LOCAL_SIGS | IMPORT_KEEP_OWNERTTRUST);
*options &= ~(IMPORT_MINIMAL | IMPORT_CLEAN
| IMPORT_REPAIR_PKS_SUBKEY_BUG
| IMPORT_MERGE_ONLY);
}
return rc;
}
/* Parse and set an import filter from string. STRING has the format
* "NAME=EXPR" with NAME being the name of the filter. Spaces before
* and after NAME are not allowed. If this function is all called
* several times all expressions for the same NAME are concatenated.
* Supported filter names are:
*
* - keep-uid :: If the expression evaluates to true for a certain
* user ID packet, that packet and all it dependencies
* will be imported. The expression may use these
* variables:
*
* - uid :: The entire user ID.
* - mbox :: The mail box part of the user ID.
* - primary :: Evaluate to true for the primary user ID.
*/
gpg_error_t
parse_and_set_import_filter (const char *string)
{
gpg_error_t err;
/* Auto register the cleanup function. */
register_mem_cleanup_func (cleanup_import_globals);
if (!strncmp (string, "keep-uid=", 9))
err = recsel_parse_expr (&import_filter.keep_uid, string+9);
else if (!strncmp (string, "drop-sig=", 9))
err = recsel_parse_expr (&import_filter.drop_sig, string+9);
else
err = gpg_error (GPG_ERR_INV_NAME);
return err;
}
/* Save the current import filters, return them, and clear the current
* filters. Returns NULL on error and sets ERRNO. */
import_filter_t
save_and_clear_import_filter (void)
{
import_filter_t filt;
filt = xtrycalloc (1, sizeof *filt);
if (!filt)
return NULL;
*filt = import_filter;
memset (&import_filter, 0, sizeof import_filter);
return filt;
}
/* Release the current import filters and restore them from NEWFILT.
* Ownership of NEWFILT is moved to this function. */
void
restore_import_filter (import_filter_t filt)
{
if (filt)
{
release_import_filter (&import_filter);
import_filter = *filt;
xfree (filt);
}
}
import_stats_t
import_new_stats_handle (void)
{
return xmalloc_clear ( sizeof (struct import_stats_s) );
}
void
import_release_stats_handle (import_stats_t p)
{
xfree (p);
}
/* Read a key from a file. Only the first key in the file is
* considered and stored at R_KEYBLOCK. FNAME is the name of the
* file.
*/
gpg_error_t
read_key_from_file (ctrl_t ctrl, const char *fname, kbnode_t *r_keyblock)
{
gpg_error_t err;
iobuf_t inp;
PACKET *pending_pkt = NULL;
kbnode_t keyblock = NULL;
u32 keyid[2];
int v3keys; /* Dummy */
int non_self; /* Dummy */
(void)ctrl;
*r_keyblock = NULL;
inp = iobuf_open (fname);
if (!inp)
err = gpg_error_from_syserror ();
else if (is_secured_file (iobuf_get_fd (inp)))
{
iobuf_close (inp);
inp = NULL;
err = gpg_error (GPG_ERR_EPERM);
}
else
err = 0;
if (err)
{
log_error (_("can't open '%s': %s\n"),
iobuf_is_pipe_filename (fname)? "[stdin]": fname,
gpg_strerror (err));
if (gpg_err_code (err) == GPG_ERR_ENOENT)
err = gpg_error (GPG_ERR_NO_PUBKEY);
goto leave;
}
/* Push the armor filter. */
{
armor_filter_context_t *afx;
afx = new_armor_context ();
afx->only_keyblocks = 1;
push_armor_filter (afx, inp);
release_armor_context (afx);
}
/* Read the first non-v3 keyblock. */
while (!(err = read_block (inp, 0, &pending_pkt, &keyblock, &v3keys)))
{
if (keyblock->pkt->pkttype == PKT_PUBLIC_KEY)
break;
log_info (_("skipping block of type %d\n"), keyblock->pkt->pkttype);
release_kbnode (keyblock);
keyblock = NULL;
}
if (err)
{
if (gpg_err_code (err) != GPG_ERR_INV_KEYRING)
log_error (_("error reading '%s': %s\n"),
iobuf_is_pipe_filename (fname)? "[stdin]": fname,
gpg_strerror (err));
goto leave;
}
keyid_from_pk (keyblock->pkt->pkt.public_key, keyid);
if (!find_next_kbnode (keyblock, PKT_USER_ID))
{
err = gpg_error (GPG_ERR_NO_USER_ID);
goto leave;
}
collapse_uids (&keyblock);
clear_kbnode_flags (keyblock);
if (chk_self_sigs (ctrl, keyblock, keyid, &non_self))
{
err = gpg_error (GPG_ERR_INV_KEYRING);
goto leave;
}
if (!delete_inv_parts (ctrl, keyblock, keyid, 0) )
{
err = gpg_error (GPG_ERR_NO_USER_ID);
goto leave;
}
*r_keyblock = keyblock;
keyblock = NULL;
leave:
if (inp)
{
iobuf_close (inp);
/* Must invalidate that ugly cache to actually close the file. */
iobuf_ioctl (NULL, IOBUF_IOCTL_INVALIDATE_CACHE, 0, (char*)fname);
}
release_kbnode (keyblock);
/* FIXME: Do we need to free PENDING_PKT ? */
return err;
}
/*
* Import the public keys from the given filename. Input may be armored.
* This function rejects all keys which are not validly self signed on at
* least one userid. Only user ids which are self signed will be imported.
* Other signatures are not checked.
*
* Actually this function does a merge. It works like this:
*
* - get the keyblock
* - check self-signatures and remove all userids and their signatures
* without/invalid self-signatures.
* - reject the keyblock, if we have no valid userid.
* - See whether we have this key already in one of our pubrings.
* If not, simply add it to the default keyring.
* - Compare the key and the self-signatures of the new and the one in
* our keyring. If they are different something weird is going on;
* ask what to do.
* - See whether we have only non-self-signature on one user id; if not
* ask the user what to do.
* - compare the signatures: If we already have this signature, check
* that they compare okay; if not, issue a warning and ask the user.
* (consider looking at the timestamp and use the newest?)
* - Simply add the signature. Can't verify here because we may not have
* the signature's public key yet; verification is done when putting it
* into the trustdb, which is done automagically as soon as this pubkey
* is used.
* - Proceed with next signature.
*
* Key revocation certificates have special handling.
*/
static gpg_error_t
import_keys_internal (ctrl_t ctrl, iobuf_t inp, char **fnames, int nnames,
import_stats_t stats_handle,
unsigned char **fpr, size_t *fpr_len,
unsigned int options,
import_screener_t screener, void *screener_arg,
int origin, const char *url)
{
int i;
gpg_error_t err = 0;
struct import_stats_s *stats = stats_handle;
if (!stats)
stats = import_new_stats_handle ();
if (inp)
{
err = import (ctrl, inp, "[stream]", stats, fpr, fpr_len, options,
screener, screener_arg, origin, url);
}
else
{
if (!fnames && !nnames)
nnames = 1; /* Ohh what a ugly hack to jump into the loop */
for (i=0; i < nnames; i++)
{
const char *fname = fnames? fnames[i] : NULL;
IOBUF inp2 = iobuf_open(fname);
if (!fname)
fname = "[stdin]";
if (inp2 && is_secured_file (iobuf_get_fd (inp2)))
{
iobuf_close (inp2);
inp2 = NULL;
gpg_err_set_errno (EPERM);
}
if (!inp2)
log_error (_("can't open '%s': %s\n"), fname, strerror (errno));
else
{
err = import (ctrl, inp2, fname, stats, fpr, fpr_len, options,
screener, screener_arg, origin, url);
iobuf_close (inp2);
/* Must invalidate that ugly cache to actually close it. */
iobuf_ioctl (NULL, IOBUF_IOCTL_INVALIDATE_CACHE, 0, (char*)fname);
if (err)
log_error ("import from '%s' failed: %s\n",
fname, gpg_strerror (err) );
}
if (!fname)
break;
}
}
if (!stats_handle)
{
if ((options & (IMPORT_SHOW | IMPORT_DRY_RUN))
!= (IMPORT_SHOW | IMPORT_DRY_RUN))
import_print_stats (stats);
import_release_stats_handle (stats);
}
/* If no fast import and the trustdb is dirty (i.e. we added a key
or userID that had something other than a selfsig, a signature
that was other than a selfsig, or any revocation), then
update/check the trustdb if the user specified by setting
interactive or by not setting no-auto-check-trustdb */
if (!(options & IMPORT_FAST))
check_or_update_trustdb (ctrl);
return err;
}
void
import_keys (ctrl_t ctrl, char **fnames, int nnames,
import_stats_t stats_handle, unsigned int options,
int origin, const char *url)
{
import_keys_internal (ctrl, NULL, fnames, nnames, stats_handle,
NULL, NULL, options, NULL, NULL, origin, url);
}
gpg_error_t
import_keys_es_stream (ctrl_t ctrl, estream_t fp,
import_stats_t stats_handle,
unsigned char **fpr, size_t *fpr_len,
unsigned int options,
import_screener_t screener, void *screener_arg,
int origin, const char *url)
{
gpg_error_t err;
iobuf_t inp;
inp = iobuf_esopen (fp, "rb", 1);
if (!inp)
{
err = gpg_error_from_syserror ();
log_error ("iobuf_esopen failed: %s\n", gpg_strerror (err));
return err;
}
err = import_keys_internal (ctrl, inp, NULL, 0, stats_handle,
fpr, fpr_len, options,
screener, screener_arg, origin, url);
iobuf_close (inp);
return err;
}
static int
import (ctrl_t ctrl, IOBUF inp, const char* fname,struct import_stats_s *stats,
unsigned char **fpr,size_t *fpr_len, unsigned int options,
import_screener_t screener, void *screener_arg,
int origin, const char *url)
{
PACKET *pending_pkt = NULL;
kbnode_t keyblock = NULL; /* Need to initialize because gcc can't
grasp the return semantics of
read_block. */
kbnode_t secattic = NULL; /* Kludge for PGP desktop percularity */
int rc = 0;
int v3keys;
getkey_disable_caches ();
if (!opt.no_armor) /* Armored reading is not disabled. */
{
armor_filter_context_t *afx;
afx = new_armor_context ();
afx->only_keyblocks = 1;
push_armor_filter (afx, inp);
release_armor_context (afx);
}
- while (!(rc = read_block (inp, !!(options & IMPORT_RESTORE),
- &pending_pkt, &keyblock, &v3keys)))
+ while (!(rc = read_block (inp, options, &pending_pkt, &keyblock, &v3keys)))
{
stats->v3keys += v3keys;
if (keyblock->pkt->pkttype == PKT_PUBLIC_KEY)
{
rc = import_one (ctrl, keyblock,
stats, fpr, fpr_len, options, 0, 0,
screener, screener_arg, origin, url, NULL);
if (secattic)
{
byte tmpfpr[MAX_FINGERPRINT_LEN];
size_t tmpfprlen;
if (!rc && !(opt.dry_run || (options & IMPORT_DRY_RUN)))
{
/* Kudge for PGP desktop - see below. */
fingerprint_from_pk (keyblock->pkt->pkt.public_key,
tmpfpr, &tmpfprlen);
rc = import_matching_seckeys (ctrl, secattic,
tmpfpr, tmpfprlen,
stats, opt.batch);
}
release_kbnode (secattic);
secattic = NULL;
}
}
else if (keyblock->pkt->pkttype == PKT_SECRET_KEY)
{
release_kbnode (secattic);
secattic = NULL;
rc = import_secret_one (ctrl, keyblock, stats,
opt.batch, options, 0,
screener, screener_arg, &secattic);
keyblock = NULL; /* Ownership was transferred. */
if (secattic)
{
if (gpg_err_code (rc) == GPG_ERR_NO_PUBKEY)
rc = 0; /* Try import after the next pubkey. */
/* The attic is a workaround for the peculiar PGP
* Desktop method of exporting a secret key: The
* exported file is the concatenation of two armored
* keyblocks; first the private one and then the public
* one. The strange thing is that the secret one has no
* binding signatures at all and thus we have not
* imported it. The attic stores that secret keys and
* we try to import it once after the very next public
* keyblock. */
}
}
else if (keyblock->pkt->pkttype == PKT_SIGNATURE
&& IS_KEY_REV (keyblock->pkt->pkt.signature) )
{
release_kbnode (secattic);
secattic = NULL;
rc = import_revoke_cert (ctrl, keyblock, options, stats);
}
else
{
release_kbnode (secattic);
secattic = NULL;
log_info (_("skipping block of type %d\n"), keyblock->pkt->pkttype);
}
release_kbnode (keyblock);
/* fixme: we should increment the not imported counter but
this does only make sense if we keep on going despite of
errors. For now we do this only if the imported key is too
large. */
if (gpg_err_code (rc) == GPG_ERR_TOO_LARGE
&& gpg_err_source (rc) == GPG_ERR_SOURCE_KEYBOX)
{
stats->not_imported++;
}
else if (rc)
break;
if (!(++stats->count % 100) && !opt.quiet)
log_info (_("%lu keys processed so far\n"), stats->count );
+
+ if (origin == KEYORG_WKD && stats->count >= 5)
+ {
+ /* We limit the number of keys _received_ from the WKD to 5.
+ * In fact there should be only one key but some sites want
+ * to store a few expired keys there also. gpg's key
+ * selection will later figure out which key to use. Note
+ * that for WKD we always return the fingerprint of the
+ * first imported key. */
+ log_info ("import from WKD stopped after %d keys\n", 5);
+ break;
+ }
}
stats->v3keys += v3keys;
if (rc == -1)
rc = 0;
else if (rc && gpg_err_code (rc) != GPG_ERR_INV_KEYRING)
log_error (_("error reading '%s': %s\n"), fname, gpg_strerror (rc));
release_kbnode (secattic);
+
+ /* When read_block loop was stopped by error, we have PENDING_PKT left. */
+ if (pending_pkt)
+ {
+ free_packet (pending_pkt, NULL);
+ xfree (pending_pkt);
+ }
return rc;
}
/* Helper to migrate secring.gpg to GnuPG 2.1. */
gpg_error_t
import_old_secring (ctrl_t ctrl, const char *fname)
{
gpg_error_t err;
iobuf_t inp;
PACKET *pending_pkt = NULL;
kbnode_t keyblock = NULL; /* Need to initialize because gcc can't
grasp the return semantics of
read_block. */
struct import_stats_s *stats;
int v3keys;
inp = iobuf_open (fname);
if (inp && is_secured_file (iobuf_get_fd (inp)))
{
iobuf_close (inp);
inp = NULL;
gpg_err_set_errno (EPERM);
}
if (!inp)
{
err = gpg_error_from_syserror ();
log_error (_("can't open '%s': %s\n"), fname, gpg_strerror (err));
return err;
}
getkey_disable_caches();
stats = import_new_stats_handle ();
while (!(err = read_block (inp, 0, &pending_pkt, &keyblock, &v3keys)))
{
if (keyblock->pkt->pkttype == PKT_SECRET_KEY)
{
err = import_secret_one (ctrl, keyblock, stats, 1, 0, 1,
NULL, NULL, NULL);
keyblock = NULL; /* Ownership was transferred. */
}
release_kbnode (keyblock);
if (err)
break;
}
import_release_stats_handle (stats);
if (err == -1)
err = 0;
else if (err && gpg_err_code (err) != GPG_ERR_INV_KEYRING)
log_error (_("error reading '%s': %s\n"), fname, gpg_strerror (err));
else if (err)
log_error ("import from '%s' failed: %s\n", fname, gpg_strerror (err));
iobuf_close (inp);
iobuf_ioctl (NULL, IOBUF_IOCTL_INVALIDATE_CACHE, 0, (char*)fname);
return err;
}
void
import_print_stats (import_stats_t stats)
{
if (!opt.quiet)
{
log_info(_("Total number processed: %lu\n"),
stats->count + stats->v3keys);
if (stats->v3keys)
log_info(_(" skipped PGP-2 keys: %lu\n"), stats->v3keys);
if (stats->skipped_new_keys )
log_info(_(" skipped new keys: %lu\n"),
stats->skipped_new_keys );
if (stats->no_user_id )
log_info(_(" w/o user IDs: %lu\n"), stats->no_user_id );
if (stats->imported)
{
log_info(_(" imported: %lu"), stats->imported );
log_printf ("\n");
}
if (stats->unchanged )
log_info(_(" unchanged: %lu\n"), stats->unchanged );
if (stats->n_uids )
log_info(_(" new user IDs: %lu\n"), stats->n_uids );
if (stats->n_subk )
log_info(_(" new subkeys: %lu\n"), stats->n_subk );
if (stats->n_sigs )
log_info(_(" new signatures: %lu\n"), stats->n_sigs );
if (stats->n_revoc )
log_info(_(" new key revocations: %lu\n"), stats->n_revoc );
if (stats->secret_read )
log_info(_(" secret keys read: %lu\n"), stats->secret_read );
if (stats->secret_imported )
log_info(_(" secret keys imported: %lu\n"), stats->secret_imported );
if (stats->secret_dups )
log_info(_(" secret keys unchanged: %lu\n"), stats->secret_dups );
if (stats->not_imported )
log_info(_(" not imported: %lu\n"), stats->not_imported );
if (stats->n_sigs_cleaned)
log_info(_(" signatures cleaned: %lu\n"),stats->n_sigs_cleaned);
if (stats->n_uids_cleaned)
log_info(_(" user IDs cleaned: %lu\n"),stats->n_uids_cleaned);
}
if (is_status_enabled ())
{
char buf[15*20];
snprintf (buf, sizeof buf,
"%lu %lu %lu 0 %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu",
stats->count + stats->v3keys,
stats->no_user_id,
stats->imported,
stats->unchanged,
stats->n_uids,
stats->n_subk,
stats->n_sigs,
stats->n_revoc,
stats->secret_read,
stats->secret_imported,
stats->secret_dups,
stats->skipped_new_keys,
stats->not_imported,
stats->v3keys );
write_status_text (STATUS_IMPORT_RES, buf);
}
}
/* Return true if PKTTYPE is valid in a keyblock. */
static int
valid_keyblock_packet (int pkttype)
{
switch (pkttype)
{
case PKT_PUBLIC_KEY:
case PKT_PUBLIC_SUBKEY:
case PKT_SECRET_KEY:
case PKT_SECRET_SUBKEY:
case PKT_SIGNATURE:
case PKT_USER_ID:
case PKT_ATTRIBUTE:
case PKT_RING_TRUST:
return 1;
default:
return 0;
}
}
-/****************
- * Read the next keyblock from stream A.
- * Meta data (ring trust packets) are only considered of WITH_META is set.
- * PENDING_PKT should be initialized to NULL and not changed by the caller.
- * Return: 0 = okay, -1 no more blocks or another errorcode.
- * The int at R_V3KEY counts the number of unsupported v3
- * keyblocks.
+/* Read the next keyblock from stream A. Meta data (ring trust
+ * packets) are only considered if OPTIONS has the IMPORT_RESTORE flag
+ * set. PENDING_PKT should be initialized to NULL and not changed by
+ * the caller.
+ *
+ * Returns 0 for okay, -1 no more blocks, or any other errorcode. The
+ * integer at R_V3KEY counts the number of unsupported v3 keyblocks.
*/
static int
-read_block( IOBUF a, int with_meta,
+read_block( IOBUF a, unsigned int options,
PACKET **pending_pkt, kbnode_t *ret_root, int *r_v3keys)
{
int rc;
struct parse_packet_ctx_s parsectx;
PACKET *pkt;
kbnode_t root = NULL;
+ kbnode_t lastnode = NULL;
int in_cert, in_v3key, skip_sigs;
+ u32 keyid[2];
+ int got_keyid = 0;
+ unsigned int dropped_nonselfsigs = 0;
*r_v3keys = 0;
if (*pending_pkt)
{
- root = new_kbnode( *pending_pkt );
+ root = lastnode = new_kbnode( *pending_pkt );
*pending_pkt = NULL;
+ log_assert (root->pkt->pkttype == PKT_PUBLIC_KEY
+ || root->pkt->pkttype == PKT_SECRET_KEY);
in_cert = 1;
+ keyid_from_pk (root->pkt->pkt.public_key, keyid);
+ got_keyid = 1;
}
else
in_cert = 0;
pkt = xmalloc (sizeof *pkt);
init_packet (pkt);
init_parse_packet (&parsectx, a);
- if (!with_meta)
+ if (!(options & IMPORT_RESTORE))
parsectx.skip_meta = 1;
in_v3key = 0;
skip_sigs = 0;
while ((rc=parse_packet (&parsectx, pkt)) != -1)
{
if (rc && (gpg_err_code (rc) == GPG_ERR_LEGACY_KEY
&& (pkt->pkttype == PKT_PUBLIC_KEY
|| pkt->pkttype == PKT_SECRET_KEY)))
{
in_v3key = 1;
++*r_v3keys;
free_packet (pkt, &parsectx);
init_packet (pkt);
continue;
}
else if (rc ) /* (ignore errors) */
{
skip_sigs = 0;
if (gpg_err_code (rc) == GPG_ERR_UNKNOWN_PACKET)
; /* Do not show a diagnostic. */
else if (gpg_err_code (rc) == GPG_ERR_INV_PACKET
&& (pkt->pkttype == PKT_USER_ID
|| pkt->pkttype == PKT_ATTRIBUTE))
{
/* This indicates a too large user id or attribute
* packet. We skip this packet and all following
* signatures. Sure, this won't allow to repair a
* garbled keyring in case one of the signatures belong
* to another user id. However, this better mitigates
* DoS using inserted user ids. */
skip_sigs = 1;
}
else if (gpg_err_code (rc) == GPG_ERR_INV_PACKET
&& (pkt->pkttype == PKT_OLD_COMMENT
|| pkt->pkttype == PKT_COMMENT))
; /* Ignore too large comment packets. */
else
{
log_error("read_block: read error: %s\n", gpg_strerror (rc) );
rc = GPG_ERR_INV_KEYRING;
goto ready;
}
free_packet (pkt, &parsectx);
init_packet(pkt);
continue;
}
if (skip_sigs)
{
if (pkt->pkttype == PKT_SIGNATURE)
{
free_packet (pkt, &parsectx);
init_packet (pkt);
continue;
}
skip_sigs = 0;
}
if (in_v3key && !(pkt->pkttype == PKT_PUBLIC_KEY
|| pkt->pkttype == PKT_SECRET_KEY))
{
free_packet (pkt, &parsectx);
init_packet(pkt);
continue;
}
in_v3key = 0;
if (!root && pkt->pkttype == PKT_SIGNATURE
&& IS_KEY_REV (pkt->pkt.signature) )
{
/* This is a revocation certificate which is handled in a
* special way. */
root = new_kbnode( pkt );
pkt = NULL;
goto ready;
}
/* Make a linked list of all packets. */
switch (pkt->pkttype)
{
case PKT_COMPRESSED:
if (check_compress_algo (pkt->pkt.compressed->algorithm))
{
rc = GPG_ERR_COMPR_ALGO;
goto ready;
}
else
{
compress_filter_context_t *cfx = xmalloc_clear( sizeof *cfx );
pkt->pkt.compressed->buf = NULL;
if (push_compress_filter2 (a, cfx,
pkt->pkt.compressed->algorithm, 1))
xfree (cfx); /* e.g. in case of compression_algo NONE. */
}
free_packet (pkt, &parsectx);
init_packet(pkt);
break;
case PKT_RING_TRUST:
/* Skip those packets unless we are in restore mode. */
if ((opt.import_options & IMPORT_RESTORE))
goto x_default;
free_packet (pkt, &parsectx);
init_packet(pkt);
break;
+ case PKT_SIGNATURE:
+ if (!in_cert)
+ goto x_default;
+ if (!(options & IMPORT_SELF_SIGS_ONLY))
+ goto x_default;
+ log_assert (got_keyid);
+ if (pkt->pkt.signature->keyid[0] == keyid[0]
+ && pkt->pkt.signature->keyid[1] == keyid[1])
+ { /* This is likely a self-signature. We import this one.
+ * Eventually we should use the ISSUER_FPR to compare
+ * self-signatures, but that will work only for v5 keys
+ * which are currently not even deployed.
+ * Note that we do not do any crypto verify here because
+ * that would defeat this very mitigation of DoS by
+ * importing a key with a huge amount of faked
+ * key-signatures. A verification will be done later in
+ * the processing anyway. Here we want a cheap an early
+ * way to drop non-self-signatures. */
+ goto x_default;
+ }
+ /* Skip this signature. */
+ dropped_nonselfsigs++;
+ free_packet (pkt, &parsectx);
+ init_packet(pkt);
+ break;
+
case PKT_PUBLIC_KEY:
case PKT_SECRET_KEY:
- if (in_cert ) /* Store this packet. */
+ if (!got_keyid)
+ {
+ keyid_from_pk (pkt->pkt.public_key, keyid);
+ got_keyid = 1;
+ }
+ if (in_cert) /* Store this packet. */
{
*pending_pkt = pkt;
pkt = NULL;
goto ready;
}
in_cert = 1;
- /* fall through */
+ goto x_default;
+
default:
x_default:
if (in_cert && valid_keyblock_packet (pkt->pkttype))
{
if (!root )
- root = new_kbnode (pkt);
+ root = lastnode = new_kbnode (pkt);
else
- add_kbnode (root, new_kbnode (pkt));
+ {
+ lastnode->next = new_kbnode (pkt);
+ lastnode = lastnode->next;
+ }
pkt = xmalloc (sizeof *pkt);
}
else
free_packet (pkt, &parsectx);
init_packet(pkt);
break;
}
}
ready:
if (rc == -1 && root )
rc = 0;
if (rc )
release_kbnode( root );
else
*ret_root = root;
free_packet (pkt, &parsectx);
deinit_parse_packet (&parsectx);
xfree( pkt );
+ if (!rc && dropped_nonselfsigs && opt.verbose)
+ log_info ("key %s: number of dropped non-self-signatures: %u\n",
+ keystr (keyid), dropped_nonselfsigs);
+
return rc;
}
/* Walk through the subkeys on a pk to find if we have the PKS
disease: multiple subkeys with their binding sigs stripped, and the
sig for the first subkey placed after the last subkey. That is,
instead of "pk uid sig sub1 bind1 sub2 bind2 sub3 bind3" we have
"pk uid sig sub1 sub2 sub3 bind1". We can't do anything about sub2
and sub3, as they are already lost, but we can try and rescue sub1
by reordering the keyblock so that it reads "pk uid sig sub1 bind1
sub2 sub3". Returns TRUE if the keyblock was modified. */
static int
fix_pks_corruption (ctrl_t ctrl, kbnode_t keyblock)
{
int changed = 0;
int keycount = 0;
kbnode_t node;
kbnode_t last = NULL;
kbnode_t sknode=NULL;
/* First determine if we have the problem at all. Look for 2 or
more subkeys in a row, followed by a single binding sig. */
for (node=keyblock; node; last=node, node=node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
keycount++;
if(!sknode)
sknode=node;
}
else if (node->pkt->pkttype == PKT_SIGNATURE
&& IS_SUBKEY_SIG (node->pkt->pkt.signature)
&& keycount >= 2
&& !node->next)
{
/* We might have the problem, as this key has two subkeys in
a row without any intervening packets. */
/* Sanity check */
if (!last)
break;
/* Temporarily attach node to sknode. */
node->next = sknode->next;
sknode->next = node;
last->next = NULL;
/* Note we aren't checking whether this binding sig is a
selfsig. This is not necessary here as the subkey and
binding sig will be rejected later if that is the
case. */
if (check_key_signature (ctrl, keyblock,node,NULL))
{
/* Not a match, so undo the changes. */
sknode->next = node->next;
last->next = node;
node->next = NULL;
break;
}
else
{
/* Mark it good so we don't need to check it again */
sknode->flag |= NODE_GOOD_SELFSIG;
changed = 1;
break;
}
}
else
keycount = 0;
}
return changed;
}
/* Versions of GnuPG before 1.4.11 and 2.0.16 allowed to import bogus
direct key signatures. A side effect of this was that a later
import of the same good direct key signatures was not possible
because the cmp_signature check in merge_blocks considered them
equal. Although direct key signatures are now checked during
import, there might still be bogus signatures sitting in a keyring.
We need to detect and delete them before doing a merge. This
function returns the number of removed sigs. */
static int
fix_bad_direct_key_sigs (ctrl_t ctrl, kbnode_t keyblock, u32 *keyid)
{
gpg_error_t err;
kbnode_t node;
int count = 0;
for (node = keyblock->next; node; node=node->next)
{
if (node->pkt->pkttype == PKT_USER_ID)
break;
if (node->pkt->pkttype == PKT_SIGNATURE
&& IS_KEY_SIG (node->pkt->pkt.signature))
{
err = check_key_signature (ctrl, keyblock, node, NULL);
if (err && gpg_err_code (err) != GPG_ERR_PUBKEY_ALGO )
{
/* If we don't know the error, we can't decide; this is
not a problem because cmp_signature can't compare the
signature either. */
log_info ("key %s: invalid direct key signature removed\n",
keystr (keyid));
delete_kbnode (node);
count++;
}
}
}
return count;
}
static void
print_import_ok (PKT_public_key *pk, unsigned int reason)
{
byte array[MAX_FINGERPRINT_LEN], *s;
char buf[MAX_FINGERPRINT_LEN*2+30], *p;
size_t i, n;
snprintf (buf, sizeof buf, "%u ", reason);
p = buf + strlen (buf);
fingerprint_from_pk (pk, array, &n);
s = array;
for (i=0; i < n ; i++, s++, p += 2)
sprintf (p, "%02X", *s);
write_status_text (STATUS_IMPORT_OK, buf);
}
static void
print_import_check (PKT_public_key * pk, PKT_user_id * id)
{
byte hexfpr[2*MAX_FINGERPRINT_LEN+1];
u32 keyid[2];
keyid_from_pk (pk, keyid);
hexfingerprint (pk, hexfpr, sizeof hexfpr);
write_status_printf (STATUS_IMPORT_CHECK, "%08X%08X %s %s",
keyid[0], keyid[1], hexfpr, id->name);
}
static void
check_prefs_warning(PKT_public_key *pk)
{
log_info(_("WARNING: key %s contains preferences for unavailable\n"
"algorithms on these user IDs:\n"), keystr_from_pk(pk));
}
static void
check_prefs (ctrl_t ctrl, kbnode_t keyblock)
{
kbnode_t node;
PKT_public_key *pk;
int problem=0;
merge_keys_and_selfsig (ctrl, keyblock);
pk=keyblock->pkt->pkt.public_key;
for(node=keyblock;node;node=node->next)
{
if(node->pkt->pkttype==PKT_USER_ID
&& node->pkt->pkt.user_id->created
&& node->pkt->pkt.user_id->prefs)
{
PKT_user_id *uid = node->pkt->pkt.user_id;
prefitem_t *prefs = uid->prefs;
char *user = utf8_to_native(uid->name,strlen(uid->name),0);
for(;prefs->type;prefs++)
{
char num[10]; /* prefs->value is a byte, so we're over
safe here */
sprintf(num,"%u",prefs->value);
if(prefs->type==PREFTYPE_SYM)
{
if (openpgp_cipher_test_algo (prefs->value))
{
const char *algo =
(openpgp_cipher_test_algo (prefs->value)
? num
: openpgp_cipher_algo_name (prefs->value));
if(!problem)
check_prefs_warning(pk);
log_info(_(" \"%s\": preference for cipher"
" algorithm %s\n"), user, algo);
problem=1;
}
}
else if(prefs->type==PREFTYPE_AEAD)
{
if (openpgp_aead_test_algo (prefs->value))
{
/* FIXME: The test below is wrong. We should
* check if ...algo_name yields a "?" and
* only in that case use NUM. */
const char *algo =
(openpgp_aead_test_algo (prefs->value)
? num
: openpgp_aead_algo_name (prefs->value));
if(!problem)
check_prefs_warning(pk);
log_info(_(" \"%s\": preference for AEAD"
" algorithm %s\n"), user, algo);
problem=1;
}
}
else if(prefs->type==PREFTYPE_HASH)
{
if(openpgp_md_test_algo(prefs->value))
{
const char *algo =
(gcry_md_test_algo (prefs->value)
? num
: gcry_md_algo_name (prefs->value));
if(!problem)
check_prefs_warning(pk);
log_info(_(" \"%s\": preference for digest"
" algorithm %s\n"), user, algo);
problem=1;
}
}
else if(prefs->type==PREFTYPE_ZIP)
{
if(check_compress_algo (prefs->value))
{
const char *algo=compress_algo_to_string(prefs->value);
if(!problem)
check_prefs_warning(pk);
log_info(_(" \"%s\": preference for compression"
" algorithm %s\n"),user,algo?algo:num);
problem=1;
}
}
}
xfree(user);
}
}
if(problem)
{
log_info(_("it is strongly suggested that you update"
" your preferences and\n"));
log_info(_("re-distribute this key to avoid potential algorithm"
" mismatch problems\n"));
if(!opt.batch)
{
strlist_t sl = NULL;
strlist_t locusr = NULL;
size_t fprlen=0;
byte fpr[MAX_FINGERPRINT_LEN], *p;
char username[(MAX_FINGERPRINT_LEN*2)+1];
unsigned int i;
p = fingerprint_from_pk (pk,fpr,&fprlen);
for(i=0;i<fprlen;i++,p++)
sprintf(username+2*i,"%02X",*p);
add_to_strlist(&locusr,username);
append_to_strlist(&sl,"updpref");
append_to_strlist(&sl,"save");
keyedit_menu (ctrl, username, locusr, sl, 1, 1 );
free_strlist(sl);
free_strlist(locusr);
}
else if(!opt.quiet)
log_info(_("you can update your preferences with:"
" gpg --edit-key %s updpref save\n"),keystr_from_pk(pk));
}
}
/* Helper for apply_*_filter in import.c and export.c. */
const char *
impex_filter_getval (void *cookie, const char *propname)
{
/* FIXME: Malloc our static buffers and access them via PARM. */
struct impex_filter_parm_s *parm = cookie;
ctrl_t ctrl = parm->ctrl;
kbnode_t node = parm->node;
static char numbuf[20];
const char *result;
log_assert (ctrl && ctrl->magic == SERVER_CONTROL_MAGIC);
if (node->pkt->pkttype == PKT_USER_ID
|| node->pkt->pkttype == PKT_ATTRIBUTE)
{
PKT_user_id *uid = node->pkt->pkt.user_id;
if (!strcmp (propname, "uid"))
result = uid->name;
else if (!strcmp (propname, "mbox"))
{
if (!uid->mbox)
{
uid->mbox = mailbox_from_userid (uid->name, 0);
}
result = uid->mbox;
}
else if (!strcmp (propname, "primary"))
{
result = uid->flags.primary? "1":"0";
}
else if (!strcmp (propname, "expired"))
{
result = uid->flags.expired? "1":"0";
}
else if (!strcmp (propname, "revoked"))
{
result = uid->flags.revoked? "1":"0";
}
else
result = NULL;
}
else if (node->pkt->pkttype == PKT_SIGNATURE)
{
PKT_signature *sig = node->pkt->pkt.signature;
if (!strcmp (propname, "sig_created"))
{
snprintf (numbuf, sizeof numbuf, "%lu", (ulong)sig->timestamp);
result = numbuf;
}
else if (!strcmp (propname, "sig_created_d"))
{
result = datestr_from_sig (sig);
}
else if (!strcmp (propname, "sig_algo"))
{
snprintf (numbuf, sizeof numbuf, "%d", sig->pubkey_algo);
result = numbuf;
}
else if (!strcmp (propname, "sig_digest_algo"))
{
snprintf (numbuf, sizeof numbuf, "%d", sig->digest_algo);
result = numbuf;
}
else if (!strcmp (propname, "expired"))
{
result = sig->flags.expired? "1":"0";
}
else
result = NULL;
}
else if (node->pkt->pkttype == PKT_PUBLIC_KEY
|| node->pkt->pkttype == PKT_SECRET_KEY
|| node->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| node->pkt->pkttype == PKT_SECRET_SUBKEY)
{
PKT_public_key *pk = node->pkt->pkt.public_key;
if (!strcmp (propname, "secret"))
{
result = (node->pkt->pkttype == PKT_SECRET_KEY
|| node->pkt->pkttype == PKT_SECRET_SUBKEY)? "1":"0";
}
else if (!strcmp (propname, "key_algo"))
{
snprintf (numbuf, sizeof numbuf, "%d", pk->pubkey_algo);
result = numbuf;
}
else if (!strcmp (propname, "key_created"))
{
snprintf (numbuf, sizeof numbuf, "%lu", (ulong)pk->timestamp);
result = numbuf;
}
else if (!strcmp (propname, "key_created_d"))
{
result = datestr_from_pk (pk);
}
else if (!strcmp (propname, "expired"))
{
result = pk->has_expired? "1":"0";
}
else if (!strcmp (propname, "revoked"))
{
result = pk->flags.revoked? "1":"0";
}
else if (!strcmp (propname, "disabled"))
{
result = pk_is_disabled (pk)? "1":"0";
}
else if (!strcmp (propname, "usage"))
{
snprintf (numbuf, sizeof numbuf, "%s%s%s%s%s",
(pk->pubkey_usage & PUBKEY_USAGE_ENC)?"e":"",
(pk->pubkey_usage & PUBKEY_USAGE_SIG)?"s":"",
(pk->pubkey_usage & PUBKEY_USAGE_CERT)?"c":"",
(pk->pubkey_usage & PUBKEY_USAGE_AUTH)?"a":"",
(pk->pubkey_usage & PUBKEY_USAGE_UNKNOWN)?"?":"");
result = numbuf;
}
else
result = NULL;
}
else
result = NULL;
return result;
}
/*
* Apply the keep-uid filter to the keyblock. The deleted nodes are
* marked and thus the caller should call commit_kbnode afterwards.
* KEYBLOCK must not have any blocks marked as deleted.
*/
static void
apply_keep_uid_filter (ctrl_t ctrl, kbnode_t keyblock, recsel_expr_t selector)
{
kbnode_t node;
struct impex_filter_parm_s parm;
parm.ctrl = ctrl;
for (node = keyblock->next; node; node = node->next )
{
if (node->pkt->pkttype == PKT_USER_ID)
{
parm.node = node;
if (!recsel_select (selector, impex_filter_getval, &parm))
{
/* log_debug ("keep-uid: deleting '%s'\n", */
/* node->pkt->pkt.user_id->name); */
/* The UID packet and all following packets up to the
* next UID or a subkey. */
delete_kbnode (node);
for (; node->next
&& node->next->pkt->pkttype != PKT_USER_ID
&& node->next->pkt->pkttype != PKT_PUBLIC_SUBKEY
&& node->next->pkt->pkttype != PKT_SECRET_SUBKEY ;
node = node->next)
delete_kbnode (node->next);
}
/* else */
/* log_debug ("keep-uid: keeping '%s'\n", */
/* node->pkt->pkt.user_id->name); */
}
}
}
/*
* Apply the drop-sig filter to the keyblock. The deleted nodes are
* marked and thus the caller should call commit_kbnode afterwards.
* KEYBLOCK must not have any blocks marked as deleted.
*/
static void
apply_drop_sig_filter (ctrl_t ctrl, kbnode_t keyblock, recsel_expr_t selector)
{
kbnode_t node;
int active = 0;
u32 main_keyid[2];
PKT_signature *sig;
struct impex_filter_parm_s parm;
parm.ctrl = ctrl;
keyid_from_pk (keyblock->pkt->pkt.public_key, main_keyid);
/* Loop over all signatures for user id and attribute packets which
* are not self signatures. */
for (node = keyblock->next; node; node = node->next )
{
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| node->pkt->pkttype == PKT_SECRET_SUBKEY)
break; /* ready. */
if (node->pkt->pkttype == PKT_USER_ID
|| node->pkt->pkttype == PKT_ATTRIBUTE)
active = 1;
if (!active)
continue;
if (node->pkt->pkttype != PKT_SIGNATURE)
continue;
sig = node->pkt->pkt.signature;
if (main_keyid[0] == sig->keyid[0] || main_keyid[1] == sig->keyid[1])
continue; /* Skip self-signatures. */
if (IS_UID_SIG(sig) || IS_UID_REV(sig))
{
parm.node = node;
if (recsel_select (selector, impex_filter_getval, &parm))
delete_kbnode (node);
}
}
}
/* Insert a key origin into a public key packet. */
static gpg_error_t
insert_key_origin_pk (PKT_public_key *pk, u32 curtime,
int origin, const char *url)
{
if (origin == KEYORG_WKD || origin == KEYORG_DANE)
{
/* For WKD and DANE we insert origin information also for the
* key but we don't record the URL because we have have no use
* for that: An update using a keyserver has higher precedence
* and will thus update this origin info. For refresh using WKD
* or DANE we need to go via the User ID anyway. Recall that we
* are only inserting a new key. */
pk->keyorg = origin;
pk->keyupdate = curtime;
}
else if (origin == KEYORG_KS && url)
{
/* If the key was retrieved from a keyserver using a fingerprint
* request we add the meta information. Note that the use of a
* fingerprint needs to be enforced by the caller of the import
* function. This is commonly triggered by verifying a modern
* signature which has an Issuer Fingerprint signature
* subpacket. */
pk->keyorg = origin;
pk->keyupdate = curtime;
xfree (pk->updateurl);
pk->updateurl = xtrystrdup (url);
if (!pk->updateurl)
return gpg_error_from_syserror ();
}
else if (origin == KEYORG_FILE)
{
pk->keyorg = origin;
pk->keyupdate = curtime;
}
else if (origin == KEYORG_URL)
{
pk->keyorg = origin;
pk->keyupdate = curtime;
if (url)
{
xfree (pk->updateurl);
pk->updateurl = xtrystrdup (url);
if (!pk->updateurl)
return gpg_error_from_syserror ();
}
}
return 0;
}
/* Insert a key origin into a user id packet. */
static gpg_error_t
insert_key_origin_uid (PKT_user_id *uid, u32 curtime,
int origin, const char *url)
{
if (origin == KEYORG_WKD || origin == KEYORG_DANE)
{
/* We insert origin information on a UID only when we received
* them via the Web Key Directory or a DANE record. The key we
* receive here from the WKD has been filtered to contain only
* the user ID as looked up in the WKD. For a DANE origin we
* this should also be the case. Thus we will see here only one
* user id. */
uid->keyorg = origin;
uid->keyupdate = curtime;
if (url)
{
xfree (uid->updateurl);
uid->updateurl = xtrystrdup (url);
if (!uid->updateurl)
return gpg_error_from_syserror ();
}
}
else if (origin == KEYORG_KS && url)
{
/* If the key was retrieved from a keyserver using a fingerprint
* request we mark that also in the user ID. However we do not
* store the keyserver URL in the UID. A later update (merge)
* from a more trusted source will replace this info. */
uid->keyorg = origin;
uid->keyupdate = curtime;
}
else if (origin == KEYORG_FILE)
{
uid->keyorg = origin;
uid->keyupdate = curtime;
}
else if (origin == KEYORG_URL)
{
uid->keyorg = origin;
uid->keyupdate = curtime;
}
return 0;
}
/* Apply meta data to KEYBLOCK. This sets the origin of the key to
* ORIGIN and the updateurl to URL. Note that this function is only
* used for a new key, that is not when we are merging keys. */
static gpg_error_t
insert_key_origin (kbnode_t keyblock, int origin, const char *url)
{
gpg_error_t err;
kbnode_t node;
u32 curtime = make_timestamp ();
for (node = keyblock; node; node = node->next)
{
if (is_deleted_kbnode (node))
;
else if (node->pkt->pkttype == PKT_PUBLIC_KEY)
{
err = insert_key_origin_pk (node->pkt->pkt.public_key, curtime,
origin, url);
if (err)
return err;
}
else if (node->pkt->pkttype == PKT_USER_ID)
{
err = insert_key_origin_uid (node->pkt->pkt.user_id, curtime,
origin, url);
if (err)
return err;
}
}
return 0;
}
/* Update meta data on KEYBLOCK. This updates the key origin on the
* public key according to ORIGIN and URL. The UIDs are already
* updated when this function is called. */
static gpg_error_t
update_key_origin (kbnode_t keyblock, u32 curtime, int origin, const char *url)
{
PKT_public_key *pk;
log_assert (keyblock->pkt->pkttype == PKT_PUBLIC_KEY);
pk = keyblock->pkt->pkt.public_key;
if (pk->keyupdate > curtime)
; /* Don't do it for a time warp. */
else if (origin == KEYORG_WKD || origin == KEYORG_DANE)
{
/* We only update the origin info if they either have never been
* set or are the origin was the same as the new one. If this
* is WKD we also update the UID to show from which user id this
* was updated. */
if (!pk->keyorg || pk->keyorg == KEYORG_WKD || pk->keyorg == KEYORG_DANE)
{
pk->keyorg = origin;
pk->keyupdate = curtime;
xfree (pk->updateurl);
pk->updateurl = NULL;
if (origin == KEYORG_WKD && url)
{
pk->updateurl = xtrystrdup (url);
if (!pk->updateurl)
return gpg_error_from_syserror ();
}
}
}
else if (origin == KEYORG_KS)
{
/* All updates from a keyserver are considered to have the
* freshed key. Thus we always set the new key origin. */
pk->keyorg = origin;
pk->keyupdate = curtime;
xfree (pk->updateurl);
pk->updateurl = NULL;
if (url)
{
pk->updateurl = xtrystrdup (url);
if (!pk->updateurl)
return gpg_error_from_syserror ();
}
}
else if (origin == KEYORG_FILE)
{
/* Updates from a file are considered to be fresh. */
pk->keyorg = origin;
pk->keyupdate = curtime;
xfree (pk->updateurl);
pk->updateurl = NULL;
}
else if (origin == KEYORG_URL)
{
/* Updates from a URL are considered to be fresh. */
pk->keyorg = origin;
pk->keyupdate = curtime;
xfree (pk->updateurl);
pk->updateurl = NULL;
if (url)
{
pk->updateurl = xtrystrdup (url);
if (!pk->updateurl)
return gpg_error_from_syserror ();
}
}
return 0;
}
/*
* Try to import one keyblock. Return an error only in serious cases,
* but never for an invalid keyblock. It uses log_error to increase
* the internal errorcount, so that invalid input can be detected by
* programs which called gpg. If SILENT is no messages are printed -
* even most error messages are suppressed. ORIGIN is the origin of
* the key (0 for unknown) and URL the corresponding URL. FROM_SK
* indicates that the key has been made from a secret key. If R_SAVED
* is not NULL a boolean will be stored indicating whether the keyblock
* has valid parts.
*/
static gpg_error_t
-import_one (ctrl_t ctrl,
- kbnode_t keyblock, struct import_stats_s *stats,
- unsigned char **fpr, size_t *fpr_len, unsigned int options,
- int from_sk, int silent,
- import_screener_t screener, void *screener_arg,
- int origin, const char *url, int *r_valid)
+import_one_real (ctrl_t ctrl,
+ kbnode_t keyblock, struct import_stats_s *stats,
+ unsigned char **fpr, size_t *fpr_len, unsigned int options,
+ int from_sk, int silent,
+ import_screener_t screener, void *screener_arg,
+ int origin, const char *url, int *r_valid)
{
gpg_error_t err = 0;
PKT_public_key *pk;
kbnode_t node, uidnode;
kbnode_t keyblock_orig = NULL;
byte fpr2[MAX_FINGERPRINT_LEN];
size_t fpr2len;
u32 keyid[2];
int new_key = 0;
int mod_key = 0;
int same_key = 0;
int non_self = 0;
size_t an;
char pkstrbuf[PUBKEY_STRING_SIZE];
int merge_keys_done = 0;
int any_filter = 0;
KEYDB_HANDLE hd = NULL;
if (r_valid)
*r_valid = 0;
/* If show-only is active we don't won't any extra output. */
if ((options & (IMPORT_SHOW | IMPORT_DRY_RUN)))
silent = 1;
/* Get the key and print some info about it. */
node = find_kbnode( keyblock, PKT_PUBLIC_KEY );
if (!node )
BUG();
pk = node->pkt->pkt.public_key;
fingerprint_from_pk (pk, fpr2, &fpr2len);
for (an = fpr2len; an < MAX_FINGERPRINT_LEN; an++)
fpr2[an] = 0;
keyid_from_pk( pk, keyid );
uidnode = find_next_kbnode( keyblock, PKT_USER_ID );
if (opt.verbose && !opt.interactive && !silent && !from_sk)
{
/* Note that we do not print this info in FROM_SK mode
* because import_secret_one already printed that. */
log_info ("pub %s/%s %s ",
pubkey_string (pk, pkstrbuf, sizeof pkstrbuf),
keystr_from_pk(pk), datestr_from_pk(pk) );
if (uidnode)
print_utf8_buffer (log_get_stream (),
uidnode->pkt->pkt.user_id->name,
uidnode->pkt->pkt.user_id->len );
log_printf ("\n");
}
/* Unless import-drop-uids has been requested we don't allow import
* of a key without UIDs. */
if (!uidnode && !(options & IMPORT_DROP_UIDS))
{
if (!silent)
log_error( _("key %s: no user ID\n"), keystr_from_pk(pk));
return 0;
}
if (screener && screener (keyblock, screener_arg))
{
log_error (_("key %s: %s\n"), keystr_from_pk (pk),
_("rejected by import screener"));
return 0;
}
if (opt.interactive && !silent)
{
if (is_status_enabled())
print_import_check (pk, uidnode->pkt->pkt.user_id);
merge_keys_and_selfsig (ctrl, keyblock);
tty_printf ("\n");
show_basic_key_info (ctrl, keyblock, from_sk);
tty_printf ("\n");
if (!cpr_get_answer_is_yes ("import.okay",
"Do you want to import this key? (y/N) "))
return 0;
}
+ /* Remove all non-self-sigs if requested. Noe that this is a NOP if
+ * that option has been globally set but we may also be called
+ * latter with the already parsed keyblock and a locally changed
+ * option. This is why we need to remove them here as well. */
+ if ((options & IMPORT_SELF_SIGS_ONLY))
+ remove_all_non_self_sigs (&keyblock, keyid);
+
/* Remove or collapse the user ids. */
if ((options & IMPORT_DROP_UIDS))
remove_all_uids (&keyblock);
else
collapse_uids (&keyblock);
/* Clean the key that we're about to import, to cut down on things
that we have to clean later. This has no practical impact on the
end result, but does result in less logging which might confuse
the user. */
if ((options & IMPORT_CLEAN))
{
merge_keys_and_selfsig (ctrl, keyblock);
clean_all_uids (ctrl, keyblock,
opt.verbose, (options&IMPORT_MINIMAL), NULL, NULL);
clean_all_subkeys (ctrl, keyblock, opt.verbose, KEY_CLEAN_NONE,
NULL, NULL);
}
clear_kbnode_flags( keyblock );
if ((options&IMPORT_REPAIR_PKS_SUBKEY_BUG)
&& fix_pks_corruption (ctrl, keyblock)
&& opt.verbose)
log_info (_("key %s: PKS subkey corruption repaired\n"),
keystr_from_pk(pk));
if ((options & IMPORT_REPAIR_KEYS))
key_check_all_keysigs (ctrl, 1, keyblock, 0, 0);
if (chk_self_sigs (ctrl, keyblock, keyid, &non_self))
return 0; /* Invalid keyblock - error already printed. */
/* If we allow such a thing, mark unsigned uids as valid */
if (opt.allow_non_selfsigned_uid)
{
for (node=keyblock; node; node = node->next )
if (node->pkt->pkttype == PKT_USER_ID
&& !(node->flag & NODE_GOOD_SELFSIG)
&& !(node->flag & NODE_BAD_SELFSIG) )
{
char *user=utf8_to_native(node->pkt->pkt.user_id->name,
node->pkt->pkt.user_id->len,0);
/* Fake a good signature status for the user id. */
node->flag |= NODE_GOOD_SELFSIG;
log_info( _("key %s: accepted non self-signed user ID \"%s\"\n"),
keystr_from_pk(pk),user);
xfree(user);
}
}
/* Delete invalid parts and without the drop option bail out if
* there are no user ids. */
if (!delete_inv_parts (ctrl, keyblock, keyid, options)
&& !(options & IMPORT_DROP_UIDS) )
{
if (!silent)
{
log_error( _("key %s: no valid user IDs\n"), keystr_from_pk(pk));
if (!opt.quiet )
log_info(_("this may be caused by a missing self-signature\n"));
}
stats->no_user_id++;
return 0;
}
/* Get rid of deleted nodes. */
commit_kbnode (&keyblock);
/* Apply import filter. */
if (import_filter.keep_uid)
{
apply_keep_uid_filter (ctrl, keyblock, import_filter.keep_uid);
commit_kbnode (&keyblock);
any_filter = 1;
}
if (import_filter.drop_sig)
{
apply_drop_sig_filter (ctrl, keyblock, import_filter.drop_sig);
commit_kbnode (&keyblock);
any_filter = 1;
}
/* If we ran any filter we need to check that at least one user id
* is left in the keyring. Note that we do not use log_error in
* this case. */
if (any_filter && !any_uid_left (keyblock))
{
if (!opt.quiet )
log_info ( _("key %s: no valid user IDs\n"), keystr_from_pk (pk));
stats->no_user_id++;
return 0;
}
/* The keyblock is valid and ready for real import. */
if (r_valid)
*r_valid = 1;
/* Show the key in the form it is merged or inserted. We skip this
* if "import-export" is also active without --armor or the output
* file has explicily been given. */
if ((options & IMPORT_SHOW)
&& !((options & IMPORT_EXPORT) && !opt.armor && !opt.outfile))
{
merge_keys_and_selfsig (ctrl, keyblock);
merge_keys_done = 1;
/* Note that we do not want to show the validity because the key
* has not yet imported. */
list_keyblock_direct (ctrl, keyblock, from_sk, 0,
opt.fingerprint || opt.with_fingerprint, 1);
es_fflush (es_stdout);
}
/* Write the keyblock to the output and do not actually import. */
if ((options & IMPORT_EXPORT))
{
if (!merge_keys_done)
{
merge_keys_and_selfsig (ctrl, keyblock);
merge_keys_done = 1;
}
err = write_keyblock_to_output (keyblock, opt.armor, opt.export_options);
goto leave;
}
if (opt.dry_run || (options & IMPORT_DRY_RUN))
goto leave;
/* Do we have this key already in one of our pubrings ? */
err = get_keyblock_byfprint_fast (&keyblock_orig, &hd,
fpr2, fpr2len, 1/*locked*/);
if ((err
&& gpg_err_code (err) != GPG_ERR_NO_PUBKEY
&& gpg_err_code (err) != GPG_ERR_UNUSABLE_PUBKEY)
|| !hd)
{
/* The !hd above is to catch a misbehaving function which
* returns NO_PUBKEY for failing to allocate a handle. */
if (!silent)
log_error (_("key %s: public key not found: %s\n"),
keystr(keyid), gpg_strerror (err));
}
else if (err && (opt.import_options&IMPORT_MERGE_ONLY) )
{
if (opt.verbose && !silent )
log_info( _("key %s: new key - skipped\n"), keystr(keyid));
err = 0;
stats->skipped_new_keys++;
}
else if (err) /* Insert this key. */
{
/* Note: ERR can only be NO_PUBKEY or UNUSABLE_PUBKEY. */
int n_sigs_cleaned, n_uids_cleaned;
err = keydb_locate_writable (hd);
if (err)
{
log_error (_("no writable keyring found: %s\n"), gpg_strerror (err));
err = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
if (opt.verbose > 1 )
log_info (_("writing to '%s'\n"), keydb_get_resource_name (hd) );
if ((options & IMPORT_CLEAN))
{
merge_keys_and_selfsig (ctrl, keyblock);
clean_all_uids (ctrl, keyblock, opt.verbose, (options&IMPORT_MINIMAL),
&n_uids_cleaned,&n_sigs_cleaned);
clean_all_subkeys (ctrl, keyblock, opt.verbose, KEY_CLEAN_NONE,
NULL, NULL);
}
/* Unless we are in restore mode apply meta data to the
* keyblock. Note that this will never change the first packet
* and thus the address of KEYBLOCK won't change. */
if ( !(options & IMPORT_RESTORE) )
{
err = insert_key_origin (keyblock, origin, url);
if (err)
{
log_error ("insert_key_origin failed: %s\n", gpg_strerror (err));
err = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
}
err = keydb_insert_keyblock (hd, keyblock );
if (err)
log_error (_("error writing keyring '%s': %s\n"),
keydb_get_resource_name (hd), gpg_strerror (err));
else if (!(opt.import_options & IMPORT_KEEP_OWNERTTRUST))
{
/* This should not be possible since we delete the
ownertrust when a key is deleted, but it can happen if
the keyring and trustdb are out of sync. It can also
be made to happen with the trusted-key command and by
importing and locally exported key. */
clear_ownertrusts (ctrl, pk);
if (non_self)
revalidation_mark (ctrl);
}
/* Release the handle and thus unlock the keyring asap. */
keydb_release (hd);
hd = NULL;
/* We are ready. */
- if (!opt.quiet && !silent)
+ if (!err && !opt.quiet && !silent)
{
- char *p = get_user_id_byfpr_native (ctrl, fpr2);
+ char *p = get_user_id_byfpr_native (ctrl, fpr2, fpr2len);
log_info (_("key %s: public key \"%s\" imported\n"),
keystr(keyid), p);
xfree(p);
}
- if (is_status_enabled())
+ if (!err && is_status_enabled())
{
char *us = get_long_user_id_string (ctrl, keyid);
write_status_text( STATUS_IMPORTED, us );
xfree(us);
print_import_ok (pk, 1);
}
- stats->imported++;
- new_key = 1;
+ if (!err)
+ {
+ stats->imported++;
+ new_key = 1;
+ }
}
else /* Key already exists - merge. */
{
int n_uids, n_sigs, n_subk, n_sigs_cleaned, n_uids_cleaned;
u32 curtime = make_timestamp ();
/* Compare the original against the new key; just to be sure nothing
* weird is going on */
if (cmp_public_keys (keyblock_orig->pkt->pkt.public_key, pk))
{
if (!silent)
log_error( _("key %s: doesn't match our copy\n"),keystr(keyid));
goto leave;
}
/* Make sure the original direct key sigs are all sane. */
n_sigs_cleaned = fix_bad_direct_key_sigs (ctrl, keyblock_orig, keyid);
if (n_sigs_cleaned)
commit_kbnode (&keyblock_orig);
/* Try to merge KEYBLOCK into KEYBLOCK_ORIG. */
clear_kbnode_flags( keyblock_orig );
clear_kbnode_flags( keyblock );
n_uids = n_sigs = n_subk = n_uids_cleaned = 0;
err = merge_blocks (ctrl, options, keyblock_orig, keyblock, keyid,
curtime, origin, url,
&n_uids, &n_sigs, &n_subk );
if (err)
goto leave;
if ((options & IMPORT_CLEAN))
{
merge_keys_and_selfsig (ctrl, keyblock_orig);
clean_all_uids (ctrl, keyblock_orig, opt.verbose,
(options&IMPORT_MINIMAL),
&n_uids_cleaned,&n_sigs_cleaned);
clean_all_subkeys (ctrl, keyblock_orig, opt.verbose, KEY_CLEAN_NONE,
NULL, NULL);
}
if (n_uids || n_sigs || n_subk || n_sigs_cleaned || n_uids_cleaned)
{
/* Unless we are in restore mode apply meta data to the
* keyblock. Note that this will never change the first packet
* and thus the address of KEYBLOCK won't change. */
if ( !(options & IMPORT_RESTORE) )
{
err = update_key_origin (keyblock_orig, curtime, origin, url);
if (err)
{
log_error ("update_key_origin failed: %s\n",
gpg_strerror (err));
goto leave;
}
}
mod_key = 1;
/* KEYBLOCK_ORIG has been updated; write */
err = keydb_update_keyblock (ctrl, hd, keyblock_orig);
if (err)
log_error (_("error writing keyring '%s': %s\n"),
keydb_get_resource_name (hd), gpg_strerror (err));
else if (non_self)
revalidation_mark (ctrl);
/* Release the handle and thus unlock the keyring asap. */
keydb_release (hd);
hd = NULL;
- /* We are ready. */
- if (!opt.quiet && !silent)
+ /* We are ready. Print and update stats if we got no error.
+ * An error here comes from writing the keyblock and thus
+ * very likely means that no update happened. */
+ if (!err && !opt.quiet && !silent)
{
- char *p = get_user_id_byfpr_native (ctrl, fpr2);
+ char *p = get_user_id_byfpr_native (ctrl, fpr2, fpr2len);
if (n_uids == 1 )
log_info( _("key %s: \"%s\" 1 new user ID\n"),
keystr(keyid),p);
else if (n_uids )
log_info( _("key %s: \"%s\" %d new user IDs\n"),
keystr(keyid),p,n_uids);
if (n_sigs == 1 )
log_info( _("key %s: \"%s\" 1 new signature\n"),
keystr(keyid), p);
else if (n_sigs )
log_info( _("key %s: \"%s\" %d new signatures\n"),
keystr(keyid), p, n_sigs );
if (n_subk == 1 )
log_info( _("key %s: \"%s\" 1 new subkey\n"),
keystr(keyid), p);
else if (n_subk )
log_info( _("key %s: \"%s\" %d new subkeys\n"),
keystr(keyid), p, n_subk );
if (n_sigs_cleaned==1)
log_info(_("key %s: \"%s\" %d signature cleaned\n"),
keystr(keyid),p,n_sigs_cleaned);
else if (n_sigs_cleaned)
log_info(_("key %s: \"%s\" %d signatures cleaned\n"),
keystr(keyid),p,n_sigs_cleaned);
if (n_uids_cleaned==1)
log_info(_("key %s: \"%s\" %d user ID cleaned\n"),
keystr(keyid),p,n_uids_cleaned);
else if (n_uids_cleaned)
log_info(_("key %s: \"%s\" %d user IDs cleaned\n"),
keystr(keyid),p,n_uids_cleaned);
xfree(p);
}
- stats->n_uids +=n_uids;
- stats->n_sigs +=n_sigs;
- stats->n_subk +=n_subk;
- stats->n_sigs_cleaned +=n_sigs_cleaned;
- stats->n_uids_cleaned +=n_uids_cleaned;
-
- if (is_status_enabled () && !silent)
- print_import_ok (pk, ((n_uids?2:0)|(n_sigs?4:0)|(n_subk?8:0)));
+ if (!err)
+ {
+ stats->n_uids +=n_uids;
+ stats->n_sigs +=n_sigs;
+ stats->n_subk +=n_subk;
+ stats->n_sigs_cleaned +=n_sigs_cleaned;
+ stats->n_uids_cleaned +=n_uids_cleaned;
+
+ if (is_status_enabled () && !silent)
+ print_import_ok (pk, ((n_uids?2:0)|(n_sigs?4:0)|(n_subk?8:0)));
+ }
}
else
{
/* Release the handle and thus unlock the keyring asap. */
keydb_release (hd);
hd = NULL;
/* FIXME: We do not track the time we last checked a key for
* updates. To do this we would need to rewrite even the
* keys which have no changes. Adding this would be useful
* for the automatic update of expired keys via the WKD in
* case the WKD still carries the expired key. See
* get_best_pubkey_byname. */
same_key = 1;
if (is_status_enabled ())
print_import_ok (pk, 0);
if (!opt.quiet && !silent)
{
- char *p = get_user_id_byfpr_native (ctrl, fpr2);
+ char *p = get_user_id_byfpr_native (ctrl, fpr2, fpr2len);
log_info( _("key %s: \"%s\" not changed\n"),keystr(keyid),p);
xfree(p);
}
stats->unchanged++;
}
}
leave:
keydb_release (hd);
if (mod_key || new_key || same_key)
{
/* A little explanation for this: we fill in the fingerprint
when importing keys as it can be useful to know the
fingerprint in certain keyserver-related cases (a keyserver
asked for a particular name, but the key doesn't have that
name). However, in cases where we're importing more than
one key at a time, we cannot know which key to fingerprint.
In these cases, rather than guessing, we do not
fingerprinting at all, and we must hope the user ID on the
keys are useful. Note that we need to do this for new
keys, merged keys and even for unchanged keys. This is
required because for example the --auto-key-locate feature
may import an already imported key and needs to know the
fingerprint of the key in all cases. */
if (fpr)
{
- xfree (*fpr);
/* Note that we need to compare against 0 here because
COUNT gets only incremented after returning from this
function. */
if (!stats->count)
- *fpr = fingerprint_from_pk (pk, NULL, fpr_len);
- else
- *fpr = NULL;
+ {
+ xfree (*fpr);
+ *fpr = fingerprint_from_pk (pk, NULL, fpr_len);
+ }
+ else if (origin != KEYORG_WKD)
+ {
+ xfree (*fpr);
+ *fpr = NULL;
+ }
}
}
/* Now that the key is definitely incorporated into the keydb, we
need to check if a designated revocation is present or if the
prefs are not rational so we can warn the user. */
if (mod_key)
{
revocation_present (ctrl, keyblock_orig);
if (!from_sk && have_secret_key_with_kid (keyid))
check_prefs (ctrl, keyblock_orig);
}
else if (new_key)
{
revocation_present (ctrl, keyblock);
if (!from_sk && have_secret_key_with_kid (keyid))
check_prefs (ctrl, keyblock);
}
release_kbnode( keyblock_orig );
return err;
}
+/* Wrapper around import_one_real to retry the import in some cases. */
+static gpg_error_t
+import_one (ctrl_t ctrl,
+ kbnode_t keyblock, struct import_stats_s *stats,
+ unsigned char **fpr, size_t *fpr_len, unsigned int options,
+ int from_sk, int silent,
+ import_screener_t screener, void *screener_arg,
+ int origin, const char *url, int *r_valid)
+{
+ gpg_error_t err;
+
+ err = import_one_real (ctrl, keyblock, stats, fpr, fpr_len, options,
+ from_sk, silent, screener, screener_arg,
+ origin, url, r_valid);
+ if (gpg_err_code (err) == GPG_ERR_TOO_LARGE
+ && gpg_err_source (err) == GPG_ERR_SOURCE_KEYBOX
+ && ((options & (IMPORT_SELF_SIGS_ONLY | IMPORT_CLEAN))
+ != (IMPORT_SELF_SIGS_ONLY | IMPORT_CLEAN)))
+ {
+ /* We hit the maximum image length. Ask the wrapper to do
+ * everything again but this time with some extra options. */
+ u32 keyid[2];
+
+ keyid_from_pk (keyblock->pkt->pkt.public_key, keyid);
+ log_info ("key %s: keyblock too large, retrying with self-sigs-only\n",
+ keystr (keyid));
+ options |= IMPORT_SELF_SIGS_ONLY | IMPORT_CLEAN;
+ err = import_one_real (ctrl, keyblock, stats, fpr, fpr_len, options,
+ from_sk, silent, screener, screener_arg,
+ origin, url, r_valid);
+ }
+ return err;
+}
+
+
/* Transfer all the secret keys in SEC_KEYBLOCK to the gpg-agent. The
* function prints diagnostics and returns an error code. If BATCH is
* true the secret keys are stored by gpg-agent in the transfer format
* (i.e. no re-protection and aksing for passphrases). If ONLY_MARKED
* is set, only those nodes with flag NODE_TRANSFER_SECKEY are
* processed. */
gpg_error_t
transfer_secret_keys (ctrl_t ctrl, struct import_stats_s *stats,
kbnode_t sec_keyblock, int batch, int force,
int only_marked)
{
gpg_error_t err = 0;
void *kek = NULL;
size_t keklen;
kbnode_t ctx = NULL;
kbnode_t node;
PKT_public_key *main_pk, *pk;
struct seckey_info *ski;
int nskey;
membuf_t mbuf;
int i, j;
void *format_args[2*PUBKEY_MAX_NSKEY];
gcry_sexp_t skey, prot, tmpsexp;
gcry_sexp_t curve = NULL;
unsigned char *transferkey = NULL;
size_t transferkeylen;
gcry_cipher_hd_t cipherhd = NULL;
unsigned char *wrappedkey = NULL;
size_t wrappedkeylen;
char *cache_nonce = NULL;
int stub_key_skipped = 0;
/* Get the current KEK. */
err = agent_keywrap_key (ctrl, 0, &kek, &keklen);
if (err)
{
log_error ("error getting the KEK: %s\n", gpg_strerror (err));
goto leave;
}
/* Prepare a cipher context. */
err = gcry_cipher_open (&cipherhd, GCRY_CIPHER_AES128,
GCRY_CIPHER_MODE_AESWRAP, 0);
if (!err)
err = gcry_cipher_setkey (cipherhd, kek, keklen);
if (err)
goto leave;
xfree (kek);
kek = NULL;
/* Note: We need to use walk_kbnode so that we skip nodes which are
* marked as deleted. */
main_pk = NULL;
while ((node = walk_kbnode (sec_keyblock, &ctx, 0)))
{
if (node->pkt->pkttype != PKT_SECRET_KEY
&& node->pkt->pkttype != PKT_SECRET_SUBKEY)
continue;
if (only_marked && !(node->flag & NODE_TRANSFER_SECKEY))
continue;
pk = node->pkt->pkt.public_key;
if (!main_pk)
main_pk = pk;
/* Make sure the keyids are available. */
keyid_from_pk (pk, NULL);
if (node->pkt->pkttype == PKT_SECRET_KEY)
{
pk->main_keyid[0] = pk->keyid[0];
pk->main_keyid[1] = pk->keyid[1];
}
else
{
pk->main_keyid[0] = main_pk->keyid[0];
pk->main_keyid[1] = main_pk->keyid[1];
}
ski = pk->seckey_info;
if (!ski)
BUG ();
if (stats)
{
stats->count++;
stats->secret_read++;
}
/* We ignore stub keys. The way we handle them in other parts
of the code is by asking the agent whether any secret key is
available for a given keyblock and then concluding that we
have a secret key; all secret (sub)keys of the keyblock the
agent does not know of are then stub keys. This works also
for card stub keys. The learn command or the card-status
command may be used to check with the agent whether a card
has been inserted and a stub key is in turn generated by the
agent. */
if (ski->s2k.mode == 1001 || ski->s2k.mode == 1002)
{
stub_key_skipped = 1;
continue;
}
/* Convert our internal secret key object into an S-expression. */
nskey = pubkey_get_nskey (pk->pubkey_algo);
if (!nskey || nskey > PUBKEY_MAX_NSKEY)
{
err = gpg_error (GPG_ERR_BAD_SECKEY);
log_error ("internal error: %s\n", gpg_strerror (err));
goto leave;
}
init_membuf (&mbuf, 50);
put_membuf_str (&mbuf, "(skey");
if (pk->pubkey_algo == PUBKEY_ALGO_ECDSA
|| pk->pubkey_algo == PUBKEY_ALGO_EDDSA
|| pk->pubkey_algo == PUBKEY_ALGO_ECDH)
{
/* The ECC case. */
char *curvestr = openpgp_oid_to_str (pk->pkey[0]);
if (!curvestr)
err = gpg_error_from_syserror ();
else
{
const char *curvename = openpgp_oid_to_curve (curvestr, 1);
gcry_sexp_release (curve);
err = gcry_sexp_build (&curve, NULL, "(curve %s)",
curvename?curvename:curvestr);
xfree (curvestr);
if (!err)
{
j = 0;
/* Append the public key element Q. */
put_membuf_str (&mbuf, " _ %m");
format_args[j++] = pk->pkey + 1;
/* Append the secret key element D. For ECDH we
skip PKEY[2] because this holds the KEK which is
not needed by gpg-agent. */
i = pk->pubkey_algo == PUBKEY_ALGO_ECDH? 3 : 2;
if (gcry_mpi_get_flag (pk->pkey[i], GCRYMPI_FLAG_USER1))
put_membuf_str (&mbuf, " e %m");
else
put_membuf_str (&mbuf, " _ %m");
format_args[j++] = pk->pkey + i;
}
}
}
else
{
/* Standard case for the old (non-ECC) algorithms. */
for (i=j=0; i < nskey; i++)
{
if (!pk->pkey[i])
continue; /* Protected keys only have NPKEY+1 elements. */
if (gcry_mpi_get_flag (pk->pkey[i], GCRYMPI_FLAG_USER1))
put_membuf_str (&mbuf, " e %m");
else
put_membuf_str (&mbuf, " _ %m");
format_args[j++] = pk->pkey + i;
}
}
put_membuf_str (&mbuf, ")");
put_membuf (&mbuf, "", 1);
if (err)
xfree (get_membuf (&mbuf, NULL));
else
{
char *format = get_membuf (&mbuf, NULL);
if (!format)
err = gpg_error_from_syserror ();
else
err = gcry_sexp_build_array (&skey, NULL, format, format_args);
xfree (format);
}
if (err)
{
log_error ("error building skey array: %s\n", gpg_strerror (err));
goto leave;
}
if (ski->is_protected)
{
char countbuf[35];
/* FIXME: Support AEAD */
/* Note that the IVLEN may be zero if we are working on a
dummy key. We can't express that in an S-expression and
thus we send dummy data for the IV. */
snprintf (countbuf, sizeof countbuf, "%lu",
(unsigned long)ski->s2k.count);
err = gcry_sexp_build
(&prot, NULL,
" (protection %s %s %b %d %s %b %s)\n",
ski->sha1chk? "sha1":"sum",
openpgp_cipher_algo_name (ski->algo),
ski->ivlen? (int)ski->ivlen:1,
ski->ivlen? ski->iv: (const unsigned char*)"X",
ski->s2k.mode,
openpgp_md_algo_name (ski->s2k.hash_algo),
(int)sizeof (ski->s2k.salt), ski->s2k.salt,
countbuf);
}
else
err = gcry_sexp_build (&prot, NULL, " (protection none)\n");
tmpsexp = NULL;
xfree (transferkey);
transferkey = NULL;
if (!err)
err = gcry_sexp_build (&tmpsexp, NULL,
"(openpgp-private-key\n"
" (version %d)\n"
" (algo %s)\n"
" %S%S\n"
" (csum %d)\n"
" %S)\n",
pk->version,
openpgp_pk_algo_name (pk->pubkey_algo),
curve, skey,
(int)(unsigned long)ski->csum, prot);
gcry_sexp_release (skey);
gcry_sexp_release (prot);
if (!err)
err = make_canon_sexp_pad (tmpsexp, 1, &transferkey, &transferkeylen);
gcry_sexp_release (tmpsexp);
if (err)
{
log_error ("error building transfer key: %s\n", gpg_strerror (err));
goto leave;
}
/* Wrap the key. */
wrappedkeylen = transferkeylen + 8;
xfree (wrappedkey);
wrappedkey = xtrymalloc (wrappedkeylen);
if (!wrappedkey)
err = gpg_error_from_syserror ();
else
err = gcry_cipher_encrypt (cipherhd, wrappedkey, wrappedkeylen,
transferkey, transferkeylen);
if (err)
goto leave;
xfree (transferkey);
transferkey = NULL;
/* Send the wrapped key to the agent. */
{
char *desc = gpg_format_keydesc (ctrl, pk, FORMAT_KEYDESC_IMPORT, 1);
err = agent_import_key (ctrl, desc, &cache_nonce,
wrappedkey, wrappedkeylen, batch, force,
pk->keyid, pk->main_keyid, pk->pubkey_algo);
xfree (desc);
}
if (!err)
{
if (opt.verbose)
log_info (_("key %s: secret key imported\n"),
keystr_from_pk_with_sub (main_pk, pk));
if (stats)
stats->secret_imported++;
}
else if ( gpg_err_code (err) == GPG_ERR_EEXIST )
{
if (opt.verbose)
log_info (_("key %s: secret key already exists\n"),
keystr_from_pk_with_sub (main_pk, pk));
err = 0;
if (stats)
stats->secret_dups++;
}
else
{
log_error (_("key %s: error sending to agent: %s\n"),
keystr_from_pk_with_sub (main_pk, pk),
gpg_strerror (err));
if (gpg_err_code (err) == GPG_ERR_CANCELED
|| gpg_err_code (err) == GPG_ERR_FULLY_CANCELED)
break; /* Don't try the other subkeys. */
}
}
if (!err && stub_key_skipped)
/* We need to notify user how to migrate stub keys. */
err = gpg_error (GPG_ERR_NOT_PROCESSED);
leave:
gcry_sexp_release (curve);
xfree (cache_nonce);
xfree (wrappedkey);
xfree (transferkey);
gcry_cipher_close (cipherhd);
xfree (kek);
return err;
}
/* Walk a secret keyblock and produce a public keyblock out of it.
* Returns a new node or NULL on error. Modifies the tag field of the
* nodes. */
static kbnode_t
sec_to_pub_keyblock (kbnode_t sec_keyblock)
{
kbnode_t pub_keyblock = NULL;
kbnode_t ctx = NULL;
kbnode_t secnode, pubnode;
+ kbnode_t lastnode = NULL;
unsigned int tag = 0;
/* Set a tag to all nodes. */
for (secnode = sec_keyblock; secnode; secnode = secnode->next)
secnode->tag = ++tag;
/* Copy. */
while ((secnode = walk_kbnode (sec_keyblock, &ctx, 0)))
{
if (secnode->pkt->pkttype == PKT_SECRET_KEY
|| secnode->pkt->pkttype == PKT_SECRET_SUBKEY)
{
/* Make a public key. */
PACKET *pkt;
PKT_public_key *pk;
pkt = xtrycalloc (1, sizeof *pkt);
pk = pkt? copy_public_key (NULL, secnode->pkt->pkt.public_key): NULL;
if (!pk)
{
xfree (pkt);
release_kbnode (pub_keyblock);
return NULL;
}
if (secnode->pkt->pkttype == PKT_SECRET_KEY)
pkt->pkttype = PKT_PUBLIC_KEY;
else
pkt->pkttype = PKT_PUBLIC_SUBKEY;
pkt->pkt.public_key = pk;
pubnode = new_kbnode (pkt);
}
else
{
pubnode = clone_kbnode (secnode);
}
pubnode->tag = secnode->tag;
if (!pub_keyblock)
- pub_keyblock = pubnode;
+ pub_keyblock = lastnode = pubnode;
else
- add_kbnode (pub_keyblock, pubnode);
+ {
+ lastnode->next = pubnode;
+ lastnode = pubnode;
+ }
}
return pub_keyblock;
}
/* Delete all notes in the keyblock at R_KEYBLOCK which are not in
* PUB_KEYBLOCK. Modifies the tags of both keyblock's nodes. */
static gpg_error_t
resync_sec_with_pub_keyblock (kbnode_t *r_keyblock, kbnode_t pub_keyblock,
kbnode_t *r_removedsecs)
{
kbnode_t sec_keyblock = *r_keyblock;
kbnode_t node, prevnode;
unsigned int *taglist;
unsigned int ntaglist, n;
kbnode_t attic = NULL;
kbnode_t *attic_head = &attic;
/* Collect all tags in an array for faster searching. */
for (ntaglist = 0, node = pub_keyblock; node; node = node->next)
ntaglist++;
taglist = xtrycalloc (ntaglist, sizeof *taglist);
if (!taglist)
return gpg_error_from_syserror ();
for (ntaglist = 0, node = pub_keyblock; node; node = node->next)
taglist[ntaglist++] = node->tag;
/* Walks over the secret keyblock and delete all nodes which are not
* in the tag list. Those nodes have been deleted in the
* pub_keyblock. Sequential search is a bit lazy and could be
* optimized by sorting and bsearch; however secret keyrings are
* short and there are easier ways to DoS the import. */
again:
for (prevnode=NULL, node=sec_keyblock; node; prevnode=node, node=node->next)
{
for (n=0; n < ntaglist; n++)
if (taglist[n] == node->tag)
break;
if (n == ntaglist) /* Not in public keyblock. */
{
if (node->pkt->pkttype == PKT_SECRET_KEY
|| node->pkt->pkttype == PKT_SECRET_SUBKEY)
{
if (!prevnode)
sec_keyblock = node->next;
else
prevnode->next = node->next;
node->next = NULL;
*attic_head = node;
attic_head = &node->next;
goto again; /* That's lame; I know. */
}
else
delete_kbnode (node);
}
}
xfree (taglist);
/* Commit the as deleted marked nodes and return the possibly
* modified keyblock and a list of removed secret key nodes. */
commit_kbnode (&sec_keyblock);
*r_keyblock = sec_keyblock;
*r_removedsecs = attic;
return 0;
}
/* Helper for import_secret_one. */
static gpg_error_t
do_transfer (ctrl_t ctrl, kbnode_t keyblock, PKT_public_key *pk,
struct import_stats_s *stats, int batch, int only_marked)
{
gpg_error_t err;
struct import_stats_s subkey_stats = {0};
err = transfer_secret_keys (ctrl, &subkey_stats, keyblock,
batch, 0, only_marked);
if (gpg_err_code (err) == GPG_ERR_NOT_PROCESSED)
{
/* TRANSLATORS: For a smartcard, each private key on host has a
* reference (stub) to a smartcard and actual private key data
* is stored on the card. A single smartcard can have up to
* three private key data. Importing private key stub is always
* skipped in 2.1, and it returns GPG_ERR_NOT_PROCESSED.
* Instead, user should be suggested to run 'gpg --card-status',
* then, references to a card will be automatically created
* again. */
log_info (_("To migrate '%s', with each smartcard, "
"run: %s\n"), "secring.gpg", "gpg --card-status");
err = 0;
}
if (!err)
{
int status = 16;
if (!opt.quiet)
log_info (_("key %s: secret key imported\n"), keystr_from_pk (pk));
if (subkey_stats.secret_imported)
{
status |= 1;
stats->secret_imported += 1;
}
if (subkey_stats.secret_dups)
stats->secret_dups += 1;
if (is_status_enabled ())
print_import_ok (pk, status);
}
return err;
}
/* If the secret keys (main or subkey) in SECKEYS have a corresponding
* public key in the public key described by (FPR,FPRLEN) import these
* parts.
*/
static gpg_error_t
import_matching_seckeys (ctrl_t ctrl, kbnode_t seckeys,
const byte *mainfpr, size_t mainfprlen,
struct import_stats_s *stats, int batch)
{
gpg_error_t err;
kbnode_t pub_keyblock = NULL;
kbnode_t node;
struct { byte fpr[MAX_FINGERPRINT_LEN]; size_t fprlen; } *fprlist = NULL;
size_t n, nfprlist;
byte fpr[MAX_FINGERPRINT_LEN];
size_t fprlen;
PKT_public_key *pk;
/* Get the entire public key block from our keystore and put all its
* fingerprints into an array. */
err = get_pubkey_byfprint (ctrl, NULL, &pub_keyblock, mainfpr, mainfprlen);
if (err)
goto leave;
log_assert (pub_keyblock && pub_keyblock->pkt->pkttype == PKT_PUBLIC_KEY);
pk = pub_keyblock->pkt->pkt.public_key;
for (nfprlist = 0, node = pub_keyblock; node; node = node->next)
if (node->pkt->pkttype == PKT_PUBLIC_KEY
|| node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
nfprlist++;
log_assert (nfprlist);
fprlist = xtrycalloc (nfprlist, sizeof *fprlist);
if (!fprlist)
{
err = gpg_error_from_syserror ();
goto leave;
}
for (n = 0, node = pub_keyblock; node; node = node->next)
if (node->pkt->pkttype == PKT_PUBLIC_KEY
|| node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
fingerprint_from_pk (node->pkt->pkt.public_key,
fprlist[n].fpr, &fprlist[n].fprlen);
n++;
}
log_assert (n == nfprlist);
/* for (n=0; n < nfprlist; n++) */
/* log_printhex (fprlist[n].fpr, fprlist[n].fprlen, "pubkey %zu:", n); */
/* Mark all secret keys which have a matching public key part in
* PUB_KEYBLOCK. */
for (node = seckeys; node; node = node->next)
{
if (node->pkt->pkttype != PKT_SECRET_KEY
&& node->pkt->pkttype != PKT_SECRET_SUBKEY)
continue; /* Should not happen. */
fingerprint_from_pk (node->pkt->pkt.public_key, fpr, &fprlen);
node->flag &= ~NODE_TRANSFER_SECKEY;
for (n=0; n < nfprlist; n++)
if (fprlist[n].fprlen == fprlen && !memcmp (fprlist[n].fpr,fpr,fprlen))
{
node->flag |= NODE_TRANSFER_SECKEY;
/* log_debug ("found matching seckey\n"); */
break;
}
}
/* Transfer all marked keys. */
err = do_transfer (ctrl, seckeys, pk, stats, batch, 1);
leave:
xfree (fprlist);
release_kbnode (pub_keyblock);
return err;
}
/* Import function for a single secret keyblock. Handling is simpler
* than for public keys. We allow secret key importing only when
* allow is true, this is so that a secret key can not be imported
* accidentally and thereby tampering with the trust calculation.
*
* Ownership of KEYBLOCK is transferred to this function!
*
* If R_SECATTIC is not null the last special sec_keyblock is stored
* there.
*/
static gpg_error_t
import_secret_one (ctrl_t ctrl, kbnode_t keyblock,
struct import_stats_s *stats, int batch,
unsigned int options, int for_migration,
import_screener_t screener, void *screener_arg,
kbnode_t *r_secattic)
{
PKT_public_key *pk;
struct seckey_info *ski;
kbnode_t node, uidnode;
u32 keyid[2];
gpg_error_t err = 0;
int nr_prev;
kbnode_t pub_keyblock;
kbnode_t attic = NULL;
byte fpr[MAX_FINGERPRINT_LEN];
size_t fprlen;
char pkstrbuf[PUBKEY_STRING_SIZE];
/* Get the key and print some info about it */
node = find_kbnode (keyblock, PKT_SECRET_KEY);
if (!node)
BUG ();
pk = node->pkt->pkt.public_key;
fingerprint_from_pk (pk, fpr, &fprlen);
keyid_from_pk (pk, keyid);
uidnode = find_next_kbnode (keyblock, PKT_USER_ID);
if (screener && screener (keyblock, screener_arg))
{
log_error (_("secret key %s: %s\n"), keystr_from_pk (pk),
_("rejected by import screener"));
release_kbnode (keyblock);
return 0;
}
if (opt.verbose && !for_migration)
{
log_info ("sec %s/%s %s ",
pubkey_string (pk, pkstrbuf, sizeof pkstrbuf),
keystr_from_pk (pk), datestr_from_pk (pk));
if (uidnode)
print_utf8_buffer (log_get_stream (), uidnode->pkt->pkt.user_id->name,
uidnode->pkt->pkt.user_id->len);
log_printf ("\n");
}
stats->secret_read++;
if ((options & IMPORT_NO_SECKEY))
{
if (!for_migration)
log_error (_("importing secret keys not allowed\n"));
release_kbnode (keyblock);
return 0;
}
if (!uidnode)
{
if (!for_migration)
log_error( _("key %s: no user ID\n"), keystr_from_pk (pk));
release_kbnode (keyblock);
return 0;
}
ski = pk->seckey_info;
if (!ski)
{
/* Actually an internal error. */
log_error ("key %s: secret key info missing\n", keystr_from_pk (pk));
release_kbnode (keyblock);
return 0;
}
/* A quick check to not import keys with an invalid protection
cipher algorithm (only checks the primary key, though). */
if (ski->algo > 110)
{
if (!for_migration)
log_error (_("key %s: secret key with invalid cipher %d"
" - skipped\n"), keystr_from_pk (pk), ski->algo);
release_kbnode (keyblock);
return 0;
}
#ifdef ENABLE_SELINUX_HACKS
if (1)
{
/* We don't allow importing secret keys because that may be used
to put a secret key into the keyring and the user might later
be tricked into signing stuff with that key. */
log_error (_("importing secret keys not allowed\n"));
release_kbnode (keyblock);
return 0;
}
#endif
clear_kbnode_flags (keyblock);
nr_prev = stats->skipped_new_keys;
/* Make a public key out of the key. */
pub_keyblock = sec_to_pub_keyblock (keyblock);
if (!pub_keyblock)
{
err = gpg_error_from_syserror ();
log_error ("key %s: failed to create public key from secret key\n",
keystr_from_pk (pk));
}
else
{
int valid;
/* Note that this outputs an IMPORT_OK status message for the
public key block, and below we will output another one for
the secret keys. FIXME? */
import_one (ctrl, pub_keyblock, stats,
NULL, NULL, options, 1, for_migration,
screener, screener_arg, 0, NULL, &valid);
/* The secret keyblock may not have nodes which are deleted in
* the public keyblock. Otherwise we would import just the
* secret key without having the public key. That would be
- * surprising and clutters out private-keys-v1.d. */
+ * surprising and clutters our private-keys-v1.d. */
err = resync_sec_with_pub_keyblock (&keyblock, pub_keyblock, &attic);
if (err)
goto leave;
if (!valid)
{
/* If the block was not valid the primary key is left in the
* original keyblock because we require that for the first
* node. Move it to ATTIC. */
if (keyblock && keyblock->pkt->pkttype == PKT_SECRET_KEY)
{
node = keyblock;
keyblock = node->next;
node->next = NULL;
if (attic)
{
node->next = attic;
attic = node;
}
else
attic = node;
}
/* Try to import the secret key iff we have a public key. */
if (attic && !(opt.dry_run || (options & IMPORT_DRY_RUN)))
err = import_matching_seckeys (ctrl, attic, fpr, fprlen,
stats, batch);
else
err = gpg_error (GPG_ERR_NO_SECKEY);
goto leave;
}
/* log_debug ("attic is:\n"); */
/* dump_kbnode (attic); */
/* Proceed with the valid parts of PUBKEYBLOCK. */
/* At least we cancel the secret key import when the public key
import was skipped due to MERGE_ONLY option and a new
key. */
if (!(opt.dry_run || (options & IMPORT_DRY_RUN))
&& stats->skipped_new_keys <= nr_prev)
{
/* Read the keyblock again to get the effects of a merge for
* the public key. */
err = get_pubkey_byfprint (ctrl, NULL, &node, fpr, fprlen);
if (err || !node)
log_error ("key %s: failed to re-lookup public key: %s\n",
keystr_from_pk (pk), gpg_strerror (err));
else
{
err = do_transfer (ctrl, keyblock, pk, stats, batch, 0);
if (!err)
check_prefs (ctrl, node);
release_kbnode (node);
if (!err && attic)
{
/* Try to import invalid subkeys. This can be the
* case if the primary secret key was imported due
* to --allow-non-selfsigned-uid. */
err = import_matching_seckeys (ctrl, attic, fpr, fprlen,
stats, batch);
}
}
}
}
leave:
release_kbnode (keyblock);
release_kbnode (pub_keyblock);
if (r_secattic)
*r_secattic = attic;
else
release_kbnode (attic);
return err;
}
/* Return the recocation reason from signature SIG. If no revocation
* reason is availabale 0 is returned, in other cases the reason
* (0..255). If R_REASON is not NULL a malloced textual
* representation of the code is stored there. If R_COMMENT is not
* NULL the comment from the reason is stored there and its length at
* R_COMMENTLEN. Note that the value at R_COMMENT is not filtered but
* user supplied data in UTF8; thus it needs to be escaped for display
* purposes. Both return values are either NULL or a malloced
* string/buffer. */
int
get_revocation_reason (PKT_signature *sig, char **r_reason,
char **r_comment, size_t *r_commentlen)
{
int reason_seq = 0;
size_t reason_n;
const byte *reason_p;
char reason_code_buf[20];
const char *reason_text = NULL;
int reason_code = 0;
if (r_reason)
*r_reason = NULL;
if (r_comment)
*r_comment = NULL;
/* Skip over empty reason packets. */
while ((reason_p = enum_sig_subpkt (sig->hashed, SIGSUBPKT_REVOC_REASON,
&reason_n, &reason_seq, NULL))
&& !reason_n)
;
if (reason_p)
{
reason_code = *reason_p;
reason_n--; reason_p++;
switch (reason_code)
{
case 0x00: reason_text = _("No reason specified"); break;
case 0x01: reason_text = _("Key is superseded"); break;
case 0x02: reason_text = _("Key has been compromised"); break;
case 0x03: reason_text = _("Key is no longer used"); break;
case 0x20: reason_text = _("User ID is no longer valid"); break;
default:
snprintf (reason_code_buf, sizeof reason_code_buf,
"code=%02x", reason_code);
reason_text = reason_code_buf;
break;
}
if (r_reason)
*r_reason = xstrdup (reason_text);
if (r_comment && reason_n)
{
*r_comment = xmalloc (reason_n);
memcpy (*r_comment, reason_p, reason_n);
*r_commentlen = reason_n;
}
}
return reason_code;
}
/* List the recocation signature as a "rvs" record. SIGRC shows the
* character from the signature verification or 0 if no public key was
* found. */
static void
list_standalone_revocation (ctrl_t ctrl, PKT_signature *sig, int sigrc)
{
char *siguid = NULL;
size_t siguidlen = 0;
char *issuer_fpr = NULL;
int reason_code = 0;
char *reason_text = NULL;
char *reason_comment = NULL;
size_t reason_commentlen;
if (sigrc != '%' && sigrc != '?' && !opt.fast_list_mode)
{
int nouid;
siguid = get_user_id (ctrl, sig->keyid, &siguidlen, &nouid);
if (nouid)
sigrc = '?';
}
reason_code = get_revocation_reason (sig, &reason_text,
&reason_comment, &reason_commentlen);
if (opt.with_colons)
{
es_fputs ("rvs:", es_stdout);
if (sigrc)
es_putc (sigrc, es_stdout);
es_fprintf (es_stdout, "::%d:%08lX%08lX:%s:%s:::",
sig->pubkey_algo,
(ulong) sig->keyid[0], (ulong) sig->keyid[1],
colon_datestr_from_sig (sig),
colon_expirestr_from_sig (sig));
if (siguid)
es_write_sanitized (es_stdout, siguid, siguidlen, ":", NULL);
es_fprintf (es_stdout, ":%02x%c", sig->sig_class,
sig->flags.exportable ? 'x' : 'l');
if (reason_text)
es_fprintf (es_stdout, ",%02x", reason_code);
es_fputs ("::", es_stdout);
if ((issuer_fpr = issuer_fpr_string (sig)))
es_fputs (issuer_fpr, es_stdout);
es_fprintf (es_stdout, ":::%d:", sig->digest_algo);
if (reason_comment)
{
es_fputs ("::::", es_stdout);
es_write_sanitized (es_stdout, reason_comment, reason_commentlen,
":", NULL);
es_putc (':', es_stdout);
}
es_putc ('\n', es_stdout);
if (opt.show_subpackets)
print_subpackets_colon (sig);
}
else /* Human readable. */
{
es_fputs ("rvs", es_stdout);
es_fprintf (es_stdout, "%c%c %c%c%c%c%c%c %s %s",
sigrc, (sig->sig_class - 0x10 > 0 &&
sig->sig_class - 0x10 <
4) ? '0' + sig->sig_class - 0x10 : ' ',
sig->flags.exportable ? ' ' : 'L',
sig->flags.revocable ? ' ' : 'R',
sig->flags.policy_url ? 'P' : ' ',
sig->flags.notation ? 'N' : ' ',
sig->flags.expired ? 'X' : ' ',
(sig->trust_depth > 9) ? 'T' : (sig->trust_depth >
0) ? '0' +
sig->trust_depth : ' ', keystr (sig->keyid),
datestr_from_sig (sig));
if (siguid)
{
es_fprintf (es_stdout, " ");
print_utf8_buffer (es_stdout, siguid, siguidlen);
}
es_putc ('\n', es_stdout);
if (sig->flags.policy_url
&& (opt.list_options & LIST_SHOW_POLICY_URLS))
show_policy_url (sig, 3, 0);
if (sig->flags.notation && (opt.list_options & LIST_SHOW_NOTATIONS))
show_notation (sig, 3, 0,
((opt.list_options & LIST_SHOW_STD_NOTATIONS) ? 1 : 0)
+
((opt.list_options & LIST_SHOW_USER_NOTATIONS) ? 2 : 0));
if (sig->flags.pref_ks
&& (opt.list_options & LIST_SHOW_KEYSERVER_URLS))
show_keyserver_url (sig, 3, 0);
if (reason_text)
{
es_fprintf (es_stdout, " %s%s\n",
_("reason for revocation: "), reason_text);
if (reason_comment)
{
const byte *s, *s_lf;
size_t n, n_lf;
s = reason_comment;
n = reason_commentlen;
s_lf = NULL;
do
{
/* We don't want any empty lines, so we skip them. */
for (;n && *s == '\n'; s++, n--)
;
if (n)
{
s_lf = memchr (s, '\n', n);
n_lf = s_lf? s_lf - s : n;
es_fprintf (es_stdout, " %s",
_("revocation comment: "));
es_write_sanitized (es_stdout, s, n_lf, NULL, NULL);
es_putc ('\n', es_stdout);
s += n_lf; n -= n_lf;
}
} while (s_lf);
}
}
}
es_fflush (es_stdout);
xfree (reason_text);
xfree (reason_comment);
xfree (siguid);
xfree (issuer_fpr);
}
/****************
* Import a revocation certificate; this is a single signature packet.
*/
static int
import_revoke_cert (ctrl_t ctrl, kbnode_t node, unsigned int options,
struct import_stats_s *stats)
{
PKT_public_key *pk = NULL;
kbnode_t onode;
kbnode_t keyblock = NULL;
KEYDB_HANDLE hd = NULL;
u32 keyid[2];
int rc = 0;
int sigrc = 0;
int silent;
/* No error output for --show-keys. */
silent = (options & (IMPORT_SHOW | IMPORT_DRY_RUN));
log_assert (!node->next );
log_assert (node->pkt->pkttype == PKT_SIGNATURE );
log_assert (IS_KEY_REV (node->pkt->pkt.signature));
keyid[0] = node->pkt->pkt.signature->keyid[0];
keyid[1] = node->pkt->pkt.signature->keyid[1];
pk = xmalloc_clear( sizeof *pk );
rc = get_pubkey (ctrl, pk, keyid );
if (gpg_err_code (rc) == GPG_ERR_NO_PUBKEY )
{
if (!silent)
log_error (_("key %s: no public key -"
" can't apply revocation certificate\n"), keystr(keyid));
rc = 0;
goto leave;
}
else if (rc )
{
log_error (_("key %s: public key not found: %s\n"),
keystr(keyid), gpg_strerror (rc));
goto leave;
}
/* Read the original keyblock. */
hd = keydb_new ();
if (!hd)
{
rc = gpg_error_from_syserror ();
goto leave;
}
{
byte afp[MAX_FINGERPRINT_LEN];
size_t an;
fingerprint_from_pk (pk, afp, &an);
rc = keydb_search_fpr (hd, afp, an);
}
if (rc)
{
log_error (_("key %s: can't locate original keyblock: %s\n"),
keystr(keyid), gpg_strerror (rc));
goto leave;
}
rc = keydb_get_keyblock (hd, &keyblock );
if (rc)
{
log_error (_("key %s: can't read original keyblock: %s\n"),
keystr(keyid), gpg_strerror (rc));
goto leave;
}
/* it is okay, that node is not in keyblock because
* check_key_signature works fine for sig_class 0x20 (KEY_REV) in
* this special case. SIGRC is only used for IMPORT_SHOW. */
rc = check_key_signature (ctrl, keyblock, node, NULL);
switch (gpg_err_code (rc))
{
case 0: sigrc = '!'; break;
case GPG_ERR_BAD_SIGNATURE: sigrc = '-'; break;
case GPG_ERR_NO_PUBKEY: sigrc = '?'; break;
case GPG_ERR_UNUSABLE_PUBKEY: sigrc = '?'; break;
default: sigrc = '%'; break;
}
if (rc )
{
if (!silent)
log_error (_("key %s: invalid revocation certificate"
": %s - rejected\n"), keystr(keyid), gpg_strerror (rc));
goto leave;
}
/* check whether we already have this */
for(onode=keyblock->next; onode; onode=onode->next ) {
if (onode->pkt->pkttype == PKT_USER_ID )
break;
else if (onode->pkt->pkttype == PKT_SIGNATURE
&& !cmp_signatures(node->pkt->pkt.signature,
onode->pkt->pkt.signature))
{
rc = 0;
goto leave; /* yes, we already know about it */
}
}
/* insert it */
insert_kbnode( keyblock, clone_kbnode(node), 0 );
/* and write the keyblock back unless in dry run mode. */
if (!(opt.dry_run || (options & IMPORT_DRY_RUN)))
{
rc = keydb_update_keyblock (ctrl, hd, keyblock );
if (rc)
log_error (_("error writing keyring '%s': %s\n"),
keydb_get_resource_name (hd), gpg_strerror (rc) );
keydb_release (hd);
hd = NULL;
/* we are ready */
if (!opt.quiet )
{
char *p=get_user_id_native (ctrl, keyid);
log_info( _("key %s: \"%s\" revocation certificate imported\n"),
keystr(keyid),p);
xfree(p);
}
/* If the key we just revoked was ultimately trusted, remove its
* ultimate trust. This doesn't stop the user from putting the
* ultimate trust back, but is a reasonable solution for now. */
if (get_ownertrust (ctrl, pk) == TRUST_ULTIMATE)
clear_ownertrusts (ctrl, pk);
revalidation_mark (ctrl);
}
stats->n_revoc++;
leave:
if ((options & IMPORT_SHOW))
list_standalone_revocation (ctrl, node->pkt->pkt.signature, sigrc);
keydb_release (hd);
release_kbnode( keyblock );
free_public_key( pk );
return rc;
}
/* Loop over the KEYBLOCK and check all self signatures. KEYID is the
* keyid of the primary key for reporting purposes. On return the
* following bits in the node flags are set:
*
* - NODE_GOOD_SELFSIG :: User ID or subkey has a self-signature
* - NODE_BAD_SELFSIG :: Used ID or subkey has an invalid self-signature
* - NODE_DELETION_MARK :: This node shall be deleted
*
* NON_SELF is set to true if there are any sigs other than self-sigs
* in this keyblock.
*
* Returns 0 on success or -1 (but not an error code) if the keyblock
* is invalid.
*/
static int
chk_self_sigs (ctrl_t ctrl, kbnode_t keyblock, u32 *keyid, int *non_self)
{
kbnode_t knode = NULL; /* The node of the current subkey. */
PKT_public_key *subpk = NULL; /* and its packet. */
kbnode_t bsnode = NULL; /* Subkey binding signature node. */
u32 bsdate = 0; /* Timestamp of that node. */
kbnode_t rsnode = NULL; /* Subkey recocation signature node. */
u32 rsdate = 0; /* Timestamp of that node. */
PKT_signature *sig;
int rc;
kbnode_t n;
for (n=keyblock; (n = find_next_kbnode (n, 0)); )
{
if (n->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
knode = n;
subpk = knode->pkt->pkt.public_key;
bsdate = 0;
rsdate = 0;
bsnode = NULL;
rsnode = NULL;
continue;
}
if ( n->pkt->pkttype != PKT_SIGNATURE )
continue;
sig = n->pkt->pkt.signature;
if ( keyid[0] != sig->keyid[0] || keyid[1] != sig->keyid[1] )
{
*non_self = 1;
continue;
}
/* This just caches the sigs for later use. That way we
import a fully-cached key which speeds things up. */
if (!opt.no_sig_cache)
check_key_signature (ctrl, keyblock, n, NULL);
if ( IS_UID_SIG(sig) || IS_UID_REV(sig) )
{
kbnode_t unode = find_prev_kbnode( keyblock, n, PKT_USER_ID );
if ( !unode )
{
log_error( _("key %s: no user ID for signature\n"),
keystr(keyid));
return -1; /* The complete keyblock is invalid. */
}
/* If it hasn't been marked valid yet, keep trying. */
if (!(unode->flag & NODE_GOOD_SELFSIG))
{
rc = check_key_signature (ctrl, keyblock, n, NULL);
if ( rc )
{
if ( opt.verbose )
{
char *p = utf8_to_native
(unode->pkt->pkt.user_id->name,
strlen (unode->pkt->pkt.user_id->name),0);
log_info (gpg_err_code(rc) == GPG_ERR_PUBKEY_ALGO ?
_("key %s: unsupported public key "
"algorithm on user ID \"%s\"\n"):
_("key %s: invalid self-signature "
"on user ID \"%s\"\n"),
keystr (keyid),p);
xfree (p);
}
}
else
unode->flag |= NODE_GOOD_SELFSIG;
}
}
else if (IS_KEY_SIG (sig))
{
rc = check_key_signature (ctrl, keyblock, n, NULL);
if ( rc )
{
if (opt.verbose)
log_info (gpg_err_code (rc) == GPG_ERR_PUBKEY_ALGO ?
_("key %s: unsupported public key algorithm\n"):
_("key %s: invalid direct key signature\n"),
keystr (keyid));
n->flag |= NODE_DELETION_MARK;
}
}
else if ( IS_SUBKEY_SIG (sig) )
{
/* Note that this works based solely on the timestamps like
the rest of gpg. If the standard gets revocation
targets, this may need to be revised. */
if ( !knode )
{
if (opt.verbose)
log_info (_("key %s: no subkey for key binding\n"),
keystr (keyid));
n->flag |= NODE_DELETION_MARK;
}
else
{
rc = check_key_signature (ctrl, keyblock, n, NULL);
if ( rc )
{
if (opt.verbose)
{
keyid_from_pk (subpk, NULL);
log_info (gpg_err_code (rc) == GPG_ERR_PUBKEY_ALGO ?
_("key %s: unsupported public key"
" algorithm\n"):
_("key %s: invalid subkey binding\n"),
keystr_with_sub (keyid, subpk->keyid));
}
n->flag |= NODE_DELETION_MARK;
}
else
{
/* It's valid, so is it newer? */
if (sig->timestamp >= bsdate)
{
knode->flag |= NODE_GOOD_SELFSIG; /* Subkey is valid. */
if (bsnode)
{
/* Delete the last binding sig since this
one is newer */
bsnode->flag |= NODE_DELETION_MARK;
if (opt.verbose)
{
keyid_from_pk (subpk, NULL);
log_info (_("key %s: removed multiple subkey"
" binding\n"),
keystr_with_sub (keyid, subpk->keyid));
}
}
bsnode = n;
bsdate = sig->timestamp;
}
else
n->flag |= NODE_DELETION_MARK; /* older */
}
}
}
else if ( IS_SUBKEY_REV (sig) )
{
/* We don't actually mark the subkey as revoked right now,
so just check that the revocation sig is the most recent
valid one. Note that we don't care if the binding sig is
newer than the revocation sig. See the comment in
getkey.c:merge_selfsigs_subkey for more. */
if ( !knode )
{
if (opt.verbose)
log_info (_("key %s: no subkey for key revocation\n"),
keystr(keyid));
n->flag |= NODE_DELETION_MARK;
}
else
{
rc = check_key_signature (ctrl, keyblock, n, NULL);
if ( rc )
{
if(opt.verbose)
log_info (gpg_err_code (rc) == GPG_ERR_PUBKEY_ALGO ?
_("key %s: unsupported public"
" key algorithm\n"):
_("key %s: invalid subkey revocation\n"),
keystr(keyid));
n->flag |= NODE_DELETION_MARK;
}
else
{
/* It's valid, so is it newer? */
if (sig->timestamp >= rsdate)
{
if (rsnode)
{
/* Delete the last revocation sig since
this one is newer. */
rsnode->flag |= NODE_DELETION_MARK;
if (opt.verbose)
log_info (_("key %s: removed multiple subkey"
" revocation\n"),keystr(keyid));
}
rsnode = n;
rsdate = sig->timestamp;
}
else
n->flag |= NODE_DELETION_MARK; /* older */
}
}
}
}
return 0;
}
/* Delete all parts which are invalid and those signatures whose
* public key algorithm is not available in this implementation; but
* consider RSA as valid, because parse/build_packets knows about it.
*
* Returns: True if at least one valid user-id is left over.
*/
static int
delete_inv_parts (ctrl_t ctrl, kbnode_t keyblock, u32 *keyid,
unsigned int options)
{
kbnode_t node;
int nvalid=0, uid_seen=0, subkey_seen=0;
PKT_public_key *pk;
for (node=keyblock->next; node; node = node->next )
{
if (node->pkt->pkttype == PKT_USER_ID)
{
uid_seen = 1;
if ((node->flag & NODE_BAD_SELFSIG)
|| !(node->flag & NODE_GOOD_SELFSIG))
{
if (opt.verbose )
{
char *p=utf8_to_native(node->pkt->pkt.user_id->name,
node->pkt->pkt.user_id->len,0);
log_info( _("key %s: skipped user ID \"%s\"\n"),
keystr(keyid),p);
xfree(p);
}
delete_kbnode( node ); /* the user-id */
/* and all following packets up to the next user-id */
while (node->next
&& node->next->pkt->pkttype != PKT_USER_ID
&& node->next->pkt->pkttype != PKT_PUBLIC_SUBKEY
&& node->next->pkt->pkttype != PKT_SECRET_SUBKEY ){
delete_kbnode( node->next );
node = node->next;
}
}
else
nvalid++;
}
else if ( node->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| node->pkt->pkttype == PKT_SECRET_SUBKEY )
{
if ((node->flag & NODE_BAD_SELFSIG)
|| !(node->flag & NODE_GOOD_SELFSIG))
{
if (opt.verbose )
{
pk = node->pkt->pkt.public_key;
keyid_from_pk (pk, NULL);
log_info (_("key %s: skipped subkey\n"),
keystr_with_sub (keyid, pk->keyid));
}
delete_kbnode( node ); /* the subkey */
/* and all following signature packets */
while (node->next
&& node->next->pkt->pkttype == PKT_SIGNATURE ) {
delete_kbnode( node->next );
node = node->next;
}
}
else
subkey_seen = 1;
}
else if (node->pkt->pkttype == PKT_SIGNATURE
&& openpgp_pk_test_algo (node->pkt->pkt.signature->pubkey_algo)
&& node->pkt->pkt.signature->pubkey_algo != PUBKEY_ALGO_RSA )
{
delete_kbnode( node ); /* build_packet() can't handle this */
}
else if (node->pkt->pkttype == PKT_SIGNATURE
&& !node->pkt->pkt.signature->flags.exportable
&& !(options&IMPORT_LOCAL_SIGS)
&& !have_secret_key_with_kid (node->pkt->pkt.signature->keyid))
{
/* here we violate the rfc a bit by still allowing
* to import non-exportable signature when we have the
* the secret key used to create this signature - it
* seems that this makes sense */
if(opt.verbose)
log_info( _("key %s: non exportable signature"
" (class 0x%02X) - skipped\n"),
keystr(keyid), node->pkt->pkt.signature->sig_class );
delete_kbnode( node );
}
else if (node->pkt->pkttype == PKT_SIGNATURE
&& IS_KEY_REV (node->pkt->pkt.signature))
{
if (uid_seen )
{
if(opt.verbose)
log_info( _("key %s: revocation certificate"
" at wrong place - skipped\n"),keystr(keyid));
delete_kbnode( node );
}
else
{
/* If the revocation cert is from a different key than
the one we're working on don't check it - it's
probably from a revocation key and won't be
verifiable with this key anyway. */
if(node->pkt->pkt.signature->keyid[0]==keyid[0]
&& node->pkt->pkt.signature->keyid[1]==keyid[1])
{
int rc = check_key_signature (ctrl, keyblock, node, NULL);
if (rc )
{
if(opt.verbose)
log_info( _("key %s: invalid revocation"
" certificate: %s - skipped\n"),
keystr(keyid), gpg_strerror (rc));
delete_kbnode( node );
}
}
}
}
else if (node->pkt->pkttype == PKT_SIGNATURE
&& (IS_SUBKEY_SIG (node->pkt->pkt.signature)
|| IS_SUBKEY_REV (node->pkt->pkt.signature))
&& !subkey_seen )
{
if(opt.verbose)
log_info( _("key %s: subkey signature"
" in wrong place - skipped\n"), keystr(keyid));
delete_kbnode( node );
}
else if (node->pkt->pkttype == PKT_SIGNATURE
&& !IS_CERT(node->pkt->pkt.signature))
{
if(opt.verbose)
log_info(_("key %s: unexpected signature class (0x%02X) -"
" skipped\n"),keystr(keyid),
node->pkt->pkt.signature->sig_class);
delete_kbnode(node);
}
else if ((node->flag & NODE_DELETION_MARK))
delete_kbnode( node );
}
/* note: because keyblock is the public key, it is never marked
* for deletion and so keyblock cannot change */
commit_kbnode( &keyblock );
return nvalid;
}
/* This function returns true if any UID is left in the keyring. */
static int
any_uid_left (kbnode_t keyblock)
{
kbnode_t node;
for (node=keyblock->next; node; node = node->next)
if (node->pkt->pkttype == PKT_USER_ID)
return 1;
return 0;
}
/* Delete all user ids from KEYBLOCK.
* Returns: True if the keyblock has changed. */
static int
remove_all_uids (kbnode_t *keyblock)
{
kbnode_t node;
int any = 0;
for (node = *keyblock; node; node = node->next)
{
if (is_deleted_kbnode (node))
continue;
if (node->pkt->pkttype != PKT_USER_ID)
continue;
/* We are at the first user id. Delete everything up to the
* first subkey. */
for (; node; node = node->next)
{
if (is_deleted_kbnode (node))
continue;
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| node->pkt->pkttype == PKT_SECRET_SUBKEY)
break;
delete_kbnode (node);
any = 1;
}
break; /* All done. */
}
commit_kbnode (keyblock);
return any;
}
+/* Delete all non-self-sigs from KEYBLOCK.
+ * Returns: True if the keyblock has changed. */
+static void
+remove_all_non_self_sigs (kbnode_t *keyblock, u32 *keyid)
+{
+ kbnode_t node;
+ unsigned int dropped = 0;
+
+ for (node = *keyblock; node; node = node->next)
+ {
+ if (is_deleted_kbnode (node))
+ continue;
+
+ if (node->pkt->pkttype != PKT_SIGNATURE)
+ continue;
+
+ if (node->pkt->pkt.signature->keyid[0] == keyid[0]
+ && node->pkt->pkt.signature->keyid[1] == keyid[1])
+ continue;
+ delete_kbnode (node);
+ dropped++;
+ }
+
+ if (dropped)
+ commit_kbnode (keyblock);
+
+ if (dropped && opt.verbose)
+ log_info ("key %s: number of dropped non-self-signatures: %u\n",
+ keystr (keyid), dropped);
+}
+
+
/*
* It may happen that the imported keyblock has duplicated user IDs.
* We check this here and collapse those user IDs together with their
* sigs into one.
* Returns: True if the keyblock has changed.
*/
int
collapse_uids (kbnode_t *keyblock)
{
kbnode_t uid1;
int any=0;
for(uid1=*keyblock;uid1;uid1=uid1->next)
{
kbnode_t uid2;
if(is_deleted_kbnode(uid1))
continue;
if(uid1->pkt->pkttype!=PKT_USER_ID)
continue;
for(uid2=uid1->next;uid2;uid2=uid2->next)
{
if(is_deleted_kbnode(uid2))
continue;
if(uid2->pkt->pkttype!=PKT_USER_ID)
continue;
if(cmp_user_ids(uid1->pkt->pkt.user_id,
uid2->pkt->pkt.user_id)==0)
{
/* We have a duplicated uid */
kbnode_t sig1,last;
any=1;
/* Now take uid2's signatures, and attach them to
uid1 */
for(last=uid2;last->next;last=last->next)
{
if(is_deleted_kbnode(last))
continue;
if(last->next->pkt->pkttype==PKT_USER_ID
|| last->next->pkt->pkttype==PKT_PUBLIC_SUBKEY
|| last->next->pkt->pkttype==PKT_SECRET_SUBKEY)
break;
}
/* Snip out uid2 */
(find_prev_kbnode(*keyblock,uid2,0))->next=last->next;
/* Now put uid2 in place as part of uid1 */
last->next=uid1->next;
uid1->next=uid2;
delete_kbnode(uid2);
/* Now dedupe uid1 */
for(sig1=uid1->next;sig1;sig1=sig1->next)
{
kbnode_t sig2;
if(is_deleted_kbnode(sig1))
continue;
if(sig1->pkt->pkttype==PKT_USER_ID
|| sig1->pkt->pkttype==PKT_PUBLIC_SUBKEY
|| sig1->pkt->pkttype==PKT_SECRET_SUBKEY)
break;
if(sig1->pkt->pkttype!=PKT_SIGNATURE)
continue;
for(sig2=sig1->next,last=sig1;sig2;last=sig2,sig2=sig2->next)
{
if(is_deleted_kbnode(sig2))
continue;
if(sig2->pkt->pkttype==PKT_USER_ID
|| sig2->pkt->pkttype==PKT_PUBLIC_SUBKEY
|| sig2->pkt->pkttype==PKT_SECRET_SUBKEY)
break;
if(sig2->pkt->pkttype!=PKT_SIGNATURE)
continue;
if(cmp_signatures(sig1->pkt->pkt.signature,
sig2->pkt->pkt.signature)==0)
{
/* We have a match, so delete the second
signature */
delete_kbnode(sig2);
sig2=last;
}
}
}
}
}
}
commit_kbnode(keyblock);
if(any && !opt.quiet)
{
const char *key="???";
if ((uid1 = find_kbnode (*keyblock, PKT_PUBLIC_KEY)) )
key = keystr_from_pk (uid1->pkt->pkt.public_key);
else if ((uid1 = find_kbnode( *keyblock, PKT_SECRET_KEY)) )
key = keystr_from_pk (uid1->pkt->pkt.public_key);
log_info (_("key %s: duplicated user ID detected - merged\n"), key);
}
return any;
}
/* Check for a 0x20 revocation from a revocation key that is not
present. This may be called without the benefit of merge_xxxx so
you can't rely on pk->revkey and friends. */
static void
revocation_present (ctrl_t ctrl, kbnode_t keyblock)
{
kbnode_t onode, inode;
PKT_public_key *pk = keyblock->pkt->pkt.public_key;
for(onode=keyblock->next;onode;onode=onode->next)
{
/* If we reach user IDs, we're done. */
if(onode->pkt->pkttype==PKT_USER_ID)
break;
if (onode->pkt->pkttype == PKT_SIGNATURE
&& IS_KEY_SIG (onode->pkt->pkt.signature)
&& onode->pkt->pkt.signature->revkey)
{
int idx;
PKT_signature *sig=onode->pkt->pkt.signature;
for(idx=0;idx<sig->numrevkeys;idx++)
{
u32 keyid[2];
keyid_from_fingerprint (ctrl, sig->revkey[idx].fpr,
sig->revkey[idx].fprlen, keyid);
for(inode=keyblock->next;inode;inode=inode->next)
{
/* If we reach user IDs, we're done. */
if(inode->pkt->pkttype==PKT_USER_ID)
break;
if (inode->pkt->pkttype == PKT_SIGNATURE
&& IS_KEY_REV (inode->pkt->pkt.signature)
&& inode->pkt->pkt.signature->keyid[0]==keyid[0]
&& inode->pkt->pkt.signature->keyid[1]==keyid[1])
{
/* Okay, we have a revocation key, and a
* revocation issued by it. Do we have the key
* itself? */
gpg_error_t err;
err = get_pubkey_byfprint_fast (NULL,
sig->revkey[idx].fpr,
sig->revkey[idx].fprlen);
if (gpg_err_code (err) == GPG_ERR_NO_PUBKEY
|| gpg_err_code (err) == GPG_ERR_UNUSABLE_PUBKEY)
{
char *tempkeystr = xstrdup (keystr_from_pk (pk));
/* No, so try and get it */
if ((opt.keyserver_options.options
& KEYSERVER_AUTO_KEY_RETRIEVE)
&& keyserver_any_configured (ctrl))
{
log_info(_("WARNING: key %s may be revoked:"
" fetching revocation key %s\n"),
tempkeystr,keystr(keyid));
keyserver_import_fprint (ctrl,
sig->revkey[idx].fpr,
sig->revkey[idx].fprlen,
opt.keyserver, 0);
/* Do we have it now? */
err = get_pubkey_byfprint_fast (NULL,
sig->revkey[idx].fpr,
sig->revkey[idx].fprlen);
}
if (gpg_err_code (err) == GPG_ERR_NO_PUBKEY
|| gpg_err_code (err) == GPG_ERR_UNUSABLE_PUBKEY)
log_info(_("WARNING: key %s may be revoked:"
" revocation key %s not present.\n"),
tempkeystr,keystr(keyid));
xfree(tempkeystr);
}
}
}
}
}
}
}
/*
* compare and merge the blocks
*
* o compare the signatures: If we already have this signature, check
* that they compare okay; if not, issue a warning and ask the user.
* o Simply add the signature. Can't verify here because we may not have
* the signature's public key yet; verification is done when putting it
* into the trustdb, which is done automagically as soon as this pubkey
* is used.
* Note: We indicate newly inserted packets with NODE_FLAG_A.
*/
static int
merge_blocks (ctrl_t ctrl, unsigned int options,
kbnode_t keyblock_orig, kbnode_t keyblock,
u32 *keyid, u32 curtime, int origin, const char *url,
int *n_uids, int *n_sigs, int *n_subk )
{
kbnode_t onode, node;
int rc, found;
/* 1st: handle revocation certificates */
for (node=keyblock->next; node; node=node->next )
{
if (node->pkt->pkttype == PKT_USER_ID )
break;
else if (node->pkt->pkttype == PKT_SIGNATURE
&& IS_KEY_REV (node->pkt->pkt.signature))
{
/* check whether we already have this */
found = 0;
for (onode=keyblock_orig->next; onode; onode=onode->next)
{
if (onode->pkt->pkttype == PKT_USER_ID )
break;
else if (onode->pkt->pkttype == PKT_SIGNATURE
&& IS_KEY_REV (onode->pkt->pkt.signature)
&& !cmp_signatures(onode->pkt->pkt.signature,
node->pkt->pkt.signature))
{
found = 1;
break;
}
}
if (!found)
{
kbnode_t n2 = clone_kbnode(node);
insert_kbnode( keyblock_orig, n2, 0 );
n2->flag |= NODE_FLAG_A;
++*n_sigs;
if(!opt.quiet)
{
char *p = get_user_id_native (ctrl, keyid);
log_info(_("key %s: \"%s\" revocation"
" certificate added\n"), keystr(keyid),p);
xfree(p);
}
}
}
}
/* 2nd: merge in any direct key (0x1F) sigs */
for(node=keyblock->next; node; node=node->next)
{
if (node->pkt->pkttype == PKT_USER_ID )
break;
else if (node->pkt->pkttype == PKT_SIGNATURE
&& IS_KEY_SIG (node->pkt->pkt.signature))
{
/* check whether we already have this */
found = 0;
for (onode=keyblock_orig->next; onode; onode=onode->next)
{
if (onode->pkt->pkttype == PKT_USER_ID)
break;
else if (onode->pkt->pkttype == PKT_SIGNATURE
&& IS_KEY_SIG (onode->pkt->pkt.signature)
&& !cmp_signatures(onode->pkt->pkt.signature,
node->pkt->pkt.signature))
{
found = 1;
break;
}
}
if (!found )
{
kbnode_t n2 = clone_kbnode(node);
insert_kbnode( keyblock_orig, n2, 0 );
n2->flag |= NODE_FLAG_A;
++*n_sigs;
if(!opt.quiet)
log_info( _("key %s: direct key signature added\n"),
keystr(keyid));
}
}
}
/* 3rd: try to merge new certificates in */
for (onode=keyblock_orig->next; onode; onode=onode->next)
{
if (!(onode->flag & NODE_FLAG_A) && onode->pkt->pkttype == PKT_USER_ID)
{
/* find the user id in the imported keyblock */
for (node=keyblock->next; node; node=node->next)
if (node->pkt->pkttype == PKT_USER_ID
&& !cmp_user_ids( onode->pkt->pkt.user_id,
node->pkt->pkt.user_id ) )
break;
if (node ) /* found: merge */
{
rc = merge_sigs (onode, node, n_sigs);
if (rc )
return rc;
}
}
}
/* 4th: add new user-ids */
for (node=keyblock->next; node; node=node->next)
{
if (node->pkt->pkttype == PKT_USER_ID)
{
/* do we have this in the original keyblock */
for (onode=keyblock_orig->next; onode; onode=onode->next )
if (onode->pkt->pkttype == PKT_USER_ID
&& !cmp_user_ids( onode->pkt->pkt.user_id,
node->pkt->pkt.user_id ) )
break;
if (!onode ) /* this is a new user id: append */
{
rc = append_new_uid (options, keyblock_orig, node,
curtime, origin, url, n_sigs);
if (rc )
return rc;
++*n_uids;
}
}
}
/* 5th: add new subkeys */
for (node=keyblock->next; node; node=node->next)
{
onode = NULL;
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
/* do we have this in the original keyblock? */
for(onode=keyblock_orig->next; onode; onode=onode->next)
if (onode->pkt->pkttype == PKT_PUBLIC_SUBKEY
&& !cmp_public_keys( onode->pkt->pkt.public_key,
node->pkt->pkt.public_key))
break;
if (!onode ) /* This is a new subkey: append. */
{
rc = append_key (keyblock_orig, node, n_sigs);
if (rc)
return rc;
++*n_subk;
}
}
else if (node->pkt->pkttype == PKT_SECRET_SUBKEY)
{
/* do we have this in the original keyblock? */
for (onode=keyblock_orig->next; onode; onode=onode->next )
if (onode->pkt->pkttype == PKT_SECRET_SUBKEY
&& !cmp_public_keys (onode->pkt->pkt.public_key,
node->pkt->pkt.public_key) )
break;
if (!onode ) /* This is a new subkey: append. */
{
rc = append_key (keyblock_orig, node, n_sigs);
if (rc )
return rc;
++*n_subk;
}
}
}
/* 6th: merge subkey certificates */
for (onode=keyblock_orig->next; onode; onode=onode->next)
{
if (!(onode->flag & NODE_FLAG_A)
&& (onode->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| onode->pkt->pkttype == PKT_SECRET_SUBKEY))
{
/* find the subkey in the imported keyblock */
for(node=keyblock->next; node; node=node->next)
{
if ((node->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| node->pkt->pkttype == PKT_SECRET_SUBKEY)
&& !cmp_public_keys( onode->pkt->pkt.public_key,
node->pkt->pkt.public_key ) )
break;
}
if (node) /* Found: merge. */
{
rc = merge_keysigs( onode, node, n_sigs);
if (rc )
return rc;
}
}
}
return 0;
}
/* Helper function for merge_blocks.
*
* Append the new userid starting with NODE and all signatures to
* KEYBLOCK. ORIGIN and URL conveys the usual key origin info. The
* integer at N_SIGS is updated with the number of new signatures.
*/
static gpg_error_t
append_new_uid (unsigned int options,
kbnode_t keyblock, kbnode_t node, u32 curtime,
int origin, const char *url, int *n_sigs)
{
gpg_error_t err;
kbnode_t n;
kbnode_t n_where = NULL;
log_assert (node->pkt->pkttype == PKT_USER_ID);
/* Find the right position for the new user id and its signatures. */
for (n = keyblock; n; n_where = n, n = n->next)
{
if (n->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| n->pkt->pkttype == PKT_SECRET_SUBKEY )
break;
}
if (!n)
n_where = NULL;
/* and append/insert */
while (node)
{
/* we add a clone to the original keyblock, because this
* one is released first. */
n = clone_kbnode(node);
if (n->pkt->pkttype == PKT_USER_ID
&& !(options & IMPORT_RESTORE) )
{
err = insert_key_origin_uid (n->pkt->pkt.user_id,
curtime, origin, url);
if (err)
return err;
}
if (n_where)
{
insert_kbnode( n_where, n, 0 );
n_where = n;
}
else
add_kbnode( keyblock, n );
n->flag |= NODE_FLAG_A;
node->flag |= NODE_FLAG_A;
if (n->pkt->pkttype == PKT_SIGNATURE )
++*n_sigs;
node = node->next;
if (node && node->pkt->pkttype != PKT_SIGNATURE )
break;
}
return 0;
}
/* Helper function for merge_blocks
* Merge the sigs from SRC onto DST. SRC and DST are both a PKT_USER_ID.
* (how should we handle comment packets here?)
*/
static int
merge_sigs (kbnode_t dst, kbnode_t src, int *n_sigs)
{
kbnode_t n, n2;
int found = 0;
log_assert (dst->pkt->pkttype == PKT_USER_ID);
log_assert (src->pkt->pkttype == PKT_USER_ID);
for (n=src->next; n && n->pkt->pkttype != PKT_USER_ID; n = n->next)
{
if (n->pkt->pkttype != PKT_SIGNATURE )
continue;
if (IS_SUBKEY_SIG (n->pkt->pkt.signature)
|| IS_SUBKEY_REV (n->pkt->pkt.signature) )
continue; /* skip signatures which are only valid on subkeys */
found = 0;
for (n2=dst->next; n2 && n2->pkt->pkttype != PKT_USER_ID; n2 = n2->next)
if (!cmp_signatures(n->pkt->pkt.signature,n2->pkt->pkt.signature))
{
found++;
break;
}
if (!found )
{
/* This signature is new or newer, append N to DST.
* We add a clone to the original keyblock, because this
* one is released first */
n2 = clone_kbnode(n);
insert_kbnode( dst, n2, PKT_SIGNATURE );
n2->flag |= NODE_FLAG_A;
n->flag |= NODE_FLAG_A;
++*n_sigs;
}
}
return 0;
}
/* Helper function for merge_blocks
* Merge the sigs from SRC onto DST. SRC and DST are both a PKT_xxx_SUBKEY.
*/
static int
merge_keysigs (kbnode_t dst, kbnode_t src, int *n_sigs)
{
kbnode_t n, n2;
int found = 0;
log_assert (dst->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| dst->pkt->pkttype == PKT_SECRET_SUBKEY);
for (n=src->next; n ; n = n->next)
{
if (n->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| n->pkt->pkttype == PKT_PUBLIC_KEY )
break;
if (n->pkt->pkttype != PKT_SIGNATURE )
continue;
found = 0;
for (n2=dst->next; n2; n2 = n2->next)
{
if (n2->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| n2->pkt->pkttype == PKT_PUBLIC_KEY )
break;
if (n2->pkt->pkttype == PKT_SIGNATURE
&& (n->pkt->pkt.signature->keyid[0]
== n2->pkt->pkt.signature->keyid[0])
&& (n->pkt->pkt.signature->keyid[1]
== n2->pkt->pkt.signature->keyid[1])
&& (n->pkt->pkt.signature->timestamp
<= n2->pkt->pkt.signature->timestamp)
&& (n->pkt->pkt.signature->sig_class
== n2->pkt->pkt.signature->sig_class))
{
found++;
break;
}
}
if (!found )
{
/* This signature is new or newer, append N to DST.
* We add a clone to the original keyblock, because this
* one is released first */
n2 = clone_kbnode(n);
insert_kbnode( dst, n2, PKT_SIGNATURE );
n2->flag |= NODE_FLAG_A;
n->flag |= NODE_FLAG_A;
++*n_sigs;
}
}
return 0;
}
/* Helper function for merge_blocks.
* Append the subkey starting with NODE and all signatures to KEYBLOCK.
* Mark all new and copied packets by setting flag bit 0.
*/
static int
append_key (kbnode_t keyblock, kbnode_t node, int *n_sigs)
{
kbnode_t n;
log_assert (node->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| node->pkt->pkttype == PKT_SECRET_SUBKEY);
while (node)
{
/* we add a clone to the original keyblock, because this
* one is released first */
n = clone_kbnode(node);
add_kbnode( keyblock, n );
n->flag |= NODE_FLAG_A;
node->flag |= NODE_FLAG_A;
if (n->pkt->pkttype == PKT_SIGNATURE )
++*n_sigs;
node = node->next;
if (node && node->pkt->pkttype != PKT_SIGNATURE )
break;
}
return 0;
}
diff --git a/g10/keydb.c b/g10/keydb.c
index 8c067e1df..a7691bbe2 100644
--- a/g10/keydb.c
+++ b/g10/keydb.c
@@ -1,2094 +1,2101 @@
/* keydb.c - key database dispatcher
* Copyright (C) 2001-2013 Free Software Foundation, Inc.
- * Coyrright (C) 2001-2015 Werner Koch
+ * Copyright (C) 2001-2015 Werner Koch
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include "gpg.h"
#include "../common/util.h"
#include "options.h"
#include "main.h" /*try_make_homedir ()*/
#include "packet.h"
#include "keyring.h"
#include "../kbx/keybox.h"
#include "keydb.h"
#include "../common/i18n.h"
static int active_handles;
typedef enum
{
KEYDB_RESOURCE_TYPE_NONE = 0,
KEYDB_RESOURCE_TYPE_KEYRING,
KEYDB_RESOURCE_TYPE_KEYBOX
} KeydbResourceType;
#define MAX_KEYDB_RESOURCES 40
struct resource_item
{
KeydbResourceType type;
union {
KEYRING_HANDLE kr;
KEYBOX_HANDLE kb;
} u;
void *token;
};
static struct resource_item all_resources[MAX_KEYDB_RESOURCES];
static int used_resources;
/* A pointer used to check for the primary key database by comparing
to the struct resource_item's TOKEN. */
static void *primary_keydb;
/* Whether we have successfully registered any resource. */
static int any_registered;
/* This is a simple cache used to return the last result of a
successful fingerprint search. This works only for keybox resources
because (due to lack of a copy_keyblock function) we need to store
an image of the keyblock which is fortunately instantly available
for keyboxes. */
enum keyblock_cache_states {
KEYBLOCK_CACHE_EMPTY,
KEYBLOCK_CACHE_PREPARED,
KEYBLOCK_CACHE_FILLED
};
struct keyblock_cache {
enum keyblock_cache_states state;
byte fpr[MAX_FINGERPRINT_LEN];
byte fprlen;
iobuf_t iobuf; /* Image of the keyblock. */
int pk_no;
int uid_no;
/* Offset of the record in the keybox. */
int resource;
off_t offset;
};
struct keydb_handle
{
/* When we locked all of the resources in ACTIVE (using keyring_lock
/ keybox_lock, as appropriate). */
int locked;
/* If this flag is set a lock will only be released by
* keydb_release. */
int keep_lock;
/* The index into ACTIVE of the resources in which the last search
result was found. Initially -1. */
int found;
/* Initially -1 (invalid). This is used to save a search result and
later restore it as the selected result. */
int saved_found;
/* The number of skipped long blobs since the last search
(keydb_search_reset). */
unsigned long skipped_long_blobs;
/* If set, this disables the use of the keyblock cache. */
int no_caching;
/* Whether the next search will be from the beginning of the
database (and thus consider all records). */
int is_reset;
/* The "file position." In our case, this is index of the current
resource in ACTIVE. */
int current;
/* The number of resources in ACTIVE. */
int used;
/* Cache of the last found and parsed key block (only used for
keyboxes, not keyrings). */
struct keyblock_cache keyblock_cache;
/* Copy of ALL_RESOURCES when keydb_new is called. */
struct resource_item active[MAX_KEYDB_RESOURCES];
};
/* Looking up keys is expensive. To hide the cost, we cache whether
keys exist in the key database. Then, if we know a key does not
exist, we don't have to spend time looking it up. This
particularly helps the --list-sigs and --check-sigs commands.
The cache stores the results in a hash using separate chaining.
Concretely: we use the LSB of the keyid to index the hash table and
each bucket consists of a linked list of entries. An entry
consists of the 64-bit key id. If a key id is not in the cache,
then we don't know whether it is in the DB or not.
To simplify the cache consistency protocol, we simply flush the
whole cache whenever a key is inserted or updated. */
#define KID_NOT_FOUND_CACHE_BUCKETS 256
static struct kid_not_found_cache_bucket *
kid_not_found_cache[KID_NOT_FOUND_CACHE_BUCKETS];
struct kid_not_found_cache_bucket
{
struct kid_not_found_cache_bucket *next;
u32 kid[2];
};
struct
{
unsigned int count; /* The current number of entries in the hash table. */
unsigned int peak; /* The peak of COUNT. */
unsigned int flushes; /* The number of flushes. */
} kid_not_found_stats;
struct
{
unsigned int handles; /* Number of handles created. */
unsigned int locks; /* Number of locks taken. */
unsigned int parse_keyblocks; /* Number of parse_keyblock_image calls. */
unsigned int get_keyblocks; /* Number of keydb_get_keyblock calls. */
unsigned int build_keyblocks; /* Number of build_keyblock_image calls. */
unsigned int update_keyblocks;/* Number of update_keyblock calls. */
unsigned int insert_keyblocks;/* Number of update_keyblock calls. */
unsigned int delete_keyblocks;/* Number of delete_keyblock calls. */
unsigned int search_resets; /* Number of keydb_search_reset calls. */
unsigned int found; /* Number of successful keydb_search calls. */
unsigned int found_cached; /* Ditto but from the cache. */
unsigned int notfound; /* Number of failed keydb_search calls. */
unsigned int notfound_cached; /* Ditto but from the cache. */
} keydb_stats;
static int lock_all (KEYDB_HANDLE hd);
static void unlock_all (KEYDB_HANDLE hd);
/* Check whether the keyid KID is in key id is definitely not in the
database.
Returns:
0 - Indeterminate: the key id is not in the cache; we don't know
whether the key is in the database or not. If you want a
definitive answer, you'll need to perform a lookup.
1 - There is definitely no key with this key id in the database.
We searched for a key with this key id previously, but we
didn't find it in the database. */
static int
kid_not_found_p (u32 *kid)
{
struct kid_not_found_cache_bucket *k;
for (k = kid_not_found_cache[kid[0] % KID_NOT_FOUND_CACHE_BUCKETS]; k; k = k->next)
if (k->kid[0] == kid[0] && k->kid[1] == kid[1])
{
if (DBG_CACHE)
log_debug ("keydb: kid_not_found_p (%08lx%08lx) => not in DB\n",
(ulong)kid[0], (ulong)kid[1]);
return 1;
}
if (DBG_CACHE)
log_debug ("keydb: kid_not_found_p (%08lx%08lx) => indeterminate\n",
(ulong)kid[0], (ulong)kid[1]);
return 0;
}
/* Insert the keyid KID into the kid_not_found_cache. FOUND is whether
the key is in the key database or not.
Note this function does not check whether the key id is already in
the cache. As such, kid_not_found_p() should be called first. */
static void
kid_not_found_insert (u32 *kid)
{
struct kid_not_found_cache_bucket *k;
if (DBG_CACHE)
log_debug ("keydb: kid_not_found_insert (%08lx%08lx)\n",
(ulong)kid[0], (ulong)kid[1]);
k = xmalloc (sizeof *k);
k->kid[0] = kid[0];
k->kid[1] = kid[1];
k->next = kid_not_found_cache[kid[0] % KID_NOT_FOUND_CACHE_BUCKETS];
kid_not_found_cache[kid[0] % KID_NOT_FOUND_CACHE_BUCKETS] = k;
kid_not_found_stats.count++;
}
/* Flush the kid not found cache. */
static void
kid_not_found_flush (void)
{
struct kid_not_found_cache_bucket *k, *knext;
int i;
if (DBG_CACHE)
log_debug ("keydb: kid_not_found_flush\n");
if (!kid_not_found_stats.count)
return;
for (i=0; i < DIM(kid_not_found_cache); i++)
{
for (k = kid_not_found_cache[i]; k; k = knext)
{
knext = k->next;
xfree (k);
}
kid_not_found_cache[i] = NULL;
}
if (kid_not_found_stats.count > kid_not_found_stats.peak)
kid_not_found_stats.peak = kid_not_found_stats.count;
kid_not_found_stats.count = 0;
kid_not_found_stats.flushes++;
}
static void
keyblock_cache_clear (struct keydb_handle *hd)
{
hd->keyblock_cache.state = KEYBLOCK_CACHE_EMPTY;
iobuf_close (hd->keyblock_cache.iobuf);
hd->keyblock_cache.iobuf = NULL;
hd->keyblock_cache.resource = -1;
hd->keyblock_cache.offset = -1;
}
/* Handle the creation of a keyring or a keybox if it does not yet
exist. Take into account that other processes might have the
keyring/keybox already locked. This lock check does not work if
the directory itself is not yet available. If IS_BOX is true the
filename is expected to refer to a keybox. If FORCE_CREATE is true
the keyring or keybox will be created.
Return 0 if it is okay to access the specified file. */
static gpg_error_t
maybe_create_keyring_or_box (char *filename, int is_box, int force_create)
{
dotlock_t lockhd = NULL;
IOBUF iobuf;
int rc;
mode_t oldmask;
char *last_slash_in_filename;
char *bak_fname = NULL;
char *tmp_fname = NULL;
int save_slash;
/* A quick test whether the filename already exists. */
if (!access (filename, F_OK))
return !access (filename, R_OK)? 0 : gpg_error (GPG_ERR_EACCES);
/* If we don't want to create a new file at all, there is no need to
go any further - bail out right here. */
if (!force_create)
return gpg_error (GPG_ERR_ENOENT);
/* First of all we try to create the home directory. Note, that we
don't do any locking here because any sane application of gpg
would create the home directory by itself and not rely on gpg's
tricky auto-creation which is anyway only done for certain home
directory name pattern. */
last_slash_in_filename = strrchr (filename, DIRSEP_C);
#if HAVE_W32_SYSTEM
{
/* Windows may either have a slash or a backslash. Take care of it. */
char *p = strrchr (filename, '/');
if (!last_slash_in_filename || p > last_slash_in_filename)
last_slash_in_filename = p;
}
#endif /*HAVE_W32_SYSTEM*/
if (!last_slash_in_filename)
return gpg_error (GPG_ERR_ENOENT); /* No slash at all - should
not happen though. */
save_slash = *last_slash_in_filename;
*last_slash_in_filename = 0;
if (access(filename, F_OK))
{
static int tried;
if (!tried)
{
tried = 1;
try_make_homedir (filename);
}
if (access (filename, F_OK))
{
rc = gpg_error_from_syserror ();
*last_slash_in_filename = save_slash;
goto leave;
}
}
*last_slash_in_filename = save_slash;
/* To avoid races with other instances of gpg trying to create or
update the keyring (it is removed during an update for a short
time), we do the next stuff in a locked state. */
lockhd = dotlock_create (filename, 0);
if (!lockhd)
{
rc = gpg_error_from_syserror ();
/* A reason for this to fail is that the directory is not
writable. However, this whole locking stuff does not make
sense if this is the case. An empty non-writable directory
with no keyring is not really useful at all. */
if (opt.verbose)
log_info ("can't allocate lock for '%s': %s\n",
filename, gpg_strerror (rc));
if (!force_create)
return gpg_error (GPG_ERR_ENOENT); /* Won't happen. */
else
return rc;
}
if ( dotlock_take (lockhd, -1) )
{
rc = gpg_error_from_syserror ();
/* This is something bad. Probably a stale lockfile. */
log_info ("can't lock '%s': %s\n", filename, gpg_strerror (rc));
goto leave;
}
/* Now the real test while we are locked. */
/* Gpg either uses pubring.gpg or pubring.kbx and thus different
* lock files. Now, when one gpg process is updating a pubring.gpg
* and thus holding the corresponding lock, a second gpg process may
* get to here at the time between the two rename operation used by
* the first process to update pubring.gpg. The lock taken above
* may not protect the second process if it tries to create a
* pubring.kbx file which would be protected by a different lock
* file.
*
* We can detect this case by checking that the two temporary files
* used by the update code exist at the same time. In that case we
* do not create a new file but act as if FORCE_CREATE has not been
* given. Obviously there is a race between our two checks but the
* worst thing is that we won't create a new file, which is better
* than to accidentally creating one. */
rc = keybox_tmp_names (filename, is_box, &bak_fname, &tmp_fname);
if (rc)
goto leave;
if (!access (filename, F_OK))
{
rc = 0; /* Okay, we may access the file now. */
goto leave;
}
if (!access (bak_fname, F_OK) && !access (tmp_fname, F_OK))
{
/* Very likely another process is updating a pubring.gpg and we
should not create a pubring.kbx. */
rc = gpg_error (GPG_ERR_ENOENT);
goto leave;
}
/* The file does not yet exist, create it now. */
oldmask = umask (077);
if (is_secured_filename (filename))
{
iobuf = NULL;
gpg_err_set_errno (EPERM);
}
else
iobuf = iobuf_create (filename, 0);
umask (oldmask);
if (!iobuf)
{
rc = gpg_error_from_syserror ();
if (is_box)
log_error (_("error creating keybox '%s': %s\n"),
filename, gpg_strerror (rc));
else
log_error (_("error creating keyring '%s': %s\n"),
filename, gpg_strerror (rc));
goto leave;
}
iobuf_close (iobuf);
/* Must invalidate that ugly cache */
iobuf_ioctl (NULL, IOBUF_IOCTL_INVALIDATE_CACHE, 0, filename);
/* Make sure that at least one record is in a new keybox file, so
that the detection magic will work the next time it is used. */
if (is_box)
{
FILE *fp = fopen (filename, "wb");
if (!fp)
rc = gpg_error_from_syserror ();
else
{
rc = _keybox_write_header_blob (fp, 1);
fclose (fp);
}
if (rc)
{
if (is_box)
log_error (_("error creating keybox '%s': %s\n"),
filename, gpg_strerror (rc));
else
log_error (_("error creating keyring '%s': %s\n"),
filename, gpg_strerror (rc));
goto leave;
}
}
if (!opt.quiet)
{
if (is_box)
log_info (_("keybox '%s' created\n"), filename);
else
log_info (_("keyring '%s' created\n"), filename);
}
rc = 0;
leave:
if (lockhd)
{
dotlock_release (lockhd);
dotlock_destroy (lockhd);
}
xfree (bak_fname);
xfree (tmp_fname);
return rc;
}
/* Helper for keydb_add_resource. Opens FILENAME to figure out the
resource type.
Returns the specified file's likely type. If the file does not
exist, returns KEYDB_RESOURCE_TYPE_NONE and sets *R_FOUND to 0.
Otherwise, tries to figure out the file's type. This is either
KEYDB_RESOURCE_TYPE_KEYBOX, KEYDB_RESOURCE_TYPE_KEYRING or
KEYDB_RESOURCE_TYPE_KEYNONE. If the file is a keybox and it has
the OpenPGP flag set, then R_OPENPGP is also set. */
static KeydbResourceType
rt_from_file (const char *filename, int *r_found, int *r_openpgp)
{
u32 magic;
unsigned char verbuf[4];
FILE *fp;
KeydbResourceType rt = KEYDB_RESOURCE_TYPE_NONE;
*r_found = *r_openpgp = 0;
fp = fopen (filename, "rb");
if (fp)
{
*r_found = 1;
if (fread (&magic, 4, 1, fp) == 1 )
{
if (magic == 0x13579ace || magic == 0xce9a5713)
; /* GDBM magic - not anymore supported. */
else if (fread (&verbuf, 4, 1, fp) == 1
&& verbuf[0] == 1
&& fread (&magic, 4, 1, fp) == 1
&& !memcmp (&magic, "KBXf", 4))
{
if ((verbuf[3] & 0x02))
*r_openpgp = 1;
rt = KEYDB_RESOURCE_TYPE_KEYBOX;
}
else
rt = KEYDB_RESOURCE_TYPE_KEYRING;
}
else /* Maybe empty: assume keyring. */
rt = KEYDB_RESOURCE_TYPE_KEYRING;
fclose (fp);
}
return rt;
}
char *
keydb_search_desc_dump (struct keydb_search_desc *desc)
{
char b[MAX_FORMATTED_FINGERPRINT_LEN + 1];
char fpr[2 * MAX_FINGERPRINT_LEN + 1];
switch (desc->mode)
{
case KEYDB_SEARCH_MODE_EXACT:
return xasprintf ("EXACT: '%s'", desc->u.name);
case KEYDB_SEARCH_MODE_SUBSTR:
return xasprintf ("SUBSTR: '%s'", desc->u.name);
case KEYDB_SEARCH_MODE_MAIL:
return xasprintf ("MAIL: '%s'", desc->u.name);
case KEYDB_SEARCH_MODE_MAILSUB:
return xasprintf ("MAILSUB: '%s'", desc->u.name);
case KEYDB_SEARCH_MODE_MAILEND:
return xasprintf ("MAILEND: '%s'", desc->u.name);
case KEYDB_SEARCH_MODE_WORDS:
return xasprintf ("WORDS: '%s'", desc->u.name);
case KEYDB_SEARCH_MODE_SHORT_KID:
return xasprintf ("SHORT_KID: '%s'",
format_keyid (desc->u.kid, KF_SHORT, b, sizeof (b)));
case KEYDB_SEARCH_MODE_LONG_KID:
return xasprintf ("LONG_KID: '%s'",
format_keyid (desc->u.kid, KF_LONG, b, sizeof (b)));
case KEYDB_SEARCH_MODE_FPR:
bin2hex (desc->u.fpr, desc->fprlen, fpr);
return xasprintf ("FPR%02d: '%s'", desc->fprlen,
format_hexfingerprint (fpr, b, sizeof (b)));
case KEYDB_SEARCH_MODE_ISSUER:
return xasprintf ("ISSUER: '%s'", desc->u.name);
case KEYDB_SEARCH_MODE_ISSUER_SN:
return xasprintf ("ISSUER_SN: '%*s'",
(int) (desc->snlen == -1
? strlen (desc->sn) : desc->snlen),
desc->sn);
case KEYDB_SEARCH_MODE_SN:
return xasprintf ("SN: '%*s'",
(int) (desc->snlen == -1
? strlen (desc->sn) : desc->snlen),
desc->sn);
case KEYDB_SEARCH_MODE_SUBJECT:
return xasprintf ("SUBJECT: '%s'", desc->u.name);
case KEYDB_SEARCH_MODE_KEYGRIP:
return xasprintf ("KEYGRIP: %s", desc->u.grip);
case KEYDB_SEARCH_MODE_FIRST:
return xasprintf ("FIRST");
case KEYDB_SEARCH_MODE_NEXT:
return xasprintf ("NEXT");
default:
return xasprintf ("Bad search mode (%d)", desc->mode);
}
}
/* Register a resource (keyring or keybox). The first keyring or
* keybox that is added using this function is created if it does not
* already exist and the KEYDB_RESOURCE_FLAG_READONLY is not set.
*
* FLAGS are a combination of the KEYDB_RESOURCE_FLAG_* constants.
*
* URL must have the following form:
*
* gnupg-ring:filename = plain keyring
* gnupg-kbx:filename = keybox file
* filename = check file's type (create as a plain keyring)
*
* Note: on systems with drive letters (Windows) invalid URLs (i.e.,
* those with an unrecognized part before the ':' such as "c:\...")
* will silently be treated as bare filenames. On other systems, such
* URLs will cause this function to return GPG_ERR_GENERAL.
*
* If KEYDB_RESOURCE_FLAG_DEFAULT is set, the resource is a keyring
* and the file ends in ".gpg", then this function also checks if a
* file with the same name, but the extension ".kbx" exists, is a
* keybox and the OpenPGP flag is set. If so, this function opens
* that resource instead.
*
* If the file is not found, KEYDB_RESOURCE_FLAG_GPGVDEF is set and
* the URL ends in ".kbx", then this function will try opening the
* same URL, but with the extension ".gpg". If that file is a keybox
* with the OpenPGP flag set or it is a keyring, then we use that
* instead.
*
* If the file is not found, KEYDB_RESOURCE_FLAG_DEFAULT is set, the
* file should be created and the file's extension is ".gpg" then we
* replace the extension with ".kbx".
*
* If the KEYDB_RESOURCE_FLAG_PRIMARY is set and the resource is a
* keyring (not a keybox), then this resource is considered the
* primary resource. This is used by keydb_locate_writable(). If
* another primary keyring is set, then that keyring is considered the
* primary.
*
* If KEYDB_RESOURCE_FLAG_READONLY is set and the resource is a
* keyring (not a keybox), then the keyring is marked as read only and
* operations just as keyring_insert_keyblock will return
* GPG_ERR_ACCESS. */
gpg_error_t
keydb_add_resource (const char *url, unsigned int flags)
{
/* The file named by the URL (i.e., without the prototype). */
const char *resname = url;
char *filename = NULL;
int create;
int read_only = !!(flags&KEYDB_RESOURCE_FLAG_READONLY);
int is_default = !!(flags&KEYDB_RESOURCE_FLAG_DEFAULT);
int is_gpgvdef = !!(flags&KEYDB_RESOURCE_FLAG_GPGVDEF);
gpg_error_t err = 0;
KeydbResourceType rt = KEYDB_RESOURCE_TYPE_NONE;
void *token;
/* Create the resource if it is the first registered one. */
create = (!read_only && !any_registered);
if (strlen (resname) > 11 && !strncmp( resname, "gnupg-ring:", 11) )
{
rt = KEYDB_RESOURCE_TYPE_KEYRING;
resname += 11;
}
else if (strlen (resname) > 10 && !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 );
err = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
#endif /* !HAVE_DRIVE_LETTERS && !__riscos__ */
if (*resname != DIRSEP_C
#ifdef HAVE_W32_SYSTEM
&& *resname != '/' /* Fixme: does not handle drive letters. */
#endif
)
{
/* Do tilde expansion etc. */
if (strchr (resname, DIRSEP_C)
#ifdef HAVE_W32_SYSTEM
|| strchr (resname, '/') /* Windows also accepts this. */
#endif
)
filename = make_filename (resname, NULL);
else
filename = make_filename (gnupg_homedir (), resname, NULL);
}
else
filename = xstrdup (resname);
/* See whether we can determine the filetype. */
if (rt == KEYDB_RESOURCE_TYPE_NONE)
{
int found, openpgp_flag;
int pass = 0;
size_t filenamelen;
check_again:
filenamelen = strlen (filename);
rt = rt_from_file (filename, &found, &openpgp_flag);
if (found)
{
/* The file exists and we have the resource type in RT.
Now let us check whether in addition to the "pubring.gpg"
a "pubring.kbx with openpgp keys exists. This is so that
GPG 2.1 will use an existing "pubring.kbx" by default iff
that file has been created or used by 2.1. This check is
needed because after creation or use of the kbx file with
2.1 an older version of gpg may have created a new
pubring.gpg for its own use. */
if (!pass && is_default && rt == KEYDB_RESOURCE_TYPE_KEYRING
&& filenamelen > 4 && !strcmp (filename+filenamelen-4, ".gpg"))
{
strcpy (filename+filenamelen-4, ".kbx");
if ((rt_from_file (filename, &found, &openpgp_flag)
== KEYDB_RESOURCE_TYPE_KEYBOX) && found && openpgp_flag)
rt = KEYDB_RESOURCE_TYPE_KEYBOX;
else /* Restore filename */
strcpy (filename+filenamelen-4, ".gpg");
}
}
else if (!pass && is_gpgvdef
&& filenamelen > 4 && !strcmp (filename+filenamelen-4, ".kbx"))
{
/* Not found but gpgv's default "trustedkeys.kbx" file has
been requested. We did not found it so now check whether
a "trustedkeys.gpg" file exists and use that instead. */
KeydbResourceType rttmp;
strcpy (filename+filenamelen-4, ".gpg");
rttmp = rt_from_file (filename, &found, &openpgp_flag);
if (found
&& ((rttmp == KEYDB_RESOURCE_TYPE_KEYBOX && openpgp_flag)
|| (rttmp == KEYDB_RESOURCE_TYPE_KEYRING)))
rt = rttmp;
else /* Restore filename */
strcpy (filename+filenamelen-4, ".kbx");
}
else if (!pass
&& is_default && create
&& filenamelen > 4 && !strcmp (filename+filenamelen-4, ".gpg"))
{
/* The file does not exist, the default resource has been
requested, the file shall be created, and the file has a
".gpg" suffix. Change the suffix to ".kbx" and try once
more. This way we achieve that we open an existing
".gpg" keyring, but create a new keybox file with an
".kbx" suffix. */
strcpy (filename+filenamelen-4, ".kbx");
pass++;
goto check_again;
}
else /* No file yet: create keybox. */
rt = KEYDB_RESOURCE_TYPE_KEYBOX;
}
switch (rt)
{
case KEYDB_RESOURCE_TYPE_NONE:
log_error ("unknown type of key resource '%s'\n", url );
err = gpg_error (GPG_ERR_GENERAL);
goto leave;
case KEYDB_RESOURCE_TYPE_KEYRING:
err = maybe_create_keyring_or_box (filename, 0, create);
if (err)
goto leave;
if (keyring_register_filename (filename, read_only, &token))
{
if (used_resources >= MAX_KEYDB_RESOURCES)
err = gpg_error (GPG_ERR_RESOURCE_LIMIT);
else
{
if ((flags & KEYDB_RESOURCE_FLAG_PRIMARY))
primary_keydb = token;
all_resources[used_resources].type = rt;
all_resources[used_resources].u.kr = NULL; /* Not used here */
all_resources[used_resources].token = token;
used_resources++;
}
}
else
{
/* This keyring was already registered, so ignore it.
However, we can still mark it as primary even if it was
already registered. */
if ((flags & KEYDB_RESOURCE_FLAG_PRIMARY))
primary_keydb = token;
}
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
{
err = maybe_create_keyring_or_box (filename, 1, create);
if (err)
goto leave;
err = keybox_register_file (filename, 0, &token);
if (!err)
{
if (used_resources >= MAX_KEYDB_RESOURCES)
err = gpg_error (GPG_ERR_RESOURCE_LIMIT);
else
{
if ((flags & KEYDB_RESOURCE_FLAG_PRIMARY))
primary_keydb = token;
all_resources[used_resources].type = rt;
all_resources[used_resources].u.kb = NULL; /* Not used here */
all_resources[used_resources].token = token;
/* FIXME: Do a compress run if needed and no other
user is currently using the keybox. */
used_resources++;
}
}
else if (gpg_err_code (err) == GPG_ERR_EEXIST)
{
/* Already registered. We will mark it as the primary key
if requested. */
if ((flags & KEYDB_RESOURCE_FLAG_PRIMARY))
primary_keydb = token;
}
}
break;
default:
log_error ("resource type of '%s' not supported\n", url);
err = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
/* fixme: check directory permissions and print a warning */
leave:
if (err)
{
log_error (_("keyblock resource '%s': %s\n"),
filename, gpg_strerror (err));
write_status_error ("add_keyblock_resource", err);
}
else
any_registered = 1;
xfree (filename);
return err;
}
void
keydb_dump_stats (void)
{
log_info ("keydb: handles=%u locks=%u parse=%u get=%u\n",
keydb_stats.handles,
keydb_stats.locks,
keydb_stats.parse_keyblocks,
keydb_stats.get_keyblocks);
log_info (" build=%u update=%u insert=%u delete=%u\n",
keydb_stats.build_keyblocks,
keydb_stats.update_keyblocks,
keydb_stats.insert_keyblocks,
keydb_stats.delete_keyblocks);
log_info (" reset=%u found=%u not=%u cache=%u not=%u\n",
keydb_stats.search_resets,
keydb_stats.found,
keydb_stats.notfound,
keydb_stats.found_cached,
keydb_stats.notfound_cached);
log_info ("kid_not_found_cache: count=%u peak=%u flushes=%u\n",
kid_not_found_stats.count,
kid_not_found_stats.peak,
kid_not_found_stats.flushes);
}
/* Create a new database handle. A database handle is similar to a
file handle: it contains a local file position. This is used when
searching: subsequent searches resume where the previous search
left off. To rewind the position, use keydb_search_reset(). This
function returns NULL on error, sets ERRNO, and prints an error
diagnostic. */
KEYDB_HANDLE
keydb_new (void)
{
KEYDB_HANDLE hd;
int i, j;
int die = 0;
int reterrno;
if (DBG_CLOCK)
log_clock ("keydb_new");
hd = xtrycalloc (1, sizeof *hd);
if (!hd)
goto leave;
hd->found = -1;
hd->saved_found = -1;
hd->is_reset = 1;
log_assert (used_resources <= MAX_KEYDB_RESOURCES);
for (i=j=0; ! die && i < used_resources; i++)
{
switch (all_resources[i].type)
{
case KEYDB_RESOURCE_TYPE_NONE: /* ignore */
break;
case KEYDB_RESOURCE_TYPE_KEYRING:
hd->active[j].type = all_resources[i].type;
hd->active[j].token = all_resources[i].token;
hd->active[j].u.kr = keyring_new (all_resources[i].token);
if (!hd->active[j].u.kr)
{
reterrno = errno;
die = 1;
}
j++;
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].u.kb = keybox_new_openpgp (all_resources[i].token, 0);
if (!hd->active[j].u.kb)
{
reterrno = errno;
die = 1;
}
j++;
break;
}
}
hd->used = j;
active_handles++;
keydb_stats.handles++;
if (die)
{
keydb_release (hd);
gpg_err_set_errno (reterrno);
hd = NULL;
}
leave:
if (!hd)
log_error (_("error opening key DB: %s\n"),
gpg_strerror (gpg_error_from_syserror()));
return hd;
}
void
keydb_release (KEYDB_HANDLE hd)
{
int i;
if (!hd)
return;
log_assert (active_handles > 0);
active_handles--;
hd->keep_lock = 0;
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_KEYRING:
keyring_release (hd->active[i].u.kr);
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
keybox_release (hd->active[i].u.kb);
break;
}
}
keyblock_cache_clear (hd);
xfree (hd);
}
/* Take a lock on the files immediately and not only during insert or
* update. This lock is released with keydb_release. */
gpg_error_t
keydb_lock (KEYDB_HANDLE hd)
{
gpg_error_t err;
if (!hd)
return gpg_error (GPG_ERR_INV_ARG);
err = lock_all (hd);
if (!err)
hd->keep_lock = 1;
return err;
}
/* Set a flag on the handle to suppress use of cached results. This
* is required for updating a keyring and for key listings. Fixme:
* Using a new parameter for keydb_new might be a better solution. */
void
keydb_disable_caching (KEYDB_HANDLE hd)
{
if (hd)
hd->no_caching = 1;
}
/* Return the file name of the resource in which the current search
* result was found or, if there is no search result, the filename of
* the current resource (i.e., the resource that the file position
* points to). Note: the filename is not necessarily the URL used to
* open it!
*
* This function only returns 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_KEYRING:
s = keyring_get_resource_name (hd->active[idx].u.kr);
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
s = keybox_get_resource_name (hd->active[idx].u.kb);
break;
}
return s? s: "";
}
static int
lock_all (KEYDB_HANDLE hd)
{
int i, rc = 0;
/* Fixme: This locking scheme may lead to a deadlock if the resources
are not added in the same order by all processes. We are
currently only allowing one resource so it is not a problem.
[Oops: Who claimed the latter]
To fix this we need to use a lock file to protect lock_all. */
for (i=0; !rc && i < hd->used; i++)
{
switch (hd->active[i].type)
{
case KEYDB_RESOURCE_TYPE_NONE:
break;
case KEYDB_RESOURCE_TYPE_KEYRING:
rc = keyring_lock (hd->active[i].u.kr, 1);
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
- rc = keybox_lock (hd->active[i].u.kb, 1);
+ rc = keybox_lock (hd->active[i].u.kb, 1, -1);
break;
}
}
if (rc)
{
/* Revert the already taken locks. */
for (i--; i >= 0; i--)
{
switch (hd->active[i].type)
{
case KEYDB_RESOURCE_TYPE_NONE:
break;
case KEYDB_RESOURCE_TYPE_KEYRING:
keyring_lock (hd->active[i].u.kr, 0);
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
- keybox_lock (hd->active[i].u.kb, 0);
+ keybox_lock (hd->active[i].u.kb, 0, 0);
break;
}
}
}
else
{
hd->locked = 1;
keydb_stats.locks++;
}
return rc;
}
static void
unlock_all (KEYDB_HANDLE hd)
{
int i;
if (!hd->locked || hd->keep_lock)
return;
for (i=hd->used-1; i >= 0; i--)
{
switch (hd->active[i].type)
{
case KEYDB_RESOURCE_TYPE_NONE:
break;
case KEYDB_RESOURCE_TYPE_KEYRING:
keyring_lock (hd->active[i].u.kr, 0);
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
- keybox_lock (hd->active[i].u.kb, 0);
+ keybox_lock (hd->active[i].u.kb, 0, 0);
break;
}
}
hd->locked = 0;
}
/* Save the last found state and invalidate the current selection
* (i.e., the entry selected by keydb_search() is invalidated and
* something like keydb_get_keyblock() will return an error). This
* does not change the file position. This makes it possible to do
* something like:
*
* keydb_search (hd, ...); // Result 1.
* keydb_push_found_state (hd);
* keydb_search_reset (hd);
* keydb_search (hd, ...); // Result 2.
* keydb_pop_found_state (hd);
* keydb_get_keyblock (hd, ...); // -> Result 1.
*
* Note: it is only possible to save a single save state at a time.
* In other words, the save stack only has room for a single
* instance of the state. */
void
keydb_push_found_state (KEYDB_HANDLE hd)
{
if (!hd)
return;
if (hd->found < 0 || hd->found >= hd->used)
{
hd->saved_found = -1;
return;
}
switch (hd->active[hd->found].type)
{
case KEYDB_RESOURCE_TYPE_NONE:
break;
case KEYDB_RESOURCE_TYPE_KEYRING:
keyring_push_found_state (hd->active[hd->found].u.kr);
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
keybox_push_found_state (hd->active[hd->found].u.kb);
break;
}
hd->saved_found = hd->found;
hd->found = -1;
}
/* Restore the previous save state. If the saved state is NULL or
invalid, this is a NOP. */
void
keydb_pop_found_state (KEYDB_HANDLE hd)
{
if (!hd)
return;
hd->found = hd->saved_found;
hd->saved_found = -1;
if (hd->found < 0 || hd->found >= hd->used)
return;
switch (hd->active[hd->found].type)
{
case KEYDB_RESOURCE_TYPE_NONE:
break;
case KEYDB_RESOURCE_TYPE_KEYRING:
keyring_pop_found_state (hd->active[hd->found].u.kr);
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
keybox_pop_found_state (hd->active[hd->found].u.kb);
break;
}
}
static gpg_error_t
parse_keyblock_image (iobuf_t iobuf, int pk_no, int uid_no,
kbnode_t *r_keyblock)
{
gpg_error_t err;
struct parse_packet_ctx_s parsectx;
PACKET *pkt;
kbnode_t keyblock = NULL;
kbnode_t node, *tail;
int in_cert, save_mode;
int pk_count, uid_count;
*r_keyblock = NULL;
pkt = xtrymalloc (sizeof *pkt);
if (!pkt)
return gpg_error_from_syserror ();
init_packet (pkt);
init_parse_packet (&parsectx, iobuf);
save_mode = set_packet_list_mode (0);
in_cert = 0;
tail = NULL;
pk_count = uid_count = 0;
while ((err = parse_packet (&parsectx, pkt)) != -1)
{
if (gpg_err_code (err) == GPG_ERR_UNKNOWN_PACKET)
{
free_packet (pkt, &parsectx);
init_packet (pkt);
continue;
}
if (err)
{
+ es_fflush (es_stdout);
log_error ("parse_keyblock_image: read error: %s\n",
gpg_strerror (err));
+ if (gpg_err_code (err) == GPG_ERR_INV_PACKET)
+ {
+ free_packet (pkt, &parsectx);
+ init_packet (pkt);
+ continue;
+ }
err = gpg_error (GPG_ERR_INV_KEYRING);
break;
}
/* Filter allowed packets. */
switch (pkt->pkttype)
{
case PKT_PUBLIC_KEY:
case PKT_PUBLIC_SUBKEY:
case PKT_SECRET_KEY:
case PKT_SECRET_SUBKEY:
case PKT_USER_ID:
case PKT_ATTRIBUTE:
case PKT_SIGNATURE:
case PKT_RING_TRUST:
break; /* Allowed per RFC. */
default:
log_info ("skipped packet of type %d in keybox\n", (int)pkt->pkttype);
free_packet(pkt, &parsectx);
init_packet(pkt);
continue;
}
/* Other sanity checks. */
if (!in_cert && pkt->pkttype != PKT_PUBLIC_KEY)
{
log_error ("parse_keyblock_image: first packet in a keybox blob "
"is not a public key packet\n");
err = gpg_error (GPG_ERR_INV_KEYRING);
break;
}
if (in_cert && (pkt->pkttype == PKT_PUBLIC_KEY
|| pkt->pkttype == PKT_SECRET_KEY))
{
log_error ("parse_keyblock_image: "
"multiple keyblocks in a keybox blob\n");
err = gpg_error (GPG_ERR_INV_KEYRING);
break;
}
in_cert = 1;
node = new_kbnode (pkt);
switch (pkt->pkttype)
{
case PKT_PUBLIC_KEY:
case PKT_PUBLIC_SUBKEY:
case PKT_SECRET_KEY:
case PKT_SECRET_SUBKEY:
if (++pk_count == pk_no)
node->flag |= 1;
break;
case PKT_USER_ID:
if (++uid_count == uid_no)
node->flag |= 2;
break;
default:
break;
}
if (!keyblock)
keyblock = node;
else
*tail = node;
tail = &node->next;
pkt = xtrymalloc (sizeof *pkt);
if (!pkt)
{
err = gpg_error_from_syserror ();
break;
}
init_packet (pkt);
}
set_packet_list_mode (save_mode);
if (err == -1 && keyblock)
err = 0; /* Got the entire keyblock. */
if (err)
release_kbnode (keyblock);
else
{
*r_keyblock = keyblock;
keydb_stats.parse_keyblocks++;
}
free_packet (pkt, &parsectx);
deinit_parse_packet (&parsectx);
xfree (pkt);
return err;
}
/* Return the keyblock last found by keydb_search() in *RET_KB.
*
* On success, the function returns 0 and the caller must free *RET_KB
* using release_kbnode(). Otherwise, the function returns an error
* code.
*
* The returned keyblock has the kbnode 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. */
gpg_error_t
keydb_get_keyblock (KEYDB_HANDLE hd, KBNODE *ret_kb)
{
gpg_error_t err = 0;
*ret_kb = NULL;
if (!hd)
return gpg_error (GPG_ERR_INV_ARG);
if (DBG_CLOCK)
log_clock ("keydb_get_keybock enter");
if (hd->keyblock_cache.state == KEYBLOCK_CACHE_FILLED)
{
err = iobuf_seek (hd->keyblock_cache.iobuf, 0);
if (err)
{
log_error ("keydb_get_keyblock: failed to rewind iobuf for cache\n");
keyblock_cache_clear (hd);
}
else
{
err = parse_keyblock_image (hd->keyblock_cache.iobuf,
hd->keyblock_cache.pk_no,
hd->keyblock_cache.uid_no,
ret_kb);
if (err)
keyblock_cache_clear (hd);
if (DBG_CLOCK)
log_clock (err? "keydb_get_keyblock leave (cached, failed)"
: "keydb_get_keyblock leave (cached)");
return err;
}
}
if (hd->found < 0 || hd->found >= hd->used)
return gpg_error (GPG_ERR_VALUE_NOT_FOUND);
switch (hd->active[hd->found].type)
{
case KEYDB_RESOURCE_TYPE_NONE:
err = gpg_error (GPG_ERR_GENERAL); /* oops */
break;
case KEYDB_RESOURCE_TYPE_KEYRING:
err = keyring_get_keyblock (hd->active[hd->found].u.kr, ret_kb);
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
{
iobuf_t iobuf;
int pk_no, uid_no;
err = keybox_get_keyblock (hd->active[hd->found].u.kb,
&iobuf, &pk_no, &uid_no);
if (!err)
{
err = parse_keyblock_image (iobuf, pk_no, uid_no, ret_kb);
if (!err && hd->keyblock_cache.state == KEYBLOCK_CACHE_PREPARED)
{
hd->keyblock_cache.state = KEYBLOCK_CACHE_FILLED;
hd->keyblock_cache.iobuf = iobuf;
hd->keyblock_cache.pk_no = pk_no;
hd->keyblock_cache.uid_no = uid_no;
}
else
{
iobuf_close (iobuf);
}
}
}
break;
}
if (hd->keyblock_cache.state != KEYBLOCK_CACHE_FILLED)
keyblock_cache_clear (hd);
if (!err)
keydb_stats.get_keyblocks++;
if (DBG_CLOCK)
log_clock (err? "keydb_get_keyblock leave (failed)"
: "keydb_get_keyblock leave");
return err;
}
/* Build a keyblock image from KEYBLOCK. Returns 0 on success and
* only then stores a new iobuf object at R_IOBUF. */
static gpg_error_t
build_keyblock_image (kbnode_t keyblock, iobuf_t *r_iobuf)
{
gpg_error_t err;
iobuf_t iobuf;
kbnode_t kbctx, node;
*r_iobuf = NULL;
iobuf = iobuf_temp ();
for (kbctx = NULL; (node = walk_kbnode (keyblock, &kbctx, 0));)
{
/* Make sure to use only packets valid on a keyblock. */
switch (node->pkt->pkttype)
{
case PKT_PUBLIC_KEY:
case PKT_PUBLIC_SUBKEY:
case PKT_SIGNATURE:
case PKT_USER_ID:
case PKT_ATTRIBUTE:
case PKT_RING_TRUST:
break;
default:
continue;
}
err = build_packet_and_meta (iobuf, node->pkt);
if (err)
{
iobuf_close (iobuf);
return err;
}
}
keydb_stats.build_keyblocks++;
*r_iobuf = iobuf;
return 0;
}
/* Update the keyblock KB (i.e., extract the fingerprint and find the
* corresponding keyblock in the keyring).
*
* This doesn't do anything if --dry-run was specified.
*
* Returns 0 on success. Otherwise, it returns an error code. Note:
* if there isn't a keyblock in the keyring corresponding to KB, then
* this function returns GPG_ERR_VALUE_NOT_FOUND.
*
* This function selects the matching record and modifies the current
* file position to point to the record just after the selected entry.
* Thus, if you do a subsequent search using HD, you should first do a
* keydb_search_reset. Further, if the selected record is important,
* you should use keydb_push_found_state and keydb_pop_found_state to
* save and restore it. */
gpg_error_t
keydb_update_keyblock (ctrl_t ctrl, KEYDB_HANDLE hd, kbnode_t kb)
{
gpg_error_t err;
PKT_public_key *pk;
KEYDB_SEARCH_DESC desc;
size_t len;
log_assert (kb);
log_assert (kb->pkt->pkttype == PKT_PUBLIC_KEY);
pk = kb->pkt->pkt.public_key;
if (!hd)
return gpg_error (GPG_ERR_INV_ARG);
kid_not_found_flush ();
keyblock_cache_clear (hd);
if (opt.dry_run)
return 0;
err = lock_all (hd);
if (err)
return err;
#ifdef USE_TOFU
tofu_notice_key_changed (ctrl, kb);
#endif
memset (&desc, 0, sizeof (desc));
fingerprint_from_pk (pk, desc.u.fpr, &len);
if (len == 20 || len == 32)
{
desc.mode = KEYDB_SEARCH_MODE_FPR;
desc.fprlen = len;
}
else
log_bug ("%s: Unsupported key length: %zu\n", __func__, len);
keydb_search_reset (hd);
err = keydb_search (hd, &desc, 1, NULL);
if (err)
return gpg_error (GPG_ERR_VALUE_NOT_FOUND);
log_assert (hd->found >= 0 && hd->found < hd->used);
switch (hd->active[hd->found].type)
{
case KEYDB_RESOURCE_TYPE_NONE:
err = gpg_error (GPG_ERR_GENERAL); /* oops */
break;
case KEYDB_RESOURCE_TYPE_KEYRING:
err = keyring_update_keyblock (hd->active[hd->found].u.kr, kb);
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
{
iobuf_t iobuf;
err = build_keyblock_image (kb, &iobuf);
if (!err)
{
err = keybox_update_keyblock (hd->active[hd->found].u.kb,
iobuf_get_temp_buffer (iobuf),
iobuf_get_temp_length (iobuf));
iobuf_close (iobuf);
}
}
break;
}
unlock_all (hd);
if (!err)
keydb_stats.update_keyblocks++;
return err;
}
/* Insert a keyblock into one of the underlying keyrings or keyboxes.
*
* Be default, the keyring / keybox from which the last search result
* came is used. If there was no previous search result (or
* keydb_search_reset was called), then the keyring / keybox where the
* next search would start is used (i.e., the current file position).
*
* Note: this doesn't do anything if --dry-run was specified.
*
* Returns 0 on success. Otherwise, it returns an error code. */
gpg_error_t
keydb_insert_keyblock (KEYDB_HANDLE hd, kbnode_t kb)
{
gpg_error_t err;
int idx;
if (!hd)
return gpg_error (GPG_ERR_INV_ARG);
kid_not_found_flush ();
keyblock_cache_clear (hd);
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);
err = lock_all (hd);
if (err)
return err;
switch (hd->active[idx].type)
{
case KEYDB_RESOURCE_TYPE_NONE:
err = gpg_error (GPG_ERR_GENERAL); /* oops */
break;
case KEYDB_RESOURCE_TYPE_KEYRING:
err = keyring_insert_keyblock (hd->active[idx].u.kr, kb);
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
{ /* We need to turn our kbnode_t list of packets into a proper
keyblock first. This is required by the OpenPGP key parser
included in the keybox code. Eventually we can change this
kludge to have the caller pass the image. */
iobuf_t iobuf;
err = build_keyblock_image (kb, &iobuf);
if (!err)
{
err = keybox_insert_keyblock (hd->active[idx].u.kb,
iobuf_get_temp_buffer (iobuf),
iobuf_get_temp_length (iobuf));
iobuf_close (iobuf);
}
}
break;
}
unlock_all (hd);
if (!err)
keydb_stats.insert_keyblocks++;
return err;
}
/* Delete the currently selected keyblock. If you haven't done a
* search yet on this database handle (or called keydb_search_reset),
* then this will return an error.
*
* Returns 0 on success or an error code, if an error occurs. */
gpg_error_t
keydb_delete_keyblock (KEYDB_HANDLE hd)
{
gpg_error_t rc;
if (!hd)
return gpg_error (GPG_ERR_INV_ARG);
kid_not_found_flush ();
keyblock_cache_clear (hd);
if (hd->found < 0 || hd->found >= hd->used)
return gpg_error (GPG_ERR_VALUE_NOT_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_KEYRING:
rc = keyring_delete_keyblock (hd->active[hd->found].u.kr);
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
rc = keybox_delete (hd->active[hd->found].u.kb);
break;
}
unlock_all (hd);
if (!rc)
keydb_stats.delete_keyblocks++;
return rc;
}
/* A database may consists of multiple keyrings / key boxes. This
* sets the "file position" to the start of the first keyring / key
* box that is writable (i.e., doesn't have the read-only flag set).
*
* This first tries the primary keyring (the last keyring (not
* keybox!) added using keydb_add_resource() and with
* KEYDB_RESOURCE_FLAG_PRIMARY set). If that is not writable, then it
* tries the keyrings / keyboxes in the order in which they were
* added. */
gpg_error_t
keydb_locate_writable (KEYDB_HANDLE hd)
{
gpg_error_t rc;
if (!hd)
return GPG_ERR_INV_ARG;
rc = keydb_search_reset (hd); /* this does reset hd->current */
if (rc)
return rc;
/* If we have a primary set, try that one first */
if (primary_keydb)
{
for ( ; hd->current >= 0 && hd->current < hd->used; hd->current++)
{
if(hd->active[hd->current].token == primary_keydb)
{
if(keyring_is_writable (hd->active[hd->current].token))
return 0;
else
break;
}
}
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_KEYRING:
if (keyring_is_writable (hd->active[hd->current].token))
return 0; /* found (hd->current is set to it) */
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 gpg_error (GPG_ERR_NOT_FOUND);
}
/* Rebuild the on-disk caches of all key resources. */
void
keydb_rebuild_caches (ctrl_t ctrl, int noisy)
{
int i, rc;
for (i=0; i < used_resources; i++)
{
if (!keyring_is_writable (all_resources[i].token))
continue;
switch (all_resources[i].type)
{
case KEYDB_RESOURCE_TYPE_NONE: /* ignore */
break;
case KEYDB_RESOURCE_TYPE_KEYRING:
rc = keyring_rebuild_cache (ctrl, all_resources[i].token,noisy);
if (rc)
log_error (_("failed to rebuild keyring cache: %s\n"),
gpg_strerror (rc));
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
/* N/A. */
break;
}
}
}
/* Return the number of skipped blocks (because they were to large to
read from a keybox) since the last search reset. */
unsigned long
keydb_get_skipped_counter (KEYDB_HANDLE hd)
{
return hd ? hd->skipped_long_blobs : 0;
}
/* Clears the current search result and resets the handle's position
* so that the next search starts at the beginning of the database
* (the start of the first resource).
*
* Returns 0 on success and an error code if an error occurred.
* (Currently, this function always returns 0 if HD is valid.) */
gpg_error_t
keydb_search_reset (KEYDB_HANDLE hd)
{
gpg_error_t rc = 0;
int i;
if (!hd)
return gpg_error (GPG_ERR_INV_ARG);
keyblock_cache_clear (hd);
if (DBG_CLOCK)
log_clock ("keydb_search_reset");
if (DBG_CACHE)
log_debug ("keydb_search: reset (hd=%p)", hd);
hd->skipped_long_blobs = 0;
hd->current = 0;
hd->found = -1;
/* Now 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_KEYRING:
rc = keyring_search_reset (hd->active[i].u.kr);
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
rc = keybox_search_reset (hd->active[i].u.kb);
break;
}
}
hd->is_reset = 1;
if (!rc)
keydb_stats.search_resets++;
return rc;
}
/* Search the database for keys matching the search description. If
* the DB contains any legacy keys, these are silently ignored.
*
* DESC is an array of search terms with NDESC entries. The search
* terms are or'd together. That is, the next entry in the DB that
* matches any of the descriptions will be returned.
*
* Note: this function resumes searching where the last search left
* off (i.e., at the current file position). If you want to search
* from the start of the database, then you need to first call
* keydb_search_reset().
*
* If no key matches the search description, returns
* GPG_ERR_NOT_FOUND. If there was a match, returns 0. If an error
* occurred, returns an error code.
*
* The returned key is considered to be selected and the raw data can,
* for instance, be returned by calling keydb_get_keyblock(). */
gpg_error_t
keydb_search (KEYDB_HANDLE hd, KEYDB_SEARCH_DESC *desc,
size_t ndesc, size_t *descindex)
{
int i;
gpg_error_t rc;
int was_reset = hd->is_reset;
/* If an entry is already in the cache, then don't add it again. */
int already_in_cache = 0;
int fprlen;
if (descindex)
*descindex = 0; /* Make sure it is always set on return. */
if (!hd)
return gpg_error (GPG_ERR_INV_ARG);
if (!any_registered)
{
write_status_error ("keydb_search", gpg_error (GPG_ERR_KEYRING_OPEN));
return gpg_error (GPG_ERR_NOT_FOUND);
}
if (DBG_CLOCK)
log_clock ("keydb_search enter");
if (DBG_LOOKUP)
{
log_debug ("%s: %zd search descriptions:\n", __func__, ndesc);
for (i = 0; i < ndesc; i ++)
{
char *t = keydb_search_desc_dump (&desc[i]);
log_debug ("%s %d: %s\n", __func__, i, t);
xfree (t);
}
}
if (ndesc == 1 && desc[0].mode == KEYDB_SEARCH_MODE_LONG_KID
&& (already_in_cache = kid_not_found_p (desc[0].u.kid)) == 1 )
{
if (DBG_CLOCK)
log_clock ("keydb_search leave (not found, cached)");
keydb_stats.notfound_cached++;
return gpg_error (GPG_ERR_NOT_FOUND);
}
/* NB: If one of the exact search modes below is used in a loop to
walk over all keys (with the same fingerprint) the caching must
have been disabled for the handle. */
if (desc[0].mode == KEYDB_SEARCH_MODE_FPR)
fprlen = desc[0].fprlen;
else
fprlen = 0;
if (!hd->no_caching
&& ndesc == 1
&& fprlen
&& hd->keyblock_cache.state == KEYBLOCK_CACHE_FILLED
&& hd->keyblock_cache.fprlen == fprlen
&& !memcmp (hd->keyblock_cache.fpr, desc[0].u.fpr, fprlen)
/* Make sure the current file position occurs before the cached
result to avoid an infinite loop. */
&& (hd->current < hd->keyblock_cache.resource
|| (hd->current == hd->keyblock_cache.resource
&& (keybox_offset (hd->active[hd->current].u.kb)
<= hd->keyblock_cache.offset))))
{
/* (DESCINDEX is already set). */
if (DBG_CLOCK)
log_clock ("keydb_search leave (cached)");
hd->current = hd->keyblock_cache.resource;
/* HD->KEYBLOCK_CACHE.OFFSET is the last byte in the record.
Seek just beyond that. */
keybox_seek (hd->active[hd->current].u.kb, hd->keyblock_cache.offset + 1);
keydb_stats.found_cached++;
return 0;
}
rc = -1;
while ((rc == -1 || gpg_err_code (rc) == GPG_ERR_EOF)
&& hd->current >= 0 && hd->current < hd->used)
{
if (DBG_LOOKUP)
log_debug ("%s: searching %s (resource %d of %d)\n",
__func__,
hd->active[hd->current].type == KEYDB_RESOURCE_TYPE_KEYRING
? "keyring"
: (hd->active[hd->current].type == KEYDB_RESOURCE_TYPE_KEYBOX
? "keybox" : "unknown type"),
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_KEYRING:
rc = keyring_search (hd->active[hd->current].u.kr, desc,
ndesc, descindex, 1);
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
do
rc = keybox_search (hd->active[hd->current].u.kb, desc,
ndesc, KEYBOX_BLOBTYPE_PGP,
descindex, &hd->skipped_long_blobs);
while (rc == GPG_ERR_LEGACY_KEY);
break;
}
if (DBG_LOOKUP)
log_debug ("%s: searched %s (resource %d of %d) => %s\n",
__func__,
hd->active[hd->current].type == KEYDB_RESOURCE_TYPE_KEYRING
? "keyring"
: (hd->active[hd->current].type == KEYDB_RESOURCE_TYPE_KEYBOX
? "keybox" : "unknown type"),
hd->current, hd->used,
rc == -1 ? "EOF" : gpg_strerror (rc));
if (rc == -1 || gpg_err_code (rc) == GPG_ERR_EOF)
{
/* EOF -> switch to next resource */
hd->current++;
}
else if (!rc)
hd->found = hd->current;
}
hd->is_reset = 0;
rc = ((rc == -1 || gpg_err_code (rc) == GPG_ERR_EOF)
? gpg_error (GPG_ERR_NOT_FOUND)
: rc);
keyblock_cache_clear (hd);
if (!hd->no_caching
&& !rc
&& ndesc == 1
&& fprlen
&& hd->active[hd->current].type == KEYDB_RESOURCE_TYPE_KEYBOX)
{
hd->keyblock_cache.state = KEYBLOCK_CACHE_PREPARED;
hd->keyblock_cache.resource = hd->current;
/* The current offset is at the start of the next record. Since
a record is at least 1 byte, we just use offset - 1, which is
within the record. */
hd->keyblock_cache.offset
= keybox_offset (hd->active[hd->current].u.kb) - 1;
memcpy (hd->keyblock_cache.fpr, desc[0].u.fpr, fprlen);
hd->keyblock_cache.fprlen = fprlen;
}
if (gpg_err_code (rc) == GPG_ERR_NOT_FOUND
&& ndesc == 1
&& desc[0].mode == KEYDB_SEARCH_MODE_LONG_KID
&& was_reset
&& !already_in_cache)
kid_not_found_insert (desc[0].u.kid);
if (DBG_CLOCK)
log_clock (rc? "keydb_search leave (not found)"
: "keydb_search leave (found)");
if (!rc)
keydb_stats.found++;
else
keydb_stats.notfound++;
return rc;
}
/* Return the first non-legacy key in the database.
*
* If you want the very first key in the database, you can directly
* call keydb_search with the search description
* KEYDB_SEARCH_MODE_FIRST. */
gpg_error_t
keydb_search_first (KEYDB_HANDLE hd)
{
gpg_error_t err;
KEYDB_SEARCH_DESC desc;
err = keydb_search_reset (hd);
if (err)
return err;
memset (&desc, 0, sizeof desc);
desc.mode = KEYDB_SEARCH_MODE_FIRST;
return keydb_search (hd, &desc, 1, NULL);
}
/* Return the next key (not the next matching key!).
*
* Unlike calling keydb_search with KEYDB_SEARCH_MODE_NEXT, this
* function silently skips legacy keys. */
gpg_error_t
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, NULL);
}
/* This is a convenience function for searching for keys with a long
* key id.
*
* Note: this function resumes searching where the last search left
* off. If you want to search the whole database, then you need to
* first call keydb_search_reset(). */
gpg_error_t
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, NULL);
}
/* This is a convenience function for searching for keys with a long
* (20 byte) fingerprint.
*
* Note: this function resumes searching where the last search left
* off. If you want to search the whole database, then you need to
* first call keydb_search_reset(). */
gpg_error_t
keydb_search_fpr (KEYDB_HANDLE hd, const byte *fpr, size_t fprlen)
{
KEYDB_SEARCH_DESC desc;
memset (&desc, 0, sizeof desc);
desc.mode = KEYDB_SEARCH_MODE_FPR;
memcpy (desc.u.fpr, fpr, fprlen);
desc.fprlen = fprlen;
return keydb_search (hd, &desc, 1, NULL);
}
diff --git a/g10/keydb.h b/g10/keydb.h
index 7cdfe9bbf..6ad8dce4c 100644
--- a/g10/keydb.h
+++ b/g10/keydb.h
@@ -1,548 +1,556 @@
/* keydb.h - Key database
* Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005,
* 2006, 2010 Free Software Foundation, Inc.
* Copyright (C) 2015, 2016 g10 Code GmbH
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#ifndef G10_KEYDB_H
#define G10_KEYDB_H
#include "../common/types.h"
#include "../common/util.h"
#include "packet.h"
/* What qualifies as a certification (key-signature in contrast to a
* data signature)? Note that a back signature is special and can be
* made by key and data signatures capable subkeys.) */
#define IS_CERT(s) (IS_KEY_SIG(s) || IS_UID_SIG(s) || IS_SUBKEY_SIG(s) \
|| IS_KEY_REV(s) || IS_UID_REV(s) || IS_SUBKEY_REV(s))
#define IS_SIG(s) (!IS_CERT(s))
#define IS_KEY_SIG(s) ((s)->sig_class == 0x1f)
#define IS_UID_SIG(s) (((s)->sig_class & ~3) == 0x10)
#define IS_SUBKEY_SIG(s) ((s)->sig_class == 0x18)
#define IS_BACK_SIG(s) ((s)->sig_class == 0x19)
#define IS_KEY_REV(s) ((s)->sig_class == 0x20)
#define IS_UID_REV(s) ((s)->sig_class == 0x30)
#define IS_SUBKEY_REV(s) ((s)->sig_class == 0x28)
struct getkey_ctx_s;
typedef struct getkey_ctx_s *GETKEY_CTX;
typedef struct getkey_ctx_s *getkey_ctx_t;
/****************
* A Keyblock is all packets which form an entire certificate;
* i.e. the public key, certificate, trust packets, user ids,
* signatures, and subkey.
*
* This structure is also used to bind arbitrary packets together.
*/
struct kbnode_struct
{
kbnode_t next;
PACKET *pkt;
int flag; /* Local use during keyblock processing (not cloned).*/
unsigned int tag; /* Ditto. */
int private_flag;
};
#define is_deleted_kbnode(a) ((a)->private_flag & 1)
#define is_cloned_kbnode(a) ((a)->private_flag & 2)
/*
* A structure to store key identification as well as some stuff
* needed for key validation.
*/
struct key_item {
struct key_item *next;
unsigned int ownertrust,min_ownertrust;
byte trust_depth;
byte trust_value;
char *trust_regexp;
u32 kid[2];
};
/* Bit flags used with build_pk_list. */
enum
{
PK_LIST_ENCRYPT_TO = 1, /* This is an encrypt-to recipient. */
PK_LIST_HIDDEN = 2, /* This is a hidden recipient. */
PK_LIST_CONFIG = 4, /* Specified via config file. */
PK_LIST_FROM_FILE = 8 /* Take key from file with that name. */
};
/* To store private data in the flags the private data must be left
* shifted by this value. */
enum
{
PK_LIST_SHIFT = 4
};
/* Structure to hold a couple of public key certificates. */
typedef struct pk_list *PK_LIST; /* Deprecated. */
typedef struct pk_list *pk_list_t;
struct pk_list
{
PK_LIST next;
PKT_public_key *pk;
int flags; /* See PK_LIST_ constants. */
};
/* Structure to hold a list of secret key certificates. */
typedef struct sk_list *SK_LIST;
struct sk_list
{
SK_LIST next;
PKT_public_key *pk;
int mark; /* not used */
};
/* structure to collect all information which can be used to
* identify a public key */
typedef struct pubkey_find_info *PUBKEY_FIND_INFO;
struct pubkey_find_info {
u32 keyid[2];
unsigned nbits;
byte pubkey_algo;
byte fingerprint[MAX_FINGERPRINT_LEN];
char userid[1];
};
/* Helper type for preference functions. */
union pref_hint
{
int digest_length;
};
/* Constants to describe from where a key was fetched or updated. */
enum
{
KEYORG_UNKNOWN = 0,
KEYORG_KS = 1, /* Public keyserver. */
KEYORG_KS_PREF = 2, /* Preferred keysrver. */
KEYORG_DANE = 3, /* OpenPGP DANE. */
KEYORG_WKD = 4, /* Web Key Directory. */
KEYORG_URL = 5, /* Trusted URL. */
KEYORG_FILE = 6, /* Trusted file. */
KEYORG_SELF = 7 /* We generated it. */
};
/*
* Check whether the signature SIG is in the klist K.
*/
static inline struct key_item *
is_in_klist (struct key_item *k, PKT_signature *sig)
{
for (; k; k = k->next)
{
if (k->kid[0] == sig->keyid[0] && k->kid[1] == sig->keyid[1])
return k;
}
return NULL;
}
/*-- keydb.c --*/
#define KEYDB_RESOURCE_FLAG_PRIMARY 2 /* The primary resource. */
#define KEYDB_RESOURCE_FLAG_DEFAULT 4 /* The default one. */
#define KEYDB_RESOURCE_FLAG_READONLY 8 /* Open in read only mode. */
#define KEYDB_RESOURCE_FLAG_GPGVDEF 16 /* Default file for gpgv. */
/* Format a search term for debugging output. The caller must free
the result. */
char *keydb_search_desc_dump (struct keydb_search_desc *desc);
/* Register a resource (keyring or keybox). */
gpg_error_t keydb_add_resource (const char *url, unsigned int flags);
/* Dump some statistics to the log. */
void keydb_dump_stats (void);
/* Create a new database handle. Returns NULL on error, sets ERRNO,
and prints an error diagnostic. */
KEYDB_HANDLE keydb_new (void);
/* Free all resources owned by the database handle. */
void keydb_release (KEYDB_HANDLE hd);
/* Take a lock on the files immediately and not only during insert or
* update. This lock is released with keydb_release. */
gpg_error_t keydb_lock (KEYDB_HANDLE hd);
/* Set a flag on the handle to suppress use of cached results. This
is required for updating a keyring and for key listings. Fixme:
Using a new parameter for keydb_new might be a better solution. */
void keydb_disable_caching (KEYDB_HANDLE hd);
/* Save the last found state and invalidate the current selection. */
void keydb_push_found_state (KEYDB_HANDLE hd);
/* Restore the previous save state. */
void keydb_pop_found_state (KEYDB_HANDLE hd);
/* Return the file name of the resource. */
const char *keydb_get_resource_name (KEYDB_HANDLE hd);
/* Return the keyblock last found by keydb_search. */
gpg_error_t keydb_get_keyblock (KEYDB_HANDLE hd, KBNODE *ret_kb);
/* Update the keyblock KB. */
gpg_error_t keydb_update_keyblock (ctrl_t ctrl, KEYDB_HANDLE hd, kbnode_t kb);
/* Insert a keyblock into one of the underlying keyrings or keyboxes. */
gpg_error_t keydb_insert_keyblock (KEYDB_HANDLE hd, kbnode_t kb);
/* Delete the currently selected keyblock. */
gpg_error_t keydb_delete_keyblock (KEYDB_HANDLE hd);
/* Find the first writable resource. */
gpg_error_t keydb_locate_writable (KEYDB_HANDLE hd);
/* Rebuild the on-disk caches of all key resources. */
void keydb_rebuild_caches (ctrl_t ctrl, int noisy);
/* Return the number of skipped blocks (because they were to large to
read from a keybox) since the last search reset. */
unsigned long keydb_get_skipped_counter (KEYDB_HANDLE hd);
/* Clears the current search result and resets the handle's position. */
gpg_error_t keydb_search_reset (KEYDB_HANDLE hd);
/* Search the database for keys matching the search description. */
gpg_error_t keydb_search (KEYDB_HANDLE hd, KEYDB_SEARCH_DESC *desc,
size_t ndesc, size_t *descindex);
/* Return the first non-legacy key in the database. */
gpg_error_t keydb_search_first (KEYDB_HANDLE hd);
/* Return the next key (not the next matching key!). */
gpg_error_t keydb_search_next (KEYDB_HANDLE hd);
/* This is a convenience function for searching for keys with a long
key id. */
gpg_error_t keydb_search_kid (KEYDB_HANDLE hd, u32 *kid);
/* This is a convenience function for searching for keys by
* fingerprint. */
gpg_error_t keydb_search_fpr (KEYDB_HANDLE hd, const byte *fpr, size_t fprlen);
/*-- pkclist.c --*/
void show_revocation_reason (ctrl_t ctrl, PKT_public_key *pk, int mode );
int check_signatures_trust (ctrl_t ctrl, PKT_signature *sig);
void release_pk_list (PK_LIST pk_list);
int build_pk_list (ctrl_t ctrl, strlist_t rcpts, PK_LIST *ret_pk_list);
gpg_error_t find_and_check_key (ctrl_t ctrl,
const char *name, unsigned int use,
int mark_hidden, int from_file,
pk_list_t *pk_list_addr);
int algo_available( preftype_t preftype, int algo,
const union pref_hint *hint );
int select_algo_from_prefs( PK_LIST pk_list, int preftype,
int request, const union pref_hint *hint);
int select_mdc_from_pklist (PK_LIST pk_list);
aead_algo_t select_aead_from_pklist (pk_list_t pk_list);
void warn_missing_aead_from_pklist (PK_LIST pk_list);
void warn_missing_aes_from_pklist (PK_LIST pk_list);
/*-- skclist.c --*/
int random_is_faked (void);
void release_sk_list( SK_LIST sk_list );
gpg_error_t build_sk_list (ctrl_t ctrl, strlist_t locusr,
SK_LIST *ret_sk_list, unsigned use);
/*-- passphrase.h --*/
int have_static_passphrase(void);
const char *get_static_passphrase (void);
void set_passphrase_from_string(const char *pass);
void read_passphrase_from_fd( int fd );
void passphrase_clear_cache (const char *cacheid);
DEK *passphrase_to_dek_ext(u32 *keyid, int pubkey_algo,
int cipher_algo, STRING2KEY *s2k, int mode,
const char *tryagain_text,
const char *custdesc, const char *custprompt,
int *canceled);
DEK *passphrase_to_dek (int cipher_algo, STRING2KEY *s2k,
int create, int nocache,
const char *tryagain_text, int *canceled);
void set_next_passphrase( const char *s );
char *get_last_passphrase(void);
void next_to_last_passphrase(void);
void emit_status_need_passphrase (ctrl_t ctrl, u32 *keyid,
u32 *mainkeyid, int pubkey_algo);
#define FORMAT_KEYDESC_NORMAL 0
#define FORMAT_KEYDESC_IMPORT 1
#define FORMAT_KEYDESC_EXPORT 2
#define FORMAT_KEYDESC_DELKEY 3
char *gpg_format_keydesc (ctrl_t ctrl,
PKT_public_key *pk, int mode, int escaped);
/*-- getkey.c --*/
/* Cache a copy of a public key in the public key cache. */
void cache_public_key( PKT_public_key *pk );
/* Disable and drop the public key cache. */
void getkey_disable_caches(void);
/* Return the public key used for signature SIG and store it at PK. */
gpg_error_t get_pubkey_for_sig (ctrl_t ctrl,
PKT_public_key *pk, PKT_signature *sig);
/* Return the public key with the key id KEYID and store it at PK. */
int get_pubkey (ctrl_t ctrl, PKT_public_key *pk, u32 *keyid);
/* Similar to get_pubkey, but it does not take PK->REQ_USAGE into
account nor does it merge in the self-signed data. This function
also only considers primary keys. */
int get_pubkey_fast (PKT_public_key *pk, u32 *keyid);
/* Return the entire keyblock used to create SIG. This is a
* specialized version of get_pubkeyblock. */
kbnode_t get_pubkeyblock_for_sig (ctrl_t ctrl, PKT_signature *sig);
/* Return the key block for the key with KEYID. */
kbnode_t get_pubkeyblock (ctrl_t ctrl, u32 *keyid);
/* A list used by get_pubkeys to gather all of the matches. */
struct pubkey_s
{
struct pubkey_s *next;
/* The key to use (either the public key or the subkey). */
PKT_public_key *pk;
kbnode_t keyblock;
};
typedef struct pubkey_s *pubkey_t;
/* Free a list of public keys. */
void pubkeys_free (pubkey_t keys);
+
+/* Mode flags for get_pubkey_byname. */
+enum get_pubkey_modes
+ {
+ GET_PUBKEY_NORMAL = 0,
+ GET_PUBKEY_NO_AKL = 1,
+ GET_PUBKEY_NO_LOCAL = 2
+ };
+
/* Find a public key identified by NAME. */
-int get_pubkey_byname (ctrl_t ctrl,
+int get_pubkey_byname (ctrl_t ctrl, enum get_pubkey_modes mode,
GETKEY_CTX *retctx, PKT_public_key *pk,
const char *name,
KBNODE *ret_keyblock, KEYDB_HANDLE *ret_kdbhd,
- int include_unusable, int no_akl );
+ int include_unusable);
/* Likewise, but only return the best match if NAME resembles a mail
* address. */
-gpg_error_t get_best_pubkey_byname (ctrl_t ctrl,
+gpg_error_t get_best_pubkey_byname (ctrl_t ctrl, enum get_pubkey_modes mode,
GETKEY_CTX *retctx, PKT_public_key *pk,
const char *name, KBNODE *ret_keyblock,
int include_unusable);
/* Get a public key directly from file FNAME. */
gpg_error_t get_pubkey_fromfile (ctrl_t ctrl,
PKT_public_key *pk, const char *fname);
/* Return the public key with the key id KEYID iff the secret key is
* available and store it at PK. */
gpg_error_t get_seckey (ctrl_t ctrl, PKT_public_key *pk, u32 *keyid);
/* Lookup a key with the specified fingerprint. */
int get_pubkey_byfprint (ctrl_t ctrl, PKT_public_key *pk, kbnode_t *r_keyblock,
const byte *fprint, size_t fprint_len);
/* This function is similar to get_pubkey_byfprint, but it doesn't
merge the self-signed data into the public key and subkeys or into
the user ids. */
gpg_error_t get_pubkey_byfprint_fast (PKT_public_key *pk,
const byte *fprint, size_t fprint_len);
/* This function is similar to get_pubkey_byfprint, but it doesn't
merge the self-signed data into the public key and subkeys or into
the user ids. */
gpg_error_t get_keyblock_byfprint_fast (kbnode_t *r_keyblock,
KEYDB_HANDLE *r_hd,
const byte *fprint, size_t fprint_len,
int lock);
/* Returns true if a secret key is available for the public key with
key id KEYID. */
int have_secret_key_with_kid (u32 *keyid);
/* Parse the --default-key parameter. Returns the last key (in terms
of when the option is given) that is available. */
const char *parse_def_secret_key (ctrl_t ctrl);
/* Look up a secret key. */
gpg_error_t get_seckey_default (ctrl_t ctrl, PKT_public_key *pk);
gpg_error_t get_seckey_default_or_card (ctrl_t ctrl, PKT_public_key *pk,
const byte *fpr, size_t fpr_len);
/* Search for keys matching some criteria. */
gpg_error_t getkey_bynames (ctrl_t ctrl,
getkey_ctx_t *retctx, PKT_public_key *pk,
strlist_t names, int want_secret,
kbnode_t *ret_keyblock);
/* Search for one key matching some criteria. */
gpg_error_t getkey_byname (ctrl_t ctrl,
getkey_ctx_t *retctx, PKT_public_key *pk,
const char *name, int want_secret,
kbnode_t *ret_keyblock);
/* Return the next search result. */
gpg_error_t getkey_next (ctrl_t ctrl, getkey_ctx_t ctx,
PKT_public_key *pk, kbnode_t *ret_keyblock);
/* Release any resources used by a key listing context. */
void getkey_end (ctrl_t ctrl, getkey_ctx_t ctx);
/* Return the database handle used by this context. The context still
owns the handle. */
KEYDB_HANDLE get_ctx_handle(GETKEY_CTX ctx);
/* Enumerate some secret keys. */
gpg_error_t enum_secret_keys (ctrl_t ctrl, void **context, PKT_public_key *pk);
/* Set the mainkey_id fields for all keys in KEYBLOCK. */
void setup_main_keyids (kbnode_t keyblock);
/* This function merges information from the self-signed data into the
data structures. */
void merge_keys_and_selfsig (ctrl_t ctrl, kbnode_t keyblock);
char *get_user_id_string_native (ctrl_t ctrl, u32 *keyid);
char *get_long_user_id_string (ctrl_t ctrl, u32 *keyid);
char *get_user_id (ctrl_t ctrl, u32 *keyid, size_t *rn, int *r_nouid);
char *get_user_id_native (ctrl_t ctrl, u32 *keyid);
-char *get_user_id_byfpr (ctrl_t ctrl, const byte *fpr, size_t *rn);
-char *get_user_id_byfpr_native (ctrl_t ctrl, const byte *fpr);
+char *get_user_id_byfpr_native (ctrl_t ctrl, const byte *fpr, size_t fprlen);
void release_akl(void);
int parse_auto_key_locate(const char *options);
int parse_key_origin (char *string);
const char *key_origin_string (int origin);
/*-- keyid.c --*/
int pubkey_letter( int algo );
char *pubkey_string (PKT_public_key *pk, char *buffer, size_t bufsize);
#define PUBKEY_STRING_SIZE 32
u32 v3_keyid (gcry_mpi_t a, u32 *ki);
void hash_public_key( gcry_md_hd_t md, PKT_public_key *pk );
char *format_keyid (u32 *keyid, int format, char *buffer, int len);
/* Return PK's keyid. The memory is owned by PK. */
u32 *pk_keyid (PKT_public_key *pk);
/* Return the keyid of the primary key associated with PK. The memory
is owned by PK. */
u32 *pk_main_keyid (PKT_public_key *pk);
/* Order A and B. If A < B then return -1, if A == B then return 0,
and if A > B then return 1. */
static int GPGRT_ATTR_UNUSED
keyid_cmp (const u32 *a, const u32 *b)
{
if (a[0] < b[0])
return -1;
if (a[0] > b[0])
return 1;
if (a[1] < b[1])
return -1;
if (a[1] > b[1])
return 1;
return 0;
}
/* Return whether PK is a primary key. */
static int GPGRT_ATTR_UNUSED
pk_is_primary (PKT_public_key *pk)
{
return keyid_cmp (pk_keyid (pk), pk_main_keyid (pk)) == 0;
}
/* Copy the keyid in SRC to DEST and return DEST. */
u32 *keyid_copy (u32 *dest, const u32 *src);
size_t keystrlen(void);
const char *keystr(u32 *keyid);
const char *keystr_with_sub (u32 *main_kid, u32 *sub_kid);
const char *keystr_from_pk(PKT_public_key *pk);
const char *keystr_from_pk_with_sub (PKT_public_key *main_pk,
PKT_public_key *sub_pk);
/* Return PK's key id as a string using the default format. PK owns
the storage. */
const char *pk_keyid_str (PKT_public_key *pk);
const char *keystr_from_desc(KEYDB_SEARCH_DESC *desc);
u32 keyid_from_pk( PKT_public_key *pk, u32 *keyid );
u32 keyid_from_sig (PKT_signature *sig, u32 *keyid );
u32 keyid_from_fingerprint (ctrl_t ctrl, const byte *fprint, size_t fprint_len,
u32 *keyid);
byte *namehash_from_uid(PKT_user_id *uid);
unsigned nbits_from_pk( PKT_public_key *pk );
/* Convert an UTC TIMESTAMP into an UTC yyyy-mm-dd string. Return
* that string. The caller should pass a buffer with at least a size
* of MK_DATESTR_SIZE. */
char *mk_datestr (char *buffer, size_t bufsize, u32 timestamp);
#define MK_DATESTR_SIZE 11
const char *datestr_from_pk( PKT_public_key *pk );
const char *datestr_from_sig( PKT_signature *sig );
const char *expirestr_from_pk( PKT_public_key *pk );
const char *expirestr_from_sig( PKT_signature *sig );
const char *revokestr_from_pk( PKT_public_key *pk );
const char *usagestr_from_pk (PKT_public_key *pk, int fill);
const char *colon_strtime (u32 t);
const char *colon_datestr_from_pk (PKT_public_key *pk);
const char *colon_datestr_from_sig (PKT_signature *sig);
const char *colon_expirestr_from_sig (PKT_signature *sig);
byte *fingerprint_from_pk( PKT_public_key *pk, byte *buf, size_t *ret_len );
char *hexfingerprint (PKT_public_key *pk, char *buffer, size_t buflen);
char *format_hexfingerprint (const char *fingerprint,
char *buffer, size_t buflen);
gpg_error_t keygrip_from_pk (PKT_public_key *pk, unsigned char *array);
gpg_error_t hexkeygrip_from_pk (PKT_public_key *pk, char **r_grip);
/*-- kbnode.c --*/
KBNODE new_kbnode( PACKET *pkt );
KBNODE clone_kbnode( KBNODE node );
void release_kbnode( KBNODE n );
void delete_kbnode( KBNODE node );
void add_kbnode( KBNODE root, KBNODE node );
void insert_kbnode( KBNODE root, KBNODE node, int pkttype );
void move_kbnode( KBNODE *root, KBNODE node, KBNODE where );
void remove_kbnode( KBNODE *root, KBNODE node );
KBNODE find_prev_kbnode( KBNODE root, KBNODE node, int pkttype );
KBNODE find_next_kbnode( KBNODE node, int pkttype );
KBNODE find_kbnode( KBNODE node, int pkttype );
KBNODE walk_kbnode( KBNODE root, KBNODE *context, int all );
void clear_kbnode_flags( KBNODE n );
int commit_kbnode( KBNODE *root );
void dump_kbnode( KBNODE node );
#endif /*G10_KEYDB_H*/
diff --git a/g10/keyedit.c b/g10/keyedit.c
index c28a565b1..1bf5de9b2 100644
--- a/g10/keyedit.c
+++ b/g10/keyedit.c
@@ -1,6273 +1,6277 @@
/* keyedit.c - Edit properties of a key
* Copyright (C) 1998-2010 Free Software Foundation, Inc.
* Copyright (C) 1998-2017 Werner Koch
* Copyright (C) 2015, 2016 g10 Code GmbH
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#ifdef HAVE_LIBREADLINE
# define GNUPG_LIBREADLINE_H_INCLUDED
# include <readline/readline.h>
#endif
#include "gpg.h"
#include "options.h"
#include "packet.h"
#include "../common/status.h"
#include "../common/iobuf.h"
#include "keydb.h"
#include "photoid.h"
#include "../common/util.h"
#include "main.h"
#include "trustdb.h"
#include "filter.h"
#include "../common/ttyio.h"
#include "../common/status.h"
#include "../common/i18n.h"
#include "keyserver-internal.h"
#include "call-agent.h"
#include "../common/host2net.h"
#include "tofu.h"
#include "key-check.h"
#include "key-clean.h"
#include "keyedit.h"
static void show_prefs (PKT_user_id * uid, PKT_signature * selfsig,
int verbose);
static void show_names (ctrl_t ctrl, estream_t fp,
kbnode_t keyblock, PKT_public_key * pk,
unsigned int flag, int with_prefs);
static void show_key_with_all_names (ctrl_t ctrl, estream_t fp,
KBNODE keyblock, int only_marked,
int with_revoker, int with_fpr,
int with_subkeys, int with_prefs,
int nowarn);
static void show_key_and_fingerprint (ctrl_t ctrl,
kbnode_t keyblock, int with_subkeys);
static void show_key_and_grip (kbnode_t keyblock);
static void subkey_expire_warning (kbnode_t keyblock);
static int menu_adduid (ctrl_t ctrl, kbnode_t keyblock,
int photo, const char *photo_name, const char *uidstr);
static void menu_deluid (KBNODE pub_keyblock);
static int menu_delsig (ctrl_t ctrl, kbnode_t pub_keyblock);
static int menu_clean (ctrl_t ctrl, kbnode_t keyblock, int self_only);
static void menu_delkey (KBNODE pub_keyblock);
static int menu_addrevoker (ctrl_t ctrl, kbnode_t pub_keyblock, int sensitive);
static gpg_error_t menu_expire (ctrl_t ctrl, kbnode_t pub_keyblock,
int unattended, u32 newexpiration);
static int menu_changeusage (ctrl_t ctrl, kbnode_t keyblock);
static int menu_backsign (ctrl_t ctrl, kbnode_t pub_keyblock);
static int menu_set_primary_uid (ctrl_t ctrl, kbnode_t pub_keyblock);
static int menu_set_preferences (ctrl_t ctrl, kbnode_t pub_keyblock);
static int menu_set_keyserver_url (ctrl_t ctrl,
const char *url, kbnode_t pub_keyblock);
static int menu_set_notation (ctrl_t ctrl,
const char *string, kbnode_t pub_keyblock);
static int menu_select_uid (KBNODE keyblock, int idx);
static int menu_select_uid_namehash (KBNODE keyblock, const char *namehash);
static int menu_select_key (KBNODE keyblock, int idx, char *p);
static int count_uids (KBNODE keyblock);
static int count_uids_with_flag (KBNODE keyblock, unsigned flag);
static int count_keys_with_flag (KBNODE keyblock, unsigned flag);
static int count_selected_uids (KBNODE keyblock);
static int real_uids_left (KBNODE keyblock);
static int count_selected_keys (KBNODE keyblock);
static int menu_revsig (ctrl_t ctrl, kbnode_t keyblock);
static int menu_revuid (ctrl_t ctrl, kbnode_t keyblock);
static int core_revuid (ctrl_t ctrl, kbnode_t keyblock, KBNODE node,
const struct revocation_reason_info *reason,
int *modified);
static int menu_revkey (ctrl_t ctrl, kbnode_t pub_keyblock);
static int menu_revsubkey (ctrl_t ctrl, kbnode_t pub_keyblock);
#ifndef NO_TRUST_MODELS
static int enable_disable_key (ctrl_t ctrl, kbnode_t keyblock, int disable);
#endif /*!NO_TRUST_MODELS*/
static void menu_showphoto (ctrl_t ctrl, kbnode_t keyblock);
static int update_trust = 0;
#define CONTROL_D ('D' - 'A' + 1)
struct sign_attrib
{
int non_exportable, non_revocable;
struct revocation_reason_info *reason;
byte trust_depth, trust_value;
char *trust_regexp;
};
/* TODO: Fix duplicated code between here and the check-sigs/list-sigs
code in keylist.c. */
static int
print_and_check_one_sig_colon (ctrl_t ctrl, kbnode_t keyblock, kbnode_t node,
int *inv_sigs, int *no_key, int *oth_err,
int *is_selfsig, int print_without_key)
{
PKT_signature *sig = node->pkt->pkt.signature;
int rc, sigrc;
/* TODO: Make sure a cached sig record here still has the pk that
issued it. See also keylist.c:list_keyblock_print */
rc = check_key_signature (ctrl, keyblock, node, is_selfsig);
switch (gpg_err_code (rc))
{
case 0:
node->flag &= ~(NODFLG_BADSIG | NODFLG_NOKEY | NODFLG_SIGERR);
sigrc = '!';
break;
case GPG_ERR_BAD_SIGNATURE:
node->flag = NODFLG_BADSIG;
sigrc = '-';
if (inv_sigs)
++ * inv_sigs;
break;
case GPG_ERR_NO_PUBKEY:
case GPG_ERR_UNUSABLE_PUBKEY:
node->flag = NODFLG_NOKEY;
sigrc = '?';
if (no_key)
++ * no_key;
break;
default:
node->flag = NODFLG_SIGERR;
sigrc = '%';
if (oth_err)
++ * oth_err;
break;
}
if (sigrc != '?' || print_without_key)
{
es_printf ("sig:%c::%d:%08lX%08lX:%lu:%lu:",
sigrc, sig->pubkey_algo, (ulong) sig->keyid[0],
(ulong) sig->keyid[1], (ulong) sig->timestamp,
(ulong) sig->expiredate);
if (sig->trust_depth || sig->trust_value)
es_printf ("%d %d", sig->trust_depth, sig->trust_value);
es_printf (":");
if (sig->trust_regexp)
es_write_sanitized (es_stdout,
sig->trust_regexp, strlen (sig->trust_regexp),
":", NULL);
es_printf ("::%02x%c\n", sig->sig_class,
sig->flags.exportable ? 'x' : 'l');
if (opt.show_subpackets)
print_subpackets_colon (sig);
}
return (sigrc == '!');
}
/*
* Print information about a signature (rc is its status), check it
* and return true if the signature is okay. NODE must be a signature
* packet. With EXTENDED set all possible signature list options will
* always be printed.
*/
int
keyedit_print_one_sig (ctrl_t ctrl, estream_t fp,
int rc, kbnode_t keyblock, kbnode_t node,
int *inv_sigs, int *no_key, int *oth_err,
int is_selfsig, int print_without_key, int extended)
{
PKT_signature *sig = node->pkt->pkt.signature;
int sigrc;
int is_rev = sig->sig_class == 0x30;
/* TODO: Make sure a cached sig record here still has the pk that
issued it. See also keylist.c:list_keyblock_print */
switch (gpg_err_code (rc))
{
case 0:
node->flag &= ~(NODFLG_BADSIG | NODFLG_NOKEY | NODFLG_SIGERR);
sigrc = '!';
break;
case GPG_ERR_BAD_SIGNATURE:
node->flag = NODFLG_BADSIG;
sigrc = '-';
if (inv_sigs)
++ * inv_sigs;
break;
case GPG_ERR_NO_PUBKEY:
case GPG_ERR_UNUSABLE_PUBKEY:
node->flag = NODFLG_NOKEY;
sigrc = '?';
if (no_key)
++ * no_key;
break;
default:
node->flag = NODFLG_SIGERR;
sigrc = '%';
if (oth_err)
++ * oth_err;
break;
}
if (sigrc != '?' || print_without_key)
{
tty_fprintf (fp, "%s%c%c %c%c%c%c%c%c %s %s",
is_rev ? "rev" : "sig", sigrc,
(sig->sig_class - 0x10 > 0 &&
sig->sig_class - 0x10 <
4) ? '0' + sig->sig_class - 0x10 : ' ',
sig->flags.exportable ? ' ' : 'L',
sig->flags.revocable ? ' ' : 'R',
sig->flags.policy_url ? 'P' : ' ',
sig->flags.notation ? 'N' : ' ',
sig->flags.expired ? 'X' : ' ',
(sig->trust_depth > 9) ? 'T' : (sig->trust_depth >
0) ? '0' +
sig->trust_depth : ' ',
keystr (sig->keyid),
datestr_from_sig (sig));
if ((opt.list_options & LIST_SHOW_SIG_EXPIRE) || extended )
tty_fprintf (fp, " %s", expirestr_from_sig (sig));
tty_fprintf (fp, " ");
if (sigrc == '%')
tty_fprintf (fp, "[%s] ", gpg_strerror (rc));
else if (sigrc == '?')
;
else if (is_selfsig)
{
tty_fprintf (fp, is_rev ? _("[revocation]") : _("[self-signature]"));
if (extended && sig->flags.chosen_selfsig)
tty_fprintf (fp, "*");
}
else
{
size_t n;
char *p = get_user_id (ctrl, sig->keyid, &n, NULL);
tty_print_utf8_string2 (fp, p, n,
opt.screen_columns - keystrlen () - 26 -
((opt.
list_options & LIST_SHOW_SIG_EXPIRE) ? 11
: 0));
xfree (p);
}
if (fp == log_get_stream ())
log_printf ("\n");
else
tty_fprintf (fp, "\n");
if (sig->flags.policy_url
&& ((opt.list_options & LIST_SHOW_POLICY_URLS) || extended))
show_policy_url (sig, 3, (!fp? -1 : fp == log_get_stream ()? 1 : 0));
if (sig->flags.notation
&& ((opt.list_options & LIST_SHOW_NOTATIONS) || extended))
show_notation (sig, 3, (!fp? -1 : fp == log_get_stream ()? 1 : 0),
((opt.
list_options & LIST_SHOW_STD_NOTATIONS) ? 1 : 0) +
((opt.
list_options & LIST_SHOW_USER_NOTATIONS) ? 2 : 0));
if (sig->flags.pref_ks
&& ((opt.list_options & LIST_SHOW_KEYSERVER_URLS) || extended))
show_keyserver_url (sig, 3, (!fp? -1 : fp == log_get_stream ()? 1 : 0));
if (extended)
{
PKT_public_key *pk = keyblock->pkt->pkt.public_key;
const unsigned char *s;
s = parse_sig_subpkt (sig->hashed, SIGSUBPKT_PRIMARY_UID, NULL);
if (s && *s)
tty_fprintf (fp, " [primary]\n");
s = parse_sig_subpkt (sig->hashed, SIGSUBPKT_KEY_EXPIRE, NULL);
if (s && buf32_to_u32 (s))
tty_fprintf (fp, " [expires: %s]\n",
isotimestamp (pk->timestamp + buf32_to_u32 (s)));
}
}
return (sigrc == '!');
}
static int
print_and_check_one_sig (ctrl_t ctrl, kbnode_t keyblock, kbnode_t node,
int *inv_sigs, int *no_key, int *oth_err,
int *is_selfsig, int print_without_key, int extended)
{
int rc;
rc = check_key_signature (ctrl, keyblock, node, is_selfsig);
return keyedit_print_one_sig (ctrl, NULL, rc,
keyblock, node, inv_sigs, no_key, oth_err,
*is_selfsig, print_without_key, extended);
}
static int
sign_mk_attrib (PKT_signature * sig, void *opaque)
{
struct sign_attrib *attrib = opaque;
byte buf[8];
if (attrib->non_exportable)
{
buf[0] = 0; /* not exportable */
build_sig_subpkt (sig, SIGSUBPKT_EXPORTABLE, buf, 1);
}
if (attrib->non_revocable)
{
buf[0] = 0; /* not revocable */
build_sig_subpkt (sig, SIGSUBPKT_REVOCABLE, buf, 1);
}
if (attrib->reason)
revocation_reason_build_cb (sig, attrib->reason);
if (attrib->trust_depth)
{
/* Not critical. If someone doesn't understand trust sigs,
this can still be a valid regular signature. */
buf[0] = attrib->trust_depth;
buf[1] = attrib->trust_value;
build_sig_subpkt (sig, SIGSUBPKT_TRUST, buf, 2);
/* Critical. If someone doesn't understands regexps, this
whole sig should be invalid. Note the +1 for the length -
regexps are null terminated. */
if (attrib->trust_regexp)
build_sig_subpkt (sig, SIGSUBPKT_FLAG_CRITICAL | SIGSUBPKT_REGEXP,
attrib->trust_regexp,
strlen (attrib->trust_regexp) + 1);
}
return 0;
}
static void
trustsig_prompt (byte * trust_value, byte * trust_depth, char **regexp)
{
char *p;
*trust_value = 0;
*trust_depth = 0;
*regexp = NULL;
/* Same string as pkclist.c:do_edit_ownertrust */
tty_printf (_
("Please decide how far you trust this user to correctly verify"
" other users' keys\n(by looking at passports, checking"
" fingerprints from different sources, etc.)\n"));
tty_printf ("\n");
tty_printf (_(" %d = I trust marginally\n"), 1);
tty_printf (_(" %d = I trust fully\n"), 2);
tty_printf ("\n");
while (*trust_value == 0)
{
p = cpr_get ("trustsig_prompt.trust_value", _("Your selection? "));
trim_spaces (p);
cpr_kill_prompt ();
/* 60 and 120 are as per RFC2440 */
if (p[0] == '1' && !p[1])
*trust_value = 60;
else if (p[0] == '2' && !p[1])
*trust_value = 120;
xfree (p);
}
tty_printf ("\n");
tty_printf (_("Please enter the depth of this trust signature.\n"
"A depth greater than 1 allows the key you are"
" signing to make\n"
"trust signatures on your behalf.\n"));
tty_printf ("\n");
while (*trust_depth == 0)
{
p = cpr_get ("trustsig_prompt.trust_depth", _("Your selection? "));
trim_spaces (p);
cpr_kill_prompt ();
*trust_depth = atoi (p);
xfree (p);
}
tty_printf ("\n");
tty_printf (_("Please enter a domain to restrict this signature, "
"or enter for none.\n"));
tty_printf ("\n");
p = cpr_get ("trustsig_prompt.trust_regexp", _("Your selection? "));
trim_spaces (p);
cpr_kill_prompt ();
if (strlen (p) > 0)
{
char *q = p;
int regexplen = 100, ind;
*regexp = xmalloc (regexplen);
/* Now mangle the domain the user entered into a regexp. To do
this, \-escape everything that isn't alphanumeric, and attach
"<[^>]+[@.]" to the front, and ">$" to the end. */
strcpy (*regexp, "<[^>]+[@.]");
ind = strlen (*regexp);
while (*q)
{
if (!((*q >= 'A' && *q <= 'Z')
|| (*q >= 'a' && *q <= 'z') || (*q >= '0' && *q <= '9')))
(*regexp)[ind++] = '\\';
(*regexp)[ind++] = *q;
if ((regexplen - ind) < 3)
{
regexplen += 100;
*regexp = xrealloc (*regexp, regexplen);
}
q++;
}
(*regexp)[ind] = '\0';
strcat (*regexp, ">$");
}
xfree (p);
tty_printf ("\n");
}
/*
* Loop over all LOCUSR and sign the uids after asking. If no
* user id is marked, all user ids will be signed; if some user_ids
* are marked only those will be signed. If QUICK is true the
* function won't ask the user and use sensible defaults.
*/
static int
sign_uids (ctrl_t ctrl, estream_t fp,
kbnode_t keyblock, strlist_t locusr, int *ret_modified,
int local, int nonrevocable, int trust, int interactive,
int quick)
{
int rc = 0;
SK_LIST sk_list = NULL;
SK_LIST sk_rover = NULL;
PKT_public_key *pk = NULL;
KBNODE node, uidnode;
PKT_public_key *primary_pk = NULL;
int select_all = !count_selected_uids (keyblock) || interactive;
/* Build a list of all signators.
*
* We use the CERT flag to request the primary which must always
* be one which is capable of signing keys. I can't see a reason
* why to sign keys using a subkey. Implementation of USAGE_CERT
* is just a hack in getkey.c and does not mean that a subkey
* marked as certification capable will be used. */
rc = build_sk_list (ctrl, locusr, &sk_list, PUBKEY_USAGE_CERT);
if (rc)
goto leave;
/* Loop over all signators. */
for (sk_rover = sk_list; sk_rover; sk_rover = sk_rover->next)
{
u32 sk_keyid[2], pk_keyid[2];
char *p, *trust_regexp = NULL;
int class = 0, selfsig = 0;
u32 duration = 0, timestamp = 0;
byte trust_depth = 0, trust_value = 0;
pk = sk_rover->pk;
keyid_from_pk (pk, sk_keyid);
/* Set mark A for all selected user ids. */
for (node = keyblock; node; node = node->next)
{
if (select_all || (node->flag & NODFLG_SELUID))
node->flag |= NODFLG_MARK_A;
else
node->flag &= ~NODFLG_MARK_A;
}
/* Reset mark for uids which are already signed. */
uidnode = NULL;
for (node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_KEY)
{
primary_pk = node->pkt->pkt.public_key;
keyid_from_pk (primary_pk, pk_keyid);
/* Is this a self-sig? */
if (pk_keyid[0] == sk_keyid[0] && pk_keyid[1] == sk_keyid[1])
selfsig = 1;
}
else if (node->pkt->pkttype == PKT_USER_ID)
{
uidnode = (node->flag & NODFLG_MARK_A) ? node : NULL;
if (uidnode)
{
int yesreally = 0;
char *user;
user = utf8_to_native (uidnode->pkt->pkt.user_id->name,
uidnode->pkt->pkt.user_id->len, 0);
if (opt.only_sign_text_ids
&& uidnode->pkt->pkt.user_id->attribs)
{
tty_fprintf (fp, _("Skipping user ID \"%s\","
" which is not a text ID.\n"),
user);
uidnode->flag &= ~NODFLG_MARK_A;
uidnode = NULL;
}
else if (uidnode->pkt->pkt.user_id->flags.revoked)
{
tty_fprintf (fp, _("User ID \"%s\" is revoked."), user);
if (selfsig)
tty_fprintf (fp, "\n");
else if (opt.expert && !quick)
{
tty_fprintf (fp, "\n");
/* No, so remove the mark and continue */
if (!cpr_get_answer_is_yes ("sign_uid.revoke_okay",
_("Are you sure you "
"still want to sign "
"it? (y/N) ")))
{
uidnode->flag &= ~NODFLG_MARK_A;
uidnode = NULL;
}
else if (interactive)
yesreally = 1;
}
else
{
uidnode->flag &= ~NODFLG_MARK_A;
uidnode = NULL;
tty_fprintf (fp, _(" Unable to sign.\n"));
}
}
else if (uidnode->pkt->pkt.user_id->flags.expired)
{
tty_fprintf (fp, _("User ID \"%s\" is expired."), user);
if (selfsig)
tty_fprintf (fp, "\n");
else if (opt.expert && !quick)
{
tty_fprintf (fp, "\n");
/* No, so remove the mark and continue */
if (!cpr_get_answer_is_yes ("sign_uid.expire_okay",
_("Are you sure you "
"still want to sign "
"it? (y/N) ")))
{
uidnode->flag &= ~NODFLG_MARK_A;
uidnode = NULL;
}
else if (interactive)
yesreally = 1;
}
else
{
uidnode->flag &= ~NODFLG_MARK_A;
uidnode = NULL;
tty_fprintf (fp, _(" Unable to sign.\n"));
}
}
else if (!uidnode->pkt->pkt.user_id->created && !selfsig)
{
tty_fprintf (fp, _("User ID \"%s\" is not self-signed."),
user);
if (opt.expert && !quick)
{
tty_fprintf (fp, "\n");
/* No, so remove the mark and continue */
if (!cpr_get_answer_is_yes ("sign_uid.nosig_okay",
_("Are you sure you "
"still want to sign "
"it? (y/N) ")))
{
uidnode->flag &= ~NODFLG_MARK_A;
uidnode = NULL;
}
else if (interactive)
yesreally = 1;
}
else
{
uidnode->flag &= ~NODFLG_MARK_A;
uidnode = NULL;
tty_fprintf (fp, _(" Unable to sign.\n"));
}
}
if (uidnode && interactive && !yesreally && !quick)
{
tty_fprintf (fp,
_("User ID \"%s\" is signable. "), user);
if (!cpr_get_answer_is_yes ("sign_uid.sign_okay",
_("Sign it? (y/N) ")))
{
uidnode->flag &= ~NODFLG_MARK_A;
uidnode = NULL;
}
}
xfree (user);
}
}
else if (uidnode && node->pkt->pkttype == PKT_SIGNATURE
&& (node->pkt->pkt.signature->sig_class & ~3) == 0x10)
{
if (sk_keyid[0] == node->pkt->pkt.signature->keyid[0]
&& sk_keyid[1] == node->pkt->pkt.signature->keyid[1])
{
char buf[50];
char *user;
user = utf8_to_native (uidnode->pkt->pkt.user_id->name,
uidnode->pkt->pkt.user_id->len, 0);
/* It's a v3 self-sig. Make it into a v4 self-sig? */
if (node->pkt->pkt.signature->version < 4
&& selfsig && !quick)
{
tty_fprintf (fp,
_("The self-signature on \"%s\"\n"
"is a PGP 2.x-style signature.\n"), user);
/* Note that the regular PGP2 warning below
still applies if there are no v4 sigs on
this key at all. */
if (opt.expert)
if (cpr_get_answer_is_yes ("sign_uid.v4_promote_okay",
_("Do you want to promote "
"it to an OpenPGP self-"
"signature? (y/N) ")))
{
node->flag |= NODFLG_DELSIG;
xfree (user);
continue;
}
}
/* Is the current signature expired? */
if (node->pkt->pkt.signature->flags.expired)
{
tty_fprintf (fp, _("Your current signature on \"%s\"\n"
"has expired.\n"), user);
if (quick || cpr_get_answer_is_yes
("sign_uid.replace_expired_okay",
_("Do you want to issue a "
"new signature to replace "
"the expired one? (y/N) ")))
{
/* Mark these for later deletion. We
don't want to delete them here, just in
case the replacement signature doesn't
happen for some reason. We only delete
these after the replacement is already
in place. */
node->flag |= NODFLG_DELSIG;
xfree (user);
continue;
}
}
if (!node->pkt->pkt.signature->flags.exportable && !local)
{
/* It's a local sig, and we want to make a
exportable sig. */
tty_fprintf (fp, _("Your current signature on \"%s\"\n"
"is a local signature.\n"), user);
if (quick || cpr_get_answer_is_yes
("sign_uid.local_promote_okay",
_("Do you want to promote "
"it to a full exportable " "signature? (y/N) ")))
{
/* Mark these for later deletion. We
don't want to delete them here, just in
case the replacement signature doesn't
happen for some reason. We only delete
these after the replacement is already
in place. */
node->flag |= NODFLG_DELSIG;
xfree (user);
continue;
}
}
/* Fixme: see whether there is a revocation in which
* case we should allow signing it again. */
if (!node->pkt->pkt.signature->flags.exportable && local)
tty_fprintf ( fp,
_("\"%s\" was already locally signed by key %s\n"),
user, keystr_from_pk (pk));
else
tty_fprintf (fp,
_("\"%s\" was already signed by key %s\n"),
user, keystr_from_pk (pk));
if (opt.expert && !quick
&& cpr_get_answer_is_yes ("sign_uid.dupe_okay",
_("Do you want to sign it "
"again anyway? (y/N) ")))
{
/* Don't delete the old sig here since this is
an --expert thing. */
xfree (user);
continue;
}
snprintf (buf, sizeof buf, "%08lX%08lX",
(ulong) pk->keyid[0], (ulong) pk->keyid[1]);
write_status_text (STATUS_ALREADY_SIGNED, buf);
uidnode->flag &= ~NODFLG_MARK_A; /* remove mark */
xfree (user);
}
}
}
/* Check whether any uids are left for signing. */
if (!count_uids_with_flag (keyblock, NODFLG_MARK_A))
{
tty_fprintf (fp, _("Nothing to sign with key %s\n"),
keystr_from_pk (pk));
continue;
}
/* Ask whether we really should sign these user id(s). */
tty_fprintf (fp, "\n");
show_key_with_all_names (ctrl, fp, keyblock, 1, 0, 1, 0, 0, 0);
tty_fprintf (fp, "\n");
if (primary_pk->expiredate && !selfsig)
{
/* Static analyzer note: A claim that PRIMARY_PK might be
NULL is not correct because it set from the public key
packet which is always the first packet in a keyblock and
parsed in the above loop over the keyblock. In case the
keyblock has no packets at all and thus the loop was not
entered the above count_uids_with_flag would have
detected this case. */
u32 now = make_timestamp ();
if (primary_pk->expiredate <= now)
{
tty_fprintf (fp, _("This key has expired!"));
if (opt.expert && !quick)
{
tty_fprintf (fp, " ");
if (!cpr_get_answer_is_yes ("sign_uid.expired_okay",
_("Are you sure you still "
"want to sign it? (y/N) ")))
continue;
}
else
{
tty_fprintf (fp, _(" Unable to sign.\n"));
continue;
}
}
else
{
tty_fprintf (fp, _("This key is due to expire on %s.\n"),
expirestr_from_pk (primary_pk));
if (opt.ask_cert_expire && !quick)
{
char *answer = cpr_get ("sign_uid.expire",
_("Do you want your signature to "
"expire at the same time? (Y/n) "));
if (answer_is_yes_no_default (answer, 1))
{
/* This fixes the signature timestamp we're
going to make as now. This is so the
expiration date is exactly correct, and not
a few seconds off (due to the time it takes
to answer the questions, enter the
passphrase, etc). */
timestamp = now;
duration = primary_pk->expiredate - now;
}
cpr_kill_prompt ();
xfree (answer);
}
}
}
/* Only ask for duration if we haven't already set it to match
the expiration of the pk */
if (!duration && !selfsig)
{
if (opt.ask_cert_expire && !quick)
duration = ask_expire_interval (1, opt.def_cert_expire);
else
duration = parse_expire_string (opt.def_cert_expire);
}
if (selfsig)
;
else
{
if (opt.batch || !opt.ask_cert_level || quick)
class = 0x10 + opt.def_cert_level;
else
{
char *answer;
tty_fprintf (fp,
_("How carefully have you verified the key you are "
"about to sign actually belongs\nto the person "
"named above? If you don't know what to "
"answer, enter \"0\".\n"));
tty_fprintf (fp, "\n");
tty_fprintf (fp, _(" (0) I will not answer.%s\n"),
opt.def_cert_level == 0 ? " (default)" : "");
tty_fprintf (fp, _(" (1) I have not checked at all.%s\n"),
opt.def_cert_level == 1 ? " (default)" : "");
tty_fprintf (fp, _(" (2) I have done casual checking.%s\n"),
opt.def_cert_level == 2 ? " (default)" : "");
tty_fprintf (fp,
_(" (3) I have done very careful checking.%s\n"),
opt.def_cert_level == 3 ? " (default)" : "");
tty_fprintf (fp, "\n");
while (class == 0)
{
answer = cpr_get ("sign_uid.class",
_("Your selection? "
"(enter '?' for more information): "));
if (answer[0] == '\0')
class = 0x10 + opt.def_cert_level; /* Default */
else if (ascii_strcasecmp (answer, "0") == 0)
class = 0x10; /* Generic */
else if (ascii_strcasecmp (answer, "1") == 0)
class = 0x11; /* Persona */
else if (ascii_strcasecmp (answer, "2") == 0)
class = 0x12; /* Casual */
else if (ascii_strcasecmp (answer, "3") == 0)
class = 0x13; /* Positive */
else
tty_fprintf (fp, _("Invalid selection.\n"));
xfree (answer);
}
}
if (trust && !quick)
trustsig_prompt (&trust_value, &trust_depth, &trust_regexp);
}
if (!quick)
{
p = get_user_id_native (ctrl, sk_keyid);
tty_fprintf (fp,
_("Are you sure that you want to sign this key with your\n"
"key \"%s\" (%s)\n"), p, keystr_from_pk (pk));
xfree (p);
}
if (selfsig)
{
tty_fprintf (fp, "\n");
tty_fprintf (fp, _("This will be a self-signature.\n"));
if (local)
{
tty_fprintf (fp, "\n");
tty_fprintf (fp, _("WARNING: the signature will not be marked "
"as non-exportable.\n"));
}
if (nonrevocable)
{
tty_fprintf (fp, "\n");
tty_fprintf (fp, _("WARNING: the signature will not be marked "
"as non-revocable.\n"));
}
}
else
{
if (local)
{
tty_fprintf (fp, "\n");
tty_fprintf (fp,
_("The signature will be marked as non-exportable.\n"));
}
if (nonrevocable)
{
tty_fprintf (fp, "\n");
tty_fprintf (fp,
_("The signature will be marked as non-revocable.\n"));
}
switch (class)
{
case 0x11:
tty_fprintf (fp, "\n");
tty_fprintf (fp, _("I have not checked this key at all.\n"));
break;
case 0x12:
tty_fprintf (fp, "\n");
tty_fprintf (fp, _("I have checked this key casually.\n"));
break;
case 0x13:
tty_fprintf (fp, "\n");
tty_fprintf (fp, _("I have checked this key very carefully.\n"));
break;
}
}
tty_fprintf (fp, "\n");
if (opt.batch && opt.answer_yes)
;
else if (quick)
;
else if (!cpr_get_answer_is_yes ("sign_uid.okay",
_("Really sign? (y/N) ")))
continue;
/* Now we can sign the user ids. */
reloop: /* (Must use this, because we are modifying the list.) */
primary_pk = NULL;
for (node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_KEY)
primary_pk = node->pkt->pkt.public_key;
else if (node->pkt->pkttype == PKT_USER_ID
&& (node->flag & NODFLG_MARK_A))
{
PACKET *pkt;
PKT_signature *sig;
struct sign_attrib attrib;
log_assert (primary_pk);
memset (&attrib, 0, sizeof attrib);
attrib.non_exportable = local;
attrib.non_revocable = nonrevocable;
attrib.trust_depth = trust_depth;
attrib.trust_value = trust_value;
attrib.trust_regexp = trust_regexp;
node->flag &= ~NODFLG_MARK_A;
/* We force creation of a v4 signature for local
* signatures, otherwise we would not generate the
* subpacket with v3 keys and the signature becomes
* exportable. */
if (selfsig)
rc = make_keysig_packet (ctrl, &sig, primary_pk,
node->pkt->pkt.user_id,
NULL,
pk,
- 0x13, 0, 0, 0,
+ 0x13,
+ 0, 0,
keygen_add_std_prefs, primary_pk,
NULL);
else
rc = make_keysig_packet (ctrl, &sig, primary_pk,
node->pkt->pkt.user_id,
NULL,
pk,
- class, 0,
+ class,
timestamp, duration,
sign_mk_attrib, &attrib,
NULL);
if (rc)
{
write_status_error ("keysig", rc);
log_error (_("signing failed: %s\n"), gpg_strerror (rc));
goto leave;
}
*ret_modified = 1; /* We changed the keyblock. */
update_trust = 1;
pkt = xmalloc_clear (sizeof *pkt);
pkt->pkttype = PKT_SIGNATURE;
pkt->pkt.signature = sig;
insert_kbnode (node, new_kbnode (pkt), PKT_SIGNATURE);
goto reloop;
}
}
/* Delete any sigs that got promoted */
for (node = keyblock; node; node = node->next)
if (node->flag & NODFLG_DELSIG)
delete_kbnode (node);
} /* End loop over signators. */
leave:
release_sk_list (sk_list);
return rc;
}
/*
* Change the passphrase of the primary and all secondary keys. Note
* that it is common to use only one passphrase for the primary and
* all subkeys. However, this is now (since GnuPG 2.1) all up to the
* gpg-agent. Returns 0 on success or an error code.
*/
static gpg_error_t
change_passphrase (ctrl_t ctrl, kbnode_t keyblock)
{
gpg_error_t err;
kbnode_t node;
PKT_public_key *pk;
int any;
u32 keyid[2], subid[2];
char *hexgrip = NULL;
char *cache_nonce = NULL;
char *passwd_nonce = NULL;
node = find_kbnode (keyblock, PKT_PUBLIC_KEY);
if (!node)
{
log_error ("Oops; public key missing!\n");
err = gpg_error (GPG_ERR_INTERNAL);
goto leave;
}
pk = node->pkt->pkt.public_key;
keyid_from_pk (pk, keyid);
/* Check whether it is likely that we will be able to change the
passphrase for any subkey. */
for (any = 0, node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_KEY
|| node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
char *serialno;
pk = node->pkt->pkt.public_key;
keyid_from_pk (pk, subid);
xfree (hexgrip);
err = hexkeygrip_from_pk (pk, &hexgrip);
if (err)
goto leave;
err = agent_get_keyinfo (ctrl, hexgrip, &serialno, NULL);
if (!err && serialno)
; /* Key on card. */
else if (gpg_err_code (err) == GPG_ERR_NOT_FOUND)
; /* Maybe stub key. */
else if (!err)
any = 1; /* Key is known. */
else
log_error ("key %s: error getting keyinfo from agent: %s\n",
keystr_with_sub (keyid, subid), gpg_strerror (err));
xfree (serialno);
}
}
err = 0;
if (!any)
{
tty_printf (_("Key has only stub or on-card key items - "
"no passphrase to change.\n"));
goto leave;
}
/* Change the passphrase for all keys. */
for (node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_KEY
|| node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
char *desc;
pk = node->pkt->pkt.public_key;
keyid_from_pk (pk, subid);
xfree (hexgrip);
err = hexkeygrip_from_pk (pk, &hexgrip);
if (err)
goto leave;
/* Note that when using --dry-run we don't change the
* passphrase but merely verify the current passphrase. */
desc = gpg_format_keydesc (ctrl, pk, FORMAT_KEYDESC_NORMAL, 1);
err = agent_passwd (ctrl, hexgrip, desc, !!opt.dry_run,
&cache_nonce, &passwd_nonce);
xfree (desc);
if (err)
log_log ((gpg_err_code (err) == GPG_ERR_CANCELED
|| gpg_err_code (err) == GPG_ERR_FULLY_CANCELED)
? GPGRT_LOGLVL_INFO : GPGRT_LOGLVL_ERROR,
_("key %s: error changing passphrase: %s\n"),
keystr_with_sub (keyid, subid),
gpg_strerror (err));
if (gpg_err_code (err) == GPG_ERR_FULLY_CANCELED)
break;
}
}
leave:
xfree (hexgrip);
xfree (cache_nonce);
xfree (passwd_nonce);
return err;
}
/* Fix various problems in the keyblock. Returns true if the keyblock
was changed. Note that a pointer to the keyblock must be given and
the function may change it (i.e. replacing the first node). */
static int
fix_keyblock (ctrl_t ctrl, kbnode_t *keyblockp)
{
int changed = 0;
if (collapse_uids (keyblockp))
changed++;
if (key_check_all_keysigs (ctrl, 1, *keyblockp, 0, 1))
changed++;
reorder_keyblock (*keyblockp);
/* If we modified the keyblock, make sure the flags are right. */
if (changed)
merge_keys_and_selfsig (ctrl, *keyblockp);
return changed;
}
static int
parse_sign_type (const char *str, int *localsig, int *nonrevokesig,
int *trustsig)
{
const char *p = str;
while (*p)
{
if (ascii_strncasecmp (p, "l", 1) == 0)
{
*localsig = 1;
p++;
}
else if (ascii_strncasecmp (p, "nr", 2) == 0)
{
*nonrevokesig = 1;
p += 2;
}
else if (ascii_strncasecmp (p, "t", 1) == 0)
{
*trustsig = 1;
p++;
}
else
return 0;
}
return 1;
}
/*
* Menu driven key editor. If seckey_check is true, then a secret key
* that matches username will be looked for. If it is false, not all
* commands will be available.
*
* Note: to keep track of certain selections we use node->mark MARKBIT_xxxx.
*/
/* Need an SK for this command */
#define KEYEDIT_NEED_SK 1
/* Need an SUB KEY for this command */
#define KEYEDIT_NEED_SUBSK 2
/* Match the tail of the string */
#define KEYEDIT_TAIL_MATCH 8
enum cmdids
{
cmdNONE = 0,
cmdQUIT, cmdHELP, cmdFPR, cmdLIST, cmdSELUID, cmdCHECK, cmdSIGN,
cmdREVSIG, cmdREVKEY, cmdREVUID, cmdDELSIG, cmdPRIMARY, cmdDEBUG,
cmdSAVE, cmdADDUID, cmdADDPHOTO, cmdDELUID, cmdADDKEY, cmdDELKEY,
cmdADDREVOKER, cmdTOGGLE, cmdSELKEY, cmdPASSWD, cmdTRUST, cmdPREF,
cmdEXPIRE, cmdCHANGEUSAGE, cmdBACKSIGN,
#ifndef NO_TRUST_MODELS
cmdENABLEKEY, cmdDISABLEKEY,
#endif /*!NO_TRUST_MODELS*/
cmdSHOWPREF,
cmdSETPREF, cmdPREFKS, cmdNOTATION, cmdINVCMD, cmdSHOWPHOTO, cmdUPDTRUST,
cmdCHKTRUST, cmdADDCARDKEY, cmdKEYTOCARD, cmdBKUPTOCARD,
cmdCLEAN, cmdMINIMIZE, cmdGRIP, cmdNOP
};
static struct
{
const char *name;
enum cmdids id;
int flags;
const char *desc;
} cmds[] =
{
{ "quit", cmdQUIT, 0, N_("quit this menu")},
{ "q", cmdQUIT, 0, NULL},
{ "save", cmdSAVE, 0, N_("save and quit")},
{ "help", cmdHELP, 0, N_("show this help")},
{ "?", cmdHELP, 0, NULL},
{ "fpr", cmdFPR, 0, N_("show key fingerprint")},
{ "grip", cmdGRIP, 0, N_("show the keygrip")},
{ "list", cmdLIST, 0, N_("list key and user IDs")},
{ "l", cmdLIST, 0, NULL},
{ "uid", cmdSELUID, 0, N_("select user ID N")},
{ "key", cmdSELKEY, 0, N_("select subkey N")},
{ "check", cmdCHECK, 0, N_("check signatures")},
{ "c", cmdCHECK, 0, NULL},
{ "change-usage", cmdCHANGEUSAGE, KEYEDIT_NEED_SK, NULL},
{ "cross-certify", cmdBACKSIGN, KEYEDIT_NEED_SK, NULL},
{ "backsign", cmdBACKSIGN, KEYEDIT_NEED_SK, NULL},
{ "sign", cmdSIGN, KEYEDIT_TAIL_MATCH,
N_("sign selected user IDs [* see below for related commands]")},
{ "s", cmdSIGN, 0, NULL},
/* "lsign" and friends will never match since "sign" comes first
and it is a tail match. They are just here so they show up in
the help menu. */
{ "lsign", cmdNOP, 0, N_("sign selected user IDs locally")},
{ "tsign", cmdNOP, 0, N_("sign selected user IDs with a trust signature")},
{ "nrsign", cmdNOP, 0,
N_("sign selected user IDs with a non-revocable signature")},
{ "debug", cmdDEBUG, 0, NULL},
{ "adduid", cmdADDUID, KEYEDIT_NEED_SK, N_("add a user ID")},
{ "addphoto", cmdADDPHOTO, KEYEDIT_NEED_SK,
N_("add a photo ID")},
{ "deluid", cmdDELUID, 0, N_("delete selected user IDs")},
/* delphoto is really deluid in disguise */
{ "delphoto", cmdDELUID, 0, NULL},
{ "addkey", cmdADDKEY, KEYEDIT_NEED_SK, N_("add a subkey")},
#ifdef ENABLE_CARD_SUPPORT
{ "addcardkey", cmdADDCARDKEY, KEYEDIT_NEED_SK,
N_("add a key to a smartcard")},
{ "keytocard", cmdKEYTOCARD, KEYEDIT_NEED_SK | KEYEDIT_NEED_SUBSK,
N_("move a key to a smartcard")},
{ "bkuptocard", cmdBKUPTOCARD, KEYEDIT_NEED_SK | KEYEDIT_NEED_SUBSK,
N_("move a backup key to a smartcard")},
#endif /*ENABLE_CARD_SUPPORT */
{ "delkey", cmdDELKEY, 0, N_("delete selected subkeys")},
{ "addrevoker", cmdADDREVOKER, KEYEDIT_NEED_SK,
N_("add a revocation key")},
{ "delsig", cmdDELSIG, 0,
N_("delete signatures from the selected user IDs")},
{ "expire", cmdEXPIRE, KEYEDIT_NEED_SK | KEYEDIT_NEED_SUBSK,
N_("change the expiration date for the key or selected subkeys")},
{ "primary", cmdPRIMARY, KEYEDIT_NEED_SK,
N_("flag the selected user ID as primary")},
{ "toggle", cmdTOGGLE, KEYEDIT_NEED_SK, NULL}, /* Dummy command. */
{ "t", cmdTOGGLE, KEYEDIT_NEED_SK, NULL},
{ "pref", cmdPREF, 0, N_("list preferences (expert)")},
{ "showpref", cmdSHOWPREF, 0, N_("list preferences (verbose)")},
{ "setpref", cmdSETPREF, KEYEDIT_NEED_SK,
N_("set preference list for the selected user IDs")},
{ "updpref", cmdSETPREF, KEYEDIT_NEED_SK, NULL},
{ "keyserver", cmdPREFKS, KEYEDIT_NEED_SK,
N_("set the preferred keyserver URL for the selected user IDs")},
{ "notation", cmdNOTATION, KEYEDIT_NEED_SK,
N_("set a notation for the selected user IDs")},
{ "passwd", cmdPASSWD, KEYEDIT_NEED_SK | KEYEDIT_NEED_SUBSK,
N_("change the passphrase")},
{ "password", cmdPASSWD, KEYEDIT_NEED_SK | KEYEDIT_NEED_SUBSK, NULL},
#ifndef NO_TRUST_MODELS
{ "trust", cmdTRUST, 0, N_("change the ownertrust")},
#endif /*!NO_TRUST_MODELS*/
{ "revsig", cmdREVSIG, 0,
N_("revoke signatures on the selected user IDs")},
{ "revuid", cmdREVUID, KEYEDIT_NEED_SK,
N_("revoke selected user IDs")},
{ "revphoto", cmdREVUID, KEYEDIT_NEED_SK, NULL},
{ "revkey", cmdREVKEY, KEYEDIT_NEED_SK,
N_("revoke key or selected subkeys")},
#ifndef NO_TRUST_MODELS
{ "enable", cmdENABLEKEY, 0, N_("enable key")},
{ "disable", cmdDISABLEKEY, 0, N_("disable key")},
#endif /*!NO_TRUST_MODELS*/
{ "showphoto", cmdSHOWPHOTO, 0, N_("show selected photo IDs")},
{ "clean", cmdCLEAN, 0,
N_("compact unusable user IDs and remove unusable signatures from key")},
{ "minimize", cmdMINIMIZE, 0,
N_("compact unusable user IDs and remove all signatures from key")},
{ NULL, cmdNONE, 0, NULL}
};
#ifdef HAVE_LIBREADLINE
/*
These two functions are used by readline for command completion.
*/
static char *
command_generator (const char *text, int state)
{
static int list_index, len;
const char *name;
/* If this is a new word to complete, initialize now. This includes
saving the length of TEXT for efficiency, and initializing the
index variable to 0. */
if (!state)
{
list_index = 0;
len = strlen (text);
}
/* Return the next partial match */
while ((name = cmds[list_index].name))
{
/* Only complete commands that have help text */
if (cmds[list_index++].desc && strncmp (name, text, len) == 0)
return strdup (name);
}
return NULL;
}
static char **
keyedit_completion (const char *text, int start, int end)
{
/* If we are at the start of a line, we try and command-complete.
If not, just do nothing for now. */
(void) end;
if (start == 0)
return rl_completion_matches (text, command_generator);
rl_attempted_completion_over = 1;
return NULL;
}
#endif /* HAVE_LIBREADLINE */
/* Main function of the menu driven key editor. */
void
keyedit_menu (ctrl_t ctrl, const char *username, strlist_t locusr,
strlist_t commands, int quiet, int seckey_check)
{
enum cmdids cmd = 0;
gpg_error_t err = 0;
KBNODE keyblock = NULL;
KEYDB_HANDLE kdbhd = NULL;
int have_seckey = 0;
int have_anyseckey = 0;
char *answer = NULL;
int redisplay = 1;
int modified = 0;
int sec_shadowing = 0;
int run_subkey_warnings = 0;
int have_commands = !!commands;
if (opt.command_fd != -1)
;
else if (opt.batch && !have_commands)
{
log_error (_("can't do this in batch mode\n"));
goto leave;
}
#ifdef HAVE_W32_SYSTEM
/* Due to Windows peculiarities we need to make sure that the
trustdb stale check is done before we open another file
(i.e. by searching for a key). In theory we could make sure
that the files are closed after use but the open/close caches
inhibits that and flushing the cache right before the stale
check is not easy to implement. Thus we take the easy way out
and run the stale check as early as possible. Note, that for
non- W32 platforms it is run indirectly trough a call to
get_validity (). */
check_trustdb_stale (ctrl);
#endif
/* Get the public key */
- err = get_pubkey_byname (ctrl, NULL, NULL, username, &keyblock, &kdbhd, 1, 1);
+ err = get_pubkey_byname (ctrl, GET_PUBKEY_NO_AKL,
+ NULL, NULL, username, &keyblock, &kdbhd, 1);
if (err)
{
log_error (_("key \"%s\" not found: %s\n"), username, gpg_strerror (err));
goto leave;
}
if (fix_keyblock (ctrl, &keyblock))
modified++;
/* See whether we have a matching secret key. */
if (seckey_check)
{
have_anyseckey = !agent_probe_any_secret_key (ctrl, keyblock);
if (have_anyseckey
&& !agent_probe_secret_key (ctrl, keyblock->pkt->pkt.public_key))
{
/* The primary key is also available. */
have_seckey = 1;
}
if (have_seckey && !quiet)
tty_printf (_("Secret key is available.\n"));
else if (have_anyseckey && !quiet)
tty_printf (_("Secret subkeys are available.\n"));
}
/* Main command loop. */
for (;;)
{
int i, arg_number, photo;
const char *arg_string = "";
char *p;
PKT_public_key *pk = keyblock->pkt->pkt.public_key;
tty_printf ("\n");
if (redisplay && !quiet)
{
/* Show using flags: with_revoker, with_subkeys. */
show_key_with_all_names (ctrl, NULL, keyblock, 0, 1, 0, 1, 0, 0);
tty_printf ("\n");
redisplay = 0;
}
if (run_subkey_warnings)
{
run_subkey_warnings = 0;
if (!count_selected_keys (keyblock))
subkey_expire_warning (keyblock);
}
do
{
xfree (answer);
if (have_commands)
{
if (commands)
{
answer = xstrdup (commands->d);
commands = commands->next;
}
else if (opt.batch)
{
answer = xstrdup ("quit");
}
else
have_commands = 0;
}
if (!have_commands)
{
#ifdef HAVE_LIBREADLINE
tty_enable_completion (keyedit_completion);
#endif
answer = cpr_get_no_help ("keyedit.prompt", GPG_NAME "> ");
cpr_kill_prompt ();
tty_disable_completion ();
}
trim_spaces (answer);
}
while (*answer == '#');
arg_number = 0; /* Here is the init which egcc complains about. */
photo = 0; /* Same here. */
if (!*answer)
cmd = cmdLIST;
else if (*answer == CONTROL_D)
cmd = cmdQUIT;
else if (digitp (answer))
{
cmd = cmdSELUID;
arg_number = atoi (answer);
}
else
{
if ((p = strchr (answer, ' ')))
{
*p++ = 0;
trim_spaces (answer);
trim_spaces (p);
arg_number = atoi (p);
arg_string = p;
}
for (i = 0; cmds[i].name; i++)
{
if (cmds[i].flags & KEYEDIT_TAIL_MATCH)
{
size_t l = strlen (cmds[i].name);
size_t a = strlen (answer);
if (a >= l)
{
if (!ascii_strcasecmp (&answer[a - l], cmds[i].name))
{
answer[a - l] = '\0';
break;
}
}
}
else if (!ascii_strcasecmp (answer, cmds[i].name))
break;
}
if ((cmds[i].flags & (KEYEDIT_NEED_SK|KEYEDIT_NEED_SUBSK))
&& !(((cmds[i].flags & KEYEDIT_NEED_SK) && have_seckey)
|| ((cmds[i].flags & KEYEDIT_NEED_SUBSK) && have_anyseckey)))
{
tty_printf (_("Need the secret key to do this.\n"));
cmd = cmdNOP;
}
else
cmd = cmds[i].id;
}
/* Dispatch the command. */
switch (cmd)
{
case cmdHELP:
for (i = 0; cmds[i].name; i++)
{
if ((cmds[i].flags & (KEYEDIT_NEED_SK|KEYEDIT_NEED_SUBSK))
&& !(((cmds[i].flags & KEYEDIT_NEED_SK) && have_seckey)
||((cmds[i].flags&KEYEDIT_NEED_SUBSK)&&have_anyseckey)))
; /* Skip those item if we do not have the secret key. */
else if (cmds[i].desc)
tty_printf ("%-11s %s\n", cmds[i].name, _(cmds[i].desc));
}
tty_printf ("\n");
tty_printf
(_("* The 'sign' command may be prefixed with an 'l' for local "
"signatures (lsign),\n"
" a 't' for trust signatures (tsign), an 'nr' for "
"non-revocable signatures\n"
" (nrsign), or any combination thereof (ltsign, "
"tnrsign, etc.).\n"));
break;
case cmdLIST:
redisplay = 1;
break;
case cmdFPR:
show_key_and_fingerprint
(ctrl,
keyblock, (*arg_string == '*'
&& (!arg_string[1] || spacep (arg_string + 1))));
break;
case cmdGRIP:
show_key_and_grip (keyblock);
break;
case cmdSELUID:
if (strlen (arg_string) == NAMEHASH_LEN * 2)
redisplay = menu_select_uid_namehash (keyblock, arg_string);
else
{
if (*arg_string == '*'
&& (!arg_string[1] || spacep (arg_string + 1)))
arg_number = -1; /* Select all. */
redisplay = menu_select_uid (keyblock, arg_number);
}
break;
case cmdSELKEY:
{
if (*arg_string == '*'
&& (!arg_string[1] || spacep (arg_string + 1)))
arg_number = -1; /* Select all. */
if (menu_select_key (keyblock, arg_number, p))
redisplay = 1;
}
break;
case cmdCHECK:
if (key_check_all_keysigs (ctrl, -1, keyblock,
count_selected_uids (keyblock),
!strcmp (arg_string, "selfsig")))
modified = 1;
break;
case cmdSIGN:
{
int localsig = 0, nonrevokesig = 0, trustsig = 0, interactive = 0;
if (pk->flags.revoked)
{
tty_printf (_("Key is revoked."));
if (opt.expert)
{
tty_printf (" ");
if (!cpr_get_answer_is_yes
("keyedit.sign_revoked.okay",
_("Are you sure you still want to sign it? (y/N) ")))
break;
}
else
{
tty_printf (_(" Unable to sign.\n"));
break;
}
}
if (count_uids (keyblock) > 1 && !count_selected_uids (keyblock))
{
int result;
if (opt.only_sign_text_ids)
result = cpr_get_answer_is_yes
("keyedit.sign_all.okay",
_("Really sign all text user IDs? (y/N) "));
else
result = cpr_get_answer_is_yes
("keyedit.sign_all.okay",
_("Really sign all user IDs? (y/N) "));
if (! result)
{
if (opt.interactive)
interactive = 1;
else
{
tty_printf (_("Hint: Select the user IDs to sign\n"));
have_commands = 0;
break;
}
}
}
/* What sort of signing are we doing? */
if (!parse_sign_type
(answer, &localsig, &nonrevokesig, &trustsig))
{
tty_printf (_("Unknown signature type '%s'\n"), answer);
break;
}
sign_uids (ctrl, NULL, keyblock, locusr, &modified,
localsig, nonrevokesig, trustsig, interactive, 0);
}
break;
case cmdDEBUG:
dump_kbnode (keyblock);
break;
case cmdTOGGLE:
/* The toggle command is a leftover from old gpg versions
where we worked with a secret and a public keyring. It
is not necessary anymore but we keep this command for the
sake of scripts using it. */
redisplay = 1;
break;
case cmdADDPHOTO:
if (RFC2440)
{
tty_printf (_("This command is not allowed while in %s mode.\n"),
gnupg_compliance_option_string (opt.compliance));
break;
}
photo = 1;
/* fall through */
case cmdADDUID:
if (menu_adduid (ctrl, keyblock, photo, arg_string, NULL))
{
update_trust = 1;
redisplay = 1;
modified = 1;
merge_keys_and_selfsig (ctrl, keyblock);
}
break;
case cmdDELUID:
{
int n1;
if (!(n1 = count_selected_uids (keyblock)))
{
tty_printf (_("You must select at least one user ID.\n"));
if (!opt.expert)
tty_printf (_("(Use the '%s' command.)\n"), "uid");
}
else if (real_uids_left (keyblock) < 1)
tty_printf (_("You can't delete the last user ID!\n"));
else if (cpr_get_answer_is_yes
("keyedit.remove.uid.okay",
n1 > 1 ? _("Really remove all selected user IDs? (y/N) ")
: _("Really remove this user ID? (y/N) ")))
{
menu_deluid (keyblock);
redisplay = 1;
modified = 1;
}
}
break;
case cmdDELSIG:
{
int n1;
if (!(n1 = count_selected_uids (keyblock)))
{
tty_printf (_("You must select at least one user ID.\n"));
if (!opt.expert)
tty_printf (_("(Use the '%s' command.)\n"), "uid");
}
else if (menu_delsig (ctrl, keyblock))
{
/* No redisplay here, because it may scroll away some
* of the status output of this command. */
modified = 1;
}
}
break;
case cmdADDKEY:
if (!generate_subkeypair (ctrl, keyblock, NULL, NULL, NULL))
{
redisplay = 1;
modified = 1;
merge_keys_and_selfsig (ctrl, keyblock);
}
break;
#ifdef ENABLE_CARD_SUPPORT
case cmdADDCARDKEY:
if (!card_generate_subkey (ctrl, keyblock))
{
redisplay = 1;
modified = 1;
merge_keys_and_selfsig (ctrl, keyblock);
}
break;
case cmdKEYTOCARD:
{
KBNODE node = NULL;
switch (count_selected_keys (keyblock))
{
case 0:
if (cpr_get_answer_is_yes
("keyedit.keytocard.use_primary",
/* TRANSLATORS: Please take care: This is about
moving the key and not about removing it. */
_("Really move the primary key? (y/N) ")))
node = keyblock;
break;
case 1:
for (node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY
&& node->flag & NODFLG_SELKEY)
break;
}
break;
default:
tty_printf (_("You must select exactly one key.\n"));
break;
}
if (node)
{
PKT_public_key *xxpk = node->pkt->pkt.public_key;
if (card_store_subkey (node, xxpk ? xxpk->pubkey_usage : 0))
{
redisplay = 1;
sec_shadowing = 1;
}
}
}
break;
case cmdBKUPTOCARD:
{
/* Ask for a filename, check whether this is really a
backup key as generated by the card generation, parse
that key and store it on card. */
KBNODE node;
char *fname;
PACKET *pkt;
IOBUF a;
struct parse_packet_ctx_s parsectx;
if (!*arg_string)
{
tty_printf (_("Command expects a filename argument\n"));
break;
}
if (*arg_string == DIRSEP_C)
fname = xstrdup (arg_string);
else if (*arg_string == '~')
fname = make_filename (arg_string, NULL);
else
fname = make_filename (gnupg_homedir (), arg_string, NULL);
/* Open that file. */
a = iobuf_open (fname);
if (a && is_secured_file (iobuf_get_fd (a)))
{
iobuf_close (a);
a = NULL;
gpg_err_set_errno (EPERM);
}
if (!a)
{
tty_printf (_("Can't open '%s': %s\n"),
fname, strerror (errno));
xfree (fname);
break;
}
/* Parse and check that file. */
pkt = xmalloc (sizeof *pkt);
init_packet (pkt);
init_parse_packet (&parsectx, a);
err = parse_packet (&parsectx, pkt);
deinit_parse_packet (&parsectx);
iobuf_close (a);
iobuf_ioctl (NULL, IOBUF_IOCTL_INVALIDATE_CACHE, 0, (char *) fname);
if (!err && pkt->pkttype != PKT_SECRET_KEY
&& pkt->pkttype != PKT_SECRET_SUBKEY)
err = GPG_ERR_NO_SECKEY;
if (err)
{
tty_printf (_("Error reading backup key from '%s': %s\n"),
fname, gpg_strerror (err));
xfree (fname);
free_packet (pkt, NULL);
xfree (pkt);
break;
}
xfree (fname);
node = new_kbnode (pkt);
/* Transfer it to gpg-agent which handles secret keys. */
err = transfer_secret_keys (ctrl, NULL, node, 1, 1, 0);
/* Treat the pkt as a public key. */
pkt->pkttype = PKT_PUBLIC_KEY;
/* Ask gpg-agent to store the secret key to card. */
if (card_store_subkey (node, 0))
{
redisplay = 1;
sec_shadowing = 1;
}
release_kbnode (node);
}
break;
#endif /* ENABLE_CARD_SUPPORT */
case cmdDELKEY:
{
int n1;
if (!(n1 = count_selected_keys (keyblock)))
{
tty_printf (_("You must select at least one key.\n"));
if (!opt.expert)
tty_printf (_("(Use the '%s' command.)\n"), "key");
}
else if (!cpr_get_answer_is_yes
("keyedit.remove.subkey.okay",
n1 > 1 ? _("Do you really want to delete the "
"selected keys? (y/N) ")
: _("Do you really want to delete this key? (y/N) ")))
;
else
{
menu_delkey (keyblock);
redisplay = 1;
modified = 1;
}
}
break;
case cmdADDREVOKER:
{
int sensitive = 0;
if (ascii_strcasecmp (arg_string, "sensitive") == 0)
sensitive = 1;
if (menu_addrevoker (ctrl, keyblock, sensitive))
{
redisplay = 1;
modified = 1;
merge_keys_and_selfsig (ctrl, keyblock);
}
}
break;
case cmdREVUID:
{
int n1;
if (!(n1 = count_selected_uids (keyblock)))
{
tty_printf (_("You must select at least one user ID.\n"));
if (!opt.expert)
tty_printf (_("(Use the '%s' command.)\n"), "uid");
}
else if (cpr_get_answer_is_yes
("keyedit.revoke.uid.okay",
n1 > 1 ? _("Really revoke all selected user IDs? (y/N) ")
: _("Really revoke this user ID? (y/N) ")))
{
if (menu_revuid (ctrl, keyblock))
{
modified = 1;
redisplay = 1;
}
}
}
break;
case cmdREVKEY:
{
int n1;
if (!(n1 = count_selected_keys (keyblock)))
{
if (cpr_get_answer_is_yes ("keyedit.revoke.subkey.okay",
_("Do you really want to revoke"
" the entire key? (y/N) ")))
{
if (menu_revkey (ctrl, keyblock))
modified = 1;
redisplay = 1;
}
}
else if (cpr_get_answer_is_yes ("keyedit.revoke.subkey.okay",
n1 > 1 ?
_("Do you really want to revoke"
" the selected subkeys? (y/N) ")
: _("Do you really want to revoke"
" this subkey? (y/N) ")))
{
if (menu_revsubkey (ctrl, keyblock))
modified = 1;
redisplay = 1;
}
if (modified)
merge_keys_and_selfsig (ctrl, keyblock);
}
break;
case cmdEXPIRE:
if (gpg_err_code (menu_expire (ctrl, keyblock, 0, 0)) == GPG_ERR_TRUE)
{
merge_keys_and_selfsig (ctrl, keyblock);
run_subkey_warnings = 1;
modified = 1;
redisplay = 1;
}
break;
case cmdCHANGEUSAGE:
if (menu_changeusage (ctrl, keyblock))
{
merge_keys_and_selfsig (ctrl, keyblock);
modified = 1;
redisplay = 1;
}
break;
case cmdBACKSIGN:
if (menu_backsign (ctrl, keyblock))
{
modified = 1;
redisplay = 1;
}
break;
case cmdPRIMARY:
if (menu_set_primary_uid (ctrl, keyblock))
{
merge_keys_and_selfsig (ctrl, keyblock);
modified = 1;
redisplay = 1;
}
break;
case cmdPASSWD:
change_passphrase (ctrl, keyblock);
break;
#ifndef NO_TRUST_MODELS
case cmdTRUST:
if (opt.trust_model == TM_EXTERNAL)
{
tty_printf (_("Owner trust may not be set while "
"using a user provided trust database\n"));
break;
}
show_key_with_all_names (ctrl, NULL, keyblock, 0, 0, 0, 1, 0, 0);
tty_printf ("\n");
if (edit_ownertrust (ctrl, find_kbnode (keyblock,
PKT_PUBLIC_KEY)->pkt->pkt.
public_key, 1))
{
redisplay = 1;
/* No real need to set update_trust here as
edit_ownertrust() calls revalidation_mark()
anyway. */
update_trust = 1;
}
break;
#endif /*!NO_TRUST_MODELS*/
case cmdPREF:
{
int count = count_selected_uids (keyblock);
log_assert (keyblock->pkt->pkttype == PKT_PUBLIC_KEY);
show_names (ctrl, NULL, keyblock, keyblock->pkt->pkt.public_key,
count ? NODFLG_SELUID : 0, 1);
}
break;
case cmdSHOWPREF:
{
int count = count_selected_uids (keyblock);
log_assert (keyblock->pkt->pkttype == PKT_PUBLIC_KEY);
show_names (ctrl, NULL, keyblock, keyblock->pkt->pkt.public_key,
count ? NODFLG_SELUID : 0, 2);
}
break;
case cmdSETPREF:
{
PKT_user_id *tempuid;
keygen_set_std_prefs (!*arg_string ? "default" : arg_string, 0);
tempuid = keygen_get_std_prefs ();
tty_printf (_("Set preference list to:\n"));
show_prefs (tempuid, NULL, 1);
free_user_id (tempuid);
if (cpr_get_answer_is_yes
("keyedit.setpref.okay",
count_selected_uids (keyblock) ?
_("Really update the preferences"
" for the selected user IDs? (y/N) ")
: _("Really update the preferences? (y/N) ")))
{
if (menu_set_preferences (ctrl, keyblock))
{
merge_keys_and_selfsig (ctrl, keyblock);
modified = 1;
redisplay = 1;
}
}
}
break;
case cmdPREFKS:
if (menu_set_keyserver_url (ctrl, *arg_string ? arg_string : NULL,
keyblock))
{
merge_keys_and_selfsig (ctrl, keyblock);
modified = 1;
redisplay = 1;
}
break;
case cmdNOTATION:
if (menu_set_notation (ctrl, *arg_string ? arg_string : NULL,
keyblock))
{
merge_keys_and_selfsig (ctrl, keyblock);
modified = 1;
redisplay = 1;
}
break;
case cmdNOP:
break;
case cmdREVSIG:
if (menu_revsig (ctrl, keyblock))
{
redisplay = 1;
modified = 1;
}
break;
#ifndef NO_TRUST_MODELS
case cmdENABLEKEY:
case cmdDISABLEKEY:
if (enable_disable_key (ctrl, keyblock, cmd == cmdDISABLEKEY))
{
redisplay = 1;
modified = 1;
}
break;
#endif /*!NO_TRUST_MODELS*/
case cmdSHOWPHOTO:
menu_showphoto (ctrl, keyblock);
break;
case cmdCLEAN:
if (menu_clean (ctrl, keyblock, 0))
redisplay = modified = 1;
break;
case cmdMINIMIZE:
if (menu_clean (ctrl, keyblock, 1))
redisplay = modified = 1;
break;
case cmdQUIT:
if (have_commands)
goto leave;
if (!modified && !sec_shadowing)
goto leave;
if (!cpr_get_answer_is_yes ("keyedit.save.okay",
_("Save changes? (y/N) ")))
{
if (cpr_enabled ()
|| cpr_get_answer_is_yes ("keyedit.cancel.okay",
_("Quit without saving? (y/N) ")))
goto leave;
break;
}
/* fall through */
case cmdSAVE:
if (modified)
{
err = keydb_update_keyblock (ctrl, kdbhd, keyblock);
if (err)
{
log_error (_("update failed: %s\n"), gpg_strerror (err));
break;
}
}
if (sec_shadowing)
{
err = agent_scd_learn (NULL, 1);
if (err)
{
log_error (_("update failed: %s\n"), gpg_strerror (err));
break;
}
}
if (!modified && !sec_shadowing)
tty_printf (_("Key not changed so no update needed.\n"));
if (update_trust)
{
revalidation_mark (ctrl);
update_trust = 0;
}
goto leave;
case cmdINVCMD:
default:
tty_printf ("\n");
tty_printf (_("Invalid command (try \"help\")\n"));
break;
}
} /* End of the main command loop. */
leave:
release_kbnode (keyblock);
keydb_release (kdbhd);
xfree (answer);
}
/* Change the passphrase of the secret key identified by USERNAME. */
void
keyedit_passwd (ctrl_t ctrl, const char *username)
{
gpg_error_t err;
PKT_public_key *pk;
kbnode_t keyblock = NULL;
pk = xtrycalloc (1, sizeof *pk);
if (!pk)
{
err = gpg_error_from_syserror ();
goto leave;
}
err = getkey_byname (ctrl, NULL, pk, username, 1, &keyblock);
if (err)
goto leave;
err = change_passphrase (ctrl, keyblock);
leave:
release_kbnode (keyblock);
free_public_key (pk);
if (err)
{
log_info ("error changing the passphrase for '%s': %s\n",
username, gpg_strerror (err));
write_status_error ("keyedit.passwd", err);
}
else
write_status_text (STATUS_SUCCESS, "keyedit.passwd");
}
/* Helper for quick commands to find the keyblock for USERNAME.
* Returns on success the key database handle at R_KDBHD and the
* keyblock at R_KEYBLOCK. */
static gpg_error_t
quick_find_keyblock (ctrl_t ctrl, const char *username,
KEYDB_HANDLE *r_kdbhd, kbnode_t *r_keyblock)
{
gpg_error_t err;
KEYDB_HANDLE kdbhd = NULL;
kbnode_t keyblock = NULL;
KEYDB_SEARCH_DESC desc;
kbnode_t node;
*r_kdbhd = NULL;
*r_keyblock = NULL;
/* Search the key; we don't want the whole getkey stuff here. */
kdbhd = keydb_new ();
if (!kdbhd)
{
/* Note that keydb_new has already used log_error. */
err = gpg_error_from_syserror ();
goto leave;
}
err = classify_user_id (username, &desc, 1);
if (!err)
err = keydb_search (kdbhd, &desc, 1, NULL);
if (!err)
{
err = keydb_get_keyblock (kdbhd, &keyblock);
if (err)
{
log_error (_("error reading keyblock: %s\n"), gpg_strerror (err));
goto leave;
}
/* Now with the keyblock retrieved, search again to detect an
ambiguous specification. We need to save the found state so
that we can do an update later. */
keydb_push_found_state (kdbhd);
err = keydb_search (kdbhd, &desc, 1, NULL);
if (!err)
err = gpg_error (GPG_ERR_AMBIGUOUS_NAME);
else if (gpg_err_code (err) == GPG_ERR_NOT_FOUND)
err = 0;
keydb_pop_found_state (kdbhd);
if (!err)
{
/* We require the secret primary key to set the primary UID. */
node = find_kbnode (keyblock, PKT_PUBLIC_KEY);
log_assert (node);
err = agent_probe_secret_key (ctrl, node->pkt->pkt.public_key);
}
}
else if (gpg_err_code (err) == GPG_ERR_NOT_FOUND)
err = gpg_error (GPG_ERR_NO_PUBKEY);
if (err)
{
log_error (_("key \"%s\" not found: %s\n"),
username, gpg_strerror (err));
goto leave;
}
fix_keyblock (ctrl, &keyblock);
merge_keys_and_selfsig (ctrl, keyblock);
*r_keyblock = keyblock;
keyblock = NULL;
*r_kdbhd = kdbhd;
kdbhd = NULL;
leave:
release_kbnode (keyblock);
keydb_release (kdbhd);
return err;
}
/* Unattended adding of a new keyid. USERNAME specifies the
key. NEWUID is the new user id to add to the key. */
void
keyedit_quick_adduid (ctrl_t ctrl, const char *username, const char *newuid)
{
gpg_error_t err;
KEYDB_HANDLE kdbhd = NULL;
kbnode_t keyblock = NULL;
char *uidstring = NULL;
uidstring = xstrdup (newuid);
trim_spaces (uidstring);
if (!*uidstring)
{
log_error ("%s\n", gpg_strerror (GPG_ERR_INV_USER_ID));
goto leave;
}
#ifdef HAVE_W32_SYSTEM
/* See keyedit_menu for why we need this. */
check_trustdb_stale (ctrl);
#endif
/* Search the key; we don't want the whole getkey stuff here. */
err = quick_find_keyblock (ctrl, username, &kdbhd, &keyblock);
if (err)
goto leave;
if (menu_adduid (ctrl, keyblock, 0, NULL, uidstring))
{
err = keydb_update_keyblock (ctrl, kdbhd, keyblock);
if (err)
{
log_error (_("update failed: %s\n"), gpg_strerror (err));
goto leave;
}
if (update_trust)
revalidation_mark (ctrl);
}
leave:
xfree (uidstring);
release_kbnode (keyblock);
keydb_release (kdbhd);
}
/* Unattended revocation of a keyid. USERNAME specifies the
key. UIDTOREV is the user id revoke from the key. */
void
keyedit_quick_revuid (ctrl_t ctrl, const char *username, const char *uidtorev)
{
gpg_error_t err;
KEYDB_HANDLE kdbhd = NULL;
kbnode_t keyblock = NULL;
kbnode_t node;
int modified = 0;
size_t revlen;
size_t valid_uids;
#ifdef HAVE_W32_SYSTEM
/* See keyedit_menu for why we need this. */
check_trustdb_stale (ctrl);
#endif
/* Search the key; we don't want the whole getkey stuff here. */
err = quick_find_keyblock (ctrl, username, &kdbhd, &keyblock);
if (err)
goto leave;
/* Too make sure that we do not revoke the last valid UID, we first
count how many valid UIDs there are. */
valid_uids = 0;
for (node = keyblock; node; node = node->next)
valid_uids += (node->pkt->pkttype == PKT_USER_ID
&& !node->pkt->pkt.user_id->flags.revoked
&& !node->pkt->pkt.user_id->flags.expired);
/* Find the right UID. */
revlen = strlen (uidtorev);
for (node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_USER_ID
&& revlen == node->pkt->pkt.user_id->len
&& !memcmp (node->pkt->pkt.user_id->name, uidtorev, revlen))
{
struct revocation_reason_info *reason;
/* Make sure that we do not revoke the last valid UID. */
if (valid_uids == 1
&& ! node->pkt->pkt.user_id->flags.revoked
&& ! node->pkt->pkt.user_id->flags.expired)
{
log_error (_("cannot revoke the last valid user ID.\n"));
err = gpg_error (GPG_ERR_INV_USER_ID);
goto leave;
}
reason = get_default_uid_revocation_reason ();
err = core_revuid (ctrl, keyblock, node, reason, &modified);
release_revocation_reason_info (reason);
if (err)
goto leave;
err = keydb_update_keyblock (ctrl, kdbhd, keyblock);
if (err)
{
log_error (_("update failed: %s\n"), gpg_strerror (err));
goto leave;
}
revalidation_mark (ctrl);
goto leave;
}
}
err = gpg_error (GPG_ERR_NO_USER_ID);
leave:
if (err)
{
log_error (_("revoking the user ID failed: %s\n"), gpg_strerror (err));
write_status_error ("keyedit.revoke.uid", err);
}
release_kbnode (keyblock);
keydb_release (kdbhd);
}
/* Unattended setting of the primary uid. USERNAME specifies the key.
PRIMARYUID is the user id which shall be primary. */
void
keyedit_quick_set_primary (ctrl_t ctrl, const char *username,
const char *primaryuid)
{
gpg_error_t err;
KEYDB_HANDLE kdbhd = NULL;
kbnode_t keyblock = NULL;
kbnode_t node;
size_t primaryuidlen;
int any;
#ifdef HAVE_W32_SYSTEM
/* See keyedit_menu for why we need this. */
check_trustdb_stale (ctrl);
#endif
err = quick_find_keyblock (ctrl, username, &kdbhd, &keyblock);
if (err)
goto leave;
/* Find and mark the UID - we mark only the first valid one. */
primaryuidlen = strlen (primaryuid);
any = 0;
for (node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_USER_ID
&& !any
&& !node->pkt->pkt.user_id->flags.revoked
&& !node->pkt->pkt.user_id->flags.expired
&& primaryuidlen == node->pkt->pkt.user_id->len
&& !memcmp (node->pkt->pkt.user_id->name, primaryuid, primaryuidlen))
{
node->flag |= NODFLG_SELUID;
any = 1;
}
else
node->flag &= ~NODFLG_SELUID;
}
if (!any)
err = gpg_error (GPG_ERR_NO_USER_ID);
else if (menu_set_primary_uid (ctrl, keyblock))
{
merge_keys_and_selfsig (ctrl, keyblock);
err = keydb_update_keyblock (ctrl, kdbhd, keyblock);
if (err)
{
log_error (_("update failed: %s\n"), gpg_strerror (err));
goto leave;
}
revalidation_mark (ctrl);
}
else
err = gpg_error (GPG_ERR_GENERAL);
if (err)
log_error (_("setting the primary user ID failed: %s\n"),
gpg_strerror (err));
leave:
release_kbnode (keyblock);
keydb_release (kdbhd);
}
/* Find a keyblock by fingerprint because only this uniquely
* identifies a key and may thus be used to select a key for
* unattended subkey creation os key signing. */
static gpg_error_t
find_by_primary_fpr (ctrl_t ctrl, const char *fpr,
kbnode_t *r_keyblock, KEYDB_HANDLE *r_kdbhd)
{
gpg_error_t err;
kbnode_t keyblock = NULL;
KEYDB_HANDLE kdbhd = NULL;
KEYDB_SEARCH_DESC desc;
byte fprbin[MAX_FINGERPRINT_LEN];
size_t fprlen;
*r_keyblock = NULL;
*r_kdbhd = NULL;
if (classify_user_id (fpr, &desc, 1)
|| desc.mode != KEYDB_SEARCH_MODE_FPR)
{
log_error (_("\"%s\" is not a fingerprint\n"), fpr);
err = gpg_error (GPG_ERR_INV_NAME);
goto leave;
}
- err = get_pubkey_byname (ctrl, NULL, NULL, fpr, &keyblock, &kdbhd, 1, 1);
+ err = get_pubkey_byname (ctrl, GET_PUBKEY_NO_AKL,
+ NULL, NULL, fpr, &keyblock, &kdbhd, 1);
if (err)
{
log_error (_("key \"%s\" not found: %s\n"), fpr, gpg_strerror (err));
goto leave;
}
/* Check that the primary fingerprint has been given. */
fingerprint_from_pk (keyblock->pkt->pkt.public_key, fprbin, &fprlen);
if (desc.mode == KEYDB_SEARCH_MODE_FPR
&& fprlen == desc.fprlen
&& !memcmp (fprbin, desc.u.fpr, fprlen))
;
else
{
log_error (_("\"%s\" is not the primary fingerprint\n"), fpr);
err = gpg_error (GPG_ERR_INV_NAME);
goto leave;
}
*r_keyblock = keyblock;
keyblock = NULL;
*r_kdbhd = kdbhd;
kdbhd = NULL;
err = 0;
leave:
release_kbnode (keyblock);
keydb_release (kdbhd);
return err;
}
/* Unattended key signing function. If the key specifified by FPR is
available and FPR is the primary fingerprint all user ids of the
key are signed using the default signing key. If UIDS is an empty
list all usable UIDs are signed, if it is not empty, only those
user ids matching one of the entries of the list are signed. With
LOCAL being true the signatures are marked as non-exportable. */
void
keyedit_quick_sign (ctrl_t ctrl, const char *fpr, strlist_t uids,
strlist_t locusr, int local)
{
gpg_error_t err;
kbnode_t keyblock = NULL;
KEYDB_HANDLE kdbhd = NULL;
int modified = 0;
PKT_public_key *pk;
kbnode_t node;
strlist_t sl;
int any;
#ifdef HAVE_W32_SYSTEM
/* See keyedit_menu for why we need this. */
check_trustdb_stale (ctrl);
#endif
/* We require a fingerprint because only this uniquely identifies a
key and may thus be used to select a key for unattended key
signing. */
if (find_by_primary_fpr (ctrl, fpr, &keyblock, &kdbhd))
goto leave;
if (fix_keyblock (ctrl, &keyblock))
modified++;
/* Give some info in verbose. */
if (opt.verbose)
{
show_key_with_all_names (ctrl, es_stdout, keyblock, 0,
1/*with_revoker*/, 1/*with_fingerprint*/,
0, 0, 1);
es_fflush (es_stdout);
}
pk = keyblock->pkt->pkt.public_key;
if (pk->flags.revoked)
{
if (!opt.verbose)
show_key_with_all_names (ctrl, es_stdout, keyblock, 0, 0, 0, 0, 0, 1);
log_error ("%s%s", _("Key is revoked."), _(" Unable to sign.\n"));
goto leave;
}
/* Set the flags according to the UIDS list. Fixme: We may want to
use classify_user_id along with dedicated compare functions so
that we match the same way as in the key lookup. */
any = 0;
menu_select_uid (keyblock, 0); /* Better clear the flags first. */
for (sl=uids; sl; sl = sl->next)
{
const char *name = sl->d;
int count = 0;
sl->flags &= ~(1|2); /* Clear flags used for error reporting. */
for (node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_USER_ID)
{
PKT_user_id *uid = node->pkt->pkt.user_id;
if (uid->attrib_data)
;
else if (*name == '='
&& strlen (name+1) == uid->len
&& !memcmp (uid->name, name + 1, uid->len))
{ /* Exact match - we don't do a check for ambiguity
* in this case. */
node->flag |= NODFLG_SELUID;
if (any != -1)
{
sl->flags |= 1; /* Report as found. */
any = 1;
}
}
else if (ascii_memistr (uid->name, uid->len,
*name == '*'? name+1:name))
{
node->flag |= NODFLG_SELUID;
if (any != -1)
{
sl->flags |= 1; /* Report as found. */
any = 1;
}
count++;
}
}
}
if (count > 1)
{
any = -1; /* Force failure at end. */
sl->flags |= 2; /* Report as ambiguous. */
}
}
/* Check whether all given user ids were found. */
for (sl=uids; sl; sl = sl->next)
if (!(sl->flags & 1))
any = -1; /* That user id was not found. */
/* Print an error if there was a problem with the user ids. */
if (uids && any < 1)
{
if (!opt.verbose)
show_key_with_all_names (ctrl, es_stdout, keyblock, 0, 0, 0, 0, 0, 1);
es_fflush (es_stdout);
for (sl=uids; sl; sl = sl->next)
{
if ((sl->flags & 2))
log_info (_("Invalid user ID '%s': %s\n"),
sl->d, gpg_strerror (GPG_ERR_AMBIGUOUS_NAME));
else if (!(sl->flags & 1))
log_info (_("Invalid user ID '%s': %s\n"),
sl->d, gpg_strerror (GPG_ERR_NOT_FOUND));
}
log_error ("%s %s", _("No matching user IDs."), _("Nothing to sign.\n"));
goto leave;
}
/* Sign. */
sign_uids (ctrl, es_stdout, keyblock, locusr, &modified, local, 0, 0, 0, 1);
es_fflush (es_stdout);
if (modified)
{
err = keydb_update_keyblock (ctrl, kdbhd, keyblock);
if (err)
{
log_error (_("update failed: %s\n"), gpg_strerror (err));
goto leave;
}
}
else
log_info (_("Key not changed so no update needed.\n"));
if (update_trust)
revalidation_mark (ctrl);
leave:
release_kbnode (keyblock);
keydb_release (kdbhd);
}
/* Unattended subkey creation function.
*
*/
void
keyedit_quick_addkey (ctrl_t ctrl, const char *fpr, const char *algostr,
const char *usagestr, const char *expirestr)
{
gpg_error_t err;
kbnode_t keyblock;
KEYDB_HANDLE kdbhd;
int modified = 0;
PKT_public_key *pk;
#ifdef HAVE_W32_SYSTEM
/* See keyedit_menu for why we need this. */
check_trustdb_stale (ctrl);
#endif
/* We require a fingerprint because only this uniquely identifies a
* key and may thus be used to select a key for unattended subkey
* creation. */
if (find_by_primary_fpr (ctrl, fpr, &keyblock, &kdbhd))
goto leave;
if (fix_keyblock (ctrl, &keyblock))
modified++;
pk = keyblock->pkt->pkt.public_key;
if (pk->flags.revoked)
{
if (!opt.verbose)
show_key_with_all_names (ctrl, es_stdout, keyblock, 0, 0, 0, 0, 0, 1);
log_error ("%s%s", _("Key is revoked."), "\n");
goto leave;
}
/* Create the subkey. Note that the called function already prints
* an error message. */
if (!generate_subkeypair (ctrl, keyblock, algostr, usagestr, expirestr))
modified = 1;
es_fflush (es_stdout);
/* Store. */
if (modified)
{
err = keydb_update_keyblock (ctrl, kdbhd, keyblock);
if (err)
{
log_error (_("update failed: %s\n"), gpg_strerror (err));
goto leave;
}
}
else
log_info (_("Key not changed so no update needed.\n"));
leave:
release_kbnode (keyblock);
keydb_release (kdbhd);
}
/* Unattended expiration setting function for the main key. If
* SUBKEYFPRS is not NULL and SUBKEYSFPRS[0] is neither NULL, it is
* expected to be an array of fingerprints for subkeys to change. It
* may also be an array which just one item "*" to indicate that all
* keys shall be set to that expiration date.
*/
void
keyedit_quick_set_expire (ctrl_t ctrl, const char *fpr, const char *expirestr,
char **subkeyfprs)
{
gpg_error_t err;
kbnode_t keyblock, node;
KEYDB_HANDLE kdbhd;
int modified = 0;
PKT_public_key *pk;
u32 expire;
int primary_only = 0;
int idx;
#ifdef HAVE_W32_SYSTEM
/* See keyedit_menu for why we need this. */
check_trustdb_stale (ctrl);
#endif
/* We require a fingerprint because only this uniquely identifies a
* key and may thus be used to select a key for unattended
* expiration setting. */
err = find_by_primary_fpr (ctrl, fpr, &keyblock, &kdbhd);
if (err)
goto leave;
if (fix_keyblock (ctrl, &keyblock))
modified++;
pk = keyblock->pkt->pkt.public_key;
if (pk->flags.revoked)
{
if (!opt.verbose)
show_key_with_all_names (ctrl, es_stdout, keyblock, 0, 0, 0, 0, 0, 1);
log_error ("%s%s", _("Key is revoked."), "\n");
err = gpg_error (GPG_ERR_CERT_REVOKED);
goto leave;
}
expire = parse_expire_string (expirestr);
if (expire == (u32)-1 )
{
log_error (_("'%s' is not a valid expiration time\n"), expirestr);
err = gpg_error (GPG_ERR_INV_VALUE);
goto leave;
}
if (expire)
expire += make_timestamp ();
/* Check whether a subkey's expiration time shall be changed or the
* expiration time of all keys. */
if (!subkeyfprs || !subkeyfprs[0])
primary_only = 1;
else if ( !strcmp (subkeyfprs[0], "*") && !subkeyfprs[1])
{
/* Change all subkeys keys which have not been revoked and are
* not yet expired. */
merge_keys_and_selfsig (ctrl, keyblock);
for (node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY
&& (pk = node->pkt->pkt.public_key)
&& !pk->flags.revoked
&& !pk->has_expired)
node->flag |= NODFLG_SELKEY;
}
}
else
{
/* Change specified subkeys. */
KEYDB_SEARCH_DESC desc;
byte fprbin[MAX_FINGERPRINT_LEN];
size_t fprlen;
err = 0;
merge_keys_and_selfsig (ctrl, keyblock);
for (idx=0; subkeyfprs[idx]; idx++)
{
int any = 0;
/* Parse the fingerprint. */
if (classify_user_id (subkeyfprs[idx], &desc, 1)
|| desc.mode != KEYDB_SEARCH_MODE_FPR)
{
log_error (_("\"%s\" is not a proper fingerprint\n"),
subkeyfprs[idx] );
if (!err)
err = gpg_error (GPG_ERR_INV_NAME);
continue;
}
/* Set the flag for the matching non revoked subkey. */
for (node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY
&& (pk = node->pkt->pkt.public_key)
&& !pk->flags.revoked )
{
fingerprint_from_pk (pk, fprbin, &fprlen);
if (fprlen == 20 && !memcmp (fprbin, desc.u.fpr, 20))
{
node->flag |= NODFLG_SELKEY;
any = 1;
}
}
}
if (!any)
{
log_error (_("subkey \"%s\" not found\n"), subkeyfprs[idx]);
if (!err)
err = gpg_error (GPG_ERR_NOT_FOUND);
}
}
if (err)
goto leave;
}
/* Set the new expiration date. */
err = menu_expire (ctrl, keyblock, primary_only? 1 : 2, expire);
if (gpg_err_code (err) == GPG_ERR_TRUE)
modified = 1;
else if (err)
goto leave;
es_fflush (es_stdout);
/* Store. */
if (modified)
{
err = keydb_update_keyblock (ctrl, kdbhd, keyblock);
if (err)
{
log_error (_("update failed: %s\n"), gpg_strerror (err));
goto leave;
}
if (update_trust)
revalidation_mark (ctrl);
}
else
log_info (_("Key not changed so no update needed.\n"));
leave:
release_kbnode (keyblock);
keydb_release (kdbhd);
if (err)
write_status_error ("set_expire", err);
}
static void
tty_print_notations (int indent, PKT_signature * sig)
{
int first = 1;
struct notation *notation, *nd;
if (indent < 0)
{
first = 0;
indent = -indent;
}
notation = sig_to_notation (sig);
for (nd = notation; nd; nd = nd->next)
{
if (!first)
tty_printf ("%*s", indent, "");
else
first = 0;
tty_print_utf8_string (nd->name, strlen (nd->name));
tty_printf ("=");
tty_print_utf8_string (nd->value, strlen (nd->value));
tty_printf ("\n");
}
free_notation (notation);
}
/*
* Show preferences of a public keyblock.
*/
static void
show_prefs (PKT_user_id * uid, PKT_signature * selfsig, int verbose)
{
const prefitem_t fake = { 0, 0 };
const prefitem_t *prefs;
int i;
if (!uid)
return;
if (uid->prefs)
prefs = uid->prefs;
else if (verbose)
prefs = &fake;
else
return;
if (verbose)
{
int any, des_seen = 0, sha1_seen = 0, uncomp_seen = 0;
tty_printf (" ");
tty_printf (_("Cipher: "));
for (i = any = 0; prefs[i].type; i++)
{
if (prefs[i].type == PREFTYPE_SYM)
{
if (any)
tty_printf (", ");
any = 1;
/* We don't want to display strings for experimental algos */
if (!openpgp_cipher_test_algo (prefs[i].value)
&& prefs[i].value < 100)
tty_printf ("%s", openpgp_cipher_algo_name (prefs[i].value));
else
tty_printf ("[%d]", prefs[i].value);
if (prefs[i].value == CIPHER_ALGO_3DES)
des_seen = 1;
}
}
if (!des_seen)
{
if (any)
tty_printf (", ");
tty_printf ("%s", openpgp_cipher_algo_name (CIPHER_ALGO_3DES));
}
tty_printf ("\n ");
tty_printf (_("AEAD: "));
for (i = any = 0; prefs[i].type; i++)
{
if (prefs[i].type == PREFTYPE_AEAD)
{
if (any)
tty_printf (", ");
any = 1;
/* We don't want to display strings for experimental algos */
if (!openpgp_aead_test_algo (prefs[i].value)
&& prefs[i].value < 100)
tty_printf ("%s", openpgp_aead_algo_name (prefs[i].value));
else
tty_printf ("[%d]", prefs[i].value);
}
}
tty_printf ("\n ");
tty_printf (_("Digest: "));
for (i = any = 0; prefs[i].type; i++)
{
if (prefs[i].type == PREFTYPE_HASH)
{
if (any)
tty_printf (", ");
any = 1;
/* We don't want to display strings for experimental algos */
if (!gcry_md_test_algo (prefs[i].value) && prefs[i].value < 100)
tty_printf ("%s", gcry_md_algo_name (prefs[i].value));
else
tty_printf ("[%d]", prefs[i].value);
if (prefs[i].value == DIGEST_ALGO_SHA1)
sha1_seen = 1;
}
}
if (!sha1_seen)
{
if (any)
tty_printf (", ");
tty_printf ("%s", gcry_md_algo_name (DIGEST_ALGO_SHA1));
}
tty_printf ("\n ");
tty_printf (_("Compression: "));
for (i = any = 0; prefs[i].type; i++)
{
if (prefs[i].type == PREFTYPE_ZIP)
{
const char *s = compress_algo_to_string (prefs[i].value);
if (any)
tty_printf (", ");
any = 1;
/* We don't want to display strings for experimental algos */
if (s && prefs[i].value < 100)
tty_printf ("%s", s);
else
tty_printf ("[%d]", prefs[i].value);
if (prefs[i].value == COMPRESS_ALGO_NONE)
uncomp_seen = 1;
}
}
if (!uncomp_seen)
{
if (any)
tty_printf (", ");
else
{
tty_printf ("%s", compress_algo_to_string (COMPRESS_ALGO_ZIP));
tty_printf (", ");
}
tty_printf ("%s", compress_algo_to_string (COMPRESS_ALGO_NONE));
}
if (uid->flags.mdc || uid->flags.aead || !uid->flags.ks_modify)
{
tty_printf ("\n ");
tty_printf (_("Features: "));
any = 0;
if (uid->flags.mdc)
{
tty_printf ("MDC");
any = 1;
}
if (!uid->flags.aead)
{
if (any)
tty_printf (", ");
tty_printf ("AEAD");
}
if (!uid->flags.ks_modify)
{
if (any)
tty_printf (", ");
tty_printf (_("Keyserver no-modify"));
}
}
tty_printf ("\n");
if (selfsig)
{
const byte *pref_ks;
size_t pref_ks_len;
pref_ks = parse_sig_subpkt (selfsig->hashed,
SIGSUBPKT_PREF_KS, &pref_ks_len);
if (pref_ks && pref_ks_len)
{
tty_printf (" ");
tty_printf (_("Preferred keyserver: "));
tty_print_utf8_string (pref_ks, pref_ks_len);
tty_printf ("\n");
}
if (selfsig->flags.notation)
{
tty_printf (" ");
tty_printf (_("Notations: "));
tty_print_notations (5 + strlen (_("Notations: ")), selfsig);
}
}
}
else
{
tty_printf (" ");
for (i = 0; prefs[i].type; i++)
{
tty_printf (" %c%d", prefs[i].type == PREFTYPE_SYM ? 'S' :
prefs[i].type == PREFTYPE_AEAD ? 'A' :
prefs[i].type == PREFTYPE_HASH ? 'H' :
prefs[i].type == PREFTYPE_ZIP ? 'Z' : '?',
prefs[i].value);
}
if (uid->flags.mdc)
tty_printf (" [mdc]");
if (uid->flags.aead)
tty_printf (" [aead]");
if (!uid->flags.ks_modify)
tty_printf (" [no-ks-modify]");
tty_printf ("\n");
}
}
/* This is the version of show_key_with_all_names used when
opt.with_colons is used. It prints all available data in a easy to
parse format and does not translate utf8 */
static void
show_key_with_all_names_colon (ctrl_t ctrl, estream_t fp, kbnode_t keyblock)
{
KBNODE node;
int i, j, ulti_hack = 0;
byte pk_version = 0;
PKT_public_key *primary = NULL;
int have_seckey;
if (!fp)
fp = es_stdout;
/* the keys */
for (node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_KEY
|| (node->pkt->pkttype == PKT_PUBLIC_SUBKEY))
{
PKT_public_key *pk = node->pkt->pkt.public_key;
u32 keyid[2];
if (node->pkt->pkttype == PKT_PUBLIC_KEY)
{
pk_version = pk->version;
primary = pk;
}
keyid_from_pk (pk, keyid);
have_seckey = !agent_probe_secret_key (ctrl, pk);
if (node->pkt->pkttype == PKT_PUBLIC_KEY)
es_fputs (have_seckey? "sec:" : "pub:", fp);
else
es_fputs (have_seckey? "ssb:" : "sub:", fp);
if (!pk->flags.valid)
es_putc ('i', fp);
else if (pk->flags.revoked)
es_putc ('r', fp);
else if (pk->has_expired)
es_putc ('e', fp);
else if (!(opt.fast_list_mode || opt.no_expensive_trust_checks))
{
int trust = get_validity_info (ctrl, keyblock, pk, NULL);
if (trust == 'u')
ulti_hack = 1;
es_putc (trust, fp);
}
es_fprintf (fp, ":%u:%d:%08lX%08lX:%lu:%lu::",
nbits_from_pk (pk),
pk->pubkey_algo,
(ulong) keyid[0], (ulong) keyid[1],
(ulong) pk->timestamp, (ulong) pk->expiredate);
if (node->pkt->pkttype == PKT_PUBLIC_KEY
&& !(opt.fast_list_mode || opt.no_expensive_trust_checks))
es_putc (get_ownertrust_info (ctrl, pk, 0), fp);
es_putc (':', fp);
es_putc (':', fp);
es_putc (':', fp);
/* Print capabilities. */
if ((pk->pubkey_usage & PUBKEY_USAGE_ENC))
es_putc ('e', fp);
if ((pk->pubkey_usage & PUBKEY_USAGE_SIG))
es_putc ('s', fp);
if ((pk->pubkey_usage & PUBKEY_USAGE_CERT))
es_putc ('c', fp);
if ((pk->pubkey_usage & PUBKEY_USAGE_AUTH))
es_putc ('a', fp);
es_putc ('\n', fp);
print_fingerprint (ctrl, fp, pk, 0);
print_revokers (fp, pk);
}
}
/* the user ids */
i = 0;
for (node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_USER_ID)
{
PKT_user_id *uid = node->pkt->pkt.user_id;
++i;
if (uid->attrib_data)
es_fputs ("uat:", fp);
else
es_fputs ("uid:", fp);
if (uid->flags.revoked)
es_fputs ("r::::::::", fp);
else if (uid->flags.expired)
es_fputs ("e::::::::", fp);
else if (opt.fast_list_mode || opt.no_expensive_trust_checks)
es_fputs ("::::::::", fp);
else
{
int uid_validity;
if (primary && !ulti_hack)
uid_validity = get_validity_info (ctrl, keyblock, primary, uid);
else
uid_validity = 'u';
es_fprintf (fp, "%c::::::::", uid_validity);
}
if (uid->attrib_data)
es_fprintf (fp, "%u %lu", uid->numattribs, uid->attrib_len);
else
es_write_sanitized (fp, uid->name, uid->len, ":", NULL);
es_putc (':', fp);
/* signature class */
es_putc (':', fp);
/* capabilities */
es_putc (':', fp);
/* preferences */
if (pk_version > 3 || uid->selfsigversion > 3)
{
const prefitem_t *prefs = uid->prefs;
for (j = 0; prefs && prefs[j].type; j++)
{
if (j)
es_putc (' ', fp);
es_fprintf (fp,
"%c%d", prefs[j].type == PREFTYPE_SYM ? 'S' :
prefs[j].type == PREFTYPE_HASH ? 'H' :
prefs[j].type == PREFTYPE_ZIP ? 'Z' : '?',
prefs[j].value);
}
if (uid->flags.mdc)
es_fputs (",mdc", fp);
if (uid->flags.aead)
es_fputs (",aead", fp);
if (!uid->flags.ks_modify)
es_fputs (",no-ks-modify", fp);
}
es_putc (':', fp);
/* flags */
es_fprintf (fp, "%d,", i);
if (uid->flags.primary)
es_putc ('p', fp);
if (uid->flags.revoked)
es_putc ('r', fp);
if (uid->flags.expired)
es_putc ('e', fp);
if ((node->flag & NODFLG_SELUID))
es_putc ('s', fp);
if ((node->flag & NODFLG_MARK_A))
es_putc ('m', fp);
es_putc (':', fp);
if (opt.trust_model == TM_TOFU || opt.trust_model == TM_TOFU_PGP)
{
#ifdef USE_TOFU
enum tofu_policy policy;
if (! tofu_get_policy (ctrl, primary, uid, &policy)
&& policy != TOFU_POLICY_NONE)
es_fprintf (fp, "%s", tofu_policy_str (policy));
#endif /*USE_TOFU*/
}
es_putc (':', fp);
es_putc ('\n', fp);
}
}
}
static void
show_names (ctrl_t ctrl, estream_t fp,
kbnode_t keyblock, PKT_public_key * pk, unsigned int flag,
int with_prefs)
{
KBNODE node;
int i = 0;
for (node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_USER_ID && !is_deleted_kbnode (node))
{
PKT_user_id *uid = node->pkt->pkt.user_id;
++i;
if (!flag || (flag && (node->flag & flag)))
{
if (!(flag & NODFLG_MARK_A) && pk)
tty_fprintf (fp, "%s ", uid_trust_string_fixed (ctrl, pk, uid));
if (flag & NODFLG_MARK_A)
tty_fprintf (fp, " ");
else if (node->flag & NODFLG_SELUID)
tty_fprintf (fp, "(%d)* ", i);
else if (uid->flags.primary)
tty_fprintf (fp, "(%d). ", i);
else
tty_fprintf (fp, "(%d) ", i);
tty_print_utf8_string2 (fp, uid->name, uid->len, 0);
tty_fprintf (fp, "\n");
if (with_prefs && pk)
{
if (pk->version > 3 || uid->selfsigversion > 3)
{
PKT_signature *selfsig = NULL;
KBNODE signode;
for (signode = node->next;
signode && signode->pkt->pkttype == PKT_SIGNATURE;
signode = signode->next)
{
if (signode->pkt->pkt.signature->
flags.chosen_selfsig)
{
selfsig = signode->pkt->pkt.signature;
break;
}
}
show_prefs (uid, selfsig, with_prefs == 2);
}
else
tty_fprintf (fp, _("There are no preferences on a"
" PGP 2.x-style user ID.\n"));
}
}
}
}
}
/*
* Display the key a the user ids, if only_marked is true, do only so
* for user ids with mark A flag set and do not display the index
* number. If FP is not NULL print to the given stream and not to the
* tty (ignored in with-colons mode).
*/
static void
show_key_with_all_names (ctrl_t ctrl, estream_t fp,
KBNODE keyblock, int only_marked, int with_revoker,
int with_fpr, int with_subkeys, int with_prefs,
int nowarn)
{
gpg_error_t err;
kbnode_t node;
int i;
int do_warn = 0;
int have_seckey = 0;
char *serialno = NULL;
PKT_public_key *primary = NULL;
char pkstrbuf[PUBKEY_STRING_SIZE];
if (opt.with_colons)
{
show_key_with_all_names_colon (ctrl, fp, keyblock);
return;
}
/* the keys */
for (node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_KEY
|| (with_subkeys && node->pkt->pkttype == PKT_PUBLIC_SUBKEY
&& !is_deleted_kbnode (node)))
{
PKT_public_key *pk = node->pkt->pkt.public_key;
const char *otrust = "err";
const char *trust = "err";
if (node->pkt->pkttype == PKT_PUBLIC_KEY)
{
/* do it here, so that debug messages don't clutter the
* output */
static int did_warn = 0;
trust = get_validity_string (ctrl, pk, NULL);
otrust = get_ownertrust_string (ctrl, pk, 0);
/* Show a warning once */
if (!did_warn
&& (get_validity (ctrl, keyblock, pk, NULL, NULL, 0)
& TRUST_FLAG_PENDING_CHECK))
{
did_warn = 1;
do_warn = 1;
}
primary = pk;
}
if (pk->flags.revoked)
{
char *user = get_user_id_string_native (ctrl, pk->revoked.keyid);
tty_fprintf (fp,
_("The following key was revoked on"
" %s by %s key %s\n"),
revokestr_from_pk (pk),
gcry_pk_algo_name (pk->revoked.algo), user);
xfree (user);
}
if (with_revoker)
{
if (!pk->revkey && pk->numrevkeys)
BUG ();
else
for (i = 0; i < pk->numrevkeys; i++)
{
u32 r_keyid[2];
char *user;
const char *algo;
algo = gcry_pk_algo_name (pk->revkey[i].algid);
keyid_from_fingerprint (ctrl, pk->revkey[i].fpr,
pk->revkey[i].fprlen, r_keyid);
user = get_user_id_string_native (ctrl, r_keyid);
tty_fprintf (fp,
_("This key may be revoked by %s key %s"),
algo ? algo : "?", user);
if (pk->revkey[i].class & 0x40)
{
tty_fprintf (fp, " ");
tty_fprintf (fp, _("(sensitive)"));
}
tty_fprintf (fp, "\n");
xfree (user);
}
}
keyid_from_pk (pk, NULL);
xfree (serialno);
serialno = NULL;
{
char *hexgrip;
err = hexkeygrip_from_pk (pk, &hexgrip);
if (err)
{
log_error ("error computing a keygrip: %s\n",
gpg_strerror (err));
have_seckey = 0;
}
else
have_seckey = !agent_get_keyinfo (ctrl, hexgrip, &serialno, NULL);
xfree (hexgrip);
}
tty_fprintf
(fp, "%s%c %s/%s",
node->pkt->pkttype == PKT_PUBLIC_KEY && have_seckey? "sec" :
node->pkt->pkttype == PKT_PUBLIC_KEY ? "pub" :
have_seckey ? "ssb" :
"sub",
(node->flag & NODFLG_SELKEY) ? '*' : ' ',
pubkey_string (pk, pkstrbuf, sizeof pkstrbuf),
keystr (pk->keyid));
if (opt.legacy_list_mode)
tty_fprintf (fp, " ");
else
tty_fprintf (fp, "\n ");
tty_fprintf (fp, _("created: %s"), datestr_from_pk (pk));
tty_fprintf (fp, " ");
if (pk->flags.revoked)
tty_fprintf (fp, _("revoked: %s"), revokestr_from_pk (pk));
else if (pk->has_expired)
tty_fprintf (fp, _("expired: %s"), expirestr_from_pk (pk));
else
tty_fprintf (fp, _("expires: %s"), expirestr_from_pk (pk));
tty_fprintf (fp, " ");
tty_fprintf (fp, _("usage: %s"), usagestr_from_pk (pk, 1));
tty_fprintf (fp, "\n");
if (serialno)
{
/* The agent told us that a secret key is available and
that it has been stored on a card. */
tty_fprintf (fp, "%*s%s", opt.legacy_list_mode? 21:5, "",
_("card-no: "));
if (strlen (serialno) == 32
&& !strncmp (serialno, "D27600012401", 12))
{
/* This is an OpenPGP card. Print the relevant part. */
/* Example: D2760001240101010001000003470000 */
/* xxxxyyyyyyyy */
tty_fprintf (fp, "%.*s %.*s\n",
4, serialno+16, 8, serialno+20);
}
else
tty_fprintf (fp, "%s\n", serialno);
}
else if (pk->seckey_info
&& pk->seckey_info->is_protected
&& pk->seckey_info->s2k.mode == 1002)
{
/* FIXME: Check whether this code path is still used. */
tty_fprintf (fp, "%*s%s", opt.legacy_list_mode? 21:5, "",
_("card-no: "));
if (pk->seckey_info->ivlen == 16
&& !memcmp (pk->seckey_info->iv,
"\xD2\x76\x00\x01\x24\x01", 6))
{
/* This is an OpenPGP card. */
for (i = 8; i < 14; i++)
{
if (i == 10)
tty_fprintf (fp, " ");
tty_fprintf (fp, "%02X", pk->seckey_info->iv[i]);
}
}
else
{
/* Unknown card: Print all. */
for (i = 0; i < pk->seckey_info->ivlen; i++)
tty_fprintf (fp, "%02X", pk->seckey_info->iv[i]);
}
tty_fprintf (fp, "\n");
}
if (node->pkt->pkttype == PKT_PUBLIC_KEY
|| node->pkt->pkttype == PKT_SECRET_KEY)
{
if (opt.trust_model != TM_ALWAYS)
{
tty_fprintf (fp, "%*s",
opt.legacy_list_mode?
((int) keystrlen () + 13):5, "");
/* Ownertrust is only meaningful for the PGP or
classic trust models, or PGP combined with TOFU */
if (opt.trust_model == TM_PGP
|| opt.trust_model == TM_CLASSIC
|| opt.trust_model == TM_TOFU_PGP)
{
int width = 14 - strlen (otrust);
if (width <= 0)
width = 1;
tty_fprintf (fp, _("trust: %s"), otrust);
tty_fprintf (fp, "%*s", width, "");
}
tty_fprintf (fp, _("validity: %s"), trust);
tty_fprintf (fp, "\n");
}
if (node->pkt->pkttype == PKT_PUBLIC_KEY
&& (get_ownertrust (ctrl, pk) & TRUST_FLAG_DISABLED))
{
tty_fprintf (fp, "*** ");
tty_fprintf (fp, _("This key has been disabled"));
tty_fprintf (fp, "\n");
}
}
if ((node->pkt->pkttype == PKT_PUBLIC_KEY
|| node->pkt->pkttype == PKT_SECRET_KEY) && with_fpr)
{
print_fingerprint (ctrl, fp, pk, 2);
tty_fprintf (fp, "\n");
}
}
}
show_names (ctrl, fp,
keyblock, primary, only_marked ? NODFLG_MARK_A : 0, with_prefs);
if (do_warn && !nowarn)
tty_fprintf (fp, _("Please note that the shown key validity"
" is not necessarily correct\n"
"unless you restart the program.\n"));
xfree (serialno);
}
/* Display basic key information. This function is suitable to show
* information on the key without any dependencies on the trustdb or
* any other internal GnuPG stuff. KEYBLOCK may either be a public or
* a secret key. This function may be called with KEYBLOCK containing
* secret keys and thus the printing of "pub" vs. "sec" does only
* depend on the packet type and not by checking with gpg-agent. If
* PRINT_SEC ist set "sec" is printed instead of "pub". */
void
show_basic_key_info (ctrl_t ctrl, kbnode_t keyblock, int print_sec)
{
KBNODE node;
int i;
char pkstrbuf[PUBKEY_STRING_SIZE];
/* The primary key */
for (node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_KEY
|| node->pkt->pkttype == PKT_SECRET_KEY)
{
PKT_public_key *pk = node->pkt->pkt.public_key;
const char *tag;
if (node->pkt->pkttype == PKT_SECRET_KEY || print_sec)
tag = "sec";
else
tag = "pub";
/* Note, we use the same format string as in other show
functions to make the translation job easier. */
tty_printf ("%s %s/%s ",
tag,
pubkey_string (pk, pkstrbuf, sizeof pkstrbuf),
keystr_from_pk (pk));
tty_printf (_("created: %s"), datestr_from_pk (pk));
tty_printf (" ");
tty_printf (_("expires: %s"), expirestr_from_pk (pk));
tty_printf ("\n");
print_fingerprint (ctrl, NULL, pk, 3);
tty_printf ("\n");
}
}
/* The user IDs. */
for (i = 0, node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_USER_ID)
{
PKT_user_id *uid = node->pkt->pkt.user_id;
++i;
tty_printf (" ");
if (uid->flags.revoked)
tty_printf ("[%s] ", _("revoked"));
else if (uid->flags.expired)
tty_printf ("[%s] ", _("expired"));
tty_print_utf8_string (uid->name, uid->len);
tty_printf ("\n");
}
}
}
static void
show_key_and_fingerprint (ctrl_t ctrl, kbnode_t keyblock, int with_subkeys)
{
kbnode_t node;
PKT_public_key *pk = NULL;
char pkstrbuf[PUBKEY_STRING_SIZE];
for (node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_KEY)
{
pk = node->pkt->pkt.public_key;
tty_printf ("pub %s/%s %s ",
pubkey_string (pk, pkstrbuf, sizeof pkstrbuf),
keystr_from_pk(pk),
datestr_from_pk (pk));
}
else if (node->pkt->pkttype == PKT_USER_ID)
{
PKT_user_id *uid = node->pkt->pkt.user_id;
tty_print_utf8_string (uid->name, uid->len);
break;
}
}
tty_printf ("\n");
if (pk)
print_fingerprint (ctrl, NULL, pk, 2);
if (with_subkeys)
{
for (node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
pk = node->pkt->pkt.public_key;
tty_printf ("sub %s/%s %s [%s]\n",
pubkey_string (pk, pkstrbuf, sizeof pkstrbuf),
keystr_from_pk(pk),
datestr_from_pk (pk),
usagestr_from_pk (pk, 0));
print_fingerprint (ctrl, NULL, pk, 4);
}
}
}
}
/* Show a listing of the primary and its subkeys along with their
keygrips. */
static void
show_key_and_grip (kbnode_t keyblock)
{
kbnode_t node;
PKT_public_key *pk = NULL;
char pkstrbuf[PUBKEY_STRING_SIZE];
char *hexgrip;
for (node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_KEY
|| node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
pk = node->pkt->pkt.public_key;
tty_printf ("%s %s/%s %s [%s]\n",
node->pkt->pkttype == PKT_PUBLIC_KEY? "pub":"sub",
pubkey_string (pk, pkstrbuf, sizeof pkstrbuf),
keystr_from_pk(pk),
datestr_from_pk (pk),
usagestr_from_pk (pk, 0));
if (!hexkeygrip_from_pk (pk, &hexgrip))
{
tty_printf (" Keygrip: %s\n", hexgrip);
xfree (hexgrip);
}
}
}
}
/* Show a warning if no uids on the key have the primary uid flag
set. */
static void
no_primary_warning (KBNODE keyblock)
{
KBNODE node;
int have_primary = 0, uid_count = 0;
/* TODO: if we ever start behaving differently with a primary or
non-primary attribute ID, we will need to check for attributes
here as well. */
for (node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_USER_ID
&& node->pkt->pkt.user_id->attrib_data == NULL)
{
uid_count++;
if (node->pkt->pkt.user_id->flags.primary == 2)
{
have_primary = 1;
break;
}
}
}
if (uid_count > 1 && !have_primary)
log_info (_
("WARNING: no user ID has been marked as primary. This command"
" may\n cause a different user ID to become"
" the assumed primary.\n"));
}
/* Print a warning if the latest encryption subkey expires soon. This
function is called after the expire data of the primary key has
been changed. */
static void
subkey_expire_warning (kbnode_t keyblock)
{
u32 curtime = make_timestamp ();
kbnode_t node;
PKT_public_key *pk;
/* u32 mainexpire = 0; */
u32 subexpire = 0;
u32 latest_date = 0;
for (node = keyblock; node; node = node->next)
{
/* if (node->pkt->pkttype == PKT_PUBLIC_KEY) */
/* { */
/* pk = node->pkt->pkt.public_key; */
/* mainexpire = pk->expiredate; */
/* } */
if (node->pkt->pkttype != PKT_PUBLIC_SUBKEY)
continue;
pk = node->pkt->pkt.public_key;
if (!pk->flags.valid)
continue;
if (pk->flags.revoked)
continue;
if (pk->timestamp > curtime)
continue; /* Ignore future keys. */
if (!(pk->pubkey_usage & PUBKEY_USAGE_ENC))
continue; /* Not an encryption key. */
if (pk->timestamp > latest_date || (!pk->timestamp && !latest_date))
{
latest_date = pk->timestamp;
subexpire = pk->expiredate;
}
}
if (!subexpire)
return; /* No valid subkey with an expiration time. */
if (curtime + (10*86400) > subexpire)
{
log_info (_("WARNING: Your encryption subkey expires soon.\n"));
log_info (_("You may want to change its expiration date too.\n"));
}
}
/*
* Ask for a new user id, add the self-signature, and update the
* keyblock. If UIDSTRING is not NULL the user ID is generated
* unattended using that string. UIDSTRING is expected to be utf-8
* encoded and white space trimmed. Returns true if there is a new
* user id.
*/
static int
menu_adduid (ctrl_t ctrl, kbnode_t pub_keyblock,
int photo, const char *photo_name, const char *uidstring)
{
PKT_user_id *uid;
PKT_public_key *pk = NULL;
PKT_signature *sig = NULL;
PACKET *pkt;
KBNODE node;
KBNODE pub_where = NULL;
gpg_error_t err;
if (photo && uidstring)
return 0; /* Not allowed. */
for (node = pub_keyblock; node; pub_where = node, node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_KEY)
pk = node->pkt->pkt.public_key;
else if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
break;
}
if (!node) /* No subkey. */
pub_where = NULL;
log_assert (pk);
if (photo)
{
int hasattrib = 0;
for (node = pub_keyblock; node; node = node->next)
if (node->pkt->pkttype == PKT_USER_ID &&
node->pkt->pkt.user_id->attrib_data != NULL)
{
hasattrib = 1;
break;
}
/* It is legal but bad for compatibility to add a photo ID to a
v3 key as it means that PGP2 will not be able to use that key
anymore. Also, PGP may not expect a photo on a v3 key.
Don't bother to ask this if the key already has a photo - any
damage has already been done at that point. -dms */
if (pk->version == 3 && !hasattrib)
{
if (opt.expert)
{
tty_printf (_("WARNING: This is a PGP2-style key. "
"Adding a photo ID may cause some versions\n"
" of PGP to reject this key.\n"));
if (!cpr_get_answer_is_yes ("keyedit.v3_photo.okay",
_("Are you sure you still want "
"to add it? (y/N) ")))
return 0;
}
else
{
tty_printf (_("You may not add a photo ID to "
"a PGP2-style key.\n"));
return 0;
}
}
uid = generate_photo_id (ctrl, pk, photo_name);
}
else
uid = generate_user_id (pub_keyblock, uidstring);
if (!uid)
{
if (uidstring)
{
write_status_error ("adduid", gpg_error (304));
log_error ("%s", _("Such a user ID already exists on this key!\n"));
}
return 0;
}
- err = make_keysig_packet (ctrl, &sig, pk, uid, NULL, pk, 0x13, 0, 0, 0,
+ err = make_keysig_packet (ctrl, &sig, pk, uid, NULL, pk, 0x13, 0, 0,
keygen_add_std_prefs, pk, NULL);
if (err)
{
write_status_error ("keysig", err);
log_error ("signing failed: %s\n", gpg_strerror (err));
free_user_id (uid);
return 0;
}
/* Insert/append to public keyblock */
pkt = xmalloc_clear (sizeof *pkt);
pkt->pkttype = PKT_USER_ID;
pkt->pkt.user_id = uid;
node = new_kbnode (pkt);
if (pub_where)
insert_kbnode (pub_where, node, 0);
else
add_kbnode (pub_keyblock, node);
pkt = xmalloc_clear (sizeof *pkt);
pkt->pkttype = PKT_SIGNATURE;
pkt->pkt.signature = sig;
if (pub_where)
insert_kbnode (node, new_kbnode (pkt), 0);
else
add_kbnode (pub_keyblock, new_kbnode (pkt));
return 1;
}
/*
* Remove all selected userids from the keyring
*/
static void
menu_deluid (KBNODE pub_keyblock)
{
KBNODE node;
int selected = 0;
for (node = pub_keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_USER_ID)
{
selected = node->flag & NODFLG_SELUID;
if (selected)
{
/* Only cause a trust update if we delete a
non-revoked user id */
if (!node->pkt->pkt.user_id->flags.revoked)
update_trust = 1;
delete_kbnode (node);
}
}
else if (selected && node->pkt->pkttype == PKT_SIGNATURE)
delete_kbnode (node);
else if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
selected = 0;
}
commit_kbnode (&pub_keyblock);
}
static int
menu_delsig (ctrl_t ctrl, kbnode_t pub_keyblock)
{
KBNODE node;
PKT_user_id *uid = NULL;
int changed = 0;
for (node = pub_keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_USER_ID)
{
uid = (node->flag & NODFLG_SELUID) ? node->pkt->pkt.user_id : NULL;
}
else if (uid && node->pkt->pkttype == PKT_SIGNATURE)
{
int okay, valid, selfsig, inv_sig, no_key, other_err;
tty_printf ("uid ");
tty_print_utf8_string (uid->name, uid->len);
tty_printf ("\n");
okay = inv_sig = no_key = other_err = 0;
if (opt.with_colons)
valid = print_and_check_one_sig_colon (ctrl, pub_keyblock, node,
&inv_sig, &no_key,
&other_err, &selfsig, 1);
else
valid = print_and_check_one_sig (ctrl, pub_keyblock, node,
&inv_sig, &no_key, &other_err,
&selfsig, 1, 0);
if (valid)
{
okay = cpr_get_answer_yes_no_quit
("keyedit.delsig.valid",
_("Delete this good signature? (y/N/q)"));
/* Only update trust if we delete a good signature.
The other two cases do not affect trust. */
if (okay)
update_trust = 1;
}
else if (inv_sig || other_err)
okay = cpr_get_answer_yes_no_quit
("keyedit.delsig.invalid",
_("Delete this invalid signature? (y/N/q)"));
else if (no_key)
okay = cpr_get_answer_yes_no_quit
("keyedit.delsig.unknown",
_("Delete this unknown signature? (y/N/q)"));
if (okay == -1)
break;
if (okay && selfsig
&& !cpr_get_answer_is_yes
("keyedit.delsig.selfsig",
_("Really delete this self-signature? (y/N)")))
okay = 0;
if (okay)
{
delete_kbnode (node);
changed++;
}
}
else if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
uid = NULL;
}
if (changed)
{
commit_kbnode (&pub_keyblock);
tty_printf (ngettext("Deleted %d signature.\n",
"Deleted %d signatures.\n", changed), changed);
}
else
tty_printf (_("Nothing deleted.\n"));
return changed;
}
static int
menu_clean (ctrl_t ctrl, kbnode_t keyblock, int self_only)
{
KBNODE uidnode;
int modified = 0, select_all = !count_selected_uids (keyblock);
for (uidnode = keyblock->next;
uidnode && uidnode->pkt->pkttype != PKT_PUBLIC_SUBKEY;
uidnode = uidnode->next)
{
if (uidnode->pkt->pkttype == PKT_USER_ID
&& (uidnode->flag & NODFLG_SELUID || select_all))
{
int uids = 0, sigs = 0;
char *user = utf8_to_native (uidnode->pkt->pkt.user_id->name,
uidnode->pkt->pkt.user_id->len,
0);
clean_one_uid (ctrl, keyblock, uidnode, opt.verbose, self_only, &uids,
&sigs);
if (uids)
{
const char *reason;
if (uidnode->pkt->pkt.user_id->flags.revoked)
reason = _("revoked");
else if (uidnode->pkt->pkt.user_id->flags.expired)
reason = _("expired");
else
reason = _("invalid");
tty_printf (_("User ID \"%s\" compacted: %s\n"), user, reason);
modified = 1;
}
else if (sigs)
{
tty_printf (ngettext("User ID \"%s\": %d signature removed\n",
"User ID \"%s\": %d signatures removed\n",
sigs), user, sigs);
modified = 1;
}
else
{
tty_printf (self_only == 1 ?
_("User ID \"%s\": already minimized\n") :
_("User ID \"%s\": already clean\n"), user);
}
xfree (user);
}
}
return modified;
}
/*
* Remove some of the secondary keys
*/
static void
menu_delkey (KBNODE pub_keyblock)
{
KBNODE node;
int selected = 0;
for (node = pub_keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
selected = node->flag & NODFLG_SELKEY;
if (selected)
delete_kbnode (node);
}
else if (selected && node->pkt->pkttype == PKT_SIGNATURE)
delete_kbnode (node);
else
selected = 0;
}
commit_kbnode (&pub_keyblock);
/* No need to set update_trust here since signing keys are no
longer used to certify other keys, so there is no change in
trust when revoking/removing them. */
}
/*
* Ask for a new revoker, create the self-signature and put it into
* the keyblock. Returns true if there is a new revoker.
*/
static int
menu_addrevoker (ctrl_t ctrl, kbnode_t pub_keyblock, int sensitive)
{
PKT_public_key *pk = NULL;
PKT_public_key *revoker_pk = NULL;
PKT_signature *sig = NULL;
PACKET *pkt;
struct revocation_key revkey;
size_t fprlen;
int rc;
log_assert (pub_keyblock->pkt->pkttype == PKT_PUBLIC_KEY);
pk = pub_keyblock->pkt->pkt.public_key;
if (pk->numrevkeys == 0 && pk->version == 3)
{
/* It is legal but bad for compatibility to add a revoker to a
v3 key as it means that PGP2 will not be able to use that key
anymore. Also, PGP may not expect a revoker on a v3 key.
Don't bother to ask this if the key already has a revoker -
any damage has already been done at that point. -dms */
if (opt.expert)
{
tty_printf (_("WARNING: This is a PGP 2.x-style key. "
"Adding a designated revoker may cause\n"
" some versions of PGP to reject this key.\n"));
if (!cpr_get_answer_is_yes ("keyedit.v3_revoker.okay",
_("Are you sure you still want "
"to add it? (y/N) ")))
return 0;
}
else
{
tty_printf (_("You may not add a designated revoker to "
"a PGP 2.x-style key.\n"));
return 0;
}
}
for (;;)
{
char *answer;
free_public_key (revoker_pk);
revoker_pk = xmalloc_clear (sizeof (*revoker_pk));
tty_printf ("\n");
answer = cpr_get_utf8
("keyedit.add_revoker",
_("Enter the user ID of the designated revoker: "));
if (answer[0] == '\0' || answer[0] == CONTROL_D)
{
xfree (answer);
goto fail;
}
/* Note that I'm requesting CERT here, which usually implies
primary keys only, but some casual testing shows that PGP and
GnuPG both can handle a designated revocation from a subkey. */
revoker_pk->req_usage = PUBKEY_USAGE_CERT;
- rc = get_pubkey_byname (ctrl, NULL, revoker_pk, answer, NULL, NULL, 1, 1);
+ rc = get_pubkey_byname (ctrl, GET_PUBKEY_NO_AKL,
+ NULL, revoker_pk, answer, NULL, NULL, 1);
if (rc)
{
log_error (_("key \"%s\" not found: %s\n"), answer,
gpg_strerror (rc));
xfree (answer);
continue;
}
xfree (answer);
fingerprint_from_pk (revoker_pk, revkey.fpr, &fprlen);
if (fprlen != 20 && fprlen != 32)
{
log_error (_("cannot appoint a PGP 2.x style key as a "
"designated revoker\n"));
continue;
}
revkey.fprlen = fprlen;
revkey.class = 0x80;
if (sensitive)
revkey.class |= 0x40;
revkey.algid = revoker_pk->pubkey_algo;
if (cmp_public_keys (revoker_pk, pk) == 0)
{
/* This actually causes no harm (after all, a key that
designates itself as a revoker is the same as a
regular key), but it's easy enough to check. */
log_error (_("you cannot appoint a key as its own "
"designated revoker\n"));
continue;
}
keyid_from_pk (pk, NULL);
/* Does this revkey already exist? */
if (!pk->revkey && pk->numrevkeys)
BUG ();
else
{
int i;
for (i = 0; i < pk->numrevkeys; i++)
{
if (memcmp (&pk->revkey[i], &revkey,
sizeof (struct revocation_key)) == 0)
{
char buf[50];
log_error (_("this key has already been designated "
"as a revoker\n"));
format_keyid (pk_keyid (pk), KF_LONG, buf, sizeof (buf));
write_status_text (STATUS_ALREADY_SIGNED, buf);
break;
}
}
if (i < pk->numrevkeys)
continue;
}
- print_pubkey_info (ctrl, NULL, revoker_pk);
+ print_key_info (ctrl, NULL, 0, revoker_pk, 0);
print_fingerprint (ctrl, NULL, revoker_pk, 2);
tty_printf ("\n");
tty_printf (_("WARNING: appointing a key as a designated revoker "
"cannot be undone!\n"));
tty_printf ("\n");
if (!cpr_get_answer_is_yes ("keyedit.add_revoker.okay",
_("Are you sure you want to appoint this "
"key as a designated revoker? (y/N) ")))
continue;
free_public_key (revoker_pk);
revoker_pk = NULL;
break;
}
- rc = make_keysig_packet (ctrl, &sig, pk, NULL, NULL, pk, 0x1F, 0, 0, 0,
+ rc = make_keysig_packet (ctrl, &sig, pk, NULL, NULL, pk, 0x1F, 0, 0,
keygen_add_revkey, &revkey, NULL);
if (rc)
{
write_status_error ("keysig", rc);
log_error ("signing failed: %s\n", gpg_strerror (rc));
goto fail;
}
/* Insert into public keyblock. */
pkt = xmalloc_clear (sizeof *pkt);
pkt->pkttype = PKT_SIGNATURE;
pkt->pkt.signature = sig;
insert_kbnode (pub_keyblock, new_kbnode (pkt), PKT_SIGNATURE);
return 1;
fail:
if (sig)
free_seckey_enc (sig);
free_public_key (revoker_pk);
return 0;
}
/* With FORCE_MAINKEY cleared this function handles the interactive
* menu option "expire". With UNATTENDED set to 1 this function only
* sets the expiration date of the primary key to NEWEXPIRATION and
* avoid all interactivity; with a value of 2 only the flagged subkeys
* are set to NEWEXPIRATION. Returns 0 if nothing was done,
* GPG_ERR_TRUE if the key was modified, or any other error code. */
static gpg_error_t
menu_expire (ctrl_t ctrl, kbnode_t pub_keyblock,
int unattended, u32 newexpiration)
{
int signumber, rc;
u32 expiredate;
int only_mainkey; /* Set if only the mainkey is to be updated. */
PKT_public_key *main_pk, *sub_pk;
PKT_user_id *uid;
kbnode_t node;
u32 keyid[2];
if (unattended)
{
only_mainkey = (unattended == 1);
expiredate = newexpiration;
}
else
{
int n1;
only_mainkey = 0;
n1 = count_selected_keys (pub_keyblock);
if (n1 > 1)
{
if (!cpr_get_answer_is_yes
("keyedit.expire_multiple_subkeys.okay",
_("Are you sure you want to change the"
" expiration time for multiple subkeys? (y/N) ")))
return gpg_error (GPG_ERR_CANCELED);;
}
else if (n1)
tty_printf (_("Changing expiration time for a subkey.\n"));
else
{
tty_printf (_("Changing expiration time for the primary key.\n"));
only_mainkey = 1;
no_primary_warning (pub_keyblock);
}
expiredate = ask_expiredate ();
}
/* Now we can actually change the self-signature(s) */
main_pk = sub_pk = NULL;
uid = NULL;
signumber = 0;
for (node = pub_keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_KEY)
{
main_pk = node->pkt->pkt.public_key;
keyid_from_pk (main_pk, keyid);
main_pk->expiredate = expiredate;
}
else if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
if ((node->flag & NODFLG_SELKEY) && unattended != 1)
{
/* The flag is set and we do not want to set the
* expiration date only for the main key. */
sub_pk = node->pkt->pkt.public_key;
sub_pk->expiredate = expiredate;
}
else
sub_pk = NULL;
}
else if (node->pkt->pkttype == PKT_USER_ID)
uid = node->pkt->pkt.user_id;
else if (main_pk && node->pkt->pkttype == PKT_SIGNATURE
&& (only_mainkey || sub_pk))
{
PKT_signature *sig = node->pkt->pkt.signature;
if (keyid[0] == sig->keyid[0] && keyid[1] == sig->keyid[1]
&& ((only_mainkey && uid
&& uid->created && (sig->sig_class & ~3) == 0x10)
|| (!only_mainkey && sig->sig_class == 0x18))
&& sig->flags.chosen_selfsig)
{
/* This is a self-signature which is to be replaced. */
PKT_signature *newsig;
PACKET *newpkt;
signumber++;
if ((only_mainkey && main_pk->version < 4)
|| (!only_mainkey && sub_pk->version < 4))
{
log_info
(_("You can't change the expiration date of a v3 key\n"));
return gpg_error (GPG_ERR_LEGACY_KEY);
}
if (only_mainkey)
rc = update_keysig_packet (ctrl,
&newsig, sig, main_pk, uid, NULL,
main_pk, keygen_add_key_expire,
main_pk);
else
rc =
update_keysig_packet (ctrl,
&newsig, sig, main_pk, NULL, sub_pk,
main_pk, keygen_add_key_expire, sub_pk);
if (rc)
{
log_error ("make_keysig_packet failed: %s\n",
gpg_strerror (rc));
if (gpg_err_code (rc) == GPG_ERR_TRUE)
rc = GPG_ERR_GENERAL;
return rc;
}
/* Replace the packet. */
newpkt = xmalloc_clear (sizeof *newpkt);
newpkt->pkttype = PKT_SIGNATURE;
newpkt->pkt.signature = newsig;
free_packet (node->pkt, NULL);
xfree (node->pkt);
node->pkt = newpkt;
sub_pk = NULL;
}
}
}
update_trust = 1;
return gpg_error (GPG_ERR_TRUE);
}
/* Change the capability of a selected key. This command should only
* be used to rectify badly created keys and as such is not suggested
* for general use. */
static int
menu_changeusage (ctrl_t ctrl, kbnode_t keyblock)
{
int n1, rc;
int mainkey = 0;
PKT_public_key *main_pk, *sub_pk;
PKT_user_id *uid;
kbnode_t node;
u32 keyid[2];
n1 = count_selected_keys (keyblock);
if (n1 > 1)
{
tty_printf (_("You must select exactly one key.\n"));
return 0;
}
else if (n1)
tty_printf (_("Changing usage of a subkey.\n"));
else
{
tty_printf (_("Changing usage of the primary key.\n"));
mainkey = 1;
}
/* Now we can actually change the self-signature(s) */
main_pk = sub_pk = NULL;
uid = NULL;
for (node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_KEY)
{
main_pk = node->pkt->pkt.public_key;
keyid_from_pk (main_pk, keyid);
}
else if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
if (node->flag & NODFLG_SELKEY)
sub_pk = node->pkt->pkt.public_key;
else
sub_pk = NULL;
}
else if (node->pkt->pkttype == PKT_USER_ID)
uid = node->pkt->pkt.user_id;
else if (main_pk && node->pkt->pkttype == PKT_SIGNATURE
&& (mainkey || sub_pk))
{
PKT_signature *sig = node->pkt->pkt.signature;
if (keyid[0] == sig->keyid[0] && keyid[1] == sig->keyid[1]
&& ((mainkey && uid
&& uid->created && (sig->sig_class & ~3) == 0x10)
|| (!mainkey && sig->sig_class == 0x18))
&& sig->flags.chosen_selfsig)
{
/* This is the self-signature which is to be replaced. */
PKT_signature *newsig;
PACKET *newpkt;
if ((mainkey && main_pk->version < 4)
|| (!mainkey && sub_pk->version < 4))
{
/* Note: This won't happen because we don't support
* v3 keys anymore. */
log_info ("You can't change the capabilities of a v3 key\n");
return 0;
}
if (mainkey)
main_pk->pubkey_usage = ask_key_flags (main_pk->pubkey_algo, 0,
main_pk->pubkey_usage);
else
sub_pk->pubkey_usage = ask_key_flags (sub_pk->pubkey_algo, 1,
sub_pk->pubkey_usage);
if (mainkey)
rc = update_keysig_packet (ctrl,
&newsig, sig, main_pk, uid, NULL,
main_pk, keygen_add_key_flags,
main_pk);
else
rc =
update_keysig_packet (ctrl,
&newsig, sig, main_pk, NULL, sub_pk,
main_pk, keygen_add_key_flags, sub_pk);
if (rc)
{
log_error ("make_keysig_packet failed: %s\n",
gpg_strerror (rc));
return 0;
}
/* Replace the packet. */
newpkt = xmalloc_clear (sizeof *newpkt);
newpkt->pkttype = PKT_SIGNATURE;
newpkt->pkt.signature = newsig;
free_packet (node->pkt, NULL);
xfree (node->pkt);
node->pkt = newpkt;
sub_pk = NULL;
break;
}
}
}
return 1;
}
static int
menu_backsign (ctrl_t ctrl, kbnode_t pub_keyblock)
{
int rc, modified = 0;
PKT_public_key *main_pk;
KBNODE node;
u32 timestamp;
log_assert (pub_keyblock->pkt->pkttype == PKT_PUBLIC_KEY);
merge_keys_and_selfsig (ctrl, pub_keyblock);
main_pk = pub_keyblock->pkt->pkt.public_key;
keyid_from_pk (main_pk, NULL);
/* We use the same timestamp for all backsigs so that we don't
reveal information about the used machine. */
timestamp = make_timestamp ();
for (node = pub_keyblock; node; node = node->next)
{
PKT_public_key *sub_pk = NULL;
KBNODE node2, sig_pk = NULL /*,sig_sk = NULL*/;
/* char *passphrase; */
/* Find a signing subkey with no backsig */
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
if (node->pkt->pkt.public_key->pubkey_usage & PUBKEY_USAGE_SIG)
{
if (node->pkt->pkt.public_key->flags.backsig)
tty_printf (_
("signing subkey %s is already cross-certified\n"),
keystr_from_pk (node->pkt->pkt.public_key));
else
sub_pk = node->pkt->pkt.public_key;
}
else
tty_printf (_("subkey %s does not sign and so does"
" not need to be cross-certified\n"),
keystr_from_pk (node->pkt->pkt.public_key));
}
if (!sub_pk)
continue;
/* Find the selected selfsig on this subkey */
for (node2 = node->next;
node2 && node2->pkt->pkttype == PKT_SIGNATURE; node2 = node2->next)
if (node2->pkt->pkt.signature->version >= 4
&& node2->pkt->pkt.signature->flags.chosen_selfsig)
{
sig_pk = node2;
break;
}
if (!sig_pk)
continue;
/* Find the secret subkey that matches the public subkey */
log_debug ("FIXME: Check whether a secret subkey is available.\n");
/* if (!sub_sk) */
/* { */
/* tty_printf (_("no secret subkey for public subkey %s - ignoring\n"), */
/* keystr_from_pk (sub_pk)); */
/* continue; */
/* } */
/* Now we can get to work. */
rc = make_backsig (ctrl,
sig_pk->pkt->pkt.signature, main_pk, sub_pk, sub_pk,
timestamp, NULL);
if (!rc)
{
PKT_signature *newsig;
PACKET *newpkt;
rc = update_keysig_packet (ctrl,
&newsig, sig_pk->pkt->pkt.signature,
main_pk, NULL, sub_pk, main_pk,
NULL, NULL);
if (!rc)
{
/* Put the new sig into place on the pubkey */
newpkt = xmalloc_clear (sizeof (*newpkt));
newpkt->pkttype = PKT_SIGNATURE;
newpkt->pkt.signature = newsig;
free_packet (sig_pk->pkt, NULL);
xfree (sig_pk->pkt);
sig_pk->pkt = newpkt;
modified = 1;
}
else
{
log_error ("update_keysig_packet failed: %s\n",
gpg_strerror (rc));
break;
}
}
else
{
log_error ("make_backsig failed: %s\n", gpg_strerror (rc));
break;
}
}
return modified;
}
static int
change_primary_uid_cb (PKT_signature * sig, void *opaque)
{
byte buf[1];
/* first clear all primary uid flags so that we are sure none are
* lingering around */
delete_sig_subpkt (sig->hashed, SIGSUBPKT_PRIMARY_UID);
delete_sig_subpkt (sig->unhashed, SIGSUBPKT_PRIMARY_UID);
/* if opaque is set,we want to set the primary id */
if (opaque)
{
buf[0] = 1;
build_sig_subpkt (sig, SIGSUBPKT_PRIMARY_UID, buf, 1);
}
return 0;
}
/*
* Set the primary uid flag for the selected UID. We will also reset
* all other primary uid flags. For this to work we have to update
* all the signature timestamps. If we would do this with the current
* time, we lose quite a lot of information, so we use a kludge to
* do this: Just increment the timestamp by one second which is
* sufficient to updated a signature during import.
*/
static int
menu_set_primary_uid (ctrl_t ctrl, kbnode_t pub_keyblock)
{
PKT_public_key *main_pk;
PKT_user_id *uid;
KBNODE node;
u32 keyid[2];
int selected;
int attribute = 0;
int modified = 0;
if (count_selected_uids (pub_keyblock) != 1)
{
tty_printf (_("Please select exactly one user ID.\n"));
return 0;
}
main_pk = NULL;
uid = NULL;
selected = 0;
/* Is our selected uid an attribute packet? */
for (node = pub_keyblock; node; node = node->next)
if (node->pkt->pkttype == PKT_USER_ID && node->flag & NODFLG_SELUID)
attribute = (node->pkt->pkt.user_id->attrib_data != NULL);
for (node = pub_keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
break; /* No more user ids expected - ready. */
if (node->pkt->pkttype == PKT_PUBLIC_KEY)
{
main_pk = node->pkt->pkt.public_key;
keyid_from_pk (main_pk, keyid);
}
else if (node->pkt->pkttype == PKT_USER_ID)
{
uid = node->pkt->pkt.user_id;
selected = node->flag & NODFLG_SELUID;
}
else if (main_pk && uid && node->pkt->pkttype == PKT_SIGNATURE)
{
PKT_signature *sig = node->pkt->pkt.signature;
if (keyid[0] == sig->keyid[0] && keyid[1] == sig->keyid[1]
&& (uid && (sig->sig_class & ~3) == 0x10)
&& attribute == (uid->attrib_data != NULL)
&& sig->flags.chosen_selfsig)
{
if (sig->version < 4)
{
char *user =
utf8_to_native (uid->name, strlen (uid->name), 0);
log_info (_("skipping v3 self-signature on user ID \"%s\"\n"),
user);
xfree (user);
}
else
{
/* This is a selfsignature which is to be replaced.
We can just ignore v3 signatures because they are
not able to carry the primary ID flag. We also
ignore self-sigs on user IDs that are not of the
same type that we are making primary. That is, if
we are making a user ID primary, we alter user IDs.
If we are making an attribute packet primary, we
alter attribute packets. */
/* FIXME: We must make sure that we only have one
self-signature per user ID here (not counting
revocations) */
PKT_signature *newsig;
PACKET *newpkt;
const byte *p;
int action;
/* See whether this signature has the primary UID flag. */
p = parse_sig_subpkt (sig->hashed,
SIGSUBPKT_PRIMARY_UID, NULL);
if (!p)
p = parse_sig_subpkt (sig->unhashed,
SIGSUBPKT_PRIMARY_UID, NULL);
if (p && *p) /* yes */
action = selected ? 0 : -1;
else /* no */
action = selected ? 1 : 0;
if (action)
{
int rc = update_keysig_packet (ctrl, &newsig, sig,
main_pk, uid, NULL,
main_pk,
change_primary_uid_cb,
action > 0 ? "x" : NULL);
if (rc)
{
log_error ("update_keysig_packet failed: %s\n",
gpg_strerror (rc));
return 0;
}
/* replace the packet */
newpkt = xmalloc_clear (sizeof *newpkt);
newpkt->pkttype = PKT_SIGNATURE;
newpkt->pkt.signature = newsig;
free_packet (node->pkt, NULL);
xfree (node->pkt);
node->pkt = newpkt;
modified = 1;
}
}
}
}
}
return modified;
}
/*
* Set preferences to new values for the selected user IDs
*/
static int
menu_set_preferences (ctrl_t ctrl, kbnode_t pub_keyblock)
{
PKT_public_key *main_pk;
PKT_user_id *uid;
KBNODE node;
u32 keyid[2];
int selected, select_all;
int modified = 0;
no_primary_warning (pub_keyblock);
select_all = !count_selected_uids (pub_keyblock);
/* Now we can actually change the self signature(s) */
main_pk = NULL;
uid = NULL;
selected = 0;
for (node = pub_keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
break; /* No more user-ids expected - ready. */
if (node->pkt->pkttype == PKT_PUBLIC_KEY)
{
main_pk = node->pkt->pkt.public_key;
keyid_from_pk (main_pk, keyid);
}
else if (node->pkt->pkttype == PKT_USER_ID)
{
uid = node->pkt->pkt.user_id;
selected = select_all || (node->flag & NODFLG_SELUID);
}
else if (main_pk && uid && selected
&& node->pkt->pkttype == PKT_SIGNATURE)
{
PKT_signature *sig = node->pkt->pkt.signature;
if (keyid[0] == sig->keyid[0] && keyid[1] == sig->keyid[1]
&& (uid && (sig->sig_class & ~3) == 0x10)
&& sig->flags.chosen_selfsig)
{
if (sig->version < 4)
{
char *user =
utf8_to_native (uid->name, strlen (uid->name), 0);
log_info (_("skipping v3 self-signature on user ID \"%s\"\n"),
user);
xfree (user);
}
else
{
/* This is a selfsignature which is to be replaced
* We have to ignore v3 signatures because they are
* not able to carry the preferences. */
PKT_signature *newsig;
PACKET *newpkt;
int rc;
rc = update_keysig_packet (ctrl, &newsig, sig,
main_pk, uid, NULL, main_pk,
keygen_upd_std_prefs, NULL);
if (rc)
{
log_error ("update_keysig_packet failed: %s\n",
gpg_strerror (rc));
return 0;
}
/* replace the packet */
newpkt = xmalloc_clear (sizeof *newpkt);
newpkt->pkttype = PKT_SIGNATURE;
newpkt->pkt.signature = newsig;
free_packet (node->pkt, NULL);
xfree (node->pkt);
node->pkt = newpkt;
modified = 1;
}
}
}
}
return modified;
}
static int
menu_set_keyserver_url (ctrl_t ctrl, const char *url, kbnode_t pub_keyblock)
{
PKT_public_key *main_pk;
PKT_user_id *uid;
KBNODE node;
u32 keyid[2];
int selected, select_all;
int modified = 0;
char *answer, *uri;
no_primary_warning (pub_keyblock);
if (url)
answer = xstrdup (url);
else
{
answer = cpr_get_utf8 ("keyedit.add_keyserver",
_("Enter your preferred keyserver URL: "));
if (answer[0] == '\0' || answer[0] == CONTROL_D)
{
xfree (answer);
return 0;
}
}
if (ascii_strcasecmp (answer, "none") == 0)
uri = NULL;
else
{
struct keyserver_spec *keyserver = NULL;
/* Sanity check the format */
keyserver = parse_keyserver_uri (answer, 1);
xfree (answer);
if (!keyserver)
{
log_info (_("could not parse keyserver URL\n"));
return 0;
}
uri = xstrdup (keyserver->uri);
free_keyserver_spec (keyserver);
}
select_all = !count_selected_uids (pub_keyblock);
/* Now we can actually change the self signature(s) */
main_pk = NULL;
uid = NULL;
selected = 0;
for (node = pub_keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
break; /* ready */
if (node->pkt->pkttype == PKT_PUBLIC_KEY)
{
main_pk = node->pkt->pkt.public_key;
keyid_from_pk (main_pk, keyid);
}
else if (node->pkt->pkttype == PKT_USER_ID)
{
uid = node->pkt->pkt.user_id;
selected = select_all || (node->flag & NODFLG_SELUID);
}
else if (main_pk && uid && selected
&& node->pkt->pkttype == PKT_SIGNATURE)
{
PKT_signature *sig = node->pkt->pkt.signature;
if (keyid[0] == sig->keyid[0] && keyid[1] == sig->keyid[1]
&& (uid && (sig->sig_class & ~3) == 0x10)
&& sig->flags.chosen_selfsig)
{
char *user = utf8_to_native (uid->name, strlen (uid->name), 0);
if (sig->version < 4)
log_info (_("skipping v3 self-signature on user ID \"%s\"\n"),
user);
else
{
/* This is a selfsignature which is to be replaced
* We have to ignore v3 signatures because they are
* not able to carry the subpacket. */
PKT_signature *newsig;
PACKET *newpkt;
int rc;
const byte *p;
size_t plen;
p = parse_sig_subpkt (sig->hashed, SIGSUBPKT_PREF_KS, &plen);
if (p && plen)
{
tty_printf ("Current preferred keyserver for user"
" ID \"%s\": ", user);
tty_print_utf8_string (p, plen);
tty_printf ("\n");
if (!cpr_get_answer_is_yes
("keyedit.confirm_keyserver",
uri
? _("Are you sure you want to replace it? (y/N) ")
: _("Are you sure you want to delete it? (y/N) ")))
continue;
}
else if (uri == NULL)
{
/* There is no current keyserver URL, so there
is no point in trying to un-set it. */
continue;
}
rc = update_keysig_packet (ctrl, &newsig, sig,
main_pk, uid, NULL,
main_pk,
keygen_add_keyserver_url, uri);
if (rc)
{
log_error ("update_keysig_packet failed: %s\n",
gpg_strerror (rc));
xfree (uri);
return 0;
}
/* replace the packet */
newpkt = xmalloc_clear (sizeof *newpkt);
newpkt->pkttype = PKT_SIGNATURE;
newpkt->pkt.signature = newsig;
free_packet (node->pkt, NULL);
xfree (node->pkt);
node->pkt = newpkt;
modified = 1;
}
xfree (user);
}
}
}
xfree (uri);
return modified;
}
static int
menu_set_notation (ctrl_t ctrl, const char *string, KBNODE pub_keyblock)
{
PKT_public_key *main_pk;
PKT_user_id *uid;
KBNODE node;
u32 keyid[2];
int selected, select_all;
int modified = 0;
char *answer;
struct notation *notation;
no_primary_warning (pub_keyblock);
if (string)
answer = xstrdup (string);
else
{
answer = cpr_get_utf8 ("keyedit.add_notation",
_("Enter the notation: "));
if (answer[0] == '\0' || answer[0] == CONTROL_D)
{
xfree (answer);
return 0;
}
}
if (!ascii_strcasecmp (answer, "none")
|| !ascii_strcasecmp (answer, "-"))
notation = NULL; /* Delete them all. */
else
{
notation = string_to_notation (answer, 0);
if (!notation)
{
xfree (answer);
return 0;
}
}
xfree (answer);
select_all = !count_selected_uids (pub_keyblock);
/* Now we can actually change the self signature(s) */
main_pk = NULL;
uid = NULL;
selected = 0;
for (node = pub_keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
break; /* ready */
if (node->pkt->pkttype == PKT_PUBLIC_KEY)
{
main_pk = node->pkt->pkt.public_key;
keyid_from_pk (main_pk, keyid);
}
else if (node->pkt->pkttype == PKT_USER_ID)
{
uid = node->pkt->pkt.user_id;
selected = select_all || (node->flag & NODFLG_SELUID);
}
else if (main_pk && uid && selected
&& node->pkt->pkttype == PKT_SIGNATURE)
{
PKT_signature *sig = node->pkt->pkt.signature;
if (keyid[0] == sig->keyid[0] && keyid[1] == sig->keyid[1]
&& (uid && (sig->sig_class & ~3) == 0x10)
&& sig->flags.chosen_selfsig)
{
char *user = utf8_to_native (uid->name, strlen (uid->name), 0);
if (sig->version < 4)
log_info (_("skipping v3 self-signature on user ID \"%s\"\n"),
user);
else
{
PKT_signature *newsig;
PACKET *newpkt;
int rc, skip = 0, addonly = 1;
if (sig->flags.notation)
{
tty_printf ("Current notations for user ID \"%s\":\n",
user);
tty_print_notations (-9, sig);
}
else
{
tty_printf ("No notations on user ID \"%s\"\n", user);
if (notation == NULL)
{
/* There are no current notations, so there
is no point in trying to un-set them. */
continue;
}
}
if (notation)
{
struct notation *n;
int deleting = 0;
notation->next = sig_to_notation (sig);
for (n = notation->next; n; n = n->next)
if (strcmp (n->name, notation->name) == 0)
{
if (notation->value)
{
if (strcmp (n->value, notation->value) == 0)
{
if (notation->flags.ignore)
{
/* Value match with a delete
flag. */
n->flags.ignore = 1;
deleting = 1;
}
else
{
/* Adding the same notation
twice, so don't add it at
all. */
skip = 1;
tty_printf ("Skipping notation:"
" %s=%s\n",
notation->name,
notation->value);
break;
}
}
}
else
{
/* No value, so it means delete. */
n->flags.ignore = 1;
deleting = 1;
}
if (n->flags.ignore)
{
tty_printf ("Removing notation: %s=%s\n",
n->name, n->value);
addonly = 0;
}
}
if (!notation->flags.ignore && !skip)
tty_printf ("Adding notation: %s=%s\n",
notation->name, notation->value);
/* We tried to delete, but had no matches. */
if (notation->flags.ignore && !deleting)
continue;
}
else
{
tty_printf ("Removing all notations\n");
addonly = 0;
}
if (skip
|| (!addonly
&&
!cpr_get_answer_is_yes ("keyedit.confirm_notation",
_("Proceed? (y/N) "))))
continue;
rc = update_keysig_packet (ctrl, &newsig, sig,
main_pk, uid, NULL,
main_pk,
keygen_add_notations, notation);
if (rc)
{
log_error ("update_keysig_packet failed: %s\n",
gpg_strerror (rc));
free_notation (notation);
xfree (user);
return 0;
}
/* replace the packet */
newpkt = xmalloc_clear (sizeof *newpkt);
newpkt->pkttype = PKT_SIGNATURE;
newpkt->pkt.signature = newsig;
free_packet (node->pkt, NULL);
xfree (node->pkt);
node->pkt = newpkt;
modified = 1;
if (notation)
{
/* Snip off the notation list from the sig */
free_notation (notation->next);
notation->next = NULL;
}
xfree (user);
}
}
}
}
free_notation (notation);
return modified;
}
/*
* Select one user id or remove all selection if IDX is 0 or select
* all if IDX is -1. Returns: True if the selection changed.
*/
static int
menu_select_uid (KBNODE keyblock, int idx)
{
KBNODE node;
int i;
if (idx == -1) /* Select all. */
{
for (node = keyblock; node; node = node->next)
if (node->pkt->pkttype == PKT_USER_ID)
node->flag |= NODFLG_SELUID;
return 1;
}
else if (idx) /* Toggle. */
{
for (i = 0, node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_USER_ID)
if (++i == idx)
break;
}
if (!node)
{
tty_printf (_("No user ID with index %d\n"), idx);
return 0;
}
for (i = 0, node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_USER_ID)
{
if (++i == idx)
{
if ((node->flag & NODFLG_SELUID))
node->flag &= ~NODFLG_SELUID;
else
node->flag |= NODFLG_SELUID;
}
}
}
}
else /* Unselect all */
{
for (node = keyblock; node; node = node->next)
if (node->pkt->pkttype == PKT_USER_ID)
node->flag &= ~NODFLG_SELUID;
}
return 1;
}
/* Search in the keyblock for a uid that matches namehash */
static int
menu_select_uid_namehash (KBNODE keyblock, const char *namehash)
{
byte hash[NAMEHASH_LEN];
KBNODE node;
int i;
log_assert (strlen (namehash) == NAMEHASH_LEN * 2);
for (i = 0; i < NAMEHASH_LEN; i++)
hash[i] = hextobyte (&namehash[i * 2]);
for (node = keyblock->next; node; node = node->next)
{
if (node->pkt->pkttype == PKT_USER_ID)
{
namehash_from_uid (node->pkt->pkt.user_id);
if (memcmp (node->pkt->pkt.user_id->namehash, hash, NAMEHASH_LEN) ==
0)
{
if (node->flag & NODFLG_SELUID)
node->flag &= ~NODFLG_SELUID;
else
node->flag |= NODFLG_SELUID;
break;
}
}
}
if (!node)
{
tty_printf (_("No user ID with hash %s\n"), namehash);
return 0;
}
return 1;
}
/*
* Select secondary keys
* Returns: True if the selection changed.
*/
static int
menu_select_key (KBNODE keyblock, int idx, char *p)
{
KBNODE node;
int i, j;
int is_hex_digits;
is_hex_digits = p && strlen (p) >= 8;
if (is_hex_digits)
{
/* Skip initial spaces. */
while (spacep (p))
p ++;
/* If the id starts with 0x accept and ignore it. */
if (p[0] == '0' && p[1] == 'x')
p += 2;
for (i = 0, j = 0; p[i]; i ++)
if (hexdigitp (&p[i]))
{
p[j] = toupper (p[i]);
j ++;
}
else if (spacep (&p[i]))
/* Skip spaces. */
{
}
else
{
is_hex_digits = 0;
break;
}
if (is_hex_digits)
/* In case we skipped some spaces, add a new NUL terminator. */
{
p[j] = 0;
/* If we skipped some spaces, make sure that we still have
at least 8 characters. */
is_hex_digits = (/* Short keyid. */
strlen (p) == 8
/* Long keyid. */
|| strlen (p) == 16
/* Fingerprints are (currently) 32 or 40
characters. */
|| strlen (p) >= 32);
}
}
if (is_hex_digits)
{
int found_one = 0;
for (node = keyblock; node; node = node->next)
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| node->pkt->pkttype == PKT_SECRET_SUBKEY)
{
int match = 0;
if (strlen (p) == 8 || strlen (p) == 16)
{
u32 kid[2];
char kid_str[17];
keyid_from_pk (node->pkt->pkt.public_key, kid);
format_keyid (kid, strlen (p) == 8 ? KF_SHORT : KF_LONG,
kid_str, sizeof (kid_str));
if (strcmp (p, kid_str) == 0)
match = 1;
}
else
{
char fp[2*MAX_FINGERPRINT_LEN + 1];
hexfingerprint (node->pkt->pkt.public_key, fp, sizeof (fp));
if (strcmp (fp, p) == 0)
match = 1;
}
if (match)
{
if ((node->flag & NODFLG_SELKEY))
node->flag &= ~NODFLG_SELKEY;
else
node->flag |= NODFLG_SELKEY;
found_one = 1;
}
}
if (found_one)
return 1;
tty_printf (_("No subkey with key ID '%s'.\n"), p);
return 0;
}
if (idx == -1) /* Select all. */
{
for (node = keyblock; node; node = node->next)
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| node->pkt->pkttype == PKT_SECRET_SUBKEY)
node->flag |= NODFLG_SELKEY;
}
else if (idx) /* Toggle selection. */
{
for (i = 0, node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| node->pkt->pkttype == PKT_SECRET_SUBKEY)
if (++i == idx)
break;
}
if (!node)
{
tty_printf (_("No subkey with index %d\n"), idx);
return 0;
}
for (i = 0, node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| node->pkt->pkttype == PKT_SECRET_SUBKEY)
if (++i == idx)
{
if ((node->flag & NODFLG_SELKEY))
node->flag &= ~NODFLG_SELKEY;
else
node->flag |= NODFLG_SELKEY;
}
}
}
else /* Unselect all. */
{
for (node = keyblock; node; node = node->next)
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| node->pkt->pkttype == PKT_SECRET_SUBKEY)
node->flag &= ~NODFLG_SELKEY;
}
return 1;
}
static int
count_uids_with_flag (KBNODE keyblock, unsigned flag)
{
KBNODE node;
int i = 0;
for (node = keyblock; node; node = node->next)
if (node->pkt->pkttype == PKT_USER_ID && (node->flag & flag))
i++;
return i;
}
static int
count_keys_with_flag (KBNODE keyblock, unsigned flag)
{
KBNODE node;
int i = 0;
for (node = keyblock; node; node = node->next)
if ((node->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| node->pkt->pkttype == PKT_SECRET_SUBKEY) && (node->flag & flag))
i++;
return i;
}
static int
count_uids (KBNODE keyblock)
{
KBNODE node;
int i = 0;
for (node = keyblock; node; node = node->next)
if (node->pkt->pkttype == PKT_USER_ID)
i++;
return i;
}
/*
* Returns true if there is at least one selected user id
*/
static int
count_selected_uids (KBNODE keyblock)
{
return count_uids_with_flag (keyblock, NODFLG_SELUID);
}
static int
count_selected_keys (KBNODE keyblock)
{
return count_keys_with_flag (keyblock, NODFLG_SELKEY);
}
/* Returns how many real (i.e. not attribute) uids are unmarked. */
static int
real_uids_left (KBNODE keyblock)
{
KBNODE node;
int real = 0;
for (node = keyblock; node; node = node->next)
if (node->pkt->pkttype == PKT_USER_ID && !(node->flag & NODFLG_SELUID) &&
!node->pkt->pkt.user_id->attrib_data)
real++;
return real;
}
/*
* Ask whether the signature should be revoked. If the user commits this,
* flag bit MARK_A is set on the signature and the user ID.
*/
static void
ask_revoke_sig (ctrl_t ctrl, kbnode_t keyblock, kbnode_t node)
{
int doit = 0;
PKT_user_id *uid;
PKT_signature *sig = node->pkt->pkt.signature;
KBNODE unode = find_prev_kbnode (keyblock, node, PKT_USER_ID);
if (!unode)
{
log_error ("Oops: no user ID for signature\n");
return;
}
uid = unode->pkt->pkt.user_id;
if (opt.with_colons)
{
if (uid->attrib_data)
printf ("uat:::::::::%u %lu", uid->numattribs, uid->attrib_len);
else
{
es_printf ("uid:::::::::");
es_write_sanitized (es_stdout, uid->name, uid->len, ":", NULL);
}
es_printf ("\n");
print_and_check_one_sig_colon (ctrl, keyblock, node,
NULL, NULL, NULL, NULL, 1);
}
else
{
char *p = utf8_to_native (unode->pkt->pkt.user_id->name,
unode->pkt->pkt.user_id->len, 0);
tty_printf (_("user ID: \"%s\"\n"), p);
xfree (p);
tty_printf (_("signed by your key %s on %s%s%s\n"),
keystr (sig->keyid), datestr_from_sig (sig),
sig->flags.exportable ? "" : _(" (non-exportable)"), "");
}
if (sig->flags.expired)
{
tty_printf (_("This signature expired on %s.\n"),
expirestr_from_sig (sig));
/* Use a different question so we can have different help text */
doit = cpr_get_answer_is_yes
("ask_revoke_sig.expired",
_("Are you sure you still want to revoke it? (y/N) "));
}
else
doit = cpr_get_answer_is_yes
("ask_revoke_sig.one",
_("Create a revocation certificate for this signature? (y/N) "));
if (doit)
{
node->flag |= NODFLG_MARK_A;
unode->flag |= NODFLG_MARK_A;
}
}
/*
* Display all user ids of the current public key together with signatures
* done by one of our keys. Then walk over all this sigs and ask the user
* whether he wants to revoke this signature.
* Return: True when the keyblock has changed.
*/
static int
menu_revsig (ctrl_t ctrl, kbnode_t keyblock)
{
PKT_signature *sig;
PKT_public_key *primary_pk;
KBNODE node;
int changed = 0;
int rc, any, skip = 1, all = !count_selected_uids (keyblock);
struct revocation_reason_info *reason = NULL;
log_assert (keyblock->pkt->pkttype == PKT_PUBLIC_KEY);
/* First check whether we have any signatures at all. */
any = 0;
for (node = keyblock; node; node = node->next)
{
node->flag &= ~(NODFLG_SELSIG | NODFLG_MARK_A);
if (node->pkt->pkttype == PKT_USER_ID)
{
if (node->flag & NODFLG_SELUID || all)
skip = 0;
else
skip = 1;
}
else if (!skip && node->pkt->pkttype == PKT_SIGNATURE
&& ((sig = node->pkt->pkt.signature),
have_secret_key_with_kid (sig->keyid)))
{
if ((sig->sig_class & ~3) == 0x10)
{
any = 1;
break;
}
}
}
if (!any)
{
tty_printf (_("Not signed by you.\n"));
return 0;
}
/* FIXME: detect duplicates here */
tty_printf (_("You have signed these user IDs on key %s:\n"),
keystr_from_pk (keyblock->pkt->pkt.public_key));
for (node = keyblock; node; node = node->next)
{
node->flag &= ~(NODFLG_SELSIG | NODFLG_MARK_A);
if (node->pkt->pkttype == PKT_USER_ID)
{
if (node->flag & NODFLG_SELUID || all)
{
PKT_user_id *uid = node->pkt->pkt.user_id;
/* Hmmm: Should we show only UIDs with a signature? */
tty_printf (" ");
tty_print_utf8_string (uid->name, uid->len);
tty_printf ("\n");
skip = 0;
}
else
skip = 1;
}
else if (!skip && node->pkt->pkttype == PKT_SIGNATURE
&& ((sig = node->pkt->pkt.signature),
have_secret_key_with_kid (sig->keyid)))
{
if ((sig->sig_class & ~3) == 0x10)
{
tty_printf (" ");
tty_printf (_("signed by your key %s on %s%s%s\n"),
keystr (sig->keyid), datestr_from_sig (sig),
sig->flags.exportable ? "" : _(" (non-exportable)"),
sig->flags.revocable ? "" : _(" (non-revocable)"));
if (sig->flags.revocable)
node->flag |= NODFLG_SELSIG;
}
else if (sig->sig_class == 0x30)
{
tty_printf (" ");
tty_printf (_("revoked by your key %s on %s\n"),
keystr (sig->keyid), datestr_from_sig (sig));
}
}
}
tty_printf ("\n");
/* ask */
for (node = keyblock; node; node = node->next)
{
if (!(node->flag & NODFLG_SELSIG))
continue;
ask_revoke_sig (ctrl, keyblock, node);
}
/* present selected */
any = 0;
for (node = keyblock; node; node = node->next)
{
if (!(node->flag & NODFLG_MARK_A))
continue;
if (!any)
{
any = 1;
tty_printf (_("You are about to revoke these signatures:\n"));
}
if (node->pkt->pkttype == PKT_USER_ID)
{
PKT_user_id *uid = node->pkt->pkt.user_id;
tty_printf (" ");
tty_print_utf8_string (uid->name, uid->len);
tty_printf ("\n");
}
else if (node->pkt->pkttype == PKT_SIGNATURE)
{
sig = node->pkt->pkt.signature;
tty_printf (" ");
tty_printf (_("signed by your key %s on %s%s%s\n"),
keystr (sig->keyid), datestr_from_sig (sig), "",
sig->flags.exportable ? "" : _(" (non-exportable)"));
}
}
if (!any)
return 0; /* none selected */
if (!cpr_get_answer_is_yes
("ask_revoke_sig.okay",
_("Really create the revocation certificates? (y/N) ")))
return 0; /* forget it */
reason = ask_revocation_reason (0, 1, 0);
if (!reason)
{ /* user decided to cancel */
return 0;
}
/* now we can sign the user ids */
reloop: /* (must use this, because we are modifying the list) */
primary_pk = keyblock->pkt->pkt.public_key;
for (node = keyblock; node; node = node->next)
{
KBNODE unode;
PACKET *pkt;
struct sign_attrib attrib;
PKT_public_key *signerkey;
if (!(node->flag & NODFLG_MARK_A)
|| node->pkt->pkttype != PKT_SIGNATURE)
continue;
unode = find_prev_kbnode (keyblock, node, PKT_USER_ID);
log_assert (unode); /* we already checked this */
memset (&attrib, 0, sizeof attrib);
attrib.reason = reason;
attrib.non_exportable = !node->pkt->pkt.signature->flags.exportable;
node->flag &= ~NODFLG_MARK_A;
signerkey = xmalloc_secure_clear (sizeof *signerkey);
if (get_seckey (ctrl, signerkey, node->pkt->pkt.signature->keyid))
{
log_info (_("no secret key\n"));
free_public_key (signerkey);
continue;
}
rc = make_keysig_packet (ctrl, &sig, primary_pk,
unode->pkt->pkt.user_id,
- NULL, signerkey, 0x30, 0, 0, 0,
+ NULL, signerkey, 0x30, 0, 0,
sign_mk_attrib, &attrib, NULL);
free_public_key (signerkey);
if (rc)
{
write_status_error ("keysig", rc);
log_error (_("signing failed: %s\n"), gpg_strerror (rc));
release_revocation_reason_info (reason);
return changed;
}
changed = 1; /* we changed the keyblock */
update_trust = 1;
/* Are we revoking our own uid? */
if (primary_pk->keyid[0] == sig->keyid[0] &&
primary_pk->keyid[1] == sig->keyid[1])
unode->pkt->pkt.user_id->flags.revoked = 1;
pkt = xmalloc_clear (sizeof *pkt);
pkt->pkttype = PKT_SIGNATURE;
pkt->pkt.signature = sig;
insert_kbnode (unode, new_kbnode (pkt), 0);
goto reloop;
}
release_revocation_reason_info (reason);
return changed;
}
/* return 0 if revocation of NODE (which must be a User ID) was
successful, non-zero if there was an error. *modified will be set
to 1 if a change was made. */
static int
core_revuid (ctrl_t ctrl, kbnode_t keyblock, KBNODE node,
const struct revocation_reason_info *reason, int *modified)
{
PKT_public_key *pk = keyblock->pkt->pkt.public_key;
gpg_error_t rc;
if (node->pkt->pkttype != PKT_USER_ID)
{
rc = gpg_error (GPG_ERR_NO_USER_ID);
write_status_error ("keysig", rc);
log_error (_("tried to revoke a non-user ID: %s\n"), gpg_strerror (rc));
return 1;
}
else
{
PKT_user_id *uid = node->pkt->pkt.user_id;
if (uid->flags.revoked)
{
char *user = utf8_to_native (uid->name, uid->len, 0);
log_info (_("user ID \"%s\" is already revoked\n"), user);
xfree (user);
}
else
{
PACKET *pkt;
PKT_signature *sig;
struct sign_attrib attrib;
u32 timestamp = make_timestamp ();
if (uid->created >= timestamp)
{
/* Okay, this is a problem. The user ID selfsig was
created in the future, so we need to warn the user and
set our revocation timestamp one second after that so
everything comes out clean. */
log_info (_("WARNING: a user ID signature is dated %d"
" seconds in the future\n"),
uid->created - timestamp);
timestamp = uid->created + 1;
}
memset (&attrib, 0, sizeof attrib);
/* should not need to cast away const here; but
revocation_reason_build_cb needs to take a non-const
- void* in order to meet the function signtuare for the
+ void* in order to meet the function signutare for the
mksubpkt argument to make_keysig_packet */
attrib.reason = (struct revocation_reason_info *)reason;
- rc = make_keysig_packet (ctrl, &sig, pk, uid, NULL, pk, 0x30, 0,
+ rc = make_keysig_packet (ctrl, &sig, pk, uid, NULL, pk, 0x30,
timestamp, 0,
sign_mk_attrib, &attrib, NULL);
if (rc)
{
write_status_error ("keysig", rc);
log_error (_("signing failed: %s\n"), gpg_strerror (rc));
return 1;
}
else
{
pkt = xmalloc_clear (sizeof *pkt);
pkt->pkttype = PKT_SIGNATURE;
pkt->pkt.signature = sig;
insert_kbnode (node, new_kbnode (pkt), 0);
#ifndef NO_TRUST_MODELS
/* If the trustdb has an entry for this key+uid then the
trustdb needs an update. */
if (!update_trust
&& ((get_validity (ctrl, keyblock, pk, uid, NULL, 0)
& TRUST_MASK)
>= TRUST_UNDEFINED))
update_trust = 1;
#endif /*!NO_TRUST_MODELS*/
node->pkt->pkt.user_id->flags.revoked = 1;
if (modified)
*modified = 1;
}
}
return 0;
}
}
/* Revoke a user ID (i.e. revoke a user ID selfsig). Return true if
keyblock changed. */
static int
menu_revuid (ctrl_t ctrl, kbnode_t pub_keyblock)
{
PKT_public_key *pk = pub_keyblock->pkt->pkt.public_key;
KBNODE node;
int changed = 0;
int rc;
struct revocation_reason_info *reason = NULL;
size_t valid_uids;
/* Note that this is correct as per the RFCs, but nevertheless
somewhat meaningless in the real world. 1991 did define the 0x30
sig class, but PGP 2.x did not actually implement it, so it would
probably be safe to use v4 revocations everywhere. -ds */
for (node = pub_keyblock; node; node = node->next)
if (pk->version > 3 || (node->pkt->pkttype == PKT_USER_ID &&
node->pkt->pkt.user_id->selfsigversion > 3))
{
if ((reason = ask_revocation_reason (0, 1, 4)))
break;
else
goto leave;
}
/* Too make sure that we do not revoke the last valid UID, we first
count how many valid UIDs there are. */
valid_uids = 0;
for (node = pub_keyblock; node; node = node->next)
valid_uids +=
node->pkt->pkttype == PKT_USER_ID
&& ! node->pkt->pkt.user_id->flags.revoked
&& ! node->pkt->pkt.user_id->flags.expired;
reloop: /* (better this way because we are modifying the keyring) */
for (node = pub_keyblock; node; node = node->next)
if (node->pkt->pkttype == PKT_USER_ID && (node->flag & NODFLG_SELUID))
{
int modified = 0;
/* Make sure that we do not revoke the last valid UID. */
if (valid_uids == 1
&& ! node->pkt->pkt.user_id->flags.revoked
&& ! node->pkt->pkt.user_id->flags.expired)
{
log_error (_("Cannot revoke the last valid user ID.\n"));
goto leave;
}
rc = core_revuid (ctrl, pub_keyblock, node, reason, &modified);
if (rc)
goto leave;
if (modified)
{
node->flag &= ~NODFLG_SELUID;
changed = 1;
goto reloop;
}
}
if (changed)
commit_kbnode (&pub_keyblock);
leave:
release_revocation_reason_info (reason);
return changed;
}
/*
* Revoke the whole key.
*/
static int
menu_revkey (ctrl_t ctrl, kbnode_t pub_keyblock)
{
PKT_public_key *pk = pub_keyblock->pkt->pkt.public_key;
int rc, changed = 0;
struct revocation_reason_info *reason;
PACKET *pkt;
PKT_signature *sig;
if (pk->flags.revoked)
{
tty_printf (_("Key %s is already revoked.\n"), keystr_from_pk (pk));
return 0;
}
reason = ask_revocation_reason (1, 0, 0);
/* user decided to cancel */
if (!reason)
return 0;
rc = make_keysig_packet (ctrl, &sig, pk, NULL, NULL, pk,
- 0x20, 0, 0, 0,
+ 0x20, 0, 0,
revocation_reason_build_cb, reason, NULL);
if (rc)
{
write_status_error ("keysig", rc);
log_error (_("signing failed: %s\n"), gpg_strerror (rc));
goto scram;
}
changed = 1; /* we changed the keyblock */
pkt = xmalloc_clear (sizeof *pkt);
pkt->pkttype = PKT_SIGNATURE;
pkt->pkt.signature = sig;
insert_kbnode (pub_keyblock, new_kbnode (pkt), 0);
commit_kbnode (&pub_keyblock);
update_trust = 1;
scram:
release_revocation_reason_info (reason);
return changed;
}
static int
menu_revsubkey (ctrl_t ctrl, kbnode_t pub_keyblock)
{
PKT_public_key *mainpk;
KBNODE node;
int changed = 0;
int rc;
struct revocation_reason_info *reason = NULL;
reason = ask_revocation_reason (1, 0, 0);
if (!reason)
return 0; /* User decided to cancel. */
reloop: /* (better this way because we are modifying the keyring) */
mainpk = pub_keyblock->pkt->pkt.public_key;
for (node = pub_keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY
&& (node->flag & NODFLG_SELKEY))
{
PACKET *pkt;
PKT_signature *sig;
PKT_public_key *subpk = node->pkt->pkt.public_key;
struct sign_attrib attrib;
if (subpk->flags.revoked)
{
tty_printf (_("Subkey %s is already revoked.\n"),
keystr_from_pk (subpk));
continue;
}
memset (&attrib, 0, sizeof attrib);
attrib.reason = reason;
node->flag &= ~NODFLG_SELKEY;
rc = make_keysig_packet (ctrl, &sig, mainpk, NULL, subpk, mainpk,
- 0x28, 0, 0, 0, sign_mk_attrib, &attrib,
+ 0x28, 0, 0, sign_mk_attrib, &attrib,
NULL);
if (rc)
{
write_status_error ("keysig", rc);
log_error (_("signing failed: %s\n"), gpg_strerror (rc));
release_revocation_reason_info (reason);
return changed;
}
changed = 1; /* we changed the keyblock */
pkt = xmalloc_clear (sizeof *pkt);
pkt->pkttype = PKT_SIGNATURE;
pkt->pkt.signature = sig;
insert_kbnode (node, new_kbnode (pkt), 0);
goto reloop;
}
}
commit_kbnode (&pub_keyblock);
/* No need to set update_trust here since signing keys no longer
are used to certify other keys, so there is no change in trust
when revoking/removing them */
release_revocation_reason_info (reason);
return changed;
}
/* Note that update_ownertrust is going to mark the trustdb dirty when
enabling or disabling a key. This is arguably sub-optimal as
disabled keys are still counted in the web of trust, but perhaps
not worth adding extra complexity to change. -ds */
#ifndef NO_TRUST_MODELS
static int
enable_disable_key (ctrl_t ctrl, kbnode_t keyblock, int disable)
{
PKT_public_key *pk =
find_kbnode (keyblock, PKT_PUBLIC_KEY)->pkt->pkt.public_key;
unsigned int trust, newtrust;
trust = newtrust = get_ownertrust (ctrl, pk);
newtrust &= ~TRUST_FLAG_DISABLED;
if (disable)
newtrust |= TRUST_FLAG_DISABLED;
if (trust == newtrust)
return 0; /* already in that state */
update_ownertrust (ctrl, pk, newtrust);
return 0;
}
#endif /*!NO_TRUST_MODELS*/
static void
menu_showphoto (ctrl_t ctrl, kbnode_t keyblock)
{
KBNODE node;
int select_all = !count_selected_uids (keyblock);
int count = 0;
PKT_public_key *pk = NULL;
/* Look for the public key first. We have to be really, really,
explicit as to which photo this is, and what key it is a UID on
since people may want to sign it. */
for (node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_KEY)
pk = node->pkt->pkt.public_key;
else if (node->pkt->pkttype == PKT_USER_ID)
{
PKT_user_id *uid = node->pkt->pkt.user_id;
count++;
if ((select_all || (node->flag & NODFLG_SELUID)) &&
uid->attribs != NULL)
{
int i;
for (i = 0; i < uid->numattribs; i++)
{
byte type;
u32 size;
if (uid->attribs[i].type == ATTRIB_IMAGE &&
parse_image_header (&uid->attribs[i], &type, &size))
{
tty_printf (_("Displaying %s photo ID of size %ld for "
"key %s (uid %d)\n"),
image_type_to_string (type, 1),
(ulong) size, keystr_from_pk (pk), count);
show_photos (ctrl, &uid->attribs[i], 1, pk, uid);
}
}
}
}
}
}
diff --git a/g10/keygen.c b/g10/keygen.c
index 64fefd231..d9037d29d 100644
--- a/g10/keygen.c
+++ b/g10/keygen.c
@@ -1,5623 +1,5777 @@
/* keygen.c - Generate a key pair
* Copyright (C) 1998-2007, 2009-2011 Free Software Foundation, Inc.
* Copyright (C) 2014, 2015, 2016, 2017, 2018 Werner Koch
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include "gpg.h"
#include "../common/util.h"
#include "main.h"
#include "packet.h"
#include "../common/ttyio.h"
#include "options.h"
#include "keydb.h"
#include "trustdb.h"
#include "../common/status.h"
#include "../common/i18n.h"
#include "keyserver-internal.h"
#include "call-agent.h"
#include "pkglue.h"
#include "../common/shareddefs.h"
#include "../common/host2net.h"
#include "../common/mbox-util.h"
/* The default algorithms. If you change them, you should ensure the value
is inside the bounds enforced by ask_keysize and gen_xxx. See also
get_keysize_range which encodes the allowed ranges. */
#define DEFAULT_STD_KEY_PARAM "rsa3072/cert,sign+rsa3072/encr"
#define FUTURE_STD_KEY_PARAM "ed25519/cert,sign+cv25519/encr"
/* When generating keys using the streamlined key generation dialog,
use this as a default expiration interval. */
const char *default_expiration_interval = "2y";
/* Flag bits used during key generation. */
#define KEYGEN_FLAG_NO_PROTECTION 1
#define KEYGEN_FLAG_TRANSIENT_KEY 2
#define KEYGEN_FLAG_CREATE_V5_KEY 4
/* Maximum number of supported algorithm preferences. */
#define MAX_PREFS 30
enum para_name {
pKEYTYPE,
pKEYLENGTH,
pKEYCURVE,
pKEYUSAGE,
pSUBKEYTYPE,
pSUBKEYLENGTH,
pSUBKEYCURVE,
pSUBKEYUSAGE,
pAUTHKEYTYPE,
pNAMEREAL,
pNAMEEMAIL,
pNAMECOMMENT,
pPREFERENCES,
pREVOKER,
pUSERID,
pCREATIONDATE,
pKEYCREATIONDATE, /* Same in seconds since epoch. */
pEXPIREDATE,
pKEYEXPIRE, /* in n seconds */
pSUBKEYEXPIRE, /* in n seconds */
pPASSPHRASE,
pSERIALNO,
pCARDBACKUPKEY,
pHANDLE,
pKEYSERVER,
pKEYGRIP,
pSUBKEYGRIP,
pVERSION, /* Desired version of the key packet. */
pSUBVERSION, /* Ditto for the subpacket. */
};
struct para_data_s {
struct para_data_s *next;
int lnr;
enum para_name key;
union {
u32 expire;
u32 creation;
unsigned int usage;
struct revocation_key revkey;
char value[1];
} u;
};
struct output_control_s
{
int lnr;
int dryrun;
unsigned int keygen_flags;
int use_files;
struct {
char *fname;
char *newfname;
IOBUF stream;
armor_filter_context_t *afx;
} pub;
};
struct opaque_data_usage_and_pk {
unsigned int usage;
PKT_public_key *pk;
};
/* FIXME: These globals vars are ugly. And using MAX_PREFS even for
* aeads is useless, given that we don't expects more than a very few
* algorithms. */
static int prefs_initialized = 0;
static byte sym_prefs[MAX_PREFS];
static int nsym_prefs;
static byte hash_prefs[MAX_PREFS];
static int nhash_prefs;
static byte zip_prefs[MAX_PREFS];
static int nzip_prefs;
static byte aead_prefs[MAX_PREFS];
static int naead_prefs;
static int mdc_available;
static int ks_modify;
static int aead_available;
static gpg_error_t parse_algo_usage_expire (ctrl_t ctrl, int for_subkey,
const char *algostr, const char *usagestr,
const char *expirestr,
int *r_algo, unsigned int *r_usage,
u32 *r_expire, unsigned int *r_nbits,
const char **r_curve, int *r_version);
static void do_generate_keypair (ctrl_t ctrl, struct para_data_s *para,
struct output_control_s *outctrl, int card );
static int write_keyblock (iobuf_t out, kbnode_t node);
static gpg_error_t gen_card_key (int keyno, int algo, int is_primary,
kbnode_t pub_root, u32 *timestamp,
u32 expireval, int keygen_flags);
static unsigned int get_keysize_range (int algo,
unsigned int *min, unsigned int *max);
/* Return the algo string for a default new key. */
const char *
get_default_pubkey_algo (void)
{
if (opt.def_new_key_algo)
{
if (*opt.def_new_key_algo && !strchr (opt.def_new_key_algo, ':'))
return opt.def_new_key_algo;
/* To avoid checking that option every time we delay that until
* here. The only thing we really need to make sure is that
* there is no colon in the string so that the --gpgconf-list
* command won't mess up its output. */
log_info (_("invalid value for option '%s'\n"), "--default-new-key-algo");
}
return DEFAULT_STD_KEY_PARAM;
}
static void
print_status_key_created (int letter, PKT_public_key *pk, const char *handle)
{
byte array[MAX_FINGERPRINT_LEN], *s;
char *buf, *p;
size_t i, n;
if (!handle)
handle = "";
buf = xmalloc (MAX_FINGERPRINT_LEN*2+31 + strlen (handle) + 1);
p = buf;
if (letter || pk)
{
*p++ = letter;
if (pk)
{
*p++ = ' ';
fingerprint_from_pk (pk, array, &n);
s = array;
/* Fixme: Use bin2hex */
for (i=0; i < n ; i++, s++, p += 2)
snprintf (p, 3, "%02X", *s);
}
}
if (*handle)
{
*p++ = ' ';
for (i=0; handle[i] && i < 100; i++)
*p++ = isspace ((unsigned int)handle[i])? '_':handle[i];
}
*p = 0;
write_status_text ((letter || pk)?STATUS_KEY_CREATED:STATUS_KEY_NOT_CREATED,
buf);
xfree (buf);
}
static void
print_status_key_not_created (const char *handle)
{
print_status_key_created (0, NULL, handle);
}
-static void
-write_uid( KBNODE root, const char *s )
+static gpg_error_t
+write_uid (kbnode_t root, const char *s)
{
- PACKET *pkt = xmalloc_clear(sizeof *pkt );
- size_t n = strlen(s);
-
- pkt->pkttype = PKT_USER_ID;
- pkt->pkt.user_id = xmalloc_clear (sizeof *pkt->pkt.user_id + n);
- pkt->pkt.user_id->len = n;
- pkt->pkt.user_id->ref = 1;
- strcpy(pkt->pkt.user_id->name, s);
- add_kbnode( root, new_kbnode( pkt ) );
+ PACKET *pkt = xmalloc_clear (sizeof *pkt);
+ size_t n = strlen (s);
+
+ if (n > MAX_UID_PACKET_LENGTH - 10)
+ return gpg_error (GPG_ERR_INV_USER_ID);
+
+ pkt->pkttype = PKT_USER_ID;
+ pkt->pkt.user_id = xmalloc_clear (sizeof *pkt->pkt.user_id + n);
+ pkt->pkt.user_id->len = n;
+ pkt->pkt.user_id->ref = 1;
+ strcpy (pkt->pkt.user_id->name, s);
+ add_kbnode (root, new_kbnode (pkt));
+ return 0;
}
static void
do_add_key_flags (PKT_signature *sig, unsigned int use)
{
byte buf[1];
buf[0] = 0;
/* The spec says that all primary keys MUST be able to certify. */
if(sig->sig_class!=0x18)
buf[0] |= 0x01;
if (use & PUBKEY_USAGE_SIG)
buf[0] |= 0x02;
if (use & PUBKEY_USAGE_ENC)
buf[0] |= 0x04 | 0x08;
if (use & PUBKEY_USAGE_AUTH)
buf[0] |= 0x20;
build_sig_subpkt (sig, SIGSUBPKT_KEY_FLAGS, buf, 1);
}
int
keygen_add_key_expire (PKT_signature *sig, void *opaque)
{
PKT_public_key *pk = opaque;
byte buf[8];
u32 u;
if (pk->expiredate)
{
if (pk->expiredate > pk->timestamp)
u = pk->expiredate - pk->timestamp;
else
u = 1;
buf[0] = (u >> 24) & 0xff;
buf[1] = (u >> 16) & 0xff;
buf[2] = (u >> 8) & 0xff;
buf[3] = u & 0xff;
build_sig_subpkt (sig, SIGSUBPKT_KEY_EXPIRE, buf, 4);
}
else
{
/* Make sure we don't leave a key expiration subpacket lying
around */
delete_sig_subpkt (sig->hashed, SIGSUBPKT_KEY_EXPIRE);
}
return 0;
}
/* Add the key usage (i.e. key flags) in SIG from the public keys
* pubkey_usage field. OPAQUE has the public key. */
int
keygen_add_key_flags (PKT_signature *sig, void *opaque)
{
PKT_public_key *pk = opaque;
do_add_key_flags (sig, pk->pubkey_usage);
return 0;
}
static int
keygen_add_key_flags_and_expire (PKT_signature *sig, void *opaque)
{
struct opaque_data_usage_and_pk *oduap = opaque;
do_add_key_flags (sig, oduap->usage);
return keygen_add_key_expire (sig, oduap->pk);
}
static int
set_one_pref (int val, int type, const char *item, byte *buf, int *nbuf)
{
int i;
for (i=0; i < *nbuf; i++ )
if (buf[i] == val)
{
log_info (_("preference '%s' duplicated\n"), item);
return -1;
}
if (*nbuf >= MAX_PREFS)
{
if(type==1)
log_info(_("too many cipher preferences\n"));
else if(type==2)
log_info(_("too many digest preferences\n"));
else if(type==3)
log_info(_("too many compression preferences\n"));
else if(type==4)
log_info(_("too many AEAD preferences\n"));
else
BUG();
return -1;
}
buf[(*nbuf)++] = val;
return 0;
}
/*
* Parse the supplied string and use it to set the standard
* preferences. The string may be in a form like the one printed by
* "pref" (something like: "S10 S3 H3 H2 Z2 Z1") or the actual
* cipher/hash/compress names. Use NULL to set the default
* preferences. Returns: 0 = okay
*/
int
keygen_set_std_prefs (const char *string,int personal)
{
byte sym[MAX_PREFS], hash[MAX_PREFS], zip[MAX_PREFS], aead[MAX_PREFS];
int nsym=0, nhash=0, nzip=0, naead=0, val, rc=0;
int mdc=1, modify=0; /* mdc defaults on, modify defaults off. */
char dummy_string[25*4+1]; /* Enough for 25 items. */
if (!string || !ascii_strcasecmp (string, "default"))
{
if (opt.def_preference_list)
string=opt.def_preference_list;
else
{
int any_compress = 0;
dummy_string[0]='\0';
/* The rationale why we use the order AES256,192,128 is
for compatibility reasons with PGP. If gpg would
define AES128 first, we would get the somewhat
confusing situation:
gpg -r pgpkey -r gpgkey ---gives--> AES256
gpg -r gpgkey -r pgpkey ---gives--> AES
Note that by using --personal-cipher-preferences it is
possible to prefer AES128.
*/
/* Make sure we do not add more than 15 items here, as we
could overflow the size of dummy_string. We currently
have at most 12. */
if ( !openpgp_cipher_test_algo (CIPHER_ALGO_AES256) )
strcat(dummy_string,"S9 ");
if ( !openpgp_cipher_test_algo (CIPHER_ALGO_AES192) )
strcat(dummy_string,"S8 ");
if ( !openpgp_cipher_test_algo (CIPHER_ALGO_AES) )
strcat(dummy_string,"S7 ");
strcat(dummy_string,"S2 "); /* 3DES */
if (opt.flags.rfc4880bis && !openpgp_aead_test_algo (AEAD_ALGO_OCB))
strcat(dummy_string,"A2 ");
if (opt.flags.rfc4880bis && !openpgp_aead_test_algo (AEAD_ALGO_EAX))
strcat(dummy_string,"A1 ");
if (personal)
{
/* The default internal hash algo order is:
* SHA-256, SHA-384, SHA-512, SHA-224, SHA-1.
*/
if (!openpgp_md_test_algo (DIGEST_ALGO_SHA256))
strcat (dummy_string, "H8 ");
if (!openpgp_md_test_algo (DIGEST_ALGO_SHA384))
strcat (dummy_string, "H9 ");
if (!openpgp_md_test_algo (DIGEST_ALGO_SHA512))
strcat (dummy_string, "H10 ");
}
else
{
/* The default advertised hash algo order is:
* SHA-512, SHA-384, SHA-256, SHA-224, SHA-1.
*/
if (!openpgp_md_test_algo (DIGEST_ALGO_SHA512))
strcat (dummy_string, "H10 ");
if (!openpgp_md_test_algo (DIGEST_ALGO_SHA384))
strcat (dummy_string, "H9 ");
if (!openpgp_md_test_algo (DIGEST_ALGO_SHA256))
strcat (dummy_string, "H8 ");
}
if (!openpgp_md_test_algo (DIGEST_ALGO_SHA224))
strcat (dummy_string, "H11 ");
strcat (dummy_string, "H2 "); /* SHA-1 */
if(!check_compress_algo(COMPRESS_ALGO_ZLIB))
{
strcat(dummy_string,"Z2 ");
any_compress = 1;
}
if(!check_compress_algo(COMPRESS_ALGO_BZIP2))
{
strcat(dummy_string,"Z3 ");
any_compress = 1;
}
if(!check_compress_algo(COMPRESS_ALGO_ZIP))
{
strcat(dummy_string,"Z1 ");
any_compress = 1;
}
/* In case we have no compress algo at all, declare that
we prefer no compression. */
if (!any_compress)
strcat(dummy_string,"Z0 ");
/* Remove the trailing space. */
if (*dummy_string && dummy_string[strlen (dummy_string)-1] == ' ')
dummy_string[strlen (dummy_string)-1] = 0;
string=dummy_string;
}
}
else if (!ascii_strcasecmp (string, "none"))
string = "";
if(strlen(string))
{
char *prefstringbuf;
char *tok, *prefstring;
/* We need a writable string. */
prefstring = prefstringbuf = xstrdup (string);
while((tok=strsep(&prefstring," ,")))
{
if((val=string_to_cipher_algo (tok)))
{
if(set_one_pref(val,1,tok,sym,&nsym))
rc=-1;
}
else if((val=string_to_digest_algo (tok)))
{
if(set_one_pref(val,2,tok,hash,&nhash))
rc=-1;
}
else if((val=string_to_compress_algo(tok))>-1)
{
if(set_one_pref(val,3,tok,zip,&nzip))
rc=-1;
}
else if ((val=string_to_aead_algo (tok)))
{
if (set_one_pref (val, 4, tok, aead, &naead))
rc = -1;
}
else if (ascii_strcasecmp(tok,"mdc")==0)
mdc=1;
else if (ascii_strcasecmp(tok,"no-mdc")==0)
mdc=0;
else if (ascii_strcasecmp(tok,"ks-modify")==0)
modify=1;
else if (ascii_strcasecmp(tok,"no-ks-modify")==0)
modify=0;
else
{
log_info (_("invalid item '%s' in preference string\n"),tok);
rc=-1;
}
}
xfree (prefstringbuf);
}
if(!rc)
{
if(personal)
{
if(personal==PREFTYPE_SYM)
{
xfree(opt.personal_cipher_prefs);
if(nsym==0)
opt.personal_cipher_prefs=NULL;
else
{
int i;
opt.personal_cipher_prefs=
xmalloc(sizeof(prefitem_t *)*(nsym+1));
for (i=0; i<nsym; i++)
{
opt.personal_cipher_prefs[i].type = PREFTYPE_SYM;
opt.personal_cipher_prefs[i].value = sym[i];
}
opt.personal_cipher_prefs[i].type = PREFTYPE_NONE;
opt.personal_cipher_prefs[i].value = 0;
}
}
else if (personal == PREFTYPE_AEAD)
{
xfree(opt.personal_aead_prefs);
if (!naead)
opt.personal_aead_prefs = NULL;
else
{
int i;
opt.personal_aead_prefs=
xmalloc(sizeof(prefitem_t *)*(naead+1));
for (i=0; i<naead; i++)
{
opt.personal_aead_prefs[i].type = PREFTYPE_AEAD;
opt.personal_aead_prefs[i].value = sym[i];
}
opt.personal_aead_prefs[i].type = PREFTYPE_NONE;
opt.personal_aead_prefs[i].value = 0;
}
}
else if(personal==PREFTYPE_HASH)
{
xfree(opt.personal_digest_prefs);
if(nhash==0)
opt.personal_digest_prefs=NULL;
else
{
int i;
opt.personal_digest_prefs=
xmalloc(sizeof(prefitem_t *)*(nhash+1));
for (i=0; i<nhash; i++)
{
opt.personal_digest_prefs[i].type = PREFTYPE_HASH;
opt.personal_digest_prefs[i].value = hash[i];
}
opt.personal_digest_prefs[i].type = PREFTYPE_NONE;
opt.personal_digest_prefs[i].value = 0;
}
}
else if(personal==PREFTYPE_ZIP)
{
xfree(opt.personal_compress_prefs);
if(nzip==0)
opt.personal_compress_prefs=NULL;
else
{
int i;
opt.personal_compress_prefs=
xmalloc(sizeof(prefitem_t *)*(nzip+1));
for (i=0; i<nzip; i++)
{
opt.personal_compress_prefs[i].type = PREFTYPE_ZIP;
opt.personal_compress_prefs[i].value = zip[i];
}
opt.personal_compress_prefs[i].type = PREFTYPE_NONE;
opt.personal_compress_prefs[i].value = 0;
}
}
}
else
{
memcpy (sym_prefs, sym, (nsym_prefs=nsym));
memcpy (hash_prefs, hash, (nhash_prefs=nhash));
memcpy (zip_prefs, zip, (nzip_prefs=nzip));
memcpy (aead_prefs, aead, (naead_prefs=naead));
mdc_available = mdc;
aead_available = !!naead;
ks_modify = modify;
prefs_initialized = 1;
}
}
return rc;
}
/* Return a fake user ID containing the preferences. Caller must
free. */
PKT_user_id *
keygen_get_std_prefs(void)
{
int i,j=0;
PKT_user_id *uid=xmalloc_clear(sizeof(PKT_user_id));
if(!prefs_initialized)
keygen_set_std_prefs(NULL,0);
uid->ref=1;
uid->prefs = xmalloc ((sizeof(prefitem_t *)*
(nsym_prefs+naead_prefs+nhash_prefs+nzip_prefs+1)));
for(i=0;i<nsym_prefs;i++,j++)
{
uid->prefs[j].type=PREFTYPE_SYM;
uid->prefs[j].value=sym_prefs[i];
}
for (i=0; i < naead_prefs; i++, j++)
{
uid->prefs[j].type = PREFTYPE_AEAD;
uid->prefs[j].value = aead_prefs[i];
}
for(i=0;i<nhash_prefs;i++,j++)
{
uid->prefs[j].type=PREFTYPE_HASH;
uid->prefs[j].value=hash_prefs[i];
}
for(i=0;i<nzip_prefs;i++,j++)
{
uid->prefs[j].type=PREFTYPE_ZIP;
uid->prefs[j].value=zip_prefs[i];
}
uid->prefs[j].type=PREFTYPE_NONE;
uid->prefs[j].value=0;
uid->flags.mdc = mdc_available;
uid->flags.aead = aead_available;
uid->flags.ks_modify = ks_modify;
return uid;
}
static void
add_feature_mdc (PKT_signature *sig,int enabled)
{
const byte *s;
size_t n;
int i;
char *buf;
s = parse_sig_subpkt (sig->hashed, SIGSUBPKT_FEATURES, &n );
/* Already set or cleared */
if (s && n &&
((enabled && (s[0] & 0x01)) || (!enabled && !(s[0] & 0x01))))
return;
if (!s || !n) { /* create a new one */
n = 1;
buf = xmalloc_clear (n);
}
else {
buf = xmalloc (n);
memcpy (buf, s, n);
}
if(enabled)
buf[0] |= 0x01; /* MDC feature */
else
buf[0] &= ~0x01;
/* Are there any bits set? */
for(i=0;i<n;i++)
if(buf[i]!=0)
break;
if(i==n)
delete_sig_subpkt (sig->hashed, SIGSUBPKT_FEATURES);
else
build_sig_subpkt (sig, SIGSUBPKT_FEATURES, buf, n);
xfree (buf);
}
static void
add_feature_aead (PKT_signature *sig, int enabled)
{
const byte *s;
size_t n;
int i;
char *buf;
s = parse_sig_subpkt (sig->hashed, SIGSUBPKT_FEATURES, &n );
if (s && n && ((enabled && (s[0] & 0x02)) || (!enabled && !(s[0] & 0x02))))
return; /* Already set or cleared */
if (!s || !n)
{ /* Create a new one */
n = 1;
buf = xmalloc_clear (n);
}
else
{
buf = xmalloc (n);
memcpy (buf, s, n);
}
if (enabled)
buf[0] |= 0x02; /* AEAD supported */
else
buf[0] &= ~0x02;
/* Are there any bits set? */
for (i=0; i < n; i++)
if (buf[i])
break;
if (i == n)
delete_sig_subpkt (sig->hashed, SIGSUBPKT_FEATURES);
else
build_sig_subpkt (sig, SIGSUBPKT_FEATURES, buf, n);
xfree (buf);
}
static void
add_feature_v5 (PKT_signature *sig, int enabled)
{
const byte *s;
size_t n;
int i;
char *buf;
s = parse_sig_subpkt (sig->hashed, SIGSUBPKT_FEATURES, &n );
if (s && n && ((enabled && (s[0] & 0x04)) || (!enabled && !(s[0] & 0x04))))
return; /* Already set or cleared */
if (!s || !n)
{ /* Create a new one */
n = 1;
buf = xmalloc_clear (n);
}
else
{
buf = xmalloc (n);
memcpy (buf, s, n);
}
if (enabled)
buf[0] |= 0x04; /* v5 key supported */
else
buf[0] &= ~0x04;
/* Are there any bits set? */
for (i=0; i < n; i++)
if (buf[i])
break;
if (i == n)
delete_sig_subpkt (sig->hashed, SIGSUBPKT_FEATURES);
else
build_sig_subpkt (sig, SIGSUBPKT_FEATURES, buf, n);
xfree (buf);
}
static void
add_keyserver_modify (PKT_signature *sig,int enabled)
{
const byte *s;
size_t n;
int i;
char *buf;
/* The keyserver modify flag is a negative flag (i.e. no-modify) */
enabled=!enabled;
s = parse_sig_subpkt (sig->hashed, SIGSUBPKT_KS_FLAGS, &n );
/* Already set or cleared */
if (s && n &&
((enabled && (s[0] & 0x80)) || (!enabled && !(s[0] & 0x80))))
return;
if (!s || !n) { /* create a new one */
n = 1;
buf = xmalloc_clear (n);
}
else {
buf = xmalloc (n);
memcpy (buf, s, n);
}
if(enabled)
buf[0] |= 0x80; /* no-modify flag */
else
buf[0] &= ~0x80;
/* Are there any bits set? */
for(i=0;i<n;i++)
if(buf[i]!=0)
break;
if(i==n)
delete_sig_subpkt (sig->hashed, SIGSUBPKT_KS_FLAGS);
else
build_sig_subpkt (sig, SIGSUBPKT_KS_FLAGS, buf, n);
xfree (buf);
}
int
keygen_upd_std_prefs (PKT_signature *sig, void *opaque)
{
(void)opaque;
if (!prefs_initialized)
keygen_set_std_prefs (NULL, 0);
if (nsym_prefs)
build_sig_subpkt (sig, SIGSUBPKT_PREF_SYM, sym_prefs, nsym_prefs);
else
{
delete_sig_subpkt (sig->hashed, SIGSUBPKT_PREF_SYM);
delete_sig_subpkt (sig->unhashed, SIGSUBPKT_PREF_SYM);
}
if (naead_prefs)
build_sig_subpkt (sig, SIGSUBPKT_PREF_AEAD, aead_prefs, naead_prefs);
else
{
delete_sig_subpkt (sig->hashed, SIGSUBPKT_PREF_AEAD);
delete_sig_subpkt (sig->unhashed, SIGSUBPKT_PREF_AEAD);
}
if (nhash_prefs)
build_sig_subpkt (sig, SIGSUBPKT_PREF_HASH, hash_prefs, nhash_prefs);
else
{
delete_sig_subpkt (sig->hashed, SIGSUBPKT_PREF_HASH);
delete_sig_subpkt (sig->unhashed, SIGSUBPKT_PREF_HASH);
}
if (nzip_prefs)
build_sig_subpkt (sig, SIGSUBPKT_PREF_COMPR, zip_prefs, nzip_prefs);
else
{
delete_sig_subpkt (sig->hashed, SIGSUBPKT_PREF_COMPR);
delete_sig_subpkt (sig->unhashed, SIGSUBPKT_PREF_COMPR);
}
/* Make sure that the MDC feature flag is set if needed. */
add_feature_mdc (sig,mdc_available);
add_feature_aead (sig, aead_available);
add_feature_v5 (sig, opt.flags.rfc4880bis);
add_keyserver_modify (sig,ks_modify);
keygen_add_keyserver_url(sig,NULL);
return 0;
}
/****************
* Add preference to the self signature packet.
* This is only called for packets with version > 3.
*/
int
keygen_add_std_prefs (PKT_signature *sig, void *opaque)
{
PKT_public_key *pk = opaque;
do_add_key_flags (sig, pk->pubkey_usage);
keygen_add_key_expire (sig, opaque );
keygen_upd_std_prefs (sig, opaque);
keygen_add_keyserver_url (sig,NULL);
return 0;
}
int
keygen_add_keyserver_url(PKT_signature *sig, void *opaque)
{
const char *url=opaque;
if(!url)
url=opt.def_keyserver_url;
if(url)
build_sig_subpkt(sig,SIGSUBPKT_PREF_KS,url,strlen(url));
else
delete_sig_subpkt (sig->hashed,SIGSUBPKT_PREF_KS);
return 0;
}
int
keygen_add_notations(PKT_signature *sig,void *opaque)
{
struct notation *notation;
/* We always start clean */
delete_sig_subpkt(sig->hashed,SIGSUBPKT_NOTATION);
delete_sig_subpkt(sig->unhashed,SIGSUBPKT_NOTATION);
sig->flags.notation=0;
for(notation=opaque;notation;notation=notation->next)
if(!notation->flags.ignore)
{
unsigned char *buf;
unsigned int n1,n2;
n1=strlen(notation->name);
if(notation->altvalue)
n2=strlen(notation->altvalue);
else if(notation->bdat)
n2=notation->blen;
else
n2=strlen(notation->value);
buf = xmalloc( 8 + n1 + n2 );
/* human readable or not */
buf[0] = notation->bdat?0:0x80;
buf[1] = buf[2] = buf[3] = 0;
buf[4] = n1 >> 8;
buf[5] = n1;
buf[6] = n2 >> 8;
buf[7] = n2;
memcpy(buf+8, notation->name, n1 );
if(notation->altvalue)
memcpy(buf+8+n1, notation->altvalue, n2 );
else if(notation->bdat)
memcpy(buf+8+n1, notation->bdat, n2 );
else
memcpy(buf+8+n1, notation->value, n2 );
build_sig_subpkt( sig, SIGSUBPKT_NOTATION |
(notation->flags.critical?SIGSUBPKT_FLAG_CRITICAL:0),
buf, 8+n1+n2 );
xfree(buf);
}
return 0;
}
int
keygen_add_revkey (PKT_signature *sig, void *opaque)
{
struct revocation_key *revkey = opaque;
byte buf[2+MAX_FINGERPRINT_LEN];
log_assert (revkey->fprlen <= MAX_FINGERPRINT_LEN);
buf[0] = revkey->class;
buf[1] = revkey->algid;
memcpy (buf + 2, revkey->fpr, revkey->fprlen);
memset (buf + 2 + revkey->fprlen, 0, sizeof (revkey->fpr) - revkey->fprlen);
build_sig_subpkt (sig, SIGSUBPKT_REV_KEY, buf, 2+revkey->fprlen);
/* All sigs with revocation keys set are nonrevocable. */
sig->flags.revocable = 0;
buf[0] = 0;
build_sig_subpkt (sig, SIGSUBPKT_REVOCABLE, buf, 1);
parse_revkeys (sig);
return 0;
}
/* Create a back-signature. If TIMESTAMP is not NULL, use it for the
signature creation time. */
gpg_error_t
make_backsig (ctrl_t ctrl, PKT_signature *sig, PKT_public_key *pk,
PKT_public_key *sub_pk, PKT_public_key *sub_psk,
u32 timestamp, const char *cache_nonce)
{
gpg_error_t err;
PKT_signature *backsig;
cache_public_key (sub_pk);
err = make_keysig_packet (ctrl, &backsig, pk, NULL, sub_pk, sub_psk, 0x19,
- 0, timestamp, 0, NULL, NULL, cache_nonce);
+ timestamp, 0, NULL, NULL, cache_nonce);
if (err)
log_error ("make_keysig_packet failed for backsig: %s\n",
gpg_strerror (err));
else
{
/* Get it into a binary packed form. */
IOBUF backsig_out = iobuf_temp();
PACKET backsig_pkt;
init_packet (&backsig_pkt);
backsig_pkt.pkttype = PKT_SIGNATURE;
backsig_pkt.pkt.signature = backsig;
err = build_packet (backsig_out, &backsig_pkt);
free_packet (&backsig_pkt, NULL);
if (err)
log_error ("build_packet failed for backsig: %s\n", gpg_strerror (err));
else
{
size_t pktlen = 0;
byte *buf = iobuf_get_temp_buffer (backsig_out);
/* Remove the packet header. */
if(buf[0]&0x40)
{
if (buf[1] < 192)
{
pktlen = buf[1];
buf += 2;
}
else if(buf[1] < 224)
{
pktlen = (buf[1]-192)*256;
pktlen += buf[2]+192;
buf += 3;
}
else if (buf[1] == 255)
{
pktlen = buf32_to_size_t (buf+2);
buf += 6;
}
else
BUG ();
}
else
{
int mark = 1;
switch (buf[0]&3)
{
case 3:
BUG ();
break;
case 2:
pktlen = (size_t)buf[mark++] << 24;
pktlen |= buf[mark++] << 16;
/* fall through */
case 1:
pktlen |= buf[mark++] << 8;
/* fall through */
case 0:
pktlen |= buf[mark++];
}
buf += mark;
}
/* Now make the binary blob into a subpacket. */
build_sig_subpkt (sig, SIGSUBPKT_SIGNATURE, buf, pktlen);
iobuf_close (backsig_out);
}
}
return err;
}
/* Write a direct key signature to the first key in ROOT using the key
PSK. REVKEY is describes the direct key signature and TIMESTAMP is
the timestamp to set on the signature. */
static gpg_error_t
write_direct_sig (ctrl_t ctrl, kbnode_t root, PKT_public_key *psk,
struct revocation_key *revkey, u32 timestamp,
const char *cache_nonce)
{
gpg_error_t err;
PACKET *pkt;
PKT_signature *sig;
KBNODE node;
PKT_public_key *pk;
if (opt.verbose)
log_info (_("writing direct signature\n"));
/* Get the pk packet from the pub_tree. */
node = find_kbnode (root, PKT_PUBLIC_KEY);
if (!node)
BUG ();
pk = node->pkt->pkt.public_key;
/* We have to cache the key, so that the verification of the
signature creation is able to retrieve the public key. */
cache_public_key (pk);
/* Make the signature. */
err = make_keysig_packet (ctrl, &sig, pk, NULL,NULL, psk, 0x1F,
- 0, timestamp, 0,
+ timestamp, 0,
keygen_add_revkey, revkey, cache_nonce);
if (err)
{
log_error ("make_keysig_packet failed: %s\n", gpg_strerror (err) );
return err;
}
pkt = xmalloc_clear (sizeof *pkt);
pkt->pkttype = PKT_SIGNATURE;
pkt->pkt.signature = sig;
add_kbnode (root, new_kbnode (pkt));
return err;
}
/* Write a self-signature to the first user id in ROOT using the key
PSK. USE and TIMESTAMP give the extra data we need for the
signature. */
static gpg_error_t
write_selfsigs (ctrl_t ctrl, kbnode_t root, PKT_public_key *psk,
unsigned int use, u32 timestamp, const char *cache_nonce)
{
gpg_error_t err;
PACKET *pkt;
PKT_signature *sig;
PKT_user_id *uid;
KBNODE node;
PKT_public_key *pk;
if (opt.verbose)
log_info (_("writing self signature\n"));
/* Get the uid packet from the list. */
node = find_kbnode (root, PKT_USER_ID);
if (!node)
BUG(); /* No user id packet in tree. */
uid = node->pkt->pkt.user_id;
/* Get the pk packet from the pub_tree. */
node = find_kbnode (root, PKT_PUBLIC_KEY);
if (!node)
BUG();
pk = node->pkt->pkt.public_key;
/* The usage has not yet been set - do it now. */
pk->pubkey_usage = use;
/* We have to cache the key, so that the verification of the
signature creation is able to retrieve the public key. */
cache_public_key (pk);
/* Make the signature. */
err = make_keysig_packet (ctrl, &sig, pk, uid, NULL, psk, 0x13,
- 0, timestamp, 0,
+ timestamp, 0,
keygen_add_std_prefs, pk, cache_nonce);
if (err)
{
log_error ("make_keysig_packet failed: %s\n", gpg_strerror (err));
return err;
}
pkt = xmalloc_clear (sizeof *pkt);
pkt->pkttype = PKT_SIGNATURE;
pkt->pkt.signature = sig;
add_kbnode (root, new_kbnode (pkt));
return err;
}
/* Write the key binding signature. If TIMESTAMP is not NULL use the
signature creation time. PRI_PSK is the key use for signing.
SUB_PSK is a key used to create a back-signature; that one is only
used if USE has the PUBKEY_USAGE_SIG capability. */
static int
write_keybinding (ctrl_t ctrl, kbnode_t root,
PKT_public_key *pri_psk, PKT_public_key *sub_psk,
unsigned int use, u32 timestamp, const char *cache_nonce)
{
gpg_error_t err;
PACKET *pkt;
PKT_signature *sig;
KBNODE node;
PKT_public_key *pri_pk, *sub_pk;
struct opaque_data_usage_and_pk oduap;
if (opt.verbose)
log_info(_("writing key binding signature\n"));
/* Get the primary pk packet from the tree. */
node = find_kbnode (root, PKT_PUBLIC_KEY);
if (!node)
BUG();
pri_pk = node->pkt->pkt.public_key;
/* We have to cache the key, so that the verification of the
* signature creation is able to retrieve the public key. */
cache_public_key (pri_pk);
/* Find the last subkey. */
sub_pk = NULL;
for (node = root; node; node = node->next )
{
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
sub_pk = node->pkt->pkt.public_key;
}
if (!sub_pk)
BUG();
/* Make the signature. */
oduap.usage = use;
oduap.pk = sub_pk;
err = make_keysig_packet (ctrl, &sig, pri_pk, NULL, sub_pk, pri_psk, 0x18,
- 0, timestamp, 0,
+ timestamp, 0,
keygen_add_key_flags_and_expire, &oduap,
cache_nonce);
if (err)
{
log_error ("make_keysig_packeto failed: %s\n", gpg_strerror (err));
return err;
}
/* Make a backsig. */
if (use & PUBKEY_USAGE_SIG)
{
err = make_backsig (ctrl,
sig, pri_pk, sub_pk, sub_psk, timestamp, cache_nonce);
if (err)
return err;
}
pkt = xmalloc_clear ( sizeof *pkt );
pkt->pkttype = PKT_SIGNATURE;
pkt->pkt.signature = sig;
add_kbnode (root, new_kbnode (pkt) );
return err;
}
static gpg_error_t
ecckey_from_sexp (gcry_mpi_t *array, gcry_sexp_t sexp, int algo)
{
gpg_error_t err;
gcry_sexp_t list, l2;
char *curve = NULL;
int i;
const char *oidstr;
unsigned int nbits;
array[0] = NULL;
array[1] = NULL;
array[2] = NULL;
list = gcry_sexp_find_token (sexp, "public-key", 0);
if (!list)
return gpg_error (GPG_ERR_INV_OBJ);
l2 = gcry_sexp_cadr (list);
gcry_sexp_release (list);
list = l2;
if (!list)
return gpg_error (GPG_ERR_NO_OBJ);
l2 = gcry_sexp_find_token (list, "curve", 0);
if (!l2)
{
err = gpg_error (GPG_ERR_NO_OBJ);
goto leave;
}
curve = gcry_sexp_nth_string (l2, 1);
if (!curve)
{
err = gpg_error (GPG_ERR_NO_OBJ);
goto leave;
}
gcry_sexp_release (l2);
oidstr = openpgp_curve_to_oid (curve, &nbits);
if (!oidstr)
{
/* That can't happen because we used one of the curves
gpg_curve_to_oid knows about. */
err = gpg_error (GPG_ERR_INV_OBJ);
goto leave;
}
err = openpgp_oid_from_str (oidstr, &array[0]);
if (err)
goto leave;
l2 = gcry_sexp_find_token (list, "q", 0);
if (!l2)
{
err = gpg_error (GPG_ERR_NO_OBJ);
goto leave;
}
array[1] = gcry_sexp_nth_mpi (l2, 1, GCRYMPI_FMT_USG);
gcry_sexp_release (l2);
if (!array[1])
{
err = gpg_error (GPG_ERR_INV_OBJ);
goto leave;
}
gcry_sexp_release (list);
if (algo == PUBKEY_ALGO_ECDH)
{
array[2] = pk_ecdh_default_params (nbits);
if (!array[2])
{
err = gpg_error_from_syserror ();
goto leave;
}
}
leave:
xfree (curve);
if (err)
{
for (i=0; i < 3; i++)
{
gcry_mpi_release (array[i]);
array[i] = NULL;
}
}
return err;
}
/* Extract key parameters from SEXP and store them in ARRAY. ELEMS is
a string where each character denotes a parameter name. TOPNAME is
the name of the top element above the elements. */
static int
key_from_sexp (gcry_mpi_t *array, gcry_sexp_t sexp,
const char *topname, const char *elems)
{
gcry_sexp_t list, l2;
const char *s;
int i, idx;
int rc = 0;
list = gcry_sexp_find_token (sexp, topname, 0);
if (!list)
return gpg_error (GPG_ERR_INV_OBJ);
l2 = gcry_sexp_cadr (list);
gcry_sexp_release (list);
list = l2;
if (!list)
return gpg_error (GPG_ERR_NO_OBJ);
for (idx=0,s=elems; *s; s++, idx++)
{
l2 = gcry_sexp_find_token (list, s, 1);
if (!l2)
{
rc = gpg_error (GPG_ERR_NO_OBJ); /* required parameter not found */
goto leave;
}
array[idx] = gcry_sexp_nth_mpi (l2, 1, GCRYMPI_FMT_USG);
gcry_sexp_release (l2);
if (!array[idx])
{
rc = gpg_error (GPG_ERR_INV_OBJ); /* required parameter invalid */
goto leave;
}
}
gcry_sexp_release (list);
leave:
if (rc)
{
for (i=0; i<idx; i++)
{
gcry_mpi_release (array[i]);
array[i] = NULL;
}
gcry_sexp_release (list);
}
return rc;
}
/* Create a keyblock using the given KEYGRIP. ALGO is the OpenPGP
algorithm of that keygrip. */
static int
do_create_from_keygrip (ctrl_t ctrl, int algo, const char *hexkeygrip,
kbnode_t pub_root, u32 timestamp, u32 expireval,
int is_subkey, int keygen_flags)
{
int err;
PACKET *pkt;
PKT_public_key *pk;
gcry_sexp_t s_key;
const char *algoelem;
if (hexkeygrip[0] == '&')
hexkeygrip++;
switch (algo)
{
case PUBKEY_ALGO_RSA: algoelem = "ne"; break;
case PUBKEY_ALGO_DSA: algoelem = "pqgy"; break;
case PUBKEY_ALGO_ELGAMAL_E: algoelem = "pgy"; break;
case PUBKEY_ALGO_ECDH:
case PUBKEY_ALGO_ECDSA: algoelem = ""; break;
case PUBKEY_ALGO_EDDSA: algoelem = ""; break;
default: return gpg_error (GPG_ERR_INTERNAL);
}
/* Ask the agent for the public key matching HEXKEYGRIP. */
{
unsigned char *public;
err = agent_readkey (ctrl, 0, hexkeygrip, &public);
if (err)
return err;
err = gcry_sexp_sscan (&s_key, NULL,
public, gcry_sexp_canon_len (public, 0, NULL, NULL));
xfree (public);
if (err)
return err;
}
/* Build a public key packet. */
pk = xtrycalloc (1, sizeof *pk);
if (!pk)
{
err = gpg_error_from_syserror ();
gcry_sexp_release (s_key);
return err;
}
pk->timestamp = timestamp;
pk->version = (keygen_flags & KEYGEN_FLAG_CREATE_V5_KEY)? 5 : 4;
if (expireval)
pk->expiredate = pk->timestamp + expireval;
pk->pubkey_algo = algo;
if (algo == PUBKEY_ALGO_ECDSA
|| algo == PUBKEY_ALGO_EDDSA
|| algo == PUBKEY_ALGO_ECDH )
err = ecckey_from_sexp (pk->pkey, s_key, algo);
else
err = key_from_sexp (pk->pkey, s_key, "public-key", algoelem);
if (err)
{
log_error ("key_from_sexp failed: %s\n", gpg_strerror (err) );
gcry_sexp_release (s_key);
free_public_key (pk);
return err;
}
gcry_sexp_release (s_key);
pkt = xtrycalloc (1, sizeof *pkt);
if (!pkt)
{
err = gpg_error_from_syserror ();
free_public_key (pk);
return err;
}
pkt->pkttype = is_subkey ? PKT_PUBLIC_SUBKEY : PKT_PUBLIC_KEY;
pkt->pkt.public_key = pk;
add_kbnode (pub_root, new_kbnode (pkt));
return 0;
}
/* Common code for the key generation function gen_xxx. */
static int
common_gen (const char *keyparms, int algo, const char *algoelem,
kbnode_t pub_root, u32 timestamp, u32 expireval, int is_subkey,
int keygen_flags, const char *passphrase,
char **cache_nonce_addr, char **passwd_nonce_addr)
{
int err;
PACKET *pkt;
PKT_public_key *pk;
gcry_sexp_t s_key;
err = agent_genkey (NULL, cache_nonce_addr, passwd_nonce_addr, keyparms,
!!(keygen_flags & KEYGEN_FLAG_NO_PROTECTION),
passphrase,
&s_key);
if (err)
{
log_error ("agent_genkey failed: %s\n", gpg_strerror (err) );
return err;
}
pk = xtrycalloc (1, sizeof *pk);
if (!pk)
{
err = gpg_error_from_syserror ();
gcry_sexp_release (s_key);
return err;
}
pk->timestamp = timestamp;
pk->version = (keygen_flags & KEYGEN_FLAG_CREATE_V5_KEY)? 5 : 4;
if (expireval)
pk->expiredate = pk->timestamp + expireval;
pk->pubkey_algo = algo;
if (algo == PUBKEY_ALGO_ECDSA
|| algo == PUBKEY_ALGO_EDDSA
|| algo == PUBKEY_ALGO_ECDH )
err = ecckey_from_sexp (pk->pkey, s_key, algo);
else
err = key_from_sexp (pk->pkey, s_key, "public-key", algoelem);
if (err)
{
log_error ("key_from_sexp failed: %s\n", gpg_strerror (err) );
gcry_sexp_release (s_key);
free_public_key (pk);
return err;
}
gcry_sexp_release (s_key);
pkt = xtrycalloc (1, sizeof *pkt);
if (!pkt)
{
err = gpg_error_from_syserror ();
free_public_key (pk);
return err;
}
pkt->pkttype = is_subkey ? PKT_PUBLIC_SUBKEY : PKT_PUBLIC_KEY;
pkt->pkt.public_key = pk;
add_kbnode (pub_root, new_kbnode (pkt));
return 0;
}
/*
* Generate an Elgamal key.
*/
static int
gen_elg (int algo, unsigned int nbits, KBNODE pub_root,
u32 timestamp, u32 expireval, int is_subkey,
int keygen_flags, const char *passphrase,
char **cache_nonce_addr, char **passwd_nonce_addr)
{
int err;
char *keyparms;
char nbitsstr[35];
log_assert (is_ELGAMAL (algo));
if (nbits < 1024)
{
nbits = 2048;
log_info (_("keysize invalid; using %u bits\n"), nbits );
}
else if (nbits > 4096)
{
nbits = 4096;
log_info (_("keysize invalid; using %u bits\n"), nbits );
}
if ((nbits % 32))
{
nbits = ((nbits + 31) / 32) * 32;
log_info (_("keysize rounded up to %u bits\n"), nbits );
}
/* Note that we use transient-key only if no-protection has also
been enabled. */
snprintf (nbitsstr, sizeof nbitsstr, "%u", nbits);
keyparms = xtryasprintf ("(genkey(%s(nbits %zu:%s)%s))",
algo == GCRY_PK_ELG_E ? "openpgp-elg" :
algo == GCRY_PK_ELG ? "elg" : "x-oops" ,
strlen (nbitsstr), nbitsstr,
((keygen_flags & KEYGEN_FLAG_TRANSIENT_KEY)
&& (keygen_flags & KEYGEN_FLAG_NO_PROTECTION))?
"(transient-key)" : "" );
if (!keyparms)
err = gpg_error_from_syserror ();
else
{
err = common_gen (keyparms, algo, "pgy",
pub_root, timestamp, expireval, is_subkey,
keygen_flags, passphrase,
cache_nonce_addr, passwd_nonce_addr);
xfree (keyparms);
}
return err;
}
/*
* Generate an DSA key
*/
static gpg_error_t
gen_dsa (unsigned int nbits, KBNODE pub_root,
u32 timestamp, u32 expireval, int is_subkey,
int keygen_flags, const char *passphrase,
char **cache_nonce_addr, char **passwd_nonce_addr)
{
int err;
unsigned int qbits;
char *keyparms;
char nbitsstr[35];
char qbitsstr[35];
if (nbits < 768)
{
nbits = 2048;
log_info(_("keysize invalid; using %u bits\n"), nbits );
}
else if ( nbits > 3072 )
{
nbits = 3072;
log_info(_("keysize invalid; using %u bits\n"), nbits );
}
if( (nbits % 64) )
{
nbits = ((nbits + 63) / 64) * 64;
log_info(_("keysize rounded up to %u bits\n"), nbits );
}
/* To comply with FIPS rules we round up to the next value unless in
expert mode. */
if (!opt.expert && nbits > 1024 && (nbits % 1024))
{
nbits = ((nbits + 1023) / 1024) * 1024;
log_info(_("keysize rounded up to %u bits\n"), nbits );
}
/*
Figure out a q size based on the key size. FIPS 180-3 says:
L = 1024, N = 160
L = 2048, N = 224
L = 2048, N = 256
L = 3072, N = 256
2048/256 is an odd pair since there is also a 2048/224 and
3072/256. Matching sizes is not a very exact science.
We'll do 256 qbits for nbits over 2047, 224 for nbits over 1024
but less than 2048, and 160 for 1024 (DSA1).
*/
if (nbits > 2047)
qbits = 256;
else if ( nbits > 1024)
qbits = 224;
else
qbits = 160;
if (qbits != 160 )
log_info (_("WARNING: some OpenPGP programs can't"
" handle a DSA key with this digest size\n"));
snprintf (nbitsstr, sizeof nbitsstr, "%u", nbits);
snprintf (qbitsstr, sizeof qbitsstr, "%u", qbits);
keyparms = xtryasprintf ("(genkey(dsa(nbits %zu:%s)(qbits %zu:%s)%s))",
strlen (nbitsstr), nbitsstr,
strlen (qbitsstr), qbitsstr,
((keygen_flags & KEYGEN_FLAG_TRANSIENT_KEY)
&& (keygen_flags & KEYGEN_FLAG_NO_PROTECTION))?
"(transient-key)" : "" );
if (!keyparms)
err = gpg_error_from_syserror ();
else
{
err = common_gen (keyparms, PUBKEY_ALGO_DSA, "pqgy",
pub_root, timestamp, expireval, is_subkey,
keygen_flags, passphrase,
cache_nonce_addr, passwd_nonce_addr);
xfree (keyparms);
}
return err;
}
/*
* Generate an ECC key
*/
static gpg_error_t
gen_ecc (int algo, const char *curve, kbnode_t pub_root,
u32 timestamp, u32 expireval, int is_subkey,
int keygen_flags, const char *passphrase,
char **cache_nonce_addr, char **passwd_nonce_addr)
{
gpg_error_t err;
char *keyparms;
log_assert (algo == PUBKEY_ALGO_ECDSA
|| algo == PUBKEY_ALGO_EDDSA
|| algo == PUBKEY_ALGO_ECDH);
if (!curve || !*curve)
return gpg_error (GPG_ERR_UNKNOWN_CURVE);
/* Map the displayed short forms of some curves to their canonical
* names. */
if (!ascii_strcasecmp (curve, "cv25519"))
curve = "Curve25519";
else if (!ascii_strcasecmp (curve, "ed25519"))
curve = "Ed25519";
/* Note that we use the "comp" flag with EdDSA to request the use of
a 0x40 compression prefix octet. */
if (algo == PUBKEY_ALGO_EDDSA)
keyparms = xtryasprintf
("(genkey(ecc(curve %zu:%s)(flags eddsa comp%s)))",
strlen (curve), curve,
(((keygen_flags & KEYGEN_FLAG_TRANSIENT_KEY)
&& (keygen_flags & KEYGEN_FLAG_NO_PROTECTION))?
" transient-key" : ""));
else if (algo == PUBKEY_ALGO_ECDH && !strcmp (curve, "Curve25519"))
keyparms = xtryasprintf
("(genkey(ecc(curve %zu:%s)(flags djb-tweak comp%s)))",
strlen (curve), curve,
(((keygen_flags & KEYGEN_FLAG_TRANSIENT_KEY)
&& (keygen_flags & KEYGEN_FLAG_NO_PROTECTION))?
" transient-key" : ""));
else
keyparms = xtryasprintf
("(genkey(ecc(curve %zu:%s)(flags nocomp%s)))",
strlen (curve), curve,
(((keygen_flags & KEYGEN_FLAG_TRANSIENT_KEY)
&& (keygen_flags & KEYGEN_FLAG_NO_PROTECTION))?
" transient-key" : ""));
if (!keyparms)
err = gpg_error_from_syserror ();
else
{
err = common_gen (keyparms, algo, "",
pub_root, timestamp, expireval, is_subkey,
keygen_flags, passphrase,
cache_nonce_addr, passwd_nonce_addr);
xfree (keyparms);
}
return err;
}
/*
* Generate an RSA key.
*/
static int
gen_rsa (int algo, unsigned int nbits, KBNODE pub_root,
u32 timestamp, u32 expireval, int is_subkey,
int keygen_flags, const char *passphrase,
char **cache_nonce_addr, char **passwd_nonce_addr)
{
int err;
char *keyparms;
char nbitsstr[35];
const unsigned maxsize = (opt.flags.large_rsa ? 8192 : 4096);
log_assert (is_RSA(algo));
if (!nbits)
nbits = get_keysize_range (algo, NULL, NULL);
if (nbits < 1024)
{
nbits = 3072;
log_info (_("keysize invalid; using %u bits\n"), nbits );
}
else if (nbits > maxsize)
{
nbits = maxsize;
log_info (_("keysize invalid; using %u bits\n"), nbits );
}
if ((nbits % 32))
{
nbits = ((nbits + 31) / 32) * 32;
log_info (_("keysize rounded up to %u bits\n"), nbits );
}
snprintf (nbitsstr, sizeof nbitsstr, "%u", nbits);
keyparms = xtryasprintf ("(genkey(rsa(nbits %zu:%s)%s))",
strlen (nbitsstr), nbitsstr,
((keygen_flags & KEYGEN_FLAG_TRANSIENT_KEY)
&& (keygen_flags & KEYGEN_FLAG_NO_PROTECTION))?
"(transient-key)" : "" );
if (!keyparms)
err = gpg_error_from_syserror ();
else
{
err = common_gen (keyparms, algo, "ne",
pub_root, timestamp, expireval, is_subkey,
keygen_flags, passphrase,
cache_nonce_addr, passwd_nonce_addr);
xfree (keyparms);
}
return err;
}
/****************
* check valid days:
* return 0 on error or the multiplier
*/
static int
check_valid_days( const char *s )
{
if( !digitp(s) )
return 0;
for( s++; *s; s++)
if( !digitp(s) )
break;
if( !*s )
return 1;
if( s[1] )
return 0; /* e.g. "2323wc" */
if( *s == 'd' || *s == 'D' )
return 1;
if( *s == 'w' || *s == 'W' )
return 7;
if( *s == 'm' || *s == 'M' )
return 30;
if( *s == 'y' || *s == 'Y' )
return 365;
return 0;
}
static void
print_key_flags(int flags)
{
if(flags&PUBKEY_USAGE_SIG)
tty_printf("%s ",_("Sign"));
if(flags&PUBKEY_USAGE_CERT)
tty_printf("%s ",_("Certify"));
if(flags&PUBKEY_USAGE_ENC)
tty_printf("%s ",_("Encrypt"));
if(flags&PUBKEY_USAGE_AUTH)
tty_printf("%s ",_("Authenticate"));
}
/* Ask for the key flags and return them. CURRENT gives the current
- * usage which should normally be given as 0. */
+ * usage which should normally be given as 0. MASK gives the allowed
+ * flags. */
unsigned int
-ask_key_flags (int algo, int subkey, unsigned int current)
+ask_key_flags_with_mask (int algo, int subkey, unsigned int current,
+ unsigned int mask)
{
/* TRANSLATORS: Please use only plain ASCII characters for the
- translation. If this is not possible use single digits. The
- string needs to 8 bytes long. Here is a description of the
- functions:
-
- s = Toggle signing capability
- e = Toggle encryption capability
- a = Toggle authentication capability
- q = Finish
- */
+ * translation. If this is not possible use single digits. The
+ * string needs to 8 bytes long. Here is a description of the
+ * functions:
+ *
+ * s = Toggle signing capability
+ * e = Toggle encryption capability
+ * a = Toggle authentication capability
+ * q = Finish
+ */
const char *togglers = _("SsEeAaQq");
char *answer = NULL;
const char *s;
- unsigned int possible = openpgp_pk_algo_usage(algo);
+ unsigned int possible;
if ( strlen(togglers) != 8 )
{
tty_printf ("NOTE: Bad translation at %s:%d. "
"Please report.\n", __FILE__, __LINE__);
togglers = "11223300";
}
- /* Only primary keys may certify. */
- if(subkey)
- possible&=~PUBKEY_USAGE_CERT;
+ /* Mask the possible usage flags. This is for example used for a
+ * card based key. */
+ possible = (openpgp_pk_algo_usage (algo) & mask);
- /* Preload the current set with the possible set, minus
- authentication if CURRENT has been given as 0. If CURRENT has
- been has non-zero we mask with all possible usages. */
+ /* However, only primary keys may certify. */
+ if (subkey)
+ possible &= ~PUBKEY_USAGE_CERT;
+
+ /* Preload the current set with the possible set, without
+ * authentication if CURRENT is 0. If CURRENT is non-zero we mask
+ * with all possible usages. */
if (current)
current &= possible;
else
current = (possible&~PUBKEY_USAGE_AUTH);
- for(;;)
+ for (;;)
{
tty_printf("\n");
- tty_printf(_("Possible actions for a %s key: "),
+ tty_printf(_("Possible actions for this %s key: "),
(algo == PUBKEY_ALGO_ECDSA
|| algo == PUBKEY_ALGO_EDDSA)
? "ECDSA/EdDSA" : openpgp_pk_algo_name (algo));
print_key_flags(possible);
tty_printf("\n");
tty_printf(_("Current allowed actions: "));
print_key_flags(current);
tty_printf("\n\n");
if(possible&PUBKEY_USAGE_SIG)
tty_printf(_(" (%c) Toggle the sign capability\n"),
togglers[0]);
if(possible&PUBKEY_USAGE_ENC)
tty_printf(_(" (%c) Toggle the encrypt capability\n"),
togglers[2]);
if(possible&PUBKEY_USAGE_AUTH)
tty_printf(_(" (%c) Toggle the authenticate capability\n"),
togglers[4]);
tty_printf(_(" (%c) Finished\n"),togglers[6]);
tty_printf("\n");
xfree(answer);
answer = cpr_get("keygen.flags",_("Your selection? "));
cpr_kill_prompt();
if (*answer == '=')
{
/* Hack to allow direct entry of the capabilities. */
current = 0;
for (s=answer+1; *s; s++)
{
if ((*s == 's' || *s == 'S') && (possible&PUBKEY_USAGE_SIG))
current |= PUBKEY_USAGE_SIG;
else if ((*s == 'e' || *s == 'E') && (possible&PUBKEY_USAGE_ENC))
current |= PUBKEY_USAGE_ENC;
else if ((*s == 'a' || *s == 'A') && (possible&PUBKEY_USAGE_AUTH))
current |= PUBKEY_USAGE_AUTH;
else if (!subkey && *s == 'c')
{
/* Accept 'c' for the primary key because USAGE_CERT
will be set anyway. This is for folks who
want to experiment with a cert-only primary key. */
current |= PUBKEY_USAGE_CERT;
}
}
break;
}
else if (strlen(answer)>1)
tty_printf(_("Invalid selection.\n"));
else if(*answer=='\0' || *answer==togglers[6] || *answer==togglers[7])
break;
else if((*answer==togglers[0] || *answer==togglers[1])
&& possible&PUBKEY_USAGE_SIG)
{
if(current&PUBKEY_USAGE_SIG)
current&=~PUBKEY_USAGE_SIG;
else
current|=PUBKEY_USAGE_SIG;
}
else if((*answer==togglers[2] || *answer==togglers[3])
&& possible&PUBKEY_USAGE_ENC)
{
if(current&PUBKEY_USAGE_ENC)
current&=~PUBKEY_USAGE_ENC;
else
current|=PUBKEY_USAGE_ENC;
}
else if((*answer==togglers[4] || *answer==togglers[5])
&& possible&PUBKEY_USAGE_AUTH)
{
if(current&PUBKEY_USAGE_AUTH)
current&=~PUBKEY_USAGE_AUTH;
else
current|=PUBKEY_USAGE_AUTH;
}
else
tty_printf(_("Invalid selection.\n"));
}
xfree(answer);
return current;
}
+unsigned int
+ask_key_flags (int algo, int subkey, unsigned int current)
+{
+ return ask_key_flags_with_mask (algo, subkey, current, ~0);
+}
+
+
/* Check whether we have a key for the key with HEXGRIP. Returns 0 if
there is no such key or the OpenPGP algo number for the key. */
static int
check_keygrip (ctrl_t ctrl, const char *hexgrip)
{
gpg_error_t err;
unsigned char *public;
size_t publiclen;
int algo;
if (hexgrip[0] == '&')
hexgrip++;
err = agent_readkey (ctrl, 0, hexgrip, &public);
if (err)
return 0;
publiclen = gcry_sexp_canon_len (public, 0, NULL, NULL);
algo = get_pk_algo_from_canon_sexp (public, publiclen);
xfree (public);
return map_pk_gcry_to_openpgp (algo);
}
/* Ask for an algorithm. The function returns the algorithm id to
* create. If ADDMODE is false the function won't show an option to
* create the primary and subkey combined and won't set R_USAGE
* either. If a combined algorithm has been selected, the subkey
* algorithm is stored at R_SUBKEY_ALGO. If R_KEYGRIP is given, the
* user has the choice to enter the keygrip of an existing key. That
* keygrip is then stored at this address. The caller needs to free
* it. */
static int
ask_algo (ctrl_t ctrl, int addmode, int *r_subkey_algo, unsigned int *r_usage,
char **r_keygrip)
{
+ gpg_error_t err;
char *keygrip = NULL;
char *answer = NULL;
int algo;
int dummy_algo;
+ char *p;
if (!r_subkey_algo)
r_subkey_algo = &dummy_algo;
tty_printf (_("Please select what kind of key you want:\n"));
#if GPG_USE_RSA
if (!addmode)
tty_printf (_(" (%d) RSA and RSA (default)\n"), 1 );
#endif
if (!addmode && opt.compliance != CO_DE_VS)
tty_printf (_(" (%d) DSA and Elgamal\n"), 2 );
if (opt.compliance != CO_DE_VS)
tty_printf (_(" (%d) DSA (sign only)\n"), 3 );
#if GPG_USE_RSA
tty_printf (_(" (%d) RSA (sign only)\n"), 4 );
#endif
if (addmode)
{
if (opt.compliance != CO_DE_VS)
tty_printf (_(" (%d) Elgamal (encrypt only)\n"), 5 );
#if GPG_USE_RSA
tty_printf (_(" (%d) RSA (encrypt only)\n"), 6 );
#endif
}
if (opt.expert)
{
if (opt.compliance != CO_DE_VS)
tty_printf (_(" (%d) DSA (set your own capabilities)\n"), 7 );
#if GPG_USE_RSA
tty_printf (_(" (%d) RSA (set your own capabilities)\n"), 8 );
#endif
}
#if GPG_USE_ECDSA || GPG_USE_ECDH || GPG_USE_EDDSA
if (opt.expert && !addmode)
tty_printf (_(" (%d) ECC and ECC\n"), 9 );
if (opt.expert)
tty_printf (_(" (%d) ECC (sign only)\n"), 10 );
if (opt.expert)
tty_printf (_(" (%d) ECC (set your own capabilities)\n"), 11 );
if (opt.expert && addmode)
tty_printf (_(" (%d) ECC (encrypt only)\n"), 12 );
#endif
if (opt.expert && r_keygrip)
tty_printf (_(" (%d) Existing key\n"), 13 );
+ if (r_keygrip)
+ tty_printf (_(" (%d) Existing key from card\n"), 14 );
for (;;)
{
*r_usage = 0;
*r_subkey_algo = 0;
xfree (answer);
answer = cpr_get ("keygen.algo", _("Your selection? "));
cpr_kill_prompt ();
algo = *answer? atoi (answer) : 1;
if (opt.compliance == CO_DE_VS
&& (algo == 2 || algo == 3 || algo == 5 || algo == 7))
{
tty_printf (_("Invalid selection.\n"));
}
else if ((algo == 1 || !strcmp (answer, "rsa+rsa")) && !addmode)
{
algo = PUBKEY_ALGO_RSA;
*r_subkey_algo = PUBKEY_ALGO_RSA;
break;
}
else if ((algo == 2 || !strcmp (answer, "dsa+elg")) && !addmode)
{
algo = PUBKEY_ALGO_DSA;
*r_subkey_algo = PUBKEY_ALGO_ELGAMAL_E;
break;
}
else if (algo == 3 || !strcmp (answer, "dsa"))
{
algo = PUBKEY_ALGO_DSA;
*r_usage = PUBKEY_USAGE_SIG;
break;
}
else if (algo == 4 || !strcmp (answer, "rsa/s"))
{
algo = PUBKEY_ALGO_RSA;
*r_usage = PUBKEY_USAGE_SIG;
break;
}
else if ((algo == 5 || !strcmp (answer, "elg")) && addmode)
{
algo = PUBKEY_ALGO_ELGAMAL_E;
*r_usage = PUBKEY_USAGE_ENC;
break;
}
else if ((algo == 6 || !strcmp (answer, "rsa/e")) && addmode)
{
algo = PUBKEY_ALGO_RSA;
*r_usage = PUBKEY_USAGE_ENC;
break;
}
else if ((algo == 7 || !strcmp (answer, "dsa/*")) && opt.expert)
{
algo = PUBKEY_ALGO_DSA;
*r_usage = ask_key_flags (algo, addmode, 0);
break;
}
else if ((algo == 8 || !strcmp (answer, "rsa/*")) && opt.expert)
{
algo = PUBKEY_ALGO_RSA;
*r_usage = ask_key_flags (algo, addmode, 0);
break;
}
else if ((algo == 9 || !strcmp (answer, "ecc+ecc"))
&& opt.expert && !addmode)
{
algo = PUBKEY_ALGO_ECDSA;
*r_subkey_algo = PUBKEY_ALGO_ECDH;
break;
}
else if ((algo == 10 || !strcmp (answer, "ecc/s")) && opt.expert)
{
algo = PUBKEY_ALGO_ECDSA;
*r_usage = PUBKEY_USAGE_SIG;
break;
}
else if ((algo == 11 || !strcmp (answer, "ecc/*")) && opt.expert)
{
algo = PUBKEY_ALGO_ECDSA;
*r_usage = ask_key_flags (algo, addmode, 0);
break;
}
else if ((algo == 12 || !strcmp (answer, "ecc/e"))
&& opt.expert && addmode)
{
algo = PUBKEY_ALGO_ECDH;
*r_usage = PUBKEY_USAGE_ENC;
break;
}
else if ((algo == 13 || !strcmp (answer, "keygrip"))
&& opt.expert && r_keygrip)
{
for (;;)
{
xfree (answer);
answer = tty_get (_("Enter the keygrip: "));
tty_kill_prompt ();
trim_spaces (answer);
if (!*answer)
{
xfree (answer);
answer = NULL;
continue;
}
if (strlen (answer) != 40 &&
!(answer[0] == '&' && strlen (answer+1) == 40))
tty_printf
(_("Not a valid keygrip (expecting 40 hex digits)\n"));
else if (!(algo = check_keygrip (ctrl, answer)) )
tty_printf (_("No key with this keygrip\n"));
else
break; /* Okay. */
}
xfree (keygrip);
keygrip = answer;
answer = NULL;
*r_usage = ask_key_flags (algo, addmode, 0);
break;
}
+ else if ((algo == 14 || !strcmp (answer, "cardkey")) && r_keygrip)
+ {
+ char *serialno;
+ strlist_t keypairlist, sl;
+ int count, selection;
+
+ err = agent_scd_serialno (&serialno, NULL);
+ if (err)
+ {
+ tty_printf (_("error reading the card: %s\n"),
+ gpg_strerror (err));
+ goto ask_again;
+ }
+ tty_printf (_("Serial number of the card: %s\n"), serialno);
+ xfree (serialno);
+
+ err = agent_scd_keypairinfo (ctrl, NULL, &keypairlist);
+ if (err)
+ {
+ tty_printf (_("error reading the card: %s\n"),
+ gpg_strerror (err));
+ goto ask_again;
+ }
+
+ do
+ {
+ tty_printf (_("Available keys:\n"));
+ for (count=1,sl=keypairlist; sl; sl = sl->next, count++)
+ {
+ gcry_sexp_t s_pkey;
+ char *algostr = NULL;
+ enum gcry_pk_algos algoid = 0;
+ const char *keyref;
+ int any = 0;
+
+ keyref = strchr (sl->d, ' ');
+ if (keyref)
+ {
+ keyref++;
+ if (!agent_scd_readkey (keyref, &s_pkey))
+ {
+ algostr = pubkey_algo_string (s_pkey, &algoid);
+ gcry_sexp_release (s_pkey);
+ }
+ }
+ /* We use the flags also encode the algo for use
+ * below. We need to tweak the algo in case
+ * GCRY_PK_ECC is returned becuase pubkey_algo_string
+ * is not aware of the OpenPGP algo mapping.
+ * FIXME: This is an ugly hack. */
+ sl->flags &= 0xff;
+ if (algoid == GCRY_PK_ECC
+ && algostr && !strncmp (algostr, "nistp", 5)
+ && !(sl->flags & GCRY_PK_USAGE_ENCR))
+ sl->flags |= (PUBKEY_ALGO_ECDSA << 8);
+ else
+ sl->flags |= (map_pk_gcry_to_openpgp (algoid) << 8);
+
+ tty_printf (" (%d) %s %s", count, sl->d, algostr);
+ if ((sl->flags & GCRY_PK_USAGE_CERT))
+ {
+ tty_printf ("%scert", any?",":" (");
+ any = 1;
+ }
+ if ((sl->flags & GCRY_PK_USAGE_SIGN))
+ {
+ tty_printf ("%ssign", any?",":" (");
+ any = 1;
+ }
+ if ((sl->flags & GCRY_PK_USAGE_AUTH))
+ {
+ tty_printf ("%sauth", any?",":" (");
+ any = 1;
+ }
+ if ((sl->flags & GCRY_PK_USAGE_ENCR))
+ {
+ tty_printf ("%sencr", any?",":" (");
+ any = 1;
+ }
+ tty_printf ("%s\n", any?")":"");
+ xfree (algostr);
+ }
+
+ xfree (answer);
+ answer = cpr_get ("keygen.cardkey", _("Your selection? "));
+ cpr_kill_prompt ();
+ trim_spaces (answer);
+ selection = atoi (answer);
+ }
+ while (!(selection > 0 && selection < count));
+
+ for (count=1,sl=keypairlist; sl; sl = sl->next, count++)
+ if (count == selection)
+ break;
+ if (!sl)
+ {
+ /* Just in case COUNT is zero (no keys). */
+ free_strlist (keypairlist);
+ goto ask_again;
+ }
+
+ xfree (keygrip);
+ keygrip = xstrdup (sl->d);
+ if ((p = strchr (keygrip, ' ')))
+ *p = 0;
+ algo = (sl->flags >>8);
+ if (opt.expert)
+ *r_usage = ask_key_flags_with_mask (algo, addmode,
+ (sl->flags & 0xff),
+ (sl->flags & 0xff));
+ else
+ {
+ *r_usage = (sl->flags & 0xff);
+ if (addmode)
+ *r_usage &= ~GCRY_PK_USAGE_CERT;
+ }
+ free_strlist (keypairlist);
+ break;
+ }
else
tty_printf (_("Invalid selection.\n"));
+ ask_again:
+ ;
}
xfree(answer);
if (r_keygrip)
*r_keygrip = keygrip;
return algo;
}
static unsigned int
get_keysize_range (int algo, unsigned int *min, unsigned int *max)
{
unsigned int def;
unsigned int dummy1, dummy2;
if (!min)
min = &dummy1;
if (!max)
max = &dummy2;
switch(algo)
{
case PUBKEY_ALGO_DSA:
*min = opt.expert? 768 : 1024;
*max=3072;
def=2048;
break;
case PUBKEY_ALGO_ECDSA:
case PUBKEY_ALGO_ECDH:
*min=256;
*max=521;
def=256;
break;
case PUBKEY_ALGO_EDDSA:
*min=255;
*max=441;
def=255;
break;
default:
*min = opt.compliance == CO_DE_VS ? 2048: 1024;
*max = 4096;
def = 3072;
break;
}
return def;
}
/* Return a fixed up keysize depending on ALGO. */
static unsigned int
fixup_keysize (unsigned int nbits, int algo, int silent)
{
if (algo == PUBKEY_ALGO_DSA && (nbits % 64))
{
nbits = ((nbits + 63) / 64) * 64;
if (!silent)
tty_printf (_("rounded up to %u bits\n"), nbits);
}
else if (algo == PUBKEY_ALGO_EDDSA)
{
if (nbits != 255 && nbits != 441)
{
if (nbits < 256)
nbits = 255;
else
nbits = 441;
if (!silent)
tty_printf (_("rounded to %u bits\n"), nbits);
}
}
else if (algo == PUBKEY_ALGO_ECDH || algo == PUBKEY_ALGO_ECDSA)
{
if (nbits != 256 && nbits != 384 && nbits != 521)
{
if (nbits < 256)
nbits = 256;
else if (nbits < 384)
nbits = 384;
else
nbits = 521;
if (!silent)
tty_printf (_("rounded to %u bits\n"), nbits);
}
}
else if ((nbits % 32))
{
nbits = ((nbits + 31) / 32) * 32;
if (!silent)
tty_printf (_("rounded up to %u bits\n"), nbits );
}
return nbits;
}
/* Ask for the key size. ALGO is the algorithm. If PRIMARY_KEYSIZE
is not 0, the function asks for the size of the encryption
subkey. */
static unsigned
ask_keysize (int algo, unsigned int primary_keysize)
{
unsigned int nbits;
unsigned int min, def, max;
int for_subkey = !!primary_keysize;
int autocomp = 0;
def = get_keysize_range (algo, &min, &max);
if (primary_keysize && !opt.expert)
{
/* Deduce the subkey size from the primary key size. */
if (algo == PUBKEY_ALGO_DSA && primary_keysize > 3072)
nbits = 3072; /* For performance reasons we don't support more
than 3072 bit DSA. However we won't see this
case anyway because DSA can't be used as an
encryption subkey ;-). */
else
nbits = primary_keysize;
autocomp = 1;
goto leave;
}
tty_printf(_("%s keys may be between %u and %u bits long.\n"),
openpgp_pk_algo_name (algo), min, max);
for (;;)
{
char *prompt, *answer;
if (for_subkey)
prompt = xasprintf (_("What keysize do you want "
"for the subkey? (%u) "), def);
else
prompt = xasprintf (_("What keysize do you want? (%u) "), def);
answer = cpr_get ("keygen.size", prompt);
cpr_kill_prompt ();
nbits = *answer? atoi (answer): def;
xfree(prompt);
xfree(answer);
if(nbits<min || nbits>max)
tty_printf(_("%s keysizes must be in the range %u-%u\n"),
openpgp_pk_algo_name (algo), min, max);
else
break;
}
tty_printf (_("Requested keysize is %u bits\n"), nbits);
leave:
nbits = fixup_keysize (nbits, algo, autocomp);
return nbits;
}
/* Ask for the curve. ALGO is the selected algorithm which this
function may adjust. Returns a const string of the name of the
curve. */
const char *
ask_curve (int *algo, int *subkey_algo, const char *current)
{
/* NB: We always use a complete algo list so that we have stable
numbers in the menu regardless on how Gpg was configured. */
struct {
const char *name;
const char* eddsa_curve; /* Corresponding EdDSA curve. */
const char *pretty_name;
unsigned int supported : 1; /* Supported by gpg. */
unsigned int de_vs : 1; /* Allowed in CO_DE_VS. */
unsigned int expert_only : 1; /* Only with --expert */
unsigned int available : 1; /* Available in Libycrypt (runtime checked) */
} curves[] = {
#if GPG_USE_ECDSA || GPG_USE_ECDH
# define MY_USE_ECDSADH 1
#else
# define MY_USE_ECDSADH 0
#endif
{ "Curve25519", "Ed25519", "Curve 25519", !!GPG_USE_EDDSA, 0, 0, 0 },
{ "Curve448", "Ed448", "Curve 448", 0/*reserved*/ , 0, 1, 0 },
{ "NIST P-256", NULL, NULL, MY_USE_ECDSADH, 0, 1, 0 },
{ "NIST P-384", NULL, NULL, MY_USE_ECDSADH, 0, 0, 0 },
{ "NIST P-521", NULL, NULL, MY_USE_ECDSADH, 0, 1, 0 },
{ "brainpoolP256r1", NULL, "Brainpool P-256", MY_USE_ECDSADH, 1, 1, 0 },
{ "brainpoolP384r1", NULL, "Brainpool P-384", MY_USE_ECDSADH, 1, 1, 0 },
{ "brainpoolP512r1", NULL, "Brainpool P-512", MY_USE_ECDSADH, 1, 1, 0 },
{ "secp256k1", NULL, NULL, MY_USE_ECDSADH, 0, 1, 0 },
};
#undef MY_USE_ECDSADH
int idx;
char *answer;
const char *result = NULL;
gcry_sexp_t keyparms;
tty_printf (_("Please select which elliptic curve you want:\n"));
keyparms = NULL;
for (idx=0; idx < DIM(curves); idx++)
{
int rc;
curves[idx].available = 0;
if (!curves[idx].supported)
continue;
if (opt.compliance==CO_DE_VS)
{
if (!curves[idx].de_vs)
continue; /* Not allowed. */
}
else if (!opt.expert && curves[idx].expert_only)
continue;
/* We need to switch from the ECDH name of the curve to the
EDDSA name of the curve if we want a signing key. */
gcry_sexp_release (keyparms);
rc = gcry_sexp_build (&keyparms, NULL,
"(public-key(ecc(curve %s)))",
curves[idx].eddsa_curve? curves[idx].eddsa_curve
/**/ : curves[idx].name);
if (rc)
continue;
if (!gcry_pk_get_curve (keyparms, 0, NULL))
continue;
if (subkey_algo && curves[idx].eddsa_curve)
{
/* Both Curve 25519 (or 448) keys are to be created. Check that
Libgcrypt also supports the real Curve25519 (or 448). */
gcry_sexp_release (keyparms);
rc = gcry_sexp_build (&keyparms, NULL,
"(public-key(ecc(curve %s)))",
curves[idx].name);
if (rc)
continue;
if (!gcry_pk_get_curve (keyparms, 0, NULL))
continue;
}
curves[idx].available = 1;
tty_printf (" (%d) %s\n", idx + 1,
curves[idx].pretty_name?
curves[idx].pretty_name:curves[idx].name);
}
gcry_sexp_release (keyparms);
for (;;)
{
answer = cpr_get ("keygen.curve", _("Your selection? "));
cpr_kill_prompt ();
idx = *answer? atoi (answer) : 1;
if (!*answer && current)
{
xfree(answer);
return NULL;
}
else if (*answer && !idx)
{
/* See whether the user entered the name of the curve. */
for (idx=0; idx < DIM(curves); idx++)
{
if (!opt.expert && curves[idx].expert_only)
continue;
if (!stricmp (curves[idx].name, answer)
|| (curves[idx].pretty_name
&& !stricmp (curves[idx].pretty_name, answer)))
break;
}
if (idx == DIM(curves))
idx = -1;
}
else
idx--;
xfree(answer);
answer = NULL;
if (idx < 0 || idx >= DIM (curves) || !curves[idx].available)
tty_printf (_("Invalid selection.\n"));
else
{
/* If the user selected a signing algorithm and Curve25519
- we need to set the algo to EdDSA and update the curve name. */
- if ((*algo == PUBKEY_ALGO_ECDSA || *algo == PUBKEY_ALGO_EDDSA)
- && curves[idx].eddsa_curve)
+ we need to set the algo to EdDSA and update the curve name.
+ If switching away from EdDSA, we need to set the algo back
+ to ECDSA. */
+ if (*algo == PUBKEY_ALGO_ECDSA || *algo == PUBKEY_ALGO_EDDSA)
{
- if (subkey_algo && *subkey_algo == PUBKEY_ALGO_ECDSA)
- *subkey_algo = PUBKEY_ALGO_EDDSA;
- *algo = PUBKEY_ALGO_EDDSA;
- result = curves[idx].eddsa_curve;
+ if (curves[idx].eddsa_curve)
+ {
+ if (subkey_algo && *subkey_algo == PUBKEY_ALGO_ECDSA)
+ *subkey_algo = PUBKEY_ALGO_EDDSA;
+ *algo = PUBKEY_ALGO_EDDSA;
+ result = curves[idx].eddsa_curve;
+ }
+ else
+ {
+ if (subkey_algo && *subkey_algo == PUBKEY_ALGO_EDDSA)
+ *subkey_algo = PUBKEY_ALGO_ECDSA;
+ *algo = PUBKEY_ALGO_ECDSA;
+ result = curves[idx].name;
+ }
}
else
result = curves[idx].name;
break;
}
}
if (!result)
result = curves[0].name;
return result;
}
/****************
* Parse an expire string and return its value in seconds.
* Returns (u32)-1 on error.
* This isn't perfect since scan_isodatestr returns unix time, and
* OpenPGP actually allows a 32-bit time *plus* a 32-bit offset.
* Because of this, we only permit setting expirations up to 2106, but
* OpenPGP could theoretically allow up to 2242. I think we'll all
* just cope for the next few years until we get a 64-bit time_t or
* similar.
*/
u32
parse_expire_string( const char *string )
{
int mult;
u32 seconds;
u32 abs_date = 0;
u32 curtime = make_timestamp ();
time_t tt;
if (!string || !*string || !strcmp (string, "none")
|| !strcmp (string, "never") || !strcmp (string, "-"))
seconds = 0;
else if (!strncmp (string, "seconds=", 8))
seconds = atoi (string+8);
else if ((abs_date = scan_isodatestr(string))
&& (abs_date+86400/2) > curtime)
seconds = (abs_date+86400/2) - curtime;
else if ((tt = isotime2epoch (string)) != (time_t)(-1))
seconds = (u32)tt - curtime;
else if ((mult = check_valid_days (string)))
seconds = atoi (string) * 86400L * mult;
else
seconds = (u32)(-1);
return seconds;
}
/* Parse a Creation-Date string which is either "1986-04-26" or
"19860426T042640". Returns 0 on error. */
static u32
parse_creation_string (const char *string)
{
u32 seconds;
if (!*string)
seconds = 0;
else if ( !strncmp (string, "seconds=", 8) )
seconds = atoi (string+8);
else if ( !(seconds = scan_isodatestr (string)))
{
time_t tmp = isotime2epoch (string);
seconds = (tmp == (time_t)(-1))? 0 : tmp;
}
return seconds;
}
/* object == 0 for a key, and 1 for a sig */
u32
ask_expire_interval(int object,const char *def_expire)
{
u32 interval;
char *answer;
switch(object)
{
case 0:
if(def_expire)
BUG();
tty_printf(_("Please specify how long the key should be valid.\n"
" 0 = key does not expire\n"
" <n> = key expires in n days\n"
" <n>w = key expires in n weeks\n"
" <n>m = key expires in n months\n"
" <n>y = key expires in n years\n"));
break;
case 1:
if(!def_expire)
BUG();
tty_printf(_("Please specify how long the signature should be valid.\n"
" 0 = signature does not expire\n"
" <n> = signature expires in n days\n"
" <n>w = signature expires in n weeks\n"
" <n>m = signature expires in n months\n"
" <n>y = signature expires in n years\n"));
break;
default:
BUG();
}
/* Note: The elgamal subkey for DSA has no expiration date because
* it must be signed with the DSA key and this one has the expiration
* date */
answer = NULL;
for(;;)
{
u32 curtime;
xfree(answer);
if(object==0)
answer = cpr_get("keygen.valid",_("Key is valid for? (0) "));
else
{
char *prompt;
prompt = xasprintf (_("Signature is valid for? (%s) "), def_expire);
answer = cpr_get("siggen.valid",prompt);
xfree(prompt);
if(*answer=='\0')
answer=xstrdup(def_expire);
}
cpr_kill_prompt();
trim_spaces(answer);
curtime = make_timestamp ();
interval = parse_expire_string( answer );
if( interval == (u32)-1 )
{
tty_printf(_("invalid value\n"));
continue;
}
if( !interval )
{
tty_printf((object==0)
? _("Key does not expire at all\n")
: _("Signature does not expire at all\n"));
}
else
{
tty_printf(object==0
? _("Key expires at %s\n")
: _("Signature expires at %s\n"),
asctimestamp((ulong)(curtime + interval) ) );
#if SIZEOF_TIME_T <= 4 && !defined (HAVE_UNSIGNED_TIME_T)
if ( (time_t)((ulong)(curtime+interval)) < 0 )
tty_printf (_("Your system can't display dates beyond 2038.\n"
"However, it will be correctly handled up to"
" 2106.\n"));
else
#endif /*SIZEOF_TIME_T*/
if ( (time_t)((unsigned long)(curtime+interval)) < curtime )
{
tty_printf (_("invalid value\n"));
continue;
}
}
if( cpr_enabled() || cpr_get_answer_is_yes("keygen.valid.okay",
_("Is this correct? (y/N) ")) )
break;
}
xfree(answer);
return interval;
}
u32
ask_expiredate()
{
u32 x = ask_expire_interval(0,NULL);
return x? make_timestamp() + x : 0;
}
static PKT_user_id *
uid_from_string (const char *string)
{
size_t n;
PKT_user_id *uid;
n = strlen (string);
uid = xmalloc_clear (sizeof *uid + n);
uid->len = n;
strcpy (uid->name, string);
uid->ref = 1;
return uid;
}
/* Return true if the user id UID already exists in the keyblock. */
static int
uid_already_in_keyblock (kbnode_t keyblock, const char *uid)
{
PKT_user_id *uidpkt = uid_from_string (uid);
kbnode_t node;
int result = 0;
for (node=keyblock; node && !result; node=node->next)
if (!is_deleted_kbnode (node)
&& node->pkt->pkttype == PKT_USER_ID
&& !cmp_user_ids (uidpkt, node->pkt->pkt.user_id))
result = 1;
free_user_id (uidpkt);
return result;
}
/* Ask for a user ID. With a MODE of 1 an extra help prompt is
printed for use during a new key creation. If KEYBLOCK is not NULL
the function prevents the creation of an already existing user
ID. IF FULL is not set some prompts are not shown. */
static char *
ask_user_id (int mode, int full, KBNODE keyblock)
{
char *answer;
char *aname, *acomment, *amail, *uid;
if ( !mode )
{
/* TRANSLATORS: This is the new string telling the user what
gpg is now going to do (i.e. ask for the parts of the user
ID). Note that if you do not translate this string, a
different string will be used, which might still have
a correct translation. */
const char *s1 =
N_("\n"
"GnuPG needs to construct a user ID to identify your key.\n"
"\n");
const char *s2 = _(s1);
if (!strcmp (s1, s2))
{
/* There is no translation for the string thus we to use
the old info text. gettext has no way to tell whether
a translation is actually available, thus we need to
to compare again. */
/* TRANSLATORS: This string is in general not anymore used
but you should keep your existing translation. In case
the new string is not translated this old string will
be used. */
const char *s3 = N_("\n"
"You need a user ID to identify your key; "
"the software constructs the user ID\n"
"from the Real Name, Comment and Email Address in this form:\n"
" \"Heinrich Heine (Der Dichter) <heinrichh@duesseldorf.de>\"\n\n");
const char *s4 = _(s3);
if (strcmp (s3, s4))
s2 = s3; /* A translation exists - use it. */
}
tty_printf ("%s", s2) ;
}
uid = aname = acomment = amail = NULL;
for(;;) {
char *p;
int fail=0;
if( !aname ) {
for(;;) {
xfree(aname);
aname = cpr_get("keygen.name",_("Real name: "));
trim_spaces(aname);
cpr_kill_prompt();
if( opt.allow_freeform_uid )
break;
if( strpbrk( aname, "<>" ) )
{
tty_printf(_("Invalid character in name\n"));
tty_printf(_("The characters '%s' and '%s' may not "
"appear in name\n"), "<", ">");
}
else if( digitp(aname) )
tty_printf(_("Name may not start with a digit\n"));
else if (*aname && strlen (aname) < 5)
{
tty_printf(_("Name must be at least 5 characters long\n"));
/* However, we allow an empty name. */
}
else
break;
}
}
if( !amail ) {
for(;;) {
xfree(amail);
amail = cpr_get("keygen.email",_("Email address: "));
trim_spaces(amail);
cpr_kill_prompt();
if( !*amail || opt.allow_freeform_uid )
break; /* no email address is okay */
else if ( !is_valid_mailbox (amail) )
tty_printf(_("Not a valid email address\n"));
else
break;
}
}
if (!acomment) {
if (full) {
for(;;) {
xfree(acomment);
acomment = cpr_get("keygen.comment",_("Comment: "));
trim_spaces(acomment);
cpr_kill_prompt();
if( !*acomment )
break; /* no comment is okay */
else if( strpbrk( acomment, "()" ) )
tty_printf(_("Invalid character in comment\n"));
else
break;
}
}
else {
xfree (acomment);
acomment = xstrdup ("");
}
}
xfree(uid);
uid = p = xmalloc(strlen(aname)+strlen(amail)+strlen(acomment)+12+10);
if (!*aname && *amail && !*acomment && !random_is_faked ())
{ /* Empty name and comment but with mail address. Use
simplified form with only the non-angle-bracketed mail
address. */
p = stpcpy (p, amail);
}
else
{
p = stpcpy (p, aname );
if (*acomment)
p = stpcpy(stpcpy(stpcpy(p," ("), acomment),")");
if (*amail)
p = stpcpy(stpcpy(stpcpy(p," <"), amail),">");
}
/* Append a warning if the RNG is switched into fake mode. */
if ( random_is_faked () )
strcpy(p, " (insecure!)" );
/* print a note in case that UTF8 mapping has to be done */
for(p=uid; *p; p++ ) {
if( *p & 0x80 ) {
tty_printf(_("You are using the '%s' character set.\n"),
get_native_charset() );
break;
}
}
tty_printf(_("You selected this USER-ID:\n \"%s\"\n\n"), uid);
if( !*amail && !opt.allow_freeform_uid
&& (strchr( aname, '@' ) || strchr( acomment, '@'))) {
fail = 1;
tty_printf(_("Please don't put the email address "
"into the real name or the comment\n") );
}
if (!fail && keyblock)
{
if (uid_already_in_keyblock (keyblock, uid))
{
tty_printf (_("Such a user ID already exists on this key!\n"));
fail = 1;
}
}
for(;;) {
/* TRANSLATORS: These are the allowed answers in
lower and uppercase. Below you will find the matching
string which should be translated accordingly and the
letter changed to match the one in the answer string.
n = Change name
c = Change comment
e = Change email
o = Okay (ready, continue)
q = Quit
*/
const char *ansstr = _("NnCcEeOoQq");
if( strlen(ansstr) != 10 )
BUG();
if( cpr_enabled() ) {
answer = xstrdup (ansstr + (fail?8:6));
answer[1] = 0;
}
else if (full) {
answer = cpr_get("keygen.userid.cmd", fail?
_("Change (N)ame, (C)omment, (E)mail or (Q)uit? ") :
_("Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? "));
cpr_kill_prompt();
}
else {
answer = cpr_get("keygen.userid.cmd", fail?
_("Change (N)ame, (E)mail, or (Q)uit? ") :
_("Change (N)ame, (E)mail, or (O)kay/(Q)uit? "));
cpr_kill_prompt();
}
if( strlen(answer) > 1 )
;
else if( *answer == ansstr[0] || *answer == ansstr[1] ) {
xfree(aname); aname = NULL;
break;
}
else if( *answer == ansstr[2] || *answer == ansstr[3] ) {
xfree(acomment); acomment = NULL;
break;
}
else if( *answer == ansstr[4] || *answer == ansstr[5] ) {
xfree(amail); amail = NULL;
break;
}
else if( *answer == ansstr[6] || *answer == ansstr[7] ) {
if( fail ) {
tty_printf(_("Please correct the error first\n"));
}
else {
xfree(aname); aname = NULL;
xfree(acomment); acomment = NULL;
xfree(amail); amail = NULL;
break;
}
}
else if( *answer == ansstr[8] || *answer == ansstr[9] ) {
xfree(aname); aname = NULL;
xfree(acomment); acomment = NULL;
xfree(amail); amail = NULL;
xfree(uid); uid = NULL;
break;
}
xfree(answer);
}
xfree(answer);
if (!amail && !acomment)
break;
xfree(uid); uid = NULL;
}
if( uid ) {
char *p = native_to_utf8( uid );
xfree( uid );
uid = p;
}
return uid;
}
/* Basic key generation. Here we divert to the actual generation
routines based on the requested algorithm. */
static int
do_create (int algo, unsigned int nbits, const char *curve, kbnode_t pub_root,
u32 timestamp, u32 expiredate, int is_subkey,
int keygen_flags, const char *passphrase,
char **cache_nonce_addr, char **passwd_nonce_addr)
{
gpg_error_t err;
/* Fixme: The entropy collecting message should be moved to a
libgcrypt progress handler. */
if (!opt.batch)
tty_printf (_(
"We need to generate a lot of random bytes. It is a good idea to perform\n"
"some other action (type on the keyboard, move the mouse, utilize the\n"
"disks) during the prime generation; this gives the random number\n"
"generator a better chance to gain enough entropy.\n") );
if (algo == PUBKEY_ALGO_ELGAMAL_E)
err = gen_elg (algo, nbits, pub_root, timestamp, expiredate, is_subkey,
keygen_flags, passphrase,
cache_nonce_addr, passwd_nonce_addr);
else if (algo == PUBKEY_ALGO_DSA)
err = gen_dsa (nbits, pub_root, timestamp, expiredate, is_subkey,
keygen_flags, passphrase,
cache_nonce_addr, passwd_nonce_addr);
else if (algo == PUBKEY_ALGO_ECDSA
|| algo == PUBKEY_ALGO_EDDSA
|| algo == PUBKEY_ALGO_ECDH)
err = gen_ecc (algo, curve, pub_root, timestamp, expiredate, is_subkey,
keygen_flags, passphrase,
cache_nonce_addr, passwd_nonce_addr);
else if (algo == PUBKEY_ALGO_RSA)
err = gen_rsa (algo, nbits, pub_root, timestamp, expiredate, is_subkey,
keygen_flags, passphrase,
cache_nonce_addr, passwd_nonce_addr);
else
BUG();
return err;
}
/* Generate a new user id packet or return NULL if canceled. If
KEYBLOCK is not NULL the function prevents the creation of an
already existing user ID. If UIDSTR is not NULL the user is not
asked but UIDSTR is used to create the user id packet; if the user
id already exists NULL is returned. UIDSTR is expected to be utf-8
encoded and should have already been checked for a valid length
etc. */
PKT_user_id *
generate_user_id (KBNODE keyblock, const char *uidstr)
{
PKT_user_id *uid;
char *p;
if (uidstr)
{
if (uid_already_in_keyblock (keyblock, uidstr))
return NULL; /* Already exists. */
uid = uid_from_string (uidstr);
}
else
{
p = ask_user_id (1, 1, keyblock);
if (!p)
return NULL; /* Canceled. */
uid = uid_from_string (p);
xfree (p);
}
return uid;
}
/* Helper for parse_key_parameter_string for one part of the
* specification string; i.e. ALGO/FLAGS. If STRING is NULL or empty
* success is returned. On error an error code is returned. Note
* that STRING may be modified by this function. NULL may be passed
* for any parameter. FOR_SUBKEY shall be true if this is used as a
* subkey. If CLEAR_CERT is set a default CERT usage will be cleared;
* this is useful if for example the default algorithm is used for a
* subkey. If R_KEYVERSION is not NULL it will receive the version of
* the key; this is currently 4 but can be changed with the flag "v5"
* to create a v5 key. */
static gpg_error_t
parse_key_parameter_part (char *string, int for_subkey, int clear_cert,
int *r_algo, unsigned int *r_size,
unsigned int *r_keyuse,
char const **r_curve, int *r_keyversion)
{
char *flags;
int algo;
char *endp;
const char *curve = NULL;
int ecdh_or_ecdsa = 0;
unsigned int size;
int keyuse;
int keyversion = 4;
int i;
const char *s;
if (!string || !*string)
return 0; /* Success. */
flags = strchr (string, '/');
if (flags)
*flags++ = 0;
algo = 0;
if (strlen (string) >= 3 && (digitp (string+3) || !string[3]))
{
if (!ascii_memcasecmp (string, "rsa", 3))
algo = PUBKEY_ALGO_RSA;
else if (!ascii_memcasecmp (string, "dsa", 3))
algo = PUBKEY_ALGO_DSA;
else if (!ascii_memcasecmp (string, "elg", 3))
algo = PUBKEY_ALGO_ELGAMAL_E;
}
if (algo)
{
if (!string[3])
size = get_keysize_range (algo, NULL, NULL);
else
{
size = strtoul (string+3, &endp, 10);
if (size < 512 || size > 16384 || *endp)
return gpg_error (GPG_ERR_INV_VALUE);
}
}
else if ((curve = openpgp_is_curve_supported (string, &algo, &size)))
{
if (!algo)
{
algo = PUBKEY_ALGO_ECDH; /* Default ECC algorithm. */
ecdh_or_ecdsa = 1; /* We may need to switch the algo. */
}
}
else
return gpg_error (GPG_ERR_UNKNOWN_CURVE);
/* Parse the flags. */
keyuse = 0;
if (flags)
{
char **tokens = NULL;
tokens = strtokenize (flags, ",");
if (!tokens)
return gpg_error_from_syserror ();
for (i=0; (s = tokens[i]); i++)
{
if (!*s)
;
else if (!ascii_strcasecmp (s, "sign"))
keyuse |= PUBKEY_USAGE_SIG;
else if (!ascii_strcasecmp (s, "encrypt")
|| !ascii_strcasecmp (s, "encr"))
keyuse |= PUBKEY_USAGE_ENC;
else if (!ascii_strcasecmp (s, "auth"))
keyuse |= PUBKEY_USAGE_AUTH;
else if (!ascii_strcasecmp (s, "cert"))
keyuse |= PUBKEY_USAGE_CERT;
else if (!ascii_strcasecmp (s, "ecdsa"))
{
if (algo == PUBKEY_ALGO_ECDH || algo == PUBKEY_ALGO_ECDSA)
algo = PUBKEY_ALGO_ECDSA;
else
{
xfree (tokens);
return gpg_error (GPG_ERR_INV_FLAG);
}
ecdh_or_ecdsa = 0;
}
else if (!ascii_strcasecmp (s, "ecdh"))
{
if (algo == PUBKEY_ALGO_ECDH || algo == PUBKEY_ALGO_ECDSA)
algo = PUBKEY_ALGO_ECDH;
else
{
xfree (tokens);
return gpg_error (GPG_ERR_INV_FLAG);
}
ecdh_or_ecdsa = 0;
}
else if (!ascii_strcasecmp (s, "eddsa"))
{
/* Not required but we allow it for consistency. */
if (algo == PUBKEY_ALGO_EDDSA)
;
else
{
xfree (tokens);
return gpg_error (GPG_ERR_INV_FLAG);
}
}
else if (!ascii_strcasecmp (s, "v5"))
{
if (opt.flags.rfc4880bis)
keyversion = 5;
}
else if (!ascii_strcasecmp (s, "v4"))
keyversion = 4;
else
{
xfree (tokens);
return gpg_error (GPG_ERR_UNKNOWN_FLAG);
}
}
xfree (tokens);
}
/* If not yet decided switch between ecdh and ecdsa. */
if (ecdh_or_ecdsa && keyuse)
algo = (keyuse & PUBKEY_USAGE_ENC)? PUBKEY_ALGO_ECDH : PUBKEY_ALGO_ECDSA;
else if (ecdh_or_ecdsa)
algo = for_subkey? PUBKEY_ALGO_ECDH : PUBKEY_ALGO_ECDSA;
/* Set or fix key usage. */
if (!keyuse)
{
if (algo == PUBKEY_ALGO_ECDSA || algo == PUBKEY_ALGO_EDDSA
|| algo == PUBKEY_ALGO_DSA)
keyuse = PUBKEY_USAGE_SIG;
else if (algo == PUBKEY_ALGO_RSA)
keyuse = for_subkey? PUBKEY_USAGE_ENC : PUBKEY_USAGE_SIG;
else
keyuse = PUBKEY_USAGE_ENC;
}
else if (algo == PUBKEY_ALGO_ECDSA || algo == PUBKEY_ALGO_EDDSA
|| algo == PUBKEY_ALGO_DSA)
{
keyuse &= ~PUBKEY_USAGE_ENC; /* Forbid encryption. */
}
else if (algo == PUBKEY_ALGO_ECDH || algo == PUBKEY_ALGO_ELGAMAL_E)
{
keyuse = PUBKEY_USAGE_ENC; /* Allow only encryption. */
}
/* Make sure a primary key can certify. */
if (!for_subkey)
keyuse |= PUBKEY_USAGE_CERT;
/* But if requested remove th cert usage. */
if (clear_cert)
keyuse &= ~PUBKEY_USAGE_CERT;
/* Check that usage is actually possible. */
if (/**/((keyuse & (PUBKEY_USAGE_SIG|PUBKEY_USAGE_AUTH|PUBKEY_USAGE_CERT))
&& !pubkey_get_nsig (algo))
|| ((keyuse & PUBKEY_USAGE_ENC)
&& !pubkey_get_nenc (algo))
|| (for_subkey && (keyuse & PUBKEY_USAGE_CERT)))
return gpg_error (GPG_ERR_WRONG_KEY_USAGE);
/* Return values. */
if (r_algo)
*r_algo = algo;
if (r_size)
{
unsigned int min, def, max;
/* Make sure the keysize is in the allowed range. */
def = get_keysize_range (algo, &min, &max);
if (!size)
size = def;
else if (size < min)
size = min;
else if (size > max)
size = max;
*r_size = fixup_keysize (size, algo, 1);
}
if (r_keyuse)
*r_keyuse = keyuse;
if (r_curve)
*r_curve = curve;
if (r_keyversion)
*r_keyversion = keyversion;
return 0;
}
/* Parse and return the standard key generation parameter.
* The string is expected to be in this format:
*
* ALGO[/FLAGS][+SUBALGO[/FLAGS]]
*
* Here ALGO is a string in the same format as printed by the
* keylisting. For example:
*
* rsa3072 := RSA with 3072 bit.
* dsa2048 := DSA with 2048 bit.
* elg2048 := Elgamal with 2048 bit.
* ed25519 := EDDSA using curve Ed25519.
* cv25519 := ECDH using curve Curve25519.
* nistp256:= ECDSA or ECDH using curve NIST P-256
*
* All strings with an unknown prefix are considered an elliptic
* curve. Curves which have no implicit algorithm require that FLAGS
* is given to select whether ECDSA or ECDH is used; this can eoither
* be done using an algorithm keyword or usage keywords.
*
* FLAGS is a comma delimited string of keywords:
*
* cert := Allow usage Certify
* sign := Allow usage Sign
* encr := Allow usage Encrypt
* auth := Allow usage Authentication
* encrypt := Alias for "encr"
* ecdsa := Use algorithm ECDSA.
* eddsa := Use algorithm EdDSA.
* ecdh := Use algorithm ECDH.
* v5 := Create version 5 key (requires option --rfc4880bis)
*
* There are several defaults and fallbacks depending on the
* algorithm. PART can be used to select which part of STRING is
* used:
* -1 := Both parts
* 0 := Only the part of the primary key
* 1 := If there is one part parse that one, if there are
* two parts parse the part which best matches the
* SUGGESTED_USE or in case that can't be evaluated the second part.
* Always return using the args for the primary key (R_ALGO,....).
*
*/
gpg_error_t
parse_key_parameter_string (const char *string, int part,
unsigned int suggested_use,
int *r_algo, unsigned int *r_size,
unsigned int *r_keyuse,
char const **r_curve,
int *r_version,
int *r_subalgo, unsigned int *r_subsize,
unsigned int *r_subkeyuse,
char const **r_subcurve,
int *r_subversion)
{
gpg_error_t err = 0;
char *primary, *secondary;
if (r_algo)
*r_algo = 0;
if (r_size)
*r_size = 0;
if (r_keyuse)
*r_keyuse = 0;
if (r_curve)
*r_curve = NULL;
if (r_version)
*r_version = 4;
if (r_subalgo)
*r_subalgo = 0;
if (r_subsize)
*r_subsize = 0;
if (r_subkeyuse)
*r_subkeyuse = 0;
if (r_subcurve)
*r_subcurve = NULL;
if (r_subversion)
*r_subversion = 4;
if (!string || !*string
|| !ascii_strcasecmp (string, "default") || !strcmp (string, "-"))
string = get_default_pubkey_algo ();
else if (!ascii_strcasecmp (string, "future-default")
|| !ascii_strcasecmp (string, "futuredefault"))
string = FUTURE_STD_KEY_PARAM;
primary = xstrdup (string);
secondary = strchr (primary, '+');
if (secondary)
*secondary++ = 0;
if (part == -1 || part == 0)
{
err = parse_key_parameter_part (primary, 0, 0, r_algo, r_size,
r_keyuse, r_curve, r_version);
if (!err && part == -1)
err = parse_key_parameter_part (secondary, 1, 0, r_subalgo, r_subsize,
r_subkeyuse, r_subcurve, r_subversion);
}
else if (part == 1)
{
/* If we have SECONDARY, use that part. If there is only one
* part consider this to be the subkey algo. In case a
* SUGGESTED_USE has been given and the usage of the secondary
* part does not match SUGGESTED_USE try again using the primary
* part. Note that when falling back to the primary key we need
* to force clearing the cert usage. */
if (secondary)
{
err = parse_key_parameter_part (secondary, 1, 0,
r_algo, r_size, r_keyuse, r_curve,
r_version);
if (!err && suggested_use && r_keyuse && !(suggested_use & *r_keyuse))
err = parse_key_parameter_part (primary, 1, 1 /*(clear cert)*/,
r_algo, r_size, r_keyuse, r_curve,
r_version);
}
else
err = parse_key_parameter_part (primary, 1, 0,
r_algo, r_size, r_keyuse, r_curve,
r_version);
}
xfree (primary);
return err;
}
/* Append R to the linked list PARA. */
static void
append_to_parameter (struct para_data_s *para, struct para_data_s *r)
{
log_assert (para);
while (para->next)
para = para->next;
para->next = r;
}
/* Release the parameter list R. */
static void
release_parameter_list (struct para_data_s *r)
{
struct para_data_s *r2;
for (; r ; r = r2)
{
r2 = r->next;
if (r->key == pPASSPHRASE && *r->u.value)
wipememory (r->u.value, strlen (r->u.value));
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;
}
/* This is similar to get_parameter_value but also returns the empty
string. This is required so that quick_generate_keypair can use an
empty Passphrase to specify no-protection. */
static const char *
get_parameter_passphrase (struct para_data_s *para)
{
struct para_data_s *r = get_parameter (para, pPASSPHRASE);
return r ? r->u.value : NULL;
}
static int
get_parameter_algo( struct para_data_s *para, enum para_name key,
int *r_default)
{
int i;
struct para_data_s *r = get_parameter( para, key );
if (r_default)
*r_default = 0;
if (!r)
return -1;
/* Note that we need to handle the ECC algorithms specified as
strings directly because Libgcrypt folds them all to ECC. */
if (!ascii_strcasecmp (r->u.value, "default"))
{
/* Note: If you change this default algo, remember to change it
* also in gpg.c:gpgconf_list. */
/* FIXME: We only allow the algo here and have a separate thing
* for the curve etc. That is a ugly but demanded for backward
* compatibility with the batch key generation. It would be
* better to make full use of parse_key_parameter_string. */
parse_key_parameter_string (NULL, 0, 0,
&i, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL);
if (r_default)
*r_default = 1;
}
else if (digitp (r->u.value))
i = atoi( r->u.value );
else if (!strcmp (r->u.value, "ELG-E")
|| !strcmp (r->u.value, "ELG"))
i = PUBKEY_ALGO_ELGAMAL_E;
else if (!ascii_strcasecmp (r->u.value, "EdDSA"))
i = PUBKEY_ALGO_EDDSA;
else if (!ascii_strcasecmp (r->u.value, "ECDSA"))
i = PUBKEY_ALGO_ECDSA;
else if (!ascii_strcasecmp (r->u.value, "ECDH"))
i = PUBKEY_ALGO_ECDH;
else
i = map_pk_gcry_to_openpgp (gcry_pk_map_name (r->u.value));
if (i == PUBKEY_ALGO_RSA_E || i == PUBKEY_ALGO_RSA_S)
i = 0; /* we don't want to allow generation of these algorithms */
return i;
}
/* Parse a usage string. The usage keywords "auth", "sign", "encr"
* may be delimited by space, tab, or comma. On error -1 is returned
* instead of the usage flags. */
static int
parse_usagestr (const char *usagestr)
{
gpg_error_t err;
char **tokens = NULL;
const char *s;
int i;
unsigned int use = 0;
tokens = strtokenize (usagestr, " \t,");
if (!tokens)
{
err = gpg_error_from_syserror ();
log_error ("strtokenize failed: %s\n", gpg_strerror (err));
return -1;
}
for (i=0; (s = tokens[i]); i++)
{
if (!*s)
;
else if (!ascii_strcasecmp (s, "sign"))
use |= PUBKEY_USAGE_SIG;
else if (!ascii_strcasecmp (s, "encrypt")
|| !ascii_strcasecmp (s, "encr"))
use |= PUBKEY_USAGE_ENC;
else if (!ascii_strcasecmp (s, "auth"))
use |= PUBKEY_USAGE_AUTH;
else if (!ascii_strcasecmp (s, "cert"))
use |= PUBKEY_USAGE_CERT;
else
{
xfree (tokens);
return -1; /* error */
}
}
xfree (tokens);
return use;
}
/*
* Parse the usage parameter and set the keyflags. Returns -1 on
* error, 0 for no usage given or 1 for usage available.
*/
static int
parse_parameter_usage (const char *fname,
struct para_data_s *para, enum para_name key)
{
struct para_data_s *r = get_parameter( para, key );
int i;
if (!r)
return 0; /* none (this is an optional parameter)*/
i = parse_usagestr (r->u.value);
if (i == -1)
{
log_error ("%s:%d: invalid usage list\n", fname, r->lnr );
return -1; /* error */
}
r->u.usage = i;
return 1;
}
static int
parse_revocation_key (const char *fname,
struct para_data_s *para, enum para_name key)
{
struct para_data_s *r = get_parameter( para, key );
struct revocation_key revkey;
char *pn;
int i;
if( !r )
return 0; /* none (this is an optional parameter) */
pn = r->u.value;
revkey.class=0x80;
revkey.algid=atoi(pn);
if(!revkey.algid)
goto fail;
/* Skip to the fpr */
while(*pn && *pn!=':')
pn++;
if(*pn!=':')
goto fail;
pn++;
for(i=0;i<MAX_FINGERPRINT_LEN && *pn;i++,pn+=2)
{
int c=hextobyte(pn);
if(c==-1)
goto fail;
revkey.fpr[i]=c;
}
if (i != 20 && i != 32)
goto fail;
/* skip to the tag */
while(*pn && *pn!='s' && *pn!='S')
pn++;
if(ascii_strcasecmp(pn,"sensitive")==0)
revkey.class|=0x40;
memcpy(&r->u.revkey,&revkey,sizeof(struct revocation_key));
return 0;
fail:
log_error("%s:%d: invalid revocation key\n", fname, r->lnr );
return -1; /* error */
}
static u32
get_parameter_u32( struct para_data_s *para, enum para_name key )
{
struct para_data_s *r = get_parameter( para, key );
if( !r )
return 0;
if( r->key == pKEYCREATIONDATE )
return r->u.creation;
if( r->key == pKEYEXPIRE || r->key == pSUBKEYEXPIRE )
return r->u.expire;
if( r->key == pKEYUSAGE || r->key == pSUBKEYUSAGE )
return r->u.usage;
return (unsigned int)strtoul( r->u.value, NULL, 10 );
}
static unsigned int
get_parameter_uint( struct para_data_s *para, enum para_name key )
{
return get_parameter_u32( para, key );
}
static struct revocation_key *
get_parameter_revkey( struct para_data_s *para, enum para_name key )
{
struct para_data_s *r = get_parameter( para, key );
return r? &r->u.revkey : NULL;
}
static int
proc_parameter_file (ctrl_t ctrl, struct para_data_s *para, const char *fname,
struct output_control_s *outctrl, int card )
{
struct para_data_s *r;
const char *s1, *s2, *s3;
size_t n;
char *p;
int is_default = 0;
int have_user_id = 0;
int err, algo;
/* Check that we have all required parameters. */
r = get_parameter( para, pKEYTYPE );
if(r)
{
algo = get_parameter_algo (para, pKEYTYPE, &is_default);
if (openpgp_pk_test_algo2 (algo, PUBKEY_USAGE_SIG))
{
log_error ("%s:%d: invalid algorithm\n", fname, r->lnr );
return -1;
}
}
else
{
log_error ("%s: no Key-Type specified\n",fname);
return -1;
}
err = parse_parameter_usage (fname, para, pKEYUSAGE);
if (!err)
{
/* Default to algo capabilities if key-usage is not provided and
no default algorithm has been requested. */
r = xmalloc_clear(sizeof(*r));
r->key = pKEYUSAGE;
r->u.usage = (is_default
? (PUBKEY_USAGE_CERT | PUBKEY_USAGE_SIG)
: openpgp_pk_algo_usage(algo));
append_to_parameter (para, r);
}
else if (err == -1)
return -1;
else
{
r = get_parameter (para, pKEYUSAGE);
if (r && (r->u.usage & ~openpgp_pk_algo_usage (algo)))
{
log_error ("%s:%d: specified Key-Usage not allowed for algo %d\n",
fname, r->lnr, algo);
return -1;
}
}
is_default = 0;
r = get_parameter( para, pSUBKEYTYPE );
if(r)
{
algo = get_parameter_algo (para, pSUBKEYTYPE, &is_default);
if (openpgp_pk_test_algo (algo))
{
log_error ("%s:%d: invalid algorithm\n", fname, r->lnr );
return -1;
}
err = parse_parameter_usage (fname, para, pSUBKEYUSAGE);
if (!err)
{
/* Default to algo capabilities if subkey-usage is not
provided */
r = xmalloc_clear (sizeof(*r));
r->key = pSUBKEYUSAGE;
r->u.usage = (is_default
? PUBKEY_USAGE_ENC
: openpgp_pk_algo_usage (algo));
append_to_parameter (para, r);
}
else if (err == -1)
return -1;
else
{
r = get_parameter (para, pSUBKEYUSAGE);
if (r && (r->u.usage & ~openpgp_pk_algo_usage (algo)))
{
log_error ("%s:%d: specified Subkey-Usage not allowed"
" for algo %d\n", fname, r->lnr, algo);
return -1;
}
}
}
if( get_parameter_value( para, pUSERID ) )
have_user_id=1;
else
{
/* create the formatted user ID */
s1 = get_parameter_value( para, pNAMEREAL );
s2 = get_parameter_value( para, pNAMECOMMENT );
s3 = get_parameter_value( para, pNAMEEMAIL );
if( s1 || s2 || s3 )
{
n = (s1?strlen(s1):0) + (s2?strlen(s2):0) + (s3?strlen(s3):0);
r = xmalloc_clear( sizeof *r + n + 20 );
r->key = pUSERID;
p = r->u.value;
if( s1 )
p = stpcpy(p, s1 );
if( s2 )
p = stpcpy(stpcpy(stpcpy(p," ("), s2 ),")");
if( s3 )
{
/* If we have only the email part, do not add the space
* and the angle brackets. */
if (*r->u.value)
p = stpcpy(stpcpy(stpcpy(p," <"), s3 ),">");
else
p = stpcpy (p, s3);
}
append_to_parameter (para, r);
have_user_id=1;
}
}
if(!have_user_id)
{
log_error("%s: no User-ID specified\n",fname);
return -1;
}
/* Set preferences, if any. */
keygen_set_std_prefs(get_parameter_value( para, pPREFERENCES ), 0);
/* Set keyserver, if any. */
s1=get_parameter_value( para, pKEYSERVER );
if(s1)
{
struct keyserver_spec *spec;
spec = parse_keyserver_uri (s1, 1);
if(spec)
{
free_keyserver_spec(spec);
opt.def_keyserver_url=s1;
}
else
{
r = get_parameter (para, pKEYSERVER);
log_error("%s:%d: invalid keyserver url\n", fname, r->lnr );
return -1;
}
}
/* Set revoker, if any. */
if (parse_revocation_key (fname, para, pREVOKER))
return -1;
/* Make KEYCREATIONDATE from Creation-Date. */
r = get_parameter (para, pCREATIONDATE);
if (r && *r->u.value)
{
u32 seconds;
seconds = parse_creation_string (r->u.value);
if (!seconds)
{
log_error ("%s:%d: invalid creation date\n", fname, r->lnr );
return -1;
}
r->u.creation = seconds;
r->key = pKEYCREATIONDATE; /* Change that entry. */
}
/* Make KEYEXPIRE from Expire-Date. */
r = get_parameter( para, pEXPIREDATE );
if( r && *r->u.value )
{
u32 seconds;
seconds = parse_expire_string( r->u.value );
if( seconds == (u32)-1 )
{
log_error("%s:%d: invalid expire date\n", fname, r->lnr );
return -1;
}
r->u.expire = seconds;
r->key = pKEYEXPIRE; /* change hat entry */
/* also set it for the subkey */
r = xmalloc_clear( sizeof *r + 20 );
r->key = pSUBKEYEXPIRE;
r->u.expire = seconds;
append_to_parameter (para, r);
}
do_generate_keypair (ctrl, para, outctrl, card );
return 0;
}
/****************
* Kludge to allow non interactive key generation controlled
* by a parameter file.
* Note, that string parameters are expected to be in UTF-8
*/
static void
read_parameter_file (ctrl_t ctrl, const char *fname )
{
static struct { const char *name;
enum para_name key;
} keywords[] = {
{ "Key-Type", pKEYTYPE},
{ "Key-Length", pKEYLENGTH },
{ "Key-Curve", pKEYCURVE },
{ "Key-Usage", pKEYUSAGE },
{ "Subkey-Type", pSUBKEYTYPE },
{ "Subkey-Length", pSUBKEYLENGTH },
{ "Subkey-Curve", pSUBKEYCURVE },
{ "Subkey-Usage", pSUBKEYUSAGE },
{ "Name-Real", pNAMEREAL },
{ "Name-Email", pNAMEEMAIL },
{ "Name-Comment", pNAMECOMMENT },
{ "Expire-Date", pEXPIREDATE },
{ "Creation-Date", pCREATIONDATE },
{ "Passphrase", pPASSPHRASE },
{ "Preferences", pPREFERENCES },
{ "Revoker", pREVOKER },
{ "Handle", pHANDLE },
{ "Keyserver", pKEYSERVER },
{ "Keygrip", pKEYGRIP },
{ "Key-Grip", pKEYGRIP },
{ "Subkey-grip", pSUBKEYGRIP },
{ "Key-Version", pVERSION },
{ "Subkey-Version", pSUBVERSION },
{ NULL, 0 }
};
IOBUF fp;
byte *line;
unsigned int maxlen, nline;
char *p;
int lnr;
const char *err = NULL;
struct para_data_s *para, *r;
int i;
struct output_control_s outctrl;
memset( &outctrl, 0, sizeof( outctrl ) );
outctrl.pub.afx = new_armor_context ();
if( !fname || !*fname)
fname = "-";
fp = iobuf_open (fname);
if (fp && is_secured_file (iobuf_get_fd (fp)))
{
iobuf_close (fp);
fp = NULL;
gpg_err_set_errno (EPERM);
}
if (!fp) {
log_error (_("can't open '%s': %s\n"), fname, strerror(errno) );
return;
}
iobuf_ioctl (fp, IOBUF_IOCTL_NO_CACHE, 1, NULL);
lnr = 0;
err = NULL;
para = NULL;
maxlen = 1024;
line = NULL;
while ( iobuf_read_line (fp, &line, &nline, &maxlen) ) {
char *keyword, *value;
lnr++;
if( !maxlen ) {
err = "line too long";
break;
}
for( p = line; isspace(*(byte*)p); p++ )
;
if( !*p || *p == '#' )
continue;
keyword = p;
if( *keyword == '%' ) {
for( ; !isspace(*(byte*)p); p++ )
;
if( *p )
*p++ = 0;
for( ; isspace(*(byte*)p); p++ )
;
value = p;
trim_trailing_ws( value, strlen(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, "%ask-passphrase" ) )
; /* Dummy for backward compatibility. */
else if( !ascii_strcasecmp( keyword, "%no-ask-passphrase" ) )
; /* Dummy for backward compatibility. */
else if( !ascii_strcasecmp( keyword, "%no-protection" ) )
outctrl.keygen_flags |= KEYGEN_FLAG_NO_PROTECTION;
else if( !ascii_strcasecmp( keyword, "%transient-key" ) )
outctrl.keygen_flags |= KEYGEN_FLAG_TRANSIENT_KEY;
else if( !ascii_strcasecmp( keyword, "%commit" ) ) {
outctrl.lnr = lnr;
if (proc_parameter_file (ctrl, para, fname, &outctrl, 0 ))
print_status_key_not_created
(get_parameter_value (para, pHANDLE));
release_parameter_list( para );
para = NULL;
}
else if( !ascii_strcasecmp( keyword, "%pubring" ) ) {
if( outctrl.pub.fname && !strcmp( outctrl.pub.fname, value ) )
; /* still the same file - ignore it */
else {
xfree( outctrl.pub.newfname );
outctrl.pub.newfname = xstrdup( value );
outctrl.use_files = 1;
}
}
else if( !ascii_strcasecmp( keyword, "%secring" ) ) {
/* Ignore this command. */
}
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( ; isspace(*(byte*)p); p++ )
;
if( !*p ) {
err = "missing argument";
break;
}
value = p;
trim_trailing_ws( value, strlen(value) );
for(i=0; keywords[i].name; i++ ) {
if( !ascii_strcasecmp( keywords[i].name, keyword ) )
break;
}
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 ) {
outctrl.lnr = lnr;
if (proc_parameter_file (ctrl, para, fname, &outctrl, 0 ))
print_status_key_not_created
(get_parameter_value (para, pHANDLE));
release_parameter_list( para );
para = NULL;
}
else {
for( r = para; r; r = r->next ) {
if( r->key == keywords[i].key )
break;
}
if( r ) {
err = "duplicate keyword";
break;
}
}
if (!opt.flags.rfc4880bis && (keywords[i].key == pVERSION
|| keywords[i].key == pSUBVERSION))
; /* Ignore version unless --rfc4880bis is active. */
else
{
r = xmalloc_clear( sizeof *r + strlen( value ) );
r->lnr = lnr;
r->key = keywords[i].key;
strcpy( r->u.value, value );
r->next = para;
para = r;
}
}
if( err )
log_error("%s:%d: %s\n", fname, lnr, err );
else if( iobuf_error (fp) ) {
log_error("%s:%d: read error\n", fname, lnr);
}
else if( para ) {
outctrl.lnr = lnr;
if (proc_parameter_file (ctrl, para, fname, &outctrl, 0 ))
print_status_key_not_created (get_parameter_value (para, pHANDLE));
}
if( outctrl.use_files ) { /* close open streams */
iobuf_close( outctrl.pub.stream );
/* Must invalidate that ugly cache to actually close it. */
if (outctrl.pub.fname)
iobuf_ioctl (NULL, IOBUF_IOCTL_INVALIDATE_CACHE,
0, (char*)outctrl.pub.fname);
xfree( outctrl.pub.fname );
xfree( outctrl.pub.newfname );
}
xfree (line);
release_parameter_list( para );
iobuf_close (fp);
release_armor_context (outctrl.pub.afx);
}
/* Helper for quick_generate_keypair. */
static struct para_data_s *
quickgen_set_para (struct para_data_s *para, int for_subkey,
int algo, int nbits, const char *curve, unsigned int use,
int version)
{
struct para_data_s *r;
r = xmalloc_clear (sizeof *r + 30);
r->key = for_subkey? pSUBKEYUSAGE : pKEYUSAGE;
if (use)
snprintf (r->u.value, 30, "%s%s%s%s",
(use & PUBKEY_USAGE_ENC)? "encr " : "",
(use & PUBKEY_USAGE_SIG)? "sign " : "",
(use & PUBKEY_USAGE_AUTH)? "auth " : "",
(use & PUBKEY_USAGE_CERT)? "cert " : "");
else
strcpy (r->u.value, for_subkey ? "encr" : "sign");
r->next = para;
para = r;
r = xmalloc_clear (sizeof *r + 20);
r->key = for_subkey? pSUBKEYTYPE : pKEYTYPE;
snprintf (r->u.value, 20, "%d", algo);
r->next = para;
para = r;
if (curve)
{
r = xmalloc_clear (sizeof *r + strlen (curve));
r->key = for_subkey? pSUBKEYCURVE : pKEYCURVE;
strcpy (r->u.value, curve);
r->next = para;
para = r;
}
else
{
r = xmalloc_clear (sizeof *r + 20);
r->key = for_subkey? pSUBKEYLENGTH : pKEYLENGTH;
sprintf (r->u.value, "%u", nbits);
r->next = para;
para = r;
}
if (opt.flags.rfc4880bis)
{
r = xmalloc_clear (sizeof *r + 20);
r->key = for_subkey? pSUBVERSION : pVERSION;
snprintf (r->u.value, 20, "%d", version);
r->next = para;
para = r;
}
return para;
}
/*
* Unattended generation of a standard key.
*/
void
quick_generate_keypair (ctrl_t ctrl, const char *uid, const char *algostr,
const char *usagestr, const char *expirestr)
{
gpg_error_t err;
struct para_data_s *para = NULL;
struct para_data_s *r;
struct output_control_s outctrl;
int use_tty;
memset (&outctrl, 0, sizeof outctrl);
use_tty = (!opt.batch && !opt.answer_yes
&& !*algostr && !*usagestr && !*expirestr
&& !cpr_enabled ()
&& gnupg_isatty (fileno (stdin))
&& gnupg_isatty (fileno (stdout))
&& gnupg_isatty (fileno (stderr)));
r = xmalloc_clear (sizeof *r + strlen (uid));
r->key = pUSERID;
strcpy (r->u.value, uid);
r->next = para;
para = r;
uid = trim_spaces (r->u.value);
if (!*uid || (!opt.allow_freeform_uid && !is_valid_user_id (uid)))
{
log_error (_("Key generation failed: %s\n"),
gpg_strerror (GPG_ERR_INV_USER_ID));
goto leave;
}
/* If gpg is directly used on the console ask whether a key with the
given user id shall really be created. */
if (use_tty)
{
tty_printf (_("About to create a key for:\n \"%s\"\n\n"), uid);
if (!cpr_get_answer_is_yes_def ("quick_keygen.okay",
_("Continue? (Y/n) "), 1))
goto leave;
}
/* Check whether such a user ID already exists. */
{
KEYDB_HANDLE kdbhd;
KEYDB_SEARCH_DESC desc;
memset (&desc, 0, sizeof desc);
desc.mode = KEYDB_SEARCH_MODE_EXACT;
desc.u.name = uid;
kdbhd = keydb_new ();
if (!kdbhd)
goto leave;
err = keydb_search (kdbhd, &desc, 1, NULL);
keydb_release (kdbhd);
if (gpg_err_code (err) != GPG_ERR_NOT_FOUND)
{
log_info (_("A key for \"%s\" already exists\n"), uid);
if (opt.answer_yes)
;
else if (!use_tty
|| !cpr_get_answer_is_yes_def ("quick_keygen.force",
_("Create anyway? (y/N) "), 0))
{
write_status_error ("genkey", gpg_error (304));
log_inc_errorcount (); /* we used log_info */
goto leave;
}
log_info (_("creating anyway\n"));
}
}
if (!*expirestr || strcmp (expirestr, "-") == 0)
expirestr = default_expiration_interval;
if ((!*algostr || !ascii_strcasecmp (algostr, "default")
|| !ascii_strcasecmp (algostr, "future-default")
|| !ascii_strcasecmp (algostr, "futuredefault"))
&& (!*usagestr || !ascii_strcasecmp (usagestr, "default")
|| !strcmp (usagestr, "-")))
{
/* Use default key parameters. */
int algo, subalgo, version, subversion;
unsigned int size, subsize;
unsigned int keyuse, subkeyuse;
const char *curve, *subcurve;
err = parse_key_parameter_string (algostr, -1, 0,
&algo, &size, &keyuse, &curve, &version,
&subalgo, &subsize, &subkeyuse,
&subcurve, &subversion);
if (err)
{
log_error (_("Key generation failed: %s\n"), gpg_strerror (err));
goto leave;
}
para = quickgen_set_para (para, 0, algo, size, curve, keyuse, version);
if (subalgo)
para = quickgen_set_para (para, 1,
subalgo, subsize, subcurve, subkeyuse,
subversion);
if (*expirestr)
{
u32 expire;
expire = parse_expire_string (expirestr);
if (expire == (u32)-1 )
{
err = gpg_error (GPG_ERR_INV_VALUE);
log_error (_("Key generation failed: %s\n"), gpg_strerror (err));
goto leave;
}
r = xmalloc_clear (sizeof *r + 20);
r->key = pKEYEXPIRE;
r->u.expire = expire;
r->next = para;
para = r;
}
}
else
{
/* Extended unattended mode. Creates only the primary key. */
int algo, version;
unsigned int use;
u32 expire;
unsigned int nbits;
const char *curve;
err = parse_algo_usage_expire (ctrl, 0, algostr, usagestr, expirestr,
&algo, &use, &expire, &nbits, &curve,
&version);
if (err)
{
log_error (_("Key generation failed: %s\n"), gpg_strerror (err) );
goto leave;
}
para = quickgen_set_para (para, 0, algo, nbits, curve, use, version);
r = xmalloc_clear (sizeof *r + 20);
r->key = pKEYEXPIRE;
r->u.expire = expire;
r->next = para;
para = r;
}
/* If the pinentry loopback mode is not and we have a static
passphrase (i.e. set with --passphrase{,-fd,-file} while in batch
mode), we use that passphrase for the new key. */
if (opt.pinentry_mode != PINENTRY_MODE_LOOPBACK
&& have_static_passphrase ())
{
const char *s = get_static_passphrase ();
r = xmalloc_clear (sizeof *r + strlen (s));
r->key = pPASSPHRASE;
strcpy (r->u.value, s);
r->next = para;
para = r;
}
proc_parameter_file (ctrl, para, "[internal]", &outctrl, 0);
leave:
release_parameter_list (para);
}
/*
* Generate a keypair (fname is only used in batch mode) If
* CARD_SERIALNO is not NULL the function will create the keys on an
* OpenPGP Card. If CARD_BACKUP_KEY has been set and CARD_SERIALNO is
* NOT NULL, the encryption key for the card is generated on the host,
* imported to the card and a backup file created by gpg-agent. If
* FULL is not set only the basic prompts are used (except for batch
* mode).
*/
void
generate_keypair (ctrl_t ctrl, int full, const char *fname,
const char *card_serialno, int card_backup_key)
{
gpg_error_t err;
unsigned int nbits;
char *uid = NULL;
int algo;
unsigned int use;
int both = 0;
u32 expire;
struct para_data_s *para = NULL;
struct para_data_s *r;
struct output_control_s outctrl;
#ifndef ENABLE_CARD_SUPPORT
(void)card_backup_key;
#endif
memset( &outctrl, 0, sizeof( outctrl ) );
if (opt.batch && card_serialno)
{
/* We don't yet support unattended key generation with a card
* serial number. */
log_error (_("can't do this in batch mode\n"));
print_further_info ("key generation with card serial number");
return;
}
if (opt.batch)
{
read_parameter_file (ctrl, fname);
return;
}
if (card_serialno)
{
#ifdef ENABLE_CARD_SUPPORT
struct agent_card_info_s info;
memset (&info, 0, sizeof (info));
err = agent_scd_getattr ("KEY-ATTR", &info);
if (err)
{
log_error (_("error getting current key info: %s\n"),
gpg_strerror (err));
return;
}
r = xcalloc (1, sizeof *r + strlen (card_serialno) );
r->key = pSERIALNO;
strcpy( r->u.value, card_serialno);
r->next = para;
para = r;
r = xcalloc (1, sizeof *r + 20 );
r->key = pKEYTYPE;
sprintf( r->u.value, "%d", info.key_attr[0].algo );
r->next = para;
para = r;
r = xcalloc (1, sizeof *r + 20 );
r->key = pKEYUSAGE;
strcpy (r->u.value, "sign");
r->next = para;
para = r;
r = xcalloc (1, sizeof *r + 20 );
r->key = pSUBKEYTYPE;
sprintf( r->u.value, "%d", info.key_attr[1].algo );
r->next = para;
para = r;
r = xcalloc (1, sizeof *r + 20 );
r->key = pSUBKEYUSAGE;
strcpy (r->u.value, "encrypt");
r->next = para;
para = r;
if (info.key_attr[1].algo == PUBKEY_ALGO_RSA)
{
r = xcalloc (1, sizeof *r + 20 );
r->key = pSUBKEYLENGTH;
sprintf( r->u.value, "%u", info.key_attr[1].nbits);
r->next = para;
para = r;
}
else if (info.key_attr[1].algo == PUBKEY_ALGO_ECDSA
|| info.key_attr[1].algo == PUBKEY_ALGO_EDDSA
|| info.key_attr[1].algo == PUBKEY_ALGO_ECDH)
{
r = xcalloc (1, sizeof *r + strlen (info.key_attr[1].curve));
r->key = pSUBKEYCURVE;
strcpy (r->u.value, info.key_attr[1].curve);
r->next = para;
para = r;
}
r = xcalloc (1, sizeof *r + 20 );
r->key = pAUTHKEYTYPE;
sprintf( r->u.value, "%d", info.key_attr[2].algo );
r->next = para;
para = r;
if (card_backup_key)
{
r = xcalloc (1, sizeof *r + 1);
r->key = pCARDBACKUPKEY;
strcpy (r->u.value, "1");
r->next = para;
para = r;
}
#endif /*ENABLE_CARD_SUPPORT*/
}
else if (full) /* Full featured key generation. */
{
int subkey_algo;
char *key_from_hexgrip = NULL;
algo = ask_algo (ctrl, 0, &subkey_algo, &use, &key_from_hexgrip);
if (key_from_hexgrip)
{
r = xmalloc_clear( sizeof *r + 20 );
r->key = pKEYTYPE;
sprintf( r->u.value, "%d", algo);
r->next = para;
para = r;
if (use)
{
r = xmalloc_clear( sizeof *r + 25 );
r->key = pKEYUSAGE;
sprintf( r->u.value, "%s%s%s",
(use & PUBKEY_USAGE_SIG)? "sign ":"",
(use & PUBKEY_USAGE_ENC)? "encrypt ":"",
(use & PUBKEY_USAGE_AUTH)? "auth":"" );
r->next = para;
para = r;
}
r = xmalloc_clear( sizeof *r + 40 );
r->key = pKEYGRIP;
strcpy (r->u.value, key_from_hexgrip);
r->next = para;
para = r;
xfree (key_from_hexgrip);
}
else
{
const char *curve = NULL;
if (subkey_algo)
{
/* Create primary and subkey at once. */
both = 1;
if (algo == PUBKEY_ALGO_ECDSA
|| algo == PUBKEY_ALGO_EDDSA
|| algo == PUBKEY_ALGO_ECDH)
{
curve = ask_curve (&algo, &subkey_algo, NULL);
r = xmalloc_clear( sizeof *r + 20 );
r->key = pKEYTYPE;
sprintf( r->u.value, "%d", algo);
r->next = para;
para = r;
nbits = 0;
r = xmalloc_clear (sizeof *r + strlen (curve));
r->key = pKEYCURVE;
strcpy (r->u.value, curve);
r->next = para;
para = r;
}
else
{
r = xmalloc_clear( sizeof *r + 20 );
r->key = pKEYTYPE;
sprintf( r->u.value, "%d", algo);
r->next = para;
para = r;
nbits = ask_keysize (algo, 0);
r = xmalloc_clear( sizeof *r + 20 );
r->key = pKEYLENGTH;
sprintf( r->u.value, "%u", nbits);
r->next = para;
para = r;
}
r = xmalloc_clear( sizeof *r + 20 );
r->key = pKEYUSAGE;
strcpy( r->u.value, "sign" );
r->next = para;
para = r;
r = xmalloc_clear( sizeof *r + 20 );
r->key = pSUBKEYTYPE;
sprintf( r->u.value, "%d", subkey_algo);
r->next = para;
para = r;
r = xmalloc_clear( sizeof *r + 20 );
r->key = pSUBKEYUSAGE;
strcpy( r->u.value, "encrypt" );
r->next = para;
para = r;
if (algo == PUBKEY_ALGO_ECDSA
|| algo == PUBKEY_ALGO_EDDSA
|| algo == PUBKEY_ALGO_ECDH)
{
if (algo == PUBKEY_ALGO_EDDSA
&& subkey_algo == PUBKEY_ALGO_ECDH)
{
/* Need to switch to a different curve for the
encryption key. */
curve = "Curve25519";
}
r = xmalloc_clear (sizeof *r + strlen (curve));
r->key = pSUBKEYCURVE;
strcpy (r->u.value, curve);
r->next = para;
para = r;
}
}
else /* Create only a single key. */
{
/* For ECC we need to ask for the curve before storing the
algo because ask_curve may change the algo. */
if (algo == PUBKEY_ALGO_ECDSA
|| algo == PUBKEY_ALGO_EDDSA
|| algo == PUBKEY_ALGO_ECDH)
{
curve = ask_curve (&algo, NULL, NULL);
r = xmalloc_clear (sizeof *r + strlen (curve));
r->key = pKEYCURVE;
strcpy (r->u.value, curve);
r->next = para;
para = r;
}
r = xmalloc_clear( sizeof *r + 20 );
r->key = pKEYTYPE;
sprintf( r->u.value, "%d", algo );
r->next = para;
para = r;
if (use)
{
r = xmalloc_clear( sizeof *r + 25 );
r->key = pKEYUSAGE;
sprintf( r->u.value, "%s%s%s",
(use & PUBKEY_USAGE_SIG)? "sign ":"",
(use & PUBKEY_USAGE_ENC)? "encrypt ":"",
(use & PUBKEY_USAGE_AUTH)? "auth":"" );
r->next = para;
para = r;
}
nbits = 0;
}
if (algo == PUBKEY_ALGO_ECDSA
|| algo == PUBKEY_ALGO_EDDSA
|| algo == PUBKEY_ALGO_ECDH)
{
/* The curve has already been set. */
}
else
{
nbits = ask_keysize (both? subkey_algo : algo, nbits);
r = xmalloc_clear( sizeof *r + 20 );
r->key = both? pSUBKEYLENGTH : pKEYLENGTH;
sprintf( r->u.value, "%u", nbits);
r->next = para;
para = r;
}
}
}
else /* Default key generation. */
{
int subalgo, version, subversion;
unsigned int size, subsize;
unsigned int keyuse, subkeyuse;
const char *curve, *subcurve;
tty_printf ( _("Note: Use \"%s %s\""
" for a full featured key generation dialog.\n"),
#if USE_GPG2_HACK
GPG_NAME "2"
#else
GPG_NAME
#endif
, "--full-generate-key" );
err = parse_key_parameter_string (NULL, -1, 0,
&algo, &size, &keyuse, &curve, &version,
&subalgo, &subsize,
&subkeyuse, &subcurve, &subversion);
if (err)
{
log_error (_("Key generation failed: %s\n"), gpg_strerror (err));
return;
}
para = quickgen_set_para (para, 0, algo, size, curve, keyuse, version);
if (subalgo)
para = quickgen_set_para (para, 1,
subalgo, subsize, subcurve, subkeyuse,
subversion);
}
expire = full? ask_expire_interval (0, NULL)
: parse_expire_string (default_expiration_interval);
r = xcalloc (1, sizeof *r + 20);
r->key = pKEYEXPIRE;
r->u.expire = expire;
r->next = para;
para = r;
r = xcalloc (1, sizeof *r + 20);
r->key = pSUBKEYEXPIRE;
r->u.expire = expire;
r->next = para;
para = r;
uid = ask_user_id (0, full, NULL);
if (!uid)
{
log_error(_("Key generation canceled.\n"));
release_parameter_list( para );
return;
}
r = xcalloc (1, sizeof *r + strlen (uid));
r->key = pUSERID;
strcpy (r->u.value, uid);
r->next = para;
para = r;
proc_parameter_file (ctrl, para, "[internal]", &outctrl, !!card_serialno);
release_parameter_list (para);
}
/* Create and delete a dummy packet to start off a list of kbnodes. */
static void
start_tree(KBNODE *tree)
{
PACKET *pkt;
pkt=xmalloc_clear(sizeof(*pkt));
pkt->pkttype=PKT_NONE;
*tree=new_kbnode(pkt);
delete_kbnode(*tree);
}
/* Write the *protected* secret key to the file. */
static gpg_error_t
card_write_key_to_backup_file (PKT_public_key *sk, const char *backup_dir)
{
gpg_error_t err = 0;
int rc;
char keyid_buffer[2 * 8 + 1];
char name_buffer[50];
char *fname;
IOBUF fp;
mode_t oldmask;
PACKET *pkt = NULL;
format_keyid (pk_keyid (sk), KF_LONG, keyid_buffer, sizeof (keyid_buffer));
snprintf (name_buffer, sizeof name_buffer, "sk_%s.gpg", keyid_buffer);
fname = make_filename (backup_dir, name_buffer, NULL);
/* Note that the umask call is not anymore needed because
iobuf_create now takes care of it. However, it does not harm
and thus we keep it. */
oldmask = umask (077);
if (is_secured_filename (fname))
{
fp = NULL;
gpg_err_set_errno (EPERM);
}
else
fp = iobuf_create (fname, 1);
umask (oldmask);
if (!fp)
{
err = gpg_error_from_syserror ();
log_error (_("can't create backup file '%s': %s\n"), fname, strerror (errno) );
goto leave;
}
pkt = xcalloc (1, sizeof *pkt);
pkt->pkttype = PKT_SECRET_KEY;
pkt->pkt.secret_key = sk;
rc = build_packet (fp, pkt);
if (rc)
{
log_error ("build packet failed: %s\n", gpg_strerror (rc));
iobuf_cancel (fp);
}
else
{
char *fprbuf;
iobuf_close (fp);
iobuf_ioctl (NULL, IOBUF_IOCTL_INVALIDATE_CACHE, 0, (char*)fname);
log_info (_("Note: backup of card key saved to '%s'\n"), fname);
fprbuf = hexfingerprint (sk, NULL, 0);
if (!fprbuf)
{
err = gpg_error_from_syserror ();
goto leave;
}
write_status_text_and_buffer (STATUS_BACKUP_KEY_CREATED, fprbuf,
fname, strlen (fname), 0);
xfree (fprbuf);
}
leave:
xfree (pkt);
xfree (fname);
return err;
}
/* Store key to card and make a backup file in OpenPGP format. */
static gpg_error_t
card_store_key_with_backup (ctrl_t ctrl, PKT_public_key *sub_psk,
const char *backup_dir)
{
PKT_public_key *sk;
gnupg_isotime_t timestamp;
gpg_error_t err;
char *hexgrip;
int rc;
struct agent_card_info_s info;
gcry_cipher_hd_t cipherhd = NULL;
char *cache_nonce = NULL;
void *kek = NULL;
size_t keklen;
sk = copy_public_key (NULL, sub_psk);
if (!sk)
return gpg_error_from_syserror ();
epoch2isotime (timestamp, (time_t)sk->timestamp);
err = hexkeygrip_from_pk (sk, &hexgrip);
if (err)
return err;
memset(&info, 0, sizeof (info));
rc = agent_scd_getattr ("SERIALNO", &info);
if (rc)
return (gpg_error_t)rc;
rc = agent_keytocard (hexgrip, 2, 1, info.serialno, timestamp);
xfree (info.serialno);
if (rc)
{
err = (gpg_error_t)rc;
goto leave;
}
err = agent_keywrap_key (ctrl, 1, &kek, &keklen);
if (err)
{
log_error ("error getting the KEK: %s\n", gpg_strerror (err));
goto leave;
}
err = gcry_cipher_open (&cipherhd, GCRY_CIPHER_AES128,
GCRY_CIPHER_MODE_AESWRAP, 0);
if (!err)
err = gcry_cipher_setkey (cipherhd, kek, keklen);
if (err)
{
log_error ("error setting up an encryption context: %s\n",
gpg_strerror (err));
goto leave;
}
err = receive_seckey_from_agent (ctrl, cipherhd, 0,
&cache_nonce, hexgrip, sk);
if (err)
{
log_error ("error getting secret key from agent: %s\n",
gpg_strerror (err));
goto leave;
}
err = card_write_key_to_backup_file (sk, backup_dir);
if (err)
log_error ("writing card key to backup file: %s\n", gpg_strerror (err));
else
/* Remove secret key data in agent side. */
agent_scd_learn (NULL, 1);
leave:
xfree (cache_nonce);
gcry_cipher_close (cipherhd);
xfree (kek);
xfree (hexgrip);
free_public_key (sk);
return err;
}
static void
do_generate_keypair (ctrl_t ctrl, struct para_data_s *para,
struct output_control_s *outctrl, int card)
{
gpg_error_t err;
KBNODE pub_root = NULL;
const char *s;
PKT_public_key *pri_psk = NULL;
PKT_public_key *sub_psk = NULL;
struct revocation_key *revkey;
int did_sub = 0;
u32 timestamp;
char *cache_nonce = NULL;
int algo;
u32 expire;
const char *key_from_hexgrip = NULL;
unsigned int keygen_flags;
if (outctrl->dryrun)
{
log_info("dry-run mode - key generation skipped\n");
return;
}
if ( outctrl->use_files )
{
if ( outctrl->pub.newfname )
{
iobuf_close(outctrl->pub.stream);
outctrl->pub.stream = NULL;
if (outctrl->pub.fname)
iobuf_ioctl (NULL, IOBUF_IOCTL_INVALIDATE_CACHE,
0, (char*)outctrl->pub.fname);
xfree( outctrl->pub.fname );
outctrl->pub.fname = outctrl->pub.newfname;
outctrl->pub.newfname = NULL;
if (is_secured_filename (outctrl->pub.fname) )
{
outctrl->pub.stream = NULL;
gpg_err_set_errno (EPERM);
}
else
outctrl->pub.stream = iobuf_create (outctrl->pub.fname, 0);
if (!outctrl->pub.stream)
{
log_error(_("can't create '%s': %s\n"), outctrl->pub.newfname,
strerror(errno) );
return;
}
if (opt.armor)
{
outctrl->pub.afx->what = 1;
push_armor_filter (outctrl->pub.afx, outctrl->pub.stream);
}
}
log_assert( outctrl->pub.stream );
if (opt.verbose)
log_info (_("writing public key to '%s'\n"), outctrl->pub.fname );
}
/* We create the packets as a tree of kbnodes. Because the
structure we create is known in advance we simply generate a
linked list. The first packet is a dummy packet which we flag as
deleted. The very first packet must always be a KEY packet. */
start_tree (&pub_root);
timestamp = get_parameter_u32 (para, pKEYCREATIONDATE);
if (!timestamp)
timestamp = make_timestamp ();
/* Note that, depending on the backend (i.e. the used scdaemon
version), the card key generation may update TIMESTAMP for each
key. Thus we need to pass TIMESTAMP to all signing function to
make sure that the binding signature is done using the timestamp
of the corresponding (sub)key and not that of the primary key.
An alternative implementation could tell the signing function the
node of the subkey but that is more work than just to pass the
current timestamp. */
algo = get_parameter_algo( para, pKEYTYPE, NULL );
expire = get_parameter_u32( para, pKEYEXPIRE );
key_from_hexgrip = get_parameter_value (para, pKEYGRIP);
keygen_flags = outctrl->keygen_flags;
if (get_parameter_uint (para, pVERSION) == 5)
keygen_flags |= KEYGEN_FLAG_CREATE_V5_KEY;
if (key_from_hexgrip)
err = do_create_from_keygrip (ctrl, algo, key_from_hexgrip,
pub_root, timestamp, expire, 0, keygen_flags);
else if (!card)
err = do_create (algo,
get_parameter_uint( para, pKEYLENGTH ),
get_parameter_value (para, pKEYCURVE),
pub_root,
timestamp,
expire, 0,
keygen_flags,
get_parameter_passphrase (para),
&cache_nonce, NULL);
else
err = gen_card_key (1, algo,
1, pub_root, &timestamp,
expire, keygen_flags);
/* Get the pointer to the generated public key packet. */
if (!err)
{
pri_psk = pub_root->next->pkt->pkt.public_key;
log_assert (pri_psk);
/* Make sure a few fields are correctly set up before going
further. */
pri_psk->flags.primary = 1;
keyid_from_pk (pri_psk, NULL);
/* We don't use pk_keyid to get keyid, because it also asserts
that main_keyid is set! */
keyid_copy (pri_psk->main_keyid, pri_psk->keyid);
}
if (!err && (revkey = get_parameter_revkey (para, pREVOKER)))
err = write_direct_sig (ctrl, pub_root, pri_psk,
revkey, timestamp, cache_nonce);
if (!err && (s = get_parameter_value (para, pUSERID)))
{
- write_uid (pub_root, s );
- err = write_selfsigs (ctrl, pub_root, pri_psk,
- get_parameter_uint (para, pKEYUSAGE), timestamp,
- cache_nonce);
+ err = write_uid (pub_root, s );
+ if (!err)
+ err = write_selfsigs (ctrl, pub_root, pri_psk,
+ get_parameter_uint (para, pKEYUSAGE), timestamp,
+ cache_nonce);
}
/* Write the auth key to the card before the encryption key. This
is a partial workaround for a PGP bug (as of this writing, all
versions including 8.1), that causes it to try and encrypt to
the most recent subkey regardless of whether that subkey is
actually an encryption type. In this case, the auth key is an
RSA key so it succeeds. */
if (!err && card && get_parameter (para, pAUTHKEYTYPE))
{
err = gen_card_key (3, get_parameter_algo( para, pAUTHKEYTYPE, NULL ),
0, pub_root, &timestamp, expire, keygen_flags);
if (!err)
err = write_keybinding (ctrl, pub_root, pri_psk, NULL,
PUBKEY_USAGE_AUTH, timestamp, cache_nonce);
}
if (!err && get_parameter (para, pSUBKEYTYPE))
{
int subkey_algo = get_parameter_algo (para, pSUBKEYTYPE, NULL);
s = NULL;
key_from_hexgrip = get_parameter_value (para, pSUBKEYGRIP);
keygen_flags = outctrl->keygen_flags;
if (get_parameter_uint (para, pSUBVERSION) == 5)
keygen_flags |= KEYGEN_FLAG_CREATE_V5_KEY;
if (key_from_hexgrip)
err = do_create_from_keygrip (ctrl, subkey_algo, key_from_hexgrip,
pub_root, timestamp,
get_parameter_u32 (para, pSUBKEYEXPIRE),
1, keygen_flags);
else if (!card || (s = get_parameter_value (para, pCARDBACKUPKEY)))
{
err = do_create (subkey_algo,
get_parameter_uint (para, pSUBKEYLENGTH),
get_parameter_value (para, pSUBKEYCURVE),
pub_root,
timestamp,
get_parameter_u32 (para, pSUBKEYEXPIRE), 1,
s? KEYGEN_FLAG_NO_PROTECTION : keygen_flags,
get_parameter_passphrase (para),
&cache_nonce, NULL);
/* Get the pointer to the generated public subkey packet. */
if (!err)
{
kbnode_t node;
for (node = pub_root; node; node = node->next)
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
sub_psk = node->pkt->pkt.public_key;
log_assert (sub_psk);
if (s)
err = card_store_key_with_backup (ctrl,
sub_psk, gnupg_homedir ());
}
}
else
{
err = gen_card_key (2, subkey_algo, 0, pub_root, &timestamp, expire,
keygen_flags);
}
if (!err)
err = write_keybinding (ctrl, pub_root, pri_psk, sub_psk,
get_parameter_uint (para, pSUBKEYUSAGE),
timestamp, cache_nonce);
did_sub = 1;
}
if (!err && outctrl->use_files) /* Direct write to specified files. */
{
err = write_keyblock (outctrl->pub.stream, pub_root);
if (err)
log_error ("can't write public key: %s\n", gpg_strerror (err));
}
else if (!err) /* Write to the standard keyrings. */
{
KEYDB_HANDLE pub_hd;
pub_hd = keydb_new ();
if (!pub_hd)
err = gpg_error_from_syserror ();
else
{
err = keydb_locate_writable (pub_hd);
if (err)
log_error (_("no writable public keyring found: %s\n"),
gpg_strerror (err));
}
if (!err && opt.verbose)
{
log_info (_("writing public key to '%s'\n"),
keydb_get_resource_name (pub_hd));
}
if (!err)
{
err = keydb_insert_keyblock (pub_hd, pub_root);
if (err)
log_error (_("error writing public keyring '%s': %s\n"),
keydb_get_resource_name (pub_hd), gpg_strerror (err));
}
keydb_release (pub_hd);
if (!err)
{
int no_enc_rsa;
PKT_public_key *pk;
no_enc_rsa = ((get_parameter_algo (para, pKEYTYPE, NULL)
== PUBKEY_ALGO_RSA)
&& get_parameter_uint (para, pKEYUSAGE)
&& !((get_parameter_uint (para, pKEYUSAGE)
& PUBKEY_USAGE_ENC)) );
pk = find_kbnode (pub_root, PKT_PUBLIC_KEY)->pkt->pkt.public_key;
keyid_from_pk (pk, pk->main_keyid);
register_trusted_keyid (pk->main_keyid);
update_ownertrust (ctrl, pk,
((get_ownertrust (ctrl, pk) & ~TRUST_MASK)
| TRUST_ULTIMATE ));
gen_standard_revoke (ctrl, pk, cache_nonce);
/* Get rid of the first empty packet. */
commit_kbnode (&pub_root);
if (!opt.batch)
{
tty_printf (_("public and secret key created and signed.\n") );
tty_printf ("\n");
merge_keys_and_selfsig (ctrl, pub_root);
list_keyblock_direct (ctrl, pub_root, 0, 1,
opt.fingerprint || opt.with_fingerprint,
1);
}
if (!opt.batch
&& (get_parameter_algo (para, pKEYTYPE, NULL) == PUBKEY_ALGO_DSA
|| no_enc_rsa )
&& !get_parameter (para, pSUBKEYTYPE) )
{
tty_printf(_("Note that this key cannot be used for "
"encryption. You may want to use\n"
"the command \"--edit-key\" to generate a "
"subkey for this purpose.\n") );
}
}
}
if (err)
{
if (opt.batch)
log_error ("key generation failed: %s\n", gpg_strerror (err) );
else
tty_printf (_("Key generation failed: %s\n"), gpg_strerror (err) );
write_status_error (card? "card_key_generate":"key_generate", err);
print_status_key_not_created ( get_parameter_value (para, pHANDLE) );
}
else
{
PKT_public_key *pk = find_kbnode (pub_root,
PKT_PUBLIC_KEY)->pkt->pkt.public_key;
print_status_key_created (did_sub? 'B':'P', pk,
get_parameter_value (para, pHANDLE));
}
release_kbnode (pub_root);
xfree (cache_nonce);
}
static gpg_error_t
parse_algo_usage_expire (ctrl_t ctrl, int for_subkey,
const char *algostr, const char *usagestr,
const char *expirestr,
int *r_algo, unsigned int *r_usage, u32 *r_expire,
unsigned int *r_nbits, const char **r_curve,
int *r_version)
{
gpg_error_t err;
int algo;
unsigned int use, nbits;
u32 expire;
int wantuse;
int version = 4;
const char *curve = NULL;
*r_curve = NULL;
nbits = 0;
/* Parse the algo string. */
if (algostr && *algostr == '&' && strlen (algostr) == 41)
{
/* Take algo from existing key. */
algo = check_keygrip (ctrl, algostr+1);
/* FIXME: We need the curve name as well. */
return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
}
err = parse_key_parameter_string (algostr, for_subkey? 1 : 0,
usagestr? parse_usagestr (usagestr):0,
&algo, &nbits, &use, &curve, &version,
NULL, NULL, NULL, NULL, NULL);
if (err)
return err;
/* Parse the usage string. */
if (!usagestr || !*usagestr
|| !ascii_strcasecmp (usagestr, "default") || !strcmp (usagestr, "-"))
; /* Keep usage from parse_key_parameter_string. */
else if ((wantuse = parse_usagestr (usagestr)) != -1)
use = wantuse;
else
return gpg_error (GPG_ERR_INV_VALUE);
/* Make sure a primary key has the CERT usage. */
if (!for_subkey)
use |= PUBKEY_USAGE_CERT;
/* Check that usage is possible. NB: We have the same check in
* parse_key_parameter_string but need it here again in case the
* separate usage value has been given. */
if (/**/((use & (PUBKEY_USAGE_SIG|PUBKEY_USAGE_AUTH|PUBKEY_USAGE_CERT))
&& !pubkey_get_nsig (algo))
|| ((use & PUBKEY_USAGE_ENC)
&& !pubkey_get_nenc (algo))
|| (for_subkey && (use & PUBKEY_USAGE_CERT)))
return gpg_error (GPG_ERR_WRONG_KEY_USAGE);
/* Parse the expire string. */
expire = parse_expire_string (expirestr);
if (expire == (u32)-1 )
return gpg_error (GPG_ERR_INV_VALUE);
if (curve)
*r_curve = curve;
*r_algo = algo;
*r_usage = use;
*r_expire = expire;
*r_nbits = nbits;
*r_version = version;
return 0;
}
/* Add a new subkey to an existing key. Returns 0 if a new key has
been generated and put into the keyblocks. If any of ALGOSTR,
USAGESTR, or EXPIRESTR is NULL interactive mode is used. */
gpg_error_t
generate_subkeypair (ctrl_t ctrl, kbnode_t keyblock, const char *algostr,
const char *usagestr, const char *expirestr)
{
gpg_error_t err = 0;
int interactive;
kbnode_t node;
PKT_public_key *pri_psk = NULL;
PKT_public_key *sub_psk = NULL;
int algo;
unsigned int use;
u32 expire;
unsigned int nbits = 0;
const char *curve = NULL;
u32 cur_time;
char *key_from_hexgrip = NULL;
char *hexgrip = NULL;
char *serialno = NULL;
char *cache_nonce = NULL;
char *passwd_nonce = NULL;
int keygen_flags = 0;
interactive = (!algostr || !usagestr || !expirestr);
/* Break out the primary key. */
node = find_kbnode (keyblock, PKT_PUBLIC_KEY);
if (!node)
{
log_error ("Oops; primary key missing in keyblock!\n");
err = gpg_error (GPG_ERR_BUG);
goto leave;
}
pri_psk = node->pkt->pkt.public_key;
cur_time = make_timestamp ();
if (pri_psk->timestamp > cur_time)
{
ulong d = pri_psk->timestamp - cur_time;
log_info ( d==1 ? _("key has been created %lu second "
"in future (time warp or clock problem)\n")
: _("key has been created %lu seconds "
"in future (time warp or clock problem)\n"), d );
if (!opt.ignore_time_conflict)
{
err = gpg_error (GPG_ERR_TIME_CONFLICT);
goto leave;
}
}
if (pri_psk->version < 4)
{
log_info (_("Note: creating subkeys for v3 keys "
"is not OpenPGP compliant\n"));
err = gpg_error (GPG_ERR_CONFLICT);
goto leave;
}
err = hexkeygrip_from_pk (pri_psk, &hexgrip);
if (err)
goto leave;
if (agent_get_keyinfo (NULL, hexgrip, &serialno, NULL))
{
if (interactive)
tty_printf (_("Secret parts of primary key are not available.\n"));
else
log_info ( _("Secret parts of primary key are not available.\n"));
err = gpg_error (GPG_ERR_NO_SECKEY);
goto leave;
}
if (serialno)
{
if (interactive)
tty_printf (_("Secret parts of primary key are stored on-card.\n"));
else
log_info ( _("Secret parts of primary key are stored on-card.\n"));
}
if (interactive)
{
algo = ask_algo (ctrl, 1, NULL, &use, &key_from_hexgrip);
log_assert (algo);
if (key_from_hexgrip)
nbits = 0;
else if (algo == PUBKEY_ALGO_ECDSA
|| algo == PUBKEY_ALGO_EDDSA
|| algo == PUBKEY_ALGO_ECDH)
curve = ask_curve (&algo, NULL, NULL);
else
nbits = ask_keysize (algo, 0);
expire = ask_expire_interval (0, NULL);
if (!cpr_enabled() && !cpr_get_answer_is_yes("keygen.sub.okay",
_("Really create? (y/N) ")))
{
err = gpg_error (GPG_ERR_CANCELED);
goto leave;
}
}
else /* Unattended mode. */
{
int version;
err = parse_algo_usage_expire (ctrl, 1, algostr, usagestr, expirestr,
&algo, &use, &expire, &nbits, &curve,
&version);
if (err)
goto leave;
if (version == 5)
keygen_flags |= KEYGEN_FLAG_CREATE_V5_KEY;
}
/* Verify the passphrase now so that we get a cache item for the
* primary key passphrase. The agent also returns a passphrase
* nonce, which we can use to set the passphrase for the subkey to
* that of the primary key. */
{
char *desc = gpg_format_keydesc (ctrl, pri_psk, FORMAT_KEYDESC_NORMAL, 1);
err = agent_passwd (ctrl, hexgrip, desc, 1 /*=verify*/,
&cache_nonce, &passwd_nonce);
xfree (desc);
if (gpg_err_code (err) == GPG_ERR_NOT_IMPLEMENTED
&& gpg_err_source (err) == GPG_ERR_SOURCE_GPGAGENT)
err = 0; /* Very likely that the key is on a card. */
if (err)
goto leave;
}
/* Start creation. */
if (key_from_hexgrip)
{
err = do_create_from_keygrip (ctrl, algo, key_from_hexgrip,
keyblock, cur_time, expire, 1,
keygen_flags);
}
else
{
const char *passwd;
/* If the pinentry loopback mode is not and we have a static
passphrase (i.e. set with --passphrase{,-fd,-file} while in batch
mode), we use that passphrase for the new subkey. */
if (opt.pinentry_mode != PINENTRY_MODE_LOOPBACK
&& have_static_passphrase ())
passwd = get_static_passphrase ();
else
passwd = NULL;
err = do_create (algo, nbits, curve,
keyblock, cur_time, expire, 1, keygen_flags,
passwd, &cache_nonce, &passwd_nonce);
}
if (err)
goto leave;
/* Get the pointer to the generated public subkey packet. */
for (node = keyblock; node; node = node->next)
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
sub_psk = node->pkt->pkt.public_key;
/* Write the binding signature. */
err = write_keybinding (ctrl, keyblock, pri_psk, sub_psk, use, cur_time,
cache_nonce);
if (err)
goto leave;
print_status_key_created ('S', sub_psk, NULL);
leave:
xfree (key_from_hexgrip);
xfree (hexgrip);
xfree (serialno);
xfree (cache_nonce);
xfree (passwd_nonce);
if (err)
log_error (_("Key generation failed: %s\n"), gpg_strerror (err) );
return err;
}
#ifdef ENABLE_CARD_SUPPORT
/* Generate a subkey on a card. */
gpg_error_t
generate_card_subkeypair (ctrl_t ctrl, kbnode_t pub_keyblock,
int keyno, const char *serialno)
{
gpg_error_t err = 0;
kbnode_t node;
PKT_public_key *pri_pk = NULL;
unsigned int use;
u32 expire;
u32 cur_time;
struct para_data_s *para = NULL;
PKT_public_key *sub_pk = NULL;
int algo;
struct agent_card_info_s info;
int keygen_flags = 0; /* FIXME!!! */
log_assert (keyno >= 1 && keyno <= 3);
memset (&info, 0, sizeof (info));
err = agent_scd_getattr ("KEY-ATTR", &info);
if (err)
{
log_error (_("error getting current key info: %s\n"), gpg_strerror (err));
return err;
}
algo = info.key_attr[keyno-1].algo;
para = xtrycalloc (1, sizeof *para + strlen (serialno) );
if (!para)
{
err = gpg_error_from_syserror ();
goto leave;
}
para->key = pSERIALNO;
strcpy (para->u.value, serialno);
/* Break out the primary secret key */
node = find_kbnode (pub_keyblock, PKT_PUBLIC_KEY);
if (!node)
{
log_error ("Oops; public key lost!\n");
err = gpg_error (GPG_ERR_INTERNAL);
goto leave;
}
pri_pk = node->pkt->pkt.public_key;
cur_time = make_timestamp();
if (pri_pk->timestamp > cur_time)
{
ulong d = pri_pk->timestamp - cur_time;
log_info (d==1 ? _("key has been created %lu second "
"in future (time warp or clock problem)\n")
: _("key has been created %lu seconds "
"in future (time warp or clock problem)\n"), d );
if (!opt.ignore_time_conflict)
{
err = gpg_error (GPG_ERR_TIME_CONFLICT);
goto leave;
}
}
if (pri_pk->version < 4)
{
log_info (_("Note: creating subkeys for v3 keys "
"is not OpenPGP compliant\n"));
err = gpg_error (GPG_ERR_NOT_SUPPORTED);
goto leave;
}
expire = ask_expire_interval (0, NULL);
if (keyno == 1)
use = PUBKEY_USAGE_SIG;
else if (keyno == 2)
use = PUBKEY_USAGE_ENC;
else
use = PUBKEY_USAGE_AUTH;
if (!cpr_enabled() && !cpr_get_answer_is_yes("keygen.cardsub.okay",
_("Really create? (y/N) ")))
{
err = gpg_error (GPG_ERR_CANCELED);
goto leave;
}
/* Note, that depending on the backend, the card key generation may
update CUR_TIME. */
err = gen_card_key (keyno, algo, 0, pub_keyblock, &cur_time, expire,
keygen_flags);
/* Get the pointer to the generated public subkey packet. */
if (!err)
{
for (node = pub_keyblock; node; node = node->next)
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
sub_pk = node->pkt->pkt.public_key;
log_assert (sub_pk);
err = write_keybinding (ctrl, pub_keyblock, pri_pk, sub_pk,
use, cur_time, NULL);
}
leave:
if (err)
log_error (_("Key generation failed: %s\n"), gpg_strerror (err) );
else
print_status_key_created ('S', sub_pk, NULL);
release_parameter_list (para);
return err;
}
#endif /* !ENABLE_CARD_SUPPORT */
/*
* Write a keyblock to an output stream
*/
static int
write_keyblock( IOBUF out, KBNODE node )
{
for( ; node ; node = node->next )
{
if(!is_deleted_kbnode(node))
{
int rc = build_packet( out, node->pkt );
if( rc )
{
log_error("build_packet(%d) failed: %s\n",
node->pkt->pkttype, gpg_strerror (rc) );
return rc;
}
}
}
return 0;
}
/* Note that timestamp is an in/out arg.
* FIXME: Does not yet support v5 keys. */
static gpg_error_t
gen_card_key (int keyno, int algo, int is_primary, kbnode_t pub_root,
u32 *timestamp, u32 expireval, int keygen_flags)
{
#ifdef ENABLE_CARD_SUPPORT
gpg_error_t err;
PACKET *pkt;
PKT_public_key *pk;
char keyid[10];
unsigned char *public;
gcry_sexp_t s_key;
snprintf (keyid, DIM(keyid), "OPENPGP.%d", keyno);
pk = xtrycalloc (1, sizeof *pk );
if (!pk)
return gpg_error_from_syserror ();
pkt = xtrycalloc (1, sizeof *pkt);
if (!pkt)
{
xfree (pk);
return gpg_error_from_syserror ();
}
/* Note: SCD knows the serialnumber, thus there is no point in passing it. */
err = agent_scd_genkey (keyno, 1, timestamp);
/* The code below is not used because we force creation of
* the a card key (3rd arg).
* if (gpg_err_code (rc) == GPG_ERR_EEXIST)
* {
* tty_printf ("\n");
* log_error ("WARNING: key does already exists!\n");
* tty_printf ("\n");
* if ( cpr_get_answer_is_yes( "keygen.card.replace_key",
* _("Replace existing key? ")))
* rc = agent_scd_genkey (keyno, 1, timestamp);
* }
*/
if (err)
{
log_error ("key generation failed: %s\n", gpg_strerror (err));
xfree (pkt);
xfree (pk);
return err;
}
/* Send the READKEY command so that the agent creates a shadow key for
card key. We need to do that now so that we are able to create
the self-signatures. */
err = agent_readkey (NULL, 1, keyid, &public);
if (err)
return err;
err = gcry_sexp_sscan (&s_key, NULL, public,
gcry_sexp_canon_len (public, 0, NULL, NULL));
xfree (public);
if (err)
return err;
if (algo == PUBKEY_ALGO_RSA)
err = key_from_sexp (pk->pkey, s_key, "public-key", "ne");
else if (algo == PUBKEY_ALGO_ECDSA
|| algo == PUBKEY_ALGO_EDDSA
|| algo == PUBKEY_ALGO_ECDH )
err = ecckey_from_sexp (pk->pkey, s_key, algo);
else
err = gpg_error (GPG_ERR_PUBKEY_ALGO);
gcry_sexp_release (s_key);
if (err)
{
log_error ("key_from_sexp failed: %s\n", gpg_strerror (err) );
free_public_key (pk);
return err;
}
pk->timestamp = *timestamp;
pk->version = (keygen_flags & KEYGEN_FLAG_CREATE_V5_KEY)? 5 : 4;
if (expireval)
pk->expiredate = pk->timestamp + expireval;
pk->pubkey_algo = algo;
pkt->pkttype = is_primary ? PKT_PUBLIC_KEY : PKT_PUBLIC_SUBKEY;
pkt->pkt.public_key = pk;
add_kbnode (pub_root, new_kbnode (pkt));
return 0;
#else
(void)keyno;
(void)is_primary;
(void)pub_root;
(void)timestamp;
(void)expireval;
return gpg_error (GPG_ERR_NOT_SUPPORTED);
#endif /*!ENABLE_CARD_SUPPORT*/
}
diff --git a/g10/keyid.c b/g10/keyid.c
index aa77b47e2..7605cb386 100644
--- a/g10/keyid.c
+++ b/g10/keyid.c
@@ -1,1074 +1,1050 @@
/* keyid.c - key ID and fingerprint handling
* Copyright (C) 1998, 1999, 2000, 2001, 2003,
* 2004, 2006, 2010 Free Software Foundation, Inc.
* Copyright (C) 2014 Werner Koch
* Copyright (C) 2016 g10 Code GmbH
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include "gpg.h"
#include "../common/util.h"
#include "main.h"
#include "packet.h"
#include "options.h"
#include "keydb.h"
#include "../common/i18n.h"
#include "rmd160.h"
#include "../common/host2net.h"
#define KEYID_STR_SIZE 19
#ifdef HAVE_UNSIGNED_TIME_T
# define IS_INVALID_TIME_T(a) ((a) == (time_t)(-1))
#else
/* Error or 32 bit time_t and value after 2038-01-19. */
# define IS_INVALID_TIME_T(a) ((a) < 0)
#endif
/* Return a letter describing the public key algorithms. */
int
pubkey_letter( int algo )
{
switch (algo)
{
case PUBKEY_ALGO_RSA: return 'R' ;
case PUBKEY_ALGO_RSA_E: return 'r' ;
case PUBKEY_ALGO_RSA_S: return 's' ;
case PUBKEY_ALGO_ELGAMAL_E: return 'g' ;
case PUBKEY_ALGO_ELGAMAL: return 'G' ;
case PUBKEY_ALGO_DSA: return 'D' ;
case PUBKEY_ALGO_ECDH: return 'e' ; /* ECC DH (encrypt only) */
case PUBKEY_ALGO_ECDSA: return 'E' ; /* ECC DSA (sign only) */
case PUBKEY_ALGO_EDDSA: return 'E' ; /* ECC EdDSA (sign only) */
default: return '?';
}
}
/* Return a string describing the public key algorithm and the
- keysize. For elliptic curves the functions prints the name of the
+ keysize. For elliptic curves the function prints the name of the
curve because the keysize is a property of the curve. The string
is copied to the supplied buffer up a length of BUFSIZE-1.
Examples for the output are:
"rsa3072" - RSA with 3072 bit
"elg1024" - Elgamal with 1024 bit
"ed25519" - ECC using the curve Ed25519.
"E_1.2.3.4" - ECC using the unsupported curve with OID "1.2.3.4".
"E_1.3.6.1.4.1.11591.2.12242973" ECC with a bogus OID.
"unknown_N" - Unknown OpenPGP algorithm N.
If the option --legacy-list-mode is active, the output use the
legacy format:
"3072R" - RSA with 3072 bit
"1024g" - Elgamal with 1024 bit
"256E" - ECDSA using a curve with 256 bit
The macro PUBKEY_STRING_SIZE may be used to allocate a buffer with
a suitable size.*/
char *
pubkey_string (PKT_public_key *pk, char *buffer, size_t bufsize)
{
const char *prefix = NULL;
if (opt.legacy_list_mode)
{
snprintf (buffer, bufsize, "%4u%c",
nbits_from_pk (pk), pubkey_letter (pk->pubkey_algo));
return buffer;
}
switch (pk->pubkey_algo)
{
case PUBKEY_ALGO_RSA:
case PUBKEY_ALGO_RSA_E:
case PUBKEY_ALGO_RSA_S: prefix = "rsa"; break;
case PUBKEY_ALGO_ELGAMAL_E: prefix = "elg"; break;
case PUBKEY_ALGO_DSA: prefix = "dsa"; break;
case PUBKEY_ALGO_ELGAMAL: prefix = "xxx"; break;
case PUBKEY_ALGO_ECDH:
case PUBKEY_ALGO_ECDSA:
case PUBKEY_ALGO_EDDSA: prefix = ""; break;
}
if (prefix && *prefix)
snprintf (buffer, bufsize, "%s%u", prefix, nbits_from_pk (pk));
else if (prefix)
{
char *curve = openpgp_oid_to_str (pk->pkey[0]);
const char *name = openpgp_oid_to_curve (curve, 0);
if (name)
snprintf (buffer, bufsize, "%s", name);
else if (curve)
snprintf (buffer, bufsize, "E_%s", curve);
else
snprintf (buffer, bufsize, "E_error");
xfree (curve);
}
else
snprintf (buffer, bufsize, "unknown_%u", (unsigned int)pk->pubkey_algo);
return buffer;
}
/* Hash a public key. This function is useful for v4 and v5
* fingerprints and for v3 or v4 key signing. */
void
hash_public_key (gcry_md_hd_t md, PKT_public_key *pk)
{
unsigned int n;
unsigned int nn[PUBKEY_MAX_NPKEY];
byte *pp[PUBKEY_MAX_NPKEY];
int i;
unsigned int nbits;
size_t nbytes;
int npkey = pubkey_get_npkey (pk->pubkey_algo);
int is_v5 = pk->version == 5;
n = is_v5? 10 : 6;
/* FIXME: We can avoid the extra malloc by calling only the first
mpi_print here which computes the required length and calling the
real mpi_print only at the end. The speed advantage would only be
for ECC (opaque MPIs) or if we could implement an mpi_print
variant with a callback handler to do the hashing. */
if (npkey==0 && pk->pkey[0]
&& gcry_mpi_get_flag (pk->pkey[0], GCRYMPI_FLAG_OPAQUE))
{
pp[0] = gcry_mpi_get_opaque (pk->pkey[0], &nbits);
nn[0] = (nbits+7)/8;
n+=nn[0];
}
else
{
for (i=0; i < npkey; i++ )
{
if (!pk->pkey[i])
{
/* This case may only happen if the parsing of the MPI
failed but the key was anyway created. May happen
during "gpg KEYFILE". */
pp[i] = NULL;
nn[i] = 0;
}
else if (gcry_mpi_get_flag (pk->pkey[i], GCRYMPI_FLAG_OPAQUE))
{
const void *p;
p = gcry_mpi_get_opaque (pk->pkey[i], &nbits);
pp[i] = xmalloc ((nbits+7)/8);
if (p)
memcpy (pp[i], p, (nbits+7)/8);
else
pp[i] = NULL;
nn[i] = (nbits+7)/8;
n += nn[i];
}
else
{
if (gcry_mpi_print (GCRYMPI_FMT_PGP, NULL, 0,
&nbytes, pk->pkey[i]))
BUG ();
pp[i] = xmalloc (nbytes);
if (gcry_mpi_print (GCRYMPI_FMT_PGP, pp[i], nbytes,
&nbytes, pk->pkey[i]))
BUG ();
nn[i] = nbytes;
n += nn[i];
}
}
}
if (is_v5)
{
gcry_md_putc ( md, 0x9a ); /* ctb */
gcry_md_putc ( md, n >> 24 ); /* 4 byte length header */
gcry_md_putc ( md, n >> 16 );
gcry_md_putc ( md, n >> 8 );
gcry_md_putc ( md, n );
gcry_md_putc ( md, pk->version );
}
else
{
gcry_md_putc ( md, 0x99 ); /* ctb */
gcry_md_putc ( md, n >> 8 ); /* 2 byte length header */
gcry_md_putc ( md, n );
gcry_md_putc ( md, pk->version );
}
gcry_md_putc ( md, pk->timestamp >> 24 );
gcry_md_putc ( md, pk->timestamp >> 16 );
gcry_md_putc ( md, pk->timestamp >> 8 );
gcry_md_putc ( md, pk->timestamp );
gcry_md_putc ( md, pk->pubkey_algo );
if (is_v5)
{
n -= 10;
gcry_md_putc ( md, n >> 24 );
gcry_md_putc ( md, n >> 16 );
gcry_md_putc ( md, n >> 8 );
gcry_md_putc ( md, n );
}
if(npkey==0 && pk->pkey[0]
&& gcry_mpi_get_flag (pk->pkey[0], GCRYMPI_FLAG_OPAQUE))
{
if (pp[0])
gcry_md_write (md, pp[0], nn[0]);
}
else
{
for(i=0; i < npkey; i++ )
{
if (pp[i])
gcry_md_write ( md, pp[i], nn[i] );
xfree(pp[i]);
}
}
}
-static gcry_md_hd_t
-do_fingerprint_md( PKT_public_key *pk )
-{
- gcry_md_hd_t md;
-
- if (gcry_md_open (&md, pk->version == 5 ? GCRY_MD_SHA256 : GCRY_MD_SHA1, 0))
- BUG ();
- hash_public_key (md,pk);
- gcry_md_final (md);
-
- return md;
-}
-
-
/* fixme: Check whether we can replace this function or if not
describe why we need it. */
u32
v3_keyid (gcry_mpi_t a, u32 *ki)
{
byte *buffer, *p;
size_t nbytes;
if (gcry_mpi_print (GCRYMPI_FMT_USG, NULL, 0, &nbytes, a ))
BUG ();
/* fixme: allocate it on the stack */
buffer = xmalloc (nbytes);
if (gcry_mpi_print( GCRYMPI_FMT_USG, buffer, nbytes, NULL, a ))
BUG ();
if (nbytes < 8) /* oops */
ki[0] = ki[1] = 0;
else
{
p = buffer + nbytes - 8;
ki[0] = buf32_to_u32 (p);
p += 4;
ki[1] = buf32_to_u32 (p);
}
xfree (buffer);
return ki[1];
}
/* Return PK's keyid. The memory is owned by PK. */
u32 *
pk_keyid (PKT_public_key *pk)
{
keyid_from_pk (pk, NULL);
/* Uncomment this for help tracking down bugs related to keyid or
main_keyid not being set correctly. */
#if 0
if (! (pk->main_keyid[0] || pk->main_keyid[1]))
log_bug ("pk->main_keyid not set!\n");
if (keyid_cmp (pk->keyid, pk->main_keyid) == 0
&& ! pk->flags.primary)
log_bug ("keyid and main_keyid are the same, but primary flag not set!\n");
if (keyid_cmp (pk->keyid, pk->main_keyid) != 0
&& pk->flags.primary)
log_bug ("keyid and main_keyid are different, but primary flag set!\n");
#endif
return pk->keyid;
}
/* Return the keyid of the primary key associated with PK. The memory
is owned by PK. */
u32 *
pk_main_keyid (PKT_public_key *pk)
{
/* Uncomment this for help tracking down bugs related to keyid or
main_keyid not being set correctly. */
#if 0
if (! (pk->main_keyid[0] || pk->main_keyid[1]))
log_bug ("pk->main_keyid not set!\n");
#endif
return pk->main_keyid;
}
/* Copy the keyid in SRC to DEST and return DEST. */
u32 *
keyid_copy (u32 *dest, const u32 *src)
{
dest[0] = src[0];
dest[1] = src[1];
return dest;
}
char *
format_keyid (u32 *keyid, int format, char *buffer, int len)
{
char tmp[KEYID_STR_SIZE];
if (! buffer)
{
buffer = tmp;
len = sizeof (tmp);
}
if (format == KF_DEFAULT)
format = opt.keyid_format;
if (format == KF_DEFAULT)
format = KF_NONE;
switch (format)
{
case KF_NONE:
if (len)
*buffer = 0;
break;
case KF_SHORT:
snprintf (buffer, len, "%08lX", (ulong)keyid[1]);
break;
case KF_LONG:
snprintf (buffer, len, "%08lX%08lX", (ulong)keyid[0], (ulong)keyid[1]);
break;
case KF_0xSHORT:
snprintf (buffer, len, "0x%08lX", (ulong)keyid[1]);
break;
case KF_0xLONG:
snprintf (buffer, len, "0x%08lX%08lX", (ulong)keyid[0],(ulong)keyid[1]);
break;
default:
BUG();
}
if (buffer == tmp)
return xstrdup (buffer);
return buffer;
}
size_t
keystrlen(void)
{
int format = opt.keyid_format;
if (format == KF_DEFAULT)
format = KF_NONE;
switch(format)
{
case KF_NONE:
return 0;
case KF_SHORT:
return 8;
case KF_LONG:
return 16;
case KF_0xSHORT:
return 10;
case KF_0xLONG:
return 18;
default:
BUG();
}
}
const char *
keystr (u32 *keyid)
{
static char keyid_str[KEYID_STR_SIZE];
int format = opt.keyid_format;
if (format == KF_DEFAULT)
format = KF_NONE;
if (format == KF_NONE)
format = KF_LONG;
return format_keyid (keyid, format, keyid_str, sizeof (keyid_str));
}
/* This function returns the key id of the main and possible the
* subkey as one string. It is used by error messages. */
const char *
keystr_with_sub (u32 *main_kid, u32 *sub_kid)
{
static char buffer[KEYID_STR_SIZE+1+KEYID_STR_SIZE];
char *p;
int format = opt.keyid_format;
if (format == KF_NONE)
format = KF_LONG;
format_keyid (main_kid, format, buffer, KEYID_STR_SIZE);
if (sub_kid)
{
p = buffer + strlen (buffer);
*p++ = '/';
format_keyid (sub_kid, format, p, KEYID_STR_SIZE);
}
return buffer;
}
const char *
keystr_from_pk(PKT_public_key *pk)
{
keyid_from_pk(pk,NULL);
return keystr(pk->keyid);
}
const char *
keystr_from_pk_with_sub (PKT_public_key *main_pk, PKT_public_key *sub_pk)
{
keyid_from_pk (main_pk, NULL);
if (sub_pk)
keyid_from_pk (sub_pk, NULL);
return keystr_with_sub (main_pk->keyid, sub_pk? sub_pk->keyid:NULL);
}
/* Return PK's key id as a string using the default format. PK owns
the storage. */
const char *
pk_keyid_str (PKT_public_key *pk)
{
return keystr (pk_keyid (pk));
}
const char *
keystr_from_desc(KEYDB_SEARCH_DESC *desc)
{
switch(desc->mode)
{
case KEYDB_SEARCH_MODE_LONG_KID:
case KEYDB_SEARCH_MODE_SHORT_KID:
return keystr(desc->u.kid);
case KEYDB_SEARCH_MODE_FPR:
{
u32 keyid[2];
if (desc->fprlen == 32)
{
keyid[0] = buf32_to_u32 (desc->u.fpr);
keyid[1] = buf32_to_u32 (desc->u.fpr+4);
}
else if (desc->fprlen == 20)
{
keyid[0] = buf32_to_u32 (desc->u.fpr+12);
keyid[1] = buf32_to_u32 (desc->u.fpr+16);
}
else if (desc->fprlen == 16)
return "?v3 fpr?";
else /* oops */
return "?vx fpr?";
return keystr(keyid);
}
default:
BUG();
}
}
+/* Compute the fingerprint and keyid and store it in PK. */
+static void
+compute_fingerprint (PKT_public_key *pk)
+{
+ const byte *dp;
+ gcry_md_hd_t md;
+ size_t len;
+
+ if (gcry_md_open (&md, pk->version == 5 ? GCRY_MD_SHA256 : GCRY_MD_SHA1, 0))
+ BUG ();
+ hash_public_key (md, pk);
+ gcry_md_final (md);
+ dp = gcry_md_read (md, 0);
+ len = gcry_md_get_algo_dlen (gcry_md_get_algo (md));
+ log_assert (len <= MAX_FINGERPRINT_LEN);
+ memcpy (pk->fpr, dp, len);
+ pk->fprlen = len;
+ if (pk->version == 5)
+ {
+ pk->keyid[0] = buf32_to_u32 (dp);
+ pk->keyid[1] = buf32_to_u32 (dp+4);
+ }
+ else
+ {
+ pk->keyid[0] = buf32_to_u32 (dp+12);
+ pk->keyid[1] = buf32_to_u32 (dp+16);
+ }
+ gcry_md_close( md);
+}
+
+
/*
* Get the keyid from the public key PK and store it at KEYID unless
* this is NULL. Returns the 32 bit short keyid.
*/
u32
keyid_from_pk (PKT_public_key *pk, u32 *keyid)
{
u32 dummy_keyid[2];
if (!keyid)
keyid = dummy_keyid;
- if( pk->keyid[0] || pk->keyid[1] )
- {
- keyid[0] = pk->keyid[0];
- keyid[1] = pk->keyid[1];
- }
- else
- {
- const byte *dp;
- gcry_md_hd_t md;
-
- md = do_fingerprint_md(pk);
- if(md)
- {
- dp = gcry_md_read ( md, 0 );
- if (pk->version == 5)
- {
- keyid[0] = buf32_to_u32 (dp);
- keyid[1] = buf32_to_u32 (dp+4);
- }
- else
- {
- keyid[0] = buf32_to_u32 (dp+12);
- keyid[1] = buf32_to_u32 (dp+16);
- }
- gcry_md_close (md);
- pk->keyid[0] = keyid[0];
- pk->keyid[1] = keyid[1];
- }
- else
- pk->keyid[0] = pk->keyid[1] = keyid[0]= keyid[1] = 0xFFFFFFFF;
- }
+ if (!pk->fprlen)
+ compute_fingerprint (pk);
+
+ keyid[0] = pk->keyid[0];
+ keyid[1] = pk->keyid[1];
return keyid[1]; /*FIXME:shortkeyid ist different for v5*/
}
/*
* Get the keyid from the fingerprint. This function is simple for
* most keys, but has to do a key lookup for old v3 keys where the
* keyid is not part of the fingerprint.
*/
u32
keyid_from_fingerprint (ctrl_t ctrl, const byte *fprint,
size_t fprint_len, u32 *keyid)
{
u32 dummy_keyid[2];
if( !keyid )
keyid = dummy_keyid;
if (fprint_len != 20 && fprint_len != 32)
{
/* This is special as we have to lookup the key first. */
PKT_public_key pk;
int rc;
memset (&pk, 0, sizeof pk);
rc = get_pubkey_byfprint (ctrl, &pk, NULL, fprint, fprint_len);
if( rc )
{
log_printhex (fprint, fprint_len,
"Oops: keyid_from_fingerprint: no pubkey; fpr:");
keyid[0] = 0;
keyid[1] = 0;
}
else
keyid_from_pk (&pk, keyid);
}
else
{
const byte *dp = fprint;
if (fprint_len == 20) /* v4 key */
{
keyid[0] = buf32_to_u32 (dp+12);
keyid[1] = buf32_to_u32 (dp+16);
}
else /* v5 key */
{
keyid[0] = buf32_to_u32 (dp);
keyid[1] = buf32_to_u32 (dp+4);
}
}
return keyid[1];
}
u32
keyid_from_sig (PKT_signature *sig, u32 *keyid)
{
if( keyid )
{
keyid[0] = sig->keyid[0];
keyid[1] = sig->keyid[1];
}
return sig->keyid[1]; /*FIXME:shortkeyid*/
}
byte *
namehash_from_uid (PKT_user_id *uid)
{
if (!uid->namehash)
{
uid->namehash = xmalloc (20);
if (uid->attrib_data)
rmd160_hash_buffer (uid->namehash, uid->attrib_data, uid->attrib_len);
else
rmd160_hash_buffer (uid->namehash, uid->name, uid->len);
}
return uid->namehash;
}
/*
* Return the number of bits used in PK.
*/
unsigned int
nbits_from_pk (PKT_public_key *pk)
{
return pubkey_nbits (pk->pubkey_algo, pk->pkey);
}
/* Convert an UTC TIMESTAMP into an UTC yyyy-mm-dd string. Return
* that string. The caller should pass a buffer with at least a size
* of MK_DATESTR_SIZE. */
char *
mk_datestr (char *buffer, size_t bufsize, u32 timestamp)
{
time_t atime = timestamp;
struct tm *tp;
if (IS_INVALID_TIME_T (atime))
strcpy (buffer, "????" "-??" "-??"); /* Mark this as invalid. */
else
{
tp = gmtime (&atime);
snprintf (buffer, bufsize, "%04d-%02d-%02d",
1900+tp->tm_year, tp->tm_mon+1, tp->tm_mday );
}
return buffer;
}
/*
* return a string with the creation date of the pk
* Note: this is alloced in a static buffer.
* Format is: yyyy-mm-dd
*/
const char *
datestr_from_pk (PKT_public_key *pk)
{
static char buffer[MK_DATESTR_SIZE];
return mk_datestr (buffer, sizeof buffer, pk->timestamp);
}
const char *
datestr_from_sig (PKT_signature *sig )
{
static char buffer[MK_DATESTR_SIZE];
return mk_datestr (buffer, sizeof buffer, sig->timestamp);
}
const char *
expirestr_from_pk (PKT_public_key *pk)
{
static char buffer[MK_DATESTR_SIZE];
if (!pk->expiredate)
return _("never ");
return mk_datestr (buffer, sizeof buffer, pk->expiredate);
}
const char *
expirestr_from_sig (PKT_signature *sig)
{
static char buffer[MK_DATESTR_SIZE];
if (!sig->expiredate)
return _("never ");
return mk_datestr (buffer, sizeof buffer, sig->expiredate);
}
const char *
revokestr_from_pk( PKT_public_key *pk )
{
static char buffer[MK_DATESTR_SIZE];
if(!pk->revoked.date)
return _("never ");
return mk_datestr (buffer, sizeof buffer, pk->revoked.date);
}
const char *
usagestr_from_pk (PKT_public_key *pk, int fill)
{
static char buffer[10];
int i = 0;
unsigned int use = pk->pubkey_usage;
if ( use & PUBKEY_USAGE_SIG )
buffer[i++] = 'S';
if ( use & PUBKEY_USAGE_CERT )
buffer[i++] = 'C';
if ( use & PUBKEY_USAGE_ENC )
buffer[i++] = 'E';
if ( (use & PUBKEY_USAGE_AUTH) )
buffer[i++] = 'A';
while (fill && i < 4)
buffer[i++] = ' ';
buffer[i] = 0;
return buffer;
}
const char *
colon_strtime (u32 t)
{
static char buf[20];
if (!t)
return "";
snprintf (buf, sizeof buf, "%lu", (ulong)t);
return buf;
}
const char *
colon_datestr_from_pk (PKT_public_key *pk)
{
static char buf[20];
snprintf (buf, sizeof buf, "%lu", (ulong)pk->timestamp);
return buf;
}
const char *
colon_datestr_from_sig (PKT_signature *sig)
{
static char buf[20];
snprintf (buf, sizeof buf, "%lu", (ulong)sig->timestamp);
return buf;
}
const char *
colon_expirestr_from_sig (PKT_signature *sig)
{
static char buf[20];
if (!sig->expiredate)
return "";
snprintf (buf, sizeof buf,"%lu", (ulong)sig->expiredate);
return buf;
}
+
/*
* Return a byte array with the fingerprint for the given PK/SK
* The length of the array is returned in ret_len. Caller must free
* the array or provide an array of length MAX_FINGERPRINT_LEN.
*/
byte *
fingerprint_from_pk (PKT_public_key *pk, byte *array, size_t *ret_len)
{
- const byte *dp;
- size_t len;
- gcry_md_hd_t md;
+ if (!pk->fprlen)
+ compute_fingerprint (pk);
- md = do_fingerprint_md (pk);
- dp = gcry_md_read (md, 0);
- len = gcry_md_get_algo_dlen (gcry_md_get_algo (md));
- log_assert (len <= MAX_FINGERPRINT_LEN);
if (!array)
- array = xmalloc ( len );
- memcpy (array, dp, len );
- if (pk->version == 5)
- {
- pk->keyid[0] = buf32_to_u32 (dp);
- pk->keyid[1] = buf32_to_u32 (dp+4);
- }
- else
- {
- pk->keyid[0] = buf32_to_u32 (dp+12);
- pk->keyid[1] = buf32_to_u32 (dp+16);
- }
- gcry_md_close( md);
+ array = xmalloc (pk->fprlen);
+ memcpy (array, pk->fpr, pk->fprlen);
if (ret_len)
- *ret_len = len;
+ *ret_len = pk->fprlen;
return array;
}
/* Return an allocated buffer with the fingerprint of PK formatted as
* a plain hexstring. If BUFFER is NULL the result is a malloc'd
* string. If BUFFER is not NULL the result will be copied into this
* buffer. In the latter case BUFLEN describes the length of the
* buffer; if this is too short the function terminates the process.
* Returns a malloc'ed string or BUFFER. A suitable length for BUFFER
* is (2*MAX_FINGERPRINT_LEN + 1). */
char *
hexfingerprint (PKT_public_key *pk, char *buffer, size_t buflen)
{
- unsigned char fpr[MAX_FINGERPRINT_LEN];
- size_t len;
+ if (!pk->fprlen)
+ compute_fingerprint (pk);
- fingerprint_from_pk (pk, fpr, &len);
if (!buffer)
{
- buffer = xtrymalloc (2 * len + 1);
+ buffer = xtrymalloc (2 * pk->fprlen + 1);
if (!buffer)
return NULL;
}
- else if (buflen < 2*len+1)
+ else if (buflen < 2 * pk->fprlen + 1)
log_fatal ("%s: buffer too short (%zu)\n", __func__, buflen);
- bin2hex (fpr, len, buffer);
+
+ bin2hex (pk->fpr, pk->fprlen, buffer);
return buffer;
}
/* Pretty print a hex fingerprint. If BUFFER is NULL the result is a
malloc'd string. If BUFFER is not NULL the result will be copied
into this buffer. In the latter case BUFLEN describes the length
of the buffer; if this is too short the function terminates the
process. Returns a malloc'ed string or BUFFER. A suitable length
for BUFFER is (MAX_FORMATTED_FINGERPRINT_LEN + 1). */
char *
format_hexfingerprint (const char *fingerprint, char *buffer, size_t buflen)
{
int hexlen = strlen (fingerprint);
int space;
int i, j;
if (hexlen == 40) /* v4 fingerprint */
{
space = (/* The characters and the NUL. */
40 + 1
/* After every fourth character, we add a space (except
the last). */
+ 40 / 4 - 1
/* Half way through we add a second space. */
+ 1);
}
else if (hexlen == 64 || hexlen == 50) /* v5 fingerprint */
{
/* The v5 fingerprint is commonly printed truncated to 25
* octets. We accept the truncated as well as the full hex
* version here and format it like this:
* B2CCB6 838332 5D61BA C50F9F 5E CD21A8 0AC8C5 2565C8 C52565
*/
hexlen = 50;
space = 8 * 6 + 2 + 8 + 1;
}
else /* Other fingerprint versions - print as is. */
{
/* We truncated here so that we do not need to provide a buffer
* of a length which is in reality never used. */
if (hexlen > MAX_FORMATTED_FINGERPRINT_LEN - 1)
hexlen = MAX_FORMATTED_FINGERPRINT_LEN - 1;
space = hexlen + 1;
}
if (!buffer)
buffer = xmalloc (space);
else if (buflen < space)
log_fatal ("%s: buffer too short (%zu)\n", __func__, buflen);
if (hexlen == 40) /* v4 fingerprint */
{
for (i = 0, j = 0; i < 40; i ++)
{
if (i && !(i % 4))
buffer[j ++] = ' ';
if (i == 40 / 2)
buffer[j ++] = ' ';
buffer[j ++] = fingerprint[i];
}
buffer[j ++] = 0;
log_assert (j == space);
}
else if (hexlen == 50) /* v5 fingerprint */
{
for (i=j=0; i < 24; i++)
{
if (i && !(i % 6))
buffer[j++] = ' ';
buffer[j++] = fingerprint[i];
}
buffer[j++] = ' ';
buffer[j++] = fingerprint[i++];
buffer[j++] = fingerprint[i++];
for (; i < 50; i++)
{
if (!((i-26) % 6))
buffer[j++] = ' ';
buffer[j++] = fingerprint[i];
}
buffer[j++] = 0;
log_assert (j == space);
}
else
{
mem2str (buffer, fingerprint, space);
}
return buffer;
}
/* 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 0 on success or an error code. */
gpg_error_t
keygrip_from_pk (PKT_public_key *pk, unsigned char *array)
{
gpg_error_t err;
gcry_sexp_t s_pkey;
if (DBG_PACKET)
log_debug ("get_keygrip for public key\n");
switch (pk->pubkey_algo)
{
case GCRY_PK_DSA:
err = gcry_sexp_build (&s_pkey, NULL,
"(public-key(dsa(p%m)(q%m)(g%m)(y%m)))",
pk->pkey[0], pk->pkey[1],
pk->pkey[2], pk->pkey[3]);
break;
case GCRY_PK_ELG:
case GCRY_PK_ELG_E:
err = gcry_sexp_build (&s_pkey, NULL,
"(public-key(elg(p%m)(g%m)(y%m)))",
pk->pkey[0], pk->pkey[1], pk->pkey[2]);
break;
case GCRY_PK_RSA:
case GCRY_PK_RSA_S:
case GCRY_PK_RSA_E:
err = gcry_sexp_build (&s_pkey, NULL,
"(public-key(rsa(n%m)(e%m)))",
pk->pkey[0], pk->pkey[1]);
break;
case PUBKEY_ALGO_EDDSA:
case PUBKEY_ALGO_ECDSA:
case PUBKEY_ALGO_ECDH:
{
char *curve = openpgp_oid_to_str (pk->pkey[0]);
if (!curve)
err = gpg_error_from_syserror ();
else
{
err = gcry_sexp_build (&s_pkey, NULL,
pk->pubkey_algo == PUBKEY_ALGO_EDDSA?
"(public-key(ecc(curve%s)(flags eddsa)(q%m)))":
(pk->pubkey_algo == PUBKEY_ALGO_ECDH
&& openpgp_oid_is_cv25519 (pk->pkey[0]))?
"(public-key(ecc(curve%s)(flags djb-tweak)(q%m)))":
"(public-key(ecc(curve%s)(q%m)))",
curve, pk->pkey[1]);
xfree (curve);
}
}
break;
default:
err = gpg_error (GPG_ERR_PUBKEY_ALGO);
break;
}
if (err)
return err;
if (!gcry_pk_get_keygrip (s_pkey, array))
{
char *hexfpr;
hexfpr = hexfingerprint (pk, NULL, 0);
log_info ("error computing keygrip (fpr=%s)\n", hexfpr);
xfree (hexfpr);
memset (array, 0, 20);
err = gpg_error (GPG_ERR_GENERAL);
}
else
{
if (DBG_PACKET)
log_printhex (array, 20, "keygrip=");
/* FIXME: Save the keygrip in PK. */
}
gcry_sexp_release (s_pkey);
return err;
}
/* Store an allocated buffer with the keygrip of PK encoded as a
hexstring at r_GRIP. Returns 0 on success. */
gpg_error_t
hexkeygrip_from_pk (PKT_public_key *pk, char **r_grip)
{
gpg_error_t err;
unsigned char grip[KEYGRIP_LEN];
*r_grip = NULL;
err = keygrip_from_pk (pk, grip);
if (!err)
{
char * buf = xtrymalloc (KEYGRIP_LEN * 2 + 1);
if (!buf)
err = gpg_error_from_syserror ();
else
{
bin2hex (grip, KEYGRIP_LEN, buf);
*r_grip = buf;
}
}
return err;
}
diff --git a/g10/keylist.c b/g10/keylist.c
index 8d5b2e0b9..1274775d2 100644
--- a/g10/keylist.c
+++ b/g10/keylist.c
@@ -1,2193 +1,2215 @@
/* keylist.c - Print information about OpenPGP keys
* Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006,
* 2008, 2010, 2012 Free Software Foundation, Inc.
* Copyright (C) 2013, 2014 Werner Koch
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#ifdef HAVE_DOSISH_SYSTEM
# include <fcntl.h> /* for setmode() */
#endif
#include "gpg.h"
#include "options.h"
#include "packet.h"
#include "../common/status.h"
#include "keydb.h"
#include "photoid.h"
#include "../common/util.h"
#include "../common/ttyio.h"
#include "trustdb.h"
#include "main.h"
#include "../common/i18n.h"
#include "../common/status.h"
#include "call-agent.h"
#include "../common/mbox-util.h"
#include "../common/zb32.h"
#include "tofu.h"
#include "../common/compliance.h"
#include "../common/pkscreening.h"
static void list_all (ctrl_t, int, int);
static void list_one (ctrl_t ctrl,
strlist_t names, int secret, int mark_secret);
-static void locate_one (ctrl_t ctrl, strlist_t names);
+static void locate_one (ctrl_t ctrl, strlist_t names, int no_local);
static void print_card_serialno (const char *serialno);
struct keylist_context
{
int check_sigs; /* If set signatures shall be verified. */
int good_sigs; /* Counter used if CHECK_SIGS is set. */
int inv_sigs; /* Counter used if CHECK_SIGS is set. */
int no_key; /* Counter used if CHECK_SIGS is set. */
int oth_err; /* Counter used if CHECK_SIGS is set. */
int no_validity; /* Do not show validity. */
};
static void list_keyblock (ctrl_t ctrl,
kbnode_t keyblock, int secret, int has_secret,
int fpr, struct keylist_context *listctx);
/* The stream used to write attribute packets to. */
static estream_t attrib_fp;
/* Release resources from a keylist context. */
static void
keylist_context_release (struct keylist_context *listctx)
{
(void)listctx; /* Nothing to release. */
}
/* List the keys. If list is NULL, all available keys are listed.
- With LOCATE_MODE set the locate algorithm is used to find a
- key. */
+ * With LOCATE_MODE set the locate algorithm is used to find a key; if
+ * in addition NO_LOCAL is set the locate does not look into the local
+ * keyring. */
void
-public_key_list (ctrl_t ctrl, strlist_t list, int locate_mode)
+public_key_list (ctrl_t ctrl, strlist_t list, int locate_mode, int no_local)
{
#ifndef NO_TRUST_MODELS
if (opt.with_colons)
{
byte trust_model, marginals, completes, cert_depth, min_cert_level;
ulong created, nextcheck;
read_trust_options (ctrl, &trust_model, &created, &nextcheck,
&marginals, &completes, &cert_depth, &min_cert_level);
es_fprintf (es_stdout, "tru:");
if (nextcheck && nextcheck <= make_timestamp ())
es_fprintf (es_stdout, "o");
if (trust_model != opt.trust_model)
es_fprintf (es_stdout, "t");
if (opt.trust_model == TM_PGP || opt.trust_model == TM_CLASSIC
|| opt.trust_model == TM_TOFU_PGP)
{
if (marginals != opt.marginals_needed)
es_fprintf (es_stdout, "m");
if (completes != opt.completes_needed)
es_fprintf (es_stdout, "c");
if (cert_depth != opt.max_cert_depth)
es_fprintf (es_stdout, "d");
if (min_cert_level != opt.min_cert_level)
es_fprintf (es_stdout, "l");
}
es_fprintf (es_stdout, ":%d:%lu:%lu", trust_model, created, nextcheck);
/* Only show marginals, completes, and cert_depth in the classic
or PGP trust models since they are not meaningful
otherwise. */
if (trust_model == TM_PGP || trust_model == TM_CLASSIC)
es_fprintf (es_stdout, ":%d:%d:%d", marginals, completes, cert_depth);
es_fprintf (es_stdout, "\n");
}
#endif /*!NO_TRUST_MODELS*/
/* We need to do the stale check right here because it might need to
update the keyring while we already have the keyring open. This
is very bad for W32 because of a sharing violation. For real OSes
it might lead to false results if we are later listing a keyring
which is associated with the inode of a deleted file. */
check_trustdb_stale (ctrl);
#ifdef USE_TOFU
tofu_begin_batch_update (ctrl);
#endif
if (locate_mode)
- locate_one (ctrl, list);
+ locate_one (ctrl, list, no_local);
else if (!list)
list_all (ctrl, 0, opt.with_secret);
else
list_one (ctrl, list, 0, opt.with_secret);
#ifdef USE_TOFU
tofu_end_batch_update (ctrl);
#endif
}
void
secret_key_list (ctrl_t ctrl, strlist_t list)
{
(void)ctrl;
check_trustdb_stale (ctrl);
if (!list)
list_all (ctrl, 1, 0);
else /* List by user id */
list_one (ctrl, list, 1, 0);
}
-char *
-format_seckey_info (ctrl_t ctrl, PKT_public_key *pk)
+
+/* Helper for print_key_info and print_key_info_log. */
+static char *
+format_key_info (ctrl_t ctrl, PKT_public_key *pk, int secret)
{
u32 keyid[2];
char *p;
char pkstrbuf[PUBKEY_STRING_SIZE];
- char *info;
+ char *result;
keyid_from_pk (pk, keyid);
- p = get_user_id_native (ctrl, keyid);
- info = xtryasprintf ("sec %s/%s %s %s",
- pubkey_string (pk, pkstrbuf, sizeof pkstrbuf),
- keystr (keyid), datestr_from_pk (pk), p);
+ /* If the pk was chosen by a particular user ID, that is the one to
+ print. */
+ if (pk->user_id)
+ p = utf8_to_native (pk->user_id->name, pk->user_id->len, 0);
+ else
+ p = get_user_id_native (ctrl, keyid);
+ result = xtryasprintf ("%s %s/%s %s %s",
+ secret? (pk->flags.primary? "sec":"ssb")
+ /* */ : (pk->flags.primary? "pub":"sub"),
+ pubkey_string (pk, pkstrbuf, sizeof pkstrbuf),
+ keystr (keyid), datestr_from_pk (pk), p);
xfree (p);
-
- return info;
+ return result;
}
+
+/* Print basic information about a public or secret key. With FP
+ * passed as NULL, the tty output interface is used, otherwise output
+ * is directed to the given stream. INDENT gives the requested
+ * indentation; if that is a negative value indentation is suppressed
+ * for the first line. SECRET tells that the PK has a secret part.
+ * FIXME: This is similar in use to print_key_line and thus both
+ * functions should eventually be united.
+ */
void
-print_seckey_info (ctrl_t ctrl, PKT_public_key *pk)
+print_key_info (ctrl_t ctrl, estream_t fp,
+ int indent, PKT_public_key *pk, int secret)
{
- char *p = format_seckey_info (ctrl, pk);
- tty_printf ("\n%s\n", p);
- xfree (p);
+ int indentabs = indent >= 0? indent : -indent;
+ char *info;
+
+ /* Note: Negative values for INDENT are not yet needed. */
+
+ info = format_key_info (ctrl, pk, secret);
+
+ if (!fp && indent >= 0)
+ tty_printf ("\n"); /* (Backward compatibility to old code) */
+ tty_fprintf (fp, "%*s%s\n", indentabs, "",
+ info? info : "[Ooops - out of core]");
+
+ xfree (info);
}
-/* Print information about the public key. With FP passed as NULL,
- the tty output interface is used, otherwise output is directed to
- the given stream. */
+
+/* Same as print_key_info put print using the log functions at
+ * LOGLEVEL. */
void
-print_pubkey_info (ctrl_t ctrl, estream_t fp, PKT_public_key *pk)
+print_key_info_log (ctrl_t ctrl, int loglevel,
+ int indent, PKT_public_key *pk, int secret)
{
- u32 keyid[2];
- char *p;
- char pkstrbuf[PUBKEY_STRING_SIZE];
+ int indentabs = indent >= 0? indent : -indent;
+ char *info;
- keyid_from_pk (pk, keyid);
+ info = format_key_info (ctrl, pk, secret);
- /* If the pk was chosen by a particular user ID, that is the one to
- print. */
- if (pk->user_id)
- p = utf8_to_native (pk->user_id->name, pk->user_id->len, 0);
- else
- p = get_user_id_native (ctrl, keyid);
+ log_log (loglevel, "%*s%s\n", indentabs, "",
+ info? info : "[Ooops - out of core]");
- if (!fp)
- tty_printf ("\n");
- tty_fprintf (fp, "%s %s/%s %s %s\n",
- pk->flags.primary? "pub":"sub",
- pubkey_string (pk, pkstrbuf, sizeof pkstrbuf),
- keystr (keyid), datestr_from_pk (pk), p);
- xfree (p);
+ xfree (info);
}
/* Print basic information of a secret key including the card serial
number information. */
#ifdef ENABLE_CARD_SUPPORT
void
print_card_key_info (estream_t fp, kbnode_t keyblock)
{
kbnode_t node;
char *hexgrip;
char *serialno;
int s2k_char;
char pkstrbuf[PUBKEY_STRING_SIZE];
int indent;
for (node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_KEY
|| node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
int rc;
PKT_public_key *pk = node->pkt->pkt.public_key;
serialno = NULL;
rc = hexkeygrip_from_pk (pk, &hexgrip);
if (rc)
{
log_error ("error computing a keygrip: %s\n", gpg_strerror (rc));
s2k_char = '?';
}
else if (!agent_get_keyinfo (NULL, hexgrip, &serialno, NULL))
s2k_char = serialno? '>':' ';
else
s2k_char = '#'; /* Key not found. */
tty_fprintf (fp, "%s%c %s/%s %n",
node->pkt->pkttype == PKT_PUBLIC_KEY ? "sec" : "ssb",
s2k_char,
pubkey_string (pk, pkstrbuf, sizeof pkstrbuf),
keystr_from_pk (pk),
&indent);
tty_fprintf (fp, _("created: %s"), datestr_from_pk (pk));
tty_fprintf (fp, " ");
tty_fprintf (fp, _("expires: %s"), expirestr_from_pk (pk));
if (serialno)
{
tty_fprintf (fp, "\n%*s%s", indent, "", _("card-no: "));
if (strlen (serialno) == 32
&& !strncmp (serialno, "D27600012401", 12))
{
/* This is an OpenPGP card. Print the relevant part. */
/* Example: D2760001240101010001000003470000 */
/* xxxxyyyyyyyy */
tty_fprintf (fp, "%.*s %.*s", 4, serialno+16, 8, serialno+20);
}
else
tty_fprintf (fp, "%s", serialno);
}
tty_fprintf (fp, "\n");
xfree (hexgrip);
xfree (serialno);
}
}
}
#endif /*ENABLE_CARD_SUPPORT*/
/* Flags = 0x01 hashed 0x02 critical. */
static void
status_one_subpacket (sigsubpkttype_t type, size_t len, int flags,
const byte * buf)
{
char status[40];
/* Don't print these. */
if (len > 256)
return;
snprintf (status, sizeof status,
"%d %u %u ", type, flags, (unsigned int) len);
write_status_text_and_buffer (STATUS_SIG_SUBPACKET, status, buf, len, 0);
}
/* Print a policy URL. Allowed values for MODE are:
* -1 - print to the TTY
* 0 - print to stdout.
* 1 - use log_info and emit status messages.
* 2 - emit only status messages.
*/
void
show_policy_url (PKT_signature * sig, int indent, int mode)
{
const byte *p;
size_t len;
int seq = 0, crit;
estream_t fp = mode < 0? NULL : mode ? log_get_stream () : es_stdout;
while ((p =
enum_sig_subpkt (sig->hashed, SIGSUBPKT_POLICY, &len, &seq, &crit)))
{
if (mode != 2)
{
const char *str;
tty_fprintf (fp, "%*s", indent, "");
if (crit)
str = _("Critical signature policy: ");
else
str = _("Signature policy: ");
if (mode > 0)
log_info ("%s", str);
else
tty_fprintf (fp, "%s", str);
tty_print_utf8_string2 (fp, p, len, 0);
tty_fprintf (fp, "\n");
}
if (mode > 0)
write_status_buffer (STATUS_POLICY_URL, p, len, 0);
}
}
/* Print a keyserver URL. Allowed values for MODE are:
* -1 - print to the TTY
* 0 - print to stdout.
* 1 - use log_info and emit status messages.
* 2 - emit only status messages.
*/
void
show_keyserver_url (PKT_signature * sig, int indent, int mode)
{
const byte *p;
size_t len;
int seq = 0, crit;
estream_t fp = mode < 0? NULL : mode ? log_get_stream () : es_stdout;
while ((p =
enum_sig_subpkt (sig->hashed, SIGSUBPKT_PREF_KS, &len, &seq,
&crit)))
{
if (mode != 2)
{
const char *str;
tty_fprintf (fp, "%*s", indent, "");
if (crit)
str = _("Critical preferred keyserver: ");
else
str = _("Preferred keyserver: ");
if (mode > 0)
log_info ("%s", str);
else
tty_fprintf (fp, "%s", str);
tty_print_utf8_string2 (fp, p, len, 0);
tty_fprintf (fp, "\n");
}
if (mode > 0)
status_one_subpacket (SIGSUBPKT_PREF_KS, len,
(crit ? 0x02 : 0) | 0x01, p);
}
}
/* Print notation data. Allowed values for MODE are:
* -1 - print to the TTY
* 0 - print to stdout.
* 1 - use log_info and emit status messages.
* 2 - emit only status messages.
*
* Defined bits in WHICH:
* 1 - standard notations
* 2 - user notations
*/
void
show_notation (PKT_signature * sig, int indent, int mode, int which)
{
estream_t fp = mode < 0? NULL : mode ? log_get_stream () : es_stdout;
notation_t nd, notations;
if (which == 0)
which = 3;
notations = sig_to_notation (sig);
/* There may be multiple notations in the same sig. */
for (nd = notations; nd; nd = nd->next)
{
if (mode != 2)
{
int has_at = !!strchr (nd->name, '@');
if ((which & 1 && !has_at) || (which & 2 && has_at))
{
const char *str;
tty_fprintf (fp, "%*s", indent, "");
if (nd->flags.critical)
str = _("Critical signature notation: ");
else
str = _("Signature notation: ");
if (mode > 0)
log_info ("%s", str);
else
tty_fprintf (fp, "%s", str);
/* This is all UTF8 */
tty_print_utf8_string2 (fp, nd->name, strlen (nd->name), 0);
tty_fprintf (fp, "=");
tty_print_utf8_string2 (fp, nd->value, strlen (nd->value), 0);
/* (We need to use log_printf so that the next call to a
log function does not insert an extra LF.) */
if (mode > 0)
log_printf ("\n");
else
tty_fprintf (fp, "\n");
}
}
if (mode > 0)
{
write_status_buffer (STATUS_NOTATION_NAME,
nd->name, strlen (nd->name), 0);
if (nd->flags.critical || nd->flags.human)
write_status_text (STATUS_NOTATION_FLAGS,
nd->flags.critical && nd->flags.human? "1 1" :
nd->flags.critical? "1 0" : "0 1");
write_status_buffer (STATUS_NOTATION_DATA,
nd->value, strlen (nd->value), 50);
}
}
free_notation (notations);
}
static void
print_signature_stats (struct keylist_context *s)
{
if (!s->check_sigs)
return; /* Signature checking was not requested. */
/* Better flush stdout so that the stats are always printed after
* the output. */
es_fflush (es_stdout);
if (s->good_sigs)
log_info (ngettext("%d good signature\n",
"%d good signatures\n", s->good_sigs), s->good_sigs);
if (s->inv_sigs)
log_info (ngettext("%d bad signature\n",
"%d bad signatures\n", s->inv_sigs), s->inv_sigs);
if (s->no_key)
log_info (ngettext("%d signature not checked due to a missing key\n",
"%d signatures not checked due to missing keys\n",
s->no_key), s->no_key);
if (s->oth_err)
log_info (ngettext("%d signature not checked due to an error\n",
"%d signatures not checked due to errors\n",
s->oth_err), s->oth_err);
}
/* List all keys. If SECRET is true only secret keys are listed. If
MARK_SECRET is true secret keys are indicated in a public key
listing. */
static void
list_all (ctrl_t ctrl, int secret, int mark_secret)
{
KEYDB_HANDLE hd;
KBNODE keyblock = NULL;
int rc = 0;
int any_secret;
const char *lastresname, *resname;
struct keylist_context listctx;
memset (&listctx, 0, sizeof (listctx));
if (opt.check_sigs)
listctx.check_sigs = 1;
hd = keydb_new ();
if (!hd)
rc = gpg_error_from_syserror ();
else
rc = keydb_search_first (hd);
if (rc)
{
if (gpg_err_code (rc) != GPG_ERR_NOT_FOUND)
log_error ("keydb_search_first failed: %s\n", gpg_strerror (rc));
goto leave;
}
lastresname = NULL;
do
{
rc = keydb_get_keyblock (hd, &keyblock);
if (rc)
{
if (gpg_err_code (rc) == GPG_ERR_LEGACY_KEY)
continue; /* Skip legacy keys. */
log_error ("keydb_get_keyblock failed: %s\n", gpg_strerror (rc));
goto leave;
}
if (secret || mark_secret)
any_secret = !agent_probe_any_secret_key (NULL, keyblock);
else
any_secret = 0;
if (secret && !any_secret)
; /* Secret key listing requested but this isn't one. */
else
{
if (!opt.with_colons && !(opt.list_options & LIST_SHOW_ONLY_FPR_MBOX))
{
resname = keydb_get_resource_name (hd);
if (lastresname != resname)
{
int i;
es_fprintf (es_stdout, "%s\n", resname);
for (i = strlen (resname); i; i--)
es_putc ('-', es_stdout);
es_putc ('\n', es_stdout);
lastresname = resname;
}
}
merge_keys_and_selfsig (ctrl, keyblock);
list_keyblock (ctrl, keyblock, secret, any_secret, opt.fingerprint,
&listctx);
}
release_kbnode (keyblock);
keyblock = NULL;
}
while (!(rc = keydb_search_next (hd)));
es_fflush (es_stdout);
if (rc && gpg_err_code (rc) != GPG_ERR_NOT_FOUND)
log_error ("keydb_search_next failed: %s\n", gpg_strerror (rc));
if (keydb_get_skipped_counter (hd))
log_info (ngettext("Warning: %lu key skipped due to its large size\n",
"Warning: %lu keys skipped due to their large sizes\n",
keydb_get_skipped_counter (hd)),
keydb_get_skipped_counter (hd));
if (opt.check_sigs && !opt.with_colons)
print_signature_stats (&listctx);
leave:
keylist_context_release (&listctx);
release_kbnode (keyblock);
keydb_release (hd);
}
static void
list_one (ctrl_t ctrl, strlist_t names, int secret, int mark_secret)
{
int rc = 0;
KBNODE keyblock = NULL;
GETKEY_CTX ctx;
const char *resname;
const char *keyring_str = _("Keyring");
int i;
struct keylist_context listctx;
memset (&listctx, 0, sizeof (listctx));
if (!secret && opt.check_sigs)
listctx.check_sigs = 1;
/* fixme: using the bynames function has the disadvantage that we
* don't know whether one of the names given was not found. OTOH,
* this function has the advantage to list the names in the
* sequence as defined by the keyDB and does not duplicate
* outputs. A solution could be do test whether all given have
* been listed (this needs a way to use the keyDB search
* functions) or to have the search function return indicators for
* found names. Yet another way is to use the keydb search
* facilities directly. */
rc = getkey_bynames (ctrl, &ctx, NULL, names, secret, &keyblock);
if (rc)
{
log_error ("error reading key: %s\n", gpg_strerror (rc));
getkey_end (ctrl, ctx);
write_status_error ("keylist.getkey", rc);
return;
}
do
{
if ((opt.list_options & LIST_SHOW_KEYRING) && !opt.with_colons)
{
resname = keydb_get_resource_name (get_ctx_handle (ctx));
es_fprintf (es_stdout, "%s: %s\n", keyring_str, resname);
for (i = strlen (resname) + strlen (keyring_str) + 2; i; i--)
es_putc ('-', es_stdout);
es_putc ('\n', es_stdout);
}
list_keyblock (ctrl,
keyblock, secret, mark_secret, opt.fingerprint, &listctx);
release_kbnode (keyblock);
}
while (!getkey_next (ctrl, ctx, NULL, &keyblock));
getkey_end (ctrl, ctx);
if (opt.check_sigs && !opt.with_colons)
print_signature_stats (&listctx);
keylist_context_release (&listctx);
}
static void
-locate_one (ctrl_t ctrl, strlist_t names)
+locate_one (ctrl_t ctrl, strlist_t names, int no_local)
{
int rc = 0;
strlist_t sl;
GETKEY_CTX ctx = NULL;
KBNODE keyblock = NULL;
struct keylist_context listctx;
memset (&listctx, 0, sizeof (listctx));
if (opt.check_sigs)
listctx.check_sigs = 1;
for (sl = names; sl; sl = sl->next)
{
- rc = get_best_pubkey_byname (ctrl, &ctx, NULL, sl->d, &keyblock, 1);
+ rc = get_best_pubkey_byname (ctrl,
+ no_local? GET_PUBKEY_NO_LOCAL
+ /* */: GET_PUBKEY_NORMAL,
+ &ctx, NULL, sl->d, &keyblock, 1);
if (rc)
{
if (gpg_err_code (rc) != GPG_ERR_NO_PUBKEY)
log_error ("error reading key: %s\n", gpg_strerror (rc));
else if (opt.verbose)
log_info (_("key \"%s\" not found: %s\n"),
sl->d, gpg_strerror (rc));
}
else
{
do
{
list_keyblock (ctrl, keyblock, 0, 0, opt.fingerprint, &listctx);
release_kbnode (keyblock);
}
while (ctx && !getkey_next (ctrl, ctx, NULL, &keyblock));
getkey_end (ctrl, ctx);
ctx = NULL;
}
}
if (opt.check_sigs && !opt.with_colons)
print_signature_stats (&listctx);
keylist_context_release (&listctx);
}
static void
print_key_data (PKT_public_key * pk)
{
int n = pk ? pubkey_get_npkey (pk->pubkey_algo) : 0;
int i;
for (i = 0; i < n; i++)
{
es_fprintf (es_stdout, "pkd:%d:%u:", i, mpi_get_nbits (pk->pkey[i]));
mpi_print (es_stdout, pk->pkey[i], 1);
es_putc (':', es_stdout);
es_putc ('\n', es_stdout);
}
}
/* Various public key screenings. (Right now just ROCA). With
* COLON_MODE set the output is formatted for use in the compliance
* field of a colon listing.
*/
static void
print_pk_screening (PKT_public_key *pk, int colon_mode)
{
gpg_error_t err;
if (is_RSA (pk->pubkey_algo) && pubkey_get_npkey (pk->pubkey_algo))
{
err = screen_key_for_roca (pk->pkey[0]);
if (!err)
;
else if (gpg_err_code (err) == GPG_ERR_TRUE)
{
if (colon_mode)
es_fprintf (es_stdout, colon_mode > 1? " %d":"%d", 6001);
else
es_fprintf (es_stdout,
" Screening: ROCA vulnerability detected\n");
}
else if (!colon_mode)
es_fprintf (es_stdout, " Screening: [ROCA check failed: %s]\n",
gpg_strerror (err));
}
}
static void
print_capabilities (ctrl_t ctrl, PKT_public_key *pk, KBNODE keyblock)
{
unsigned int use = pk->pubkey_usage;
int c_printed = 0;
if (use & PUBKEY_USAGE_ENC)
es_putc ('e', es_stdout);
if (use & PUBKEY_USAGE_SIG)
{
es_putc ('s', es_stdout);
if (pk->flags.primary)
{
es_putc ('c', es_stdout);
/* The PUBKEY_USAGE_CERT flag was introduced later and we
used to always print 'c' for a primary key. To avoid any
regression here we better track whether we printed 'c'
already. */
c_printed = 1;
}
}
if ((use & PUBKEY_USAGE_CERT) && !c_printed)
es_putc ('c', es_stdout);
if ((use & PUBKEY_USAGE_AUTH))
es_putc ('a', es_stdout);
if ((use & PUBKEY_USAGE_UNKNOWN))
es_putc ('?', es_stdout);
if (keyblock)
{
/* Figure out the usable capabilities. */
KBNODE k;
int enc = 0, sign = 0, cert = 0, auth = 0, disabled = 0;
for (k = keyblock; k; k = k->next)
{
if (k->pkt->pkttype == PKT_PUBLIC_KEY
|| k->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
pk = k->pkt->pkt.public_key;
if (pk->flags.primary)
disabled = pk_is_disabled (pk);
if (pk->flags.valid && !pk->flags.revoked && !pk->has_expired)
{
if (pk->pubkey_usage & PUBKEY_USAGE_ENC)
enc = 1;
if (pk->pubkey_usage & PUBKEY_USAGE_SIG)
{
sign = 1;
if (pk->flags.primary)
cert = 1;
}
if (pk->pubkey_usage & PUBKEY_USAGE_CERT)
cert = 1;
if ((pk->pubkey_usage & PUBKEY_USAGE_AUTH))
auth = 1;
}
}
}
if (enc)
es_putc ('E', es_stdout);
if (sign)
es_putc ('S', es_stdout);
if (cert)
es_putc ('C', es_stdout);
if (auth)
es_putc ('A', es_stdout);
if (disabled)
es_putc ('D', es_stdout);
}
es_putc (':', es_stdout);
}
/* FLAGS: 0x01 hashed
0x02 critical */
static void
print_one_subpacket (sigsubpkttype_t type, size_t len, int flags,
const byte * buf)
{
size_t i;
es_fprintf (es_stdout, "spk:%d:%u:%u:", type, flags, (unsigned int) len);
for (i = 0; i < len; i++)
{
/* printable ascii other than : and % */
if (buf[i] >= 32 && buf[i] <= 126 && buf[i] != ':' && buf[i] != '%')
es_fprintf (es_stdout, "%c", buf[i]);
else
es_fprintf (es_stdout, "%%%02X", buf[i]);
}
es_fprintf (es_stdout, "\n");
}
void
print_subpackets_colon (PKT_signature * sig)
{
byte *i;
log_assert (opt.show_subpackets);
for (i = opt.show_subpackets; *i; i++)
{
const byte *p;
size_t len;
int seq, crit;
seq = 0;
while ((p = enum_sig_subpkt (sig->hashed, *i, &len, &seq, &crit)))
print_one_subpacket (*i, len, 0x01 | (crit ? 0x02 : 0), p);
seq = 0;
while ((p = enum_sig_subpkt (sig->unhashed, *i, &len, &seq, &crit)))
print_one_subpacket (*i, len, 0x00 | (crit ? 0x02 : 0), p);
}
}
void
dump_attribs (const PKT_user_id *uid, PKT_public_key *pk)
{
int i;
if (!attrib_fp)
return;
for (i = 0; i < uid->numattribs; i++)
{
if (is_status_enabled ())
{
byte array[MAX_FINGERPRINT_LEN], *p;
char buf[(MAX_FINGERPRINT_LEN * 2) + 90];
size_t j, n;
if (!pk)
BUG ();
fingerprint_from_pk (pk, array, &n);
p = array;
for (j = 0; j < n; j++, p++)
sprintf (buf + 2 * j, "%02X", *p);
sprintf (buf + strlen (buf), " %lu %u %u %u %lu %lu %u",
(ulong) uid->attribs[i].len, uid->attribs[i].type, i + 1,
uid->numattribs, (ulong) uid->created,
(ulong) uid->expiredate,
((uid->flags.primary ? 0x01 : 0) | (uid->flags.revoked ? 0x02 : 0) |
(uid->flags.expired ? 0x04 : 0)));
write_status_text (STATUS_ATTRIBUTE, buf);
}
es_fwrite (uid->attribs[i].data, uid->attribs[i].len, 1, attrib_fp);
es_fflush (attrib_fp);
}
}
static void
list_keyblock_print (ctrl_t ctrl, kbnode_t keyblock, int secret, int fpr,
struct keylist_context *listctx)
{
int rc;
KBNODE kbctx;
KBNODE node;
PKT_public_key *pk;
int skip_sigs = 0;
char *hexgrip = NULL;
char *serialno = NULL;
/* Get the keyid from the keyblock. */
node = find_kbnode (keyblock, PKT_PUBLIC_KEY);
if (!node)
{
log_error ("Oops; key lost!\n");
dump_kbnode (keyblock);
return;
}
pk = node->pkt->pkt.public_key;
if (secret || opt.with_keygrip)
{
rc = hexkeygrip_from_pk (pk, &hexgrip);
if (rc)
log_error ("error computing a keygrip: %s\n", gpg_strerror (rc));
}
if (secret)
{
/* Encode some info about the secret key in SECRET. */
if (!agent_get_keyinfo (NULL, hexgrip, &serialno, NULL))
secret = serialno? 3 : 1;
else
secret = 2; /* Key not found. */
}
if (!listctx->no_validity)
check_trustdb_stale (ctrl);
/* Print the "pub" line and in KF_NONE mode the fingerprint. */
print_key_line (ctrl, es_stdout, pk, secret);
if (fpr)
print_fingerprint (ctrl, NULL, pk, 0);
if (opt.with_keygrip && hexgrip)
es_fprintf (es_stdout, " Keygrip = %s\n", hexgrip);
if (serialno)
print_card_serialno (serialno);
if (opt.with_key_data)
print_key_data (pk);
if (opt.with_key_screening)
print_pk_screening (pk, 0);
if (opt.with_key_origin
&& (pk->keyorg || pk->keyupdate || pk->updateurl))
{
char updatestr[MK_DATESTR_SIZE];
es_fprintf (es_stdout, " origin=%s last=%s %s",
key_origin_string (pk->keyorg),
mk_datestr (updatestr, sizeof updatestr, pk->keyupdate),
pk->updateurl? "url=":"");
if (pk->updateurl)
print_utf8_string (es_stdout, pk->updateurl);
es_putc ('\n', es_stdout);
}
for (kbctx = NULL; (node = walk_kbnode (keyblock, &kbctx, 0));)
{
if (node->pkt->pkttype == PKT_USER_ID)
{
PKT_user_id *uid = node->pkt->pkt.user_id;
int indent;
int kl = opt.keyid_format == KF_NONE? 10 : keystrlen ();
if ((uid->flags.expired || uid->flags.revoked)
&& !(opt.list_options & LIST_SHOW_UNUSABLE_UIDS))
{
skip_sigs = 1;
continue;
}
else
skip_sigs = 0;
if (attrib_fp && uid->attrib_data != NULL)
dump_attribs (uid, pk);
if ((uid->flags.revoked || uid->flags.expired)
|| ((opt.list_options & LIST_SHOW_UID_VALIDITY)
&& !listctx->no_validity))
{
const char *validity;
validity = uid_trust_string_fixed (ctrl, pk, uid);
indent = ((kl + (opt.legacy_list_mode? 9:11))
- atoi (uid_trust_string_fixed (ctrl, NULL, NULL)));
if (indent < 0 || indent > 40)
indent = 0;
es_fprintf (es_stdout, "uid%*s%s ", indent, "", validity);
}
else
{
indent = kl + (opt.legacy_list_mode? 10:12);
es_fprintf (es_stdout, "uid%*s", indent, "");
}
print_utf8_buffer (es_stdout, uid->name, uid->len);
es_putc ('\n', es_stdout);
if (opt.with_wkd_hash)
{
char *mbox, *hash, *p;
char hashbuf[32];
mbox = mailbox_from_userid (uid->name, 0);
if (mbox && (p = strchr (mbox, '@')))
{
*p++ = 0;
gcry_md_hash_buffer (GCRY_MD_SHA1, hashbuf,
mbox, strlen (mbox));
hash = zb32_encode (hashbuf, 8*20);
if (hash)
{
es_fprintf (es_stdout, " %*s%s@%s\n",
indent, "", hash, p);
xfree (hash);
}
}
xfree (mbox);
}
if (opt.with_key_origin
&& (uid->keyorg || uid->keyupdate || uid->updateurl))
{
char updatestr[MK_DATESTR_SIZE];
es_fprintf (es_stdout, " %*sorigin=%s last=%s %s",
indent, "",
key_origin_string (uid->keyorg),
mk_datestr (updatestr, sizeof updatestr,
uid->keyupdate),
uid->updateurl? "url=":"");
if (uid->updateurl)
print_utf8_string (es_stdout, uid->updateurl);
es_putc ('\n', es_stdout);
}
if ((opt.list_options & LIST_SHOW_PHOTOS) && uid->attribs != NULL)
show_photos (ctrl, uid->attribs, uid->numattribs, pk, uid);
}
else if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
PKT_public_key *pk2 = node->pkt->pkt.public_key;
if ((pk2->flags.revoked || pk2->has_expired)
&& !(opt.list_options & LIST_SHOW_UNUSABLE_SUBKEYS))
{
skip_sigs = 1;
continue;
}
else
skip_sigs = 0;
xfree (serialno); serialno = NULL;
xfree (hexgrip); hexgrip = NULL;
if (secret || opt.with_keygrip)
{
rc = hexkeygrip_from_pk (pk2, &hexgrip);
if (rc)
log_error ("error computing a keygrip: %s\n",
gpg_strerror (rc));
}
if (secret)
{
if (!agent_get_keyinfo (NULL, hexgrip, &serialno, NULL))
secret = serialno? 3 : 1;
else
secret = 2; /* Key not found. */
}
/* Print the "sub" line. */
print_key_line (ctrl, es_stdout, pk2, secret);
if (fpr > 1 || opt.with_subkey_fingerprint)
{
print_fingerprint (ctrl, NULL, pk2, 0);
if (serialno)
print_card_serialno (serialno);
}
if (opt.with_keygrip && hexgrip)
es_fprintf (es_stdout, " Keygrip = %s\n", hexgrip);
if (opt.with_key_data)
print_key_data (pk2);
if (opt.with_key_screening)
print_pk_screening (pk2, 0);
}
else if (opt.list_sigs
&& node->pkt->pkttype == PKT_SIGNATURE && !skip_sigs)
{
PKT_signature *sig = node->pkt->pkt.signature;
int sigrc;
char *sigstr;
char *reason_text = NULL;
char *reason_comment = NULL;
size_t reason_commentlen;
if (listctx->check_sigs)
{
rc = check_key_signature (ctrl, keyblock, node, NULL);
switch (gpg_err_code (rc))
{
case 0:
listctx->good_sigs++;
sigrc = '!';
break;
case GPG_ERR_BAD_SIGNATURE:
listctx->inv_sigs++;
sigrc = '-';
break;
case GPG_ERR_NO_PUBKEY:
case GPG_ERR_UNUSABLE_PUBKEY:
listctx->no_key++;
continue;
default:
listctx->oth_err++;
sigrc = '%';
break;
}
/* TODO: Make sure a cached sig record here still has
the pk that issued it. See also
keyedit.c:print_and_check_one_sig */
}
else
{
rc = 0;
sigrc = ' ';
}
if (sig->sig_class == 0x20 || sig->sig_class == 0x28
|| sig->sig_class == 0x30)
{
sigstr = "rev";
get_revocation_reason (sig, &reason_text,
&reason_comment, &reason_commentlen);
}
else if ((sig->sig_class & ~3) == 0x10)
sigstr = "sig";
else if (sig->sig_class == 0x18)
sigstr = "sig";
else if (sig->sig_class == 0x1F)
sigstr = "sig";
else
{
es_fprintf (es_stdout, "sig "
"[unexpected signature class 0x%02x]\n",
sig->sig_class);
continue;
}
es_fputs (sigstr, es_stdout);
es_fprintf (es_stdout, "%c%c %c%c%c%c%c%c %s %s",
sigrc, (sig->sig_class - 0x10 > 0 &&
sig->sig_class - 0x10 <
4) ? '0' + sig->sig_class - 0x10 : ' ',
sig->flags.exportable ? ' ' : 'L',
sig->flags.revocable ? ' ' : 'R',
sig->flags.policy_url ? 'P' : ' ',
sig->flags.notation ? 'N' : ' ',
sig->flags.expired ? 'X' : ' ',
(sig->trust_depth > 9) ? 'T' : (sig->trust_depth >
0) ? '0' +
sig->trust_depth : ' ', keystr (sig->keyid),
datestr_from_sig (sig));
if (opt.list_options & LIST_SHOW_SIG_EXPIRE)
es_fprintf (es_stdout, " %s", expirestr_from_sig (sig));
es_fprintf (es_stdout, " ");
if (sigrc == '%')
es_fprintf (es_stdout, "[%s] ", gpg_strerror (rc));
else if (sigrc == '?')
;
else if (!opt.fast_list_mode)
{
size_t n;
char *p = get_user_id (ctrl, sig->keyid, &n, NULL);
print_utf8_buffer (es_stdout, p, n);
xfree (p);
}
es_putc ('\n', es_stdout);
if (sig->flags.policy_url
&& (opt.list_options & LIST_SHOW_POLICY_URLS))
show_policy_url (sig, 3, 0);
if (sig->flags.notation && (opt.list_options & LIST_SHOW_NOTATIONS))
show_notation (sig, 3, 0,
((opt.
list_options & LIST_SHOW_STD_NOTATIONS) ? 1 : 0)
+
((opt.
list_options & LIST_SHOW_USER_NOTATIONS) ? 2 :
0));
if (sig->flags.pref_ks
&& (opt.list_options & LIST_SHOW_KEYSERVER_URLS))
show_keyserver_url (sig, 3, 0);
if (reason_text)
{
es_fprintf (es_stdout, " %s%s\n",
_("reason for revocation: "), reason_text);
if (reason_comment)
{
const byte *s, *s_lf;
size_t n, n_lf;
s = reason_comment;
n = reason_commentlen;
s_lf = NULL;
do
{
/* We don't want any empty lines, so we skip them. */
for (;n && *s == '\n'; s++, n--)
;
if (n)
{
s_lf = memchr (s, '\n', n);
n_lf = s_lf? s_lf - s : n;
es_fprintf (es_stdout, " %s",
_("revocation comment: "));
es_write_sanitized (es_stdout, s, n_lf, NULL, NULL);
es_putc ('\n', es_stdout);
s += n_lf; n -= n_lf;
}
} while (s_lf);
}
}
xfree (reason_text);
xfree (reason_comment);
/* fixme: check or list other sigs here */
}
}
es_putc ('\n', es_stdout);
xfree (serialno);
xfree (hexgrip);
}
/* Do a simple key listing printing only the fingerprint and the mail
* address of valid keys. */
static void
list_keyblock_simple (ctrl_t ctrl, kbnode_t keyblock)
{
gpg_err_code_t ec;
kbnode_t kbctx;
kbnode_t node;
char hexfpr[2*MAX_FINGERPRINT_LEN+1];
char *mbox;
(void)ctrl;
node = find_kbnode (keyblock, PKT_PUBLIC_KEY);
if (!node)
{
log_error ("Oops; key lost!\n");
dump_kbnode (keyblock);
return;
}
hexfingerprint (node->pkt->pkt.public_key, hexfpr, sizeof hexfpr);
for (kbctx = NULL; (node = walk_kbnode (keyblock, &kbctx, 0));)
{
if (node->pkt->pkttype == PKT_USER_ID)
{
PKT_user_id *uid = node->pkt->pkt.user_id;
if (uid->attrib_data)
continue;
if (uid->flags.expired || uid->flags.revoked)
continue;
mbox = mailbox_from_userid (uid->name, 0);
if (!mbox)
{
ec = gpg_err_code_from_syserror ();
if (ec != GPG_ERR_EINVAL)
log_error ("error getting mailbox from user-id: %s\n",
gpg_strerror (ec));
continue;
}
es_fprintf (es_stdout, "%s %s\n", hexfpr, mbox);
xfree (mbox);
}
}
}
void
print_revokers (estream_t fp, PKT_public_key * pk)
{
/* print the revoker record */
if (!pk->revkey && pk->numrevkeys)
BUG ();
else
{
int i, j;
for (i = 0; i < pk->numrevkeys; i++)
{
byte *p;
es_fprintf (fp, "rvk:::%d::::::", pk->revkey[i].algid);
p = pk->revkey[i].fpr;
for (j = 0; j < pk->revkey[i].fprlen; j++, p++)
es_fprintf (fp, "%02X", *p);
es_fprintf (fp, ":%02x%s:\n",
pk->revkey[i].class,
(pk->revkey[i].class & 0x40) ? "s" : "");
}
}
}
/* Print the compliance flags to field 18. PK is the public key.
* KEYLENGTH is the length of the key in bits and CURVENAME is either
* NULL or the name of the curve. The latter two args are here
* merely because the caller has already computed them. */
static void
print_compliance_flags (PKT_public_key *pk,
unsigned int keylength, const char *curvename)
{
int any = 0;
if (!keylength)
keylength = nbits_from_pk (pk);
if (pk->version == 5)
{
es_fputs (gnupg_status_compliance_flag (CO_GNUPG), es_stdout);
any++;
}
if (gnupg_pk_is_compliant (CO_DE_VS, pk->pubkey_algo, pk->pkey,
keylength, curvename))
{
es_fprintf (es_stdout, any ? " %s" : "%s",
gnupg_status_compliance_flag (CO_DE_VS));
any++;
}
if (opt.with_key_screening)
print_pk_screening (pk, 1+any);
}
/* List a key in colon mode. If SECRET is true this is a secret key
record (i.e. requested via --list-secret-key). If HAS_SECRET a
secret key is available even if SECRET is not set. */
static void
list_keyblock_colon (ctrl_t ctrl, kbnode_t keyblock,
int secret, int has_secret)
{
int rc;
KBNODE kbctx;
KBNODE node;
PKT_public_key *pk;
u32 keyid[2];
int trustletter = 0;
int trustletter_print;
int ownertrust_print;
int ulti_hack = 0;
int i;
char *hexgrip_buffer = NULL;
const char *hexgrip = NULL;
char *serialno = NULL;
int stubkey;
unsigned int keylength;
char *curve = NULL;
const char *curvename = NULL;
/* Get the keyid from the keyblock. */
node = find_kbnode (keyblock, PKT_PUBLIC_KEY);
if (!node)
{
log_error ("Oops; key lost!\n");
dump_kbnode (keyblock);
return;
}
pk = node->pkt->pkt.public_key;
if (secret || has_secret || opt.with_keygrip || opt.with_key_data)
{
rc = hexkeygrip_from_pk (pk, &hexgrip_buffer);
if (rc)
log_error ("error computing a keygrip: %s\n", gpg_strerror (rc));
/* In the error case we print an empty string so that we have a
* "grp" record for each and subkey - even if it is empty. This
* may help to prevent sync problems. */
hexgrip = hexgrip_buffer? hexgrip_buffer : "";
}
stubkey = 0;
if ((secret || has_secret)
&& agent_get_keyinfo (NULL, hexgrip, &serialno, NULL))
stubkey = 1; /* Key not found. */
keyid_from_pk (pk, keyid);
if (!pk->flags.valid)
trustletter_print = 'i';
else if (pk->flags.revoked)
trustletter_print = 'r';
else if (pk->has_expired)
trustletter_print = 'e';
else if (opt.fast_list_mode || opt.no_expensive_trust_checks)
trustletter_print = 0;
else
{
trustletter = get_validity_info (ctrl, keyblock, pk, NULL);
if (trustletter == 'u')
ulti_hack = 1;
trustletter_print = trustletter;
}
if (!opt.fast_list_mode && !opt.no_expensive_trust_checks)
ownertrust_print = get_ownertrust_info (ctrl, pk, 0);
else
ownertrust_print = 0;
keylength = nbits_from_pk (pk);
es_fputs (secret? "sec:":"pub:", es_stdout);
if (trustletter_print)
es_putc (trustletter_print, es_stdout);
es_fprintf (es_stdout, ":%u:%d:%08lX%08lX:%s:%s::",
keylength,
pk->pubkey_algo,
(ulong) keyid[0], (ulong) keyid[1],
colon_datestr_from_pk (pk), colon_strtime (pk->expiredate));
if (ownertrust_print)
es_putc (ownertrust_print, es_stdout);
es_putc (':', es_stdout);
es_putc (':', es_stdout);
es_putc (':', es_stdout);
print_capabilities (ctrl, pk, keyblock);
es_putc (':', es_stdout); /* End of field 13. */
es_putc (':', es_stdout); /* End of field 14. */
if (secret || has_secret)
{
if (stubkey)
es_putc ('#', es_stdout);
else if (serialno)
es_fputs (serialno, es_stdout);
else if (has_secret)
es_putc ('+', es_stdout);
}
es_putc (':', es_stdout); /* End of field 15. */
es_putc (':', es_stdout); /* End of field 16. */
if (pk->pubkey_algo == PUBKEY_ALGO_ECDSA
|| pk->pubkey_algo == PUBKEY_ALGO_EDDSA
|| pk->pubkey_algo == PUBKEY_ALGO_ECDH)
{
curve = openpgp_oid_to_str (pk->pkey[0]);
curvename = openpgp_oid_to_curve (curve, 0);
if (!curvename)
curvename = curve;
es_fputs (curvename, es_stdout);
}
es_putc (':', es_stdout); /* End of field 17. */
print_compliance_flags (pk, keylength, curvename);
es_putc (':', es_stdout); /* End of field 18 (compliance). */
if (pk->keyupdate)
es_fputs (colon_strtime (pk->keyupdate), es_stdout);
es_putc (':', es_stdout); /* End of field 19 (last_update). */
es_fprintf (es_stdout, "%d%s", pk->keyorg, pk->updateurl? " ":"");
if (pk->updateurl)
es_write_sanitized (es_stdout, pk->updateurl, strlen (pk->updateurl),
":", NULL);
es_putc (':', es_stdout); /* End of field 20 (origin). */
es_putc ('\n', es_stdout);
print_revokers (es_stdout, pk);
print_fingerprint (ctrl, NULL, pk, 0);
if (hexgrip)
es_fprintf (es_stdout, "grp:::::::::%s:\n", hexgrip);
if (opt.with_key_data)
print_key_data (pk);
for (kbctx = NULL; (node = walk_kbnode (keyblock, &kbctx, 0));)
{
if (node->pkt->pkttype == PKT_USER_ID)
{
PKT_user_id *uid = node->pkt->pkt.user_id;
int uid_validity;
if (attrib_fp && uid->attrib_data != NULL)
dump_attribs (uid, pk);
if (uid->flags.revoked)
uid_validity = 'r';
else if (uid->flags.expired)
uid_validity = 'e';
else if (opt.no_expensive_trust_checks)
uid_validity = 0;
else if (ulti_hack)
uid_validity = 'u';
else
uid_validity = get_validity_info (ctrl, keyblock, pk, uid);
es_fputs (uid->attrib_data? "uat:":"uid:", es_stdout);
if (uid_validity)
es_putc (uid_validity, es_stdout);
es_fputs ("::::", es_stdout);
es_fprintf (es_stdout, "%s:", colon_strtime (uid->created));
es_fprintf (es_stdout, "%s:", colon_strtime (uid->expiredate));
namehash_from_uid (uid);
for (i = 0; i < 20; i++)
es_fprintf (es_stdout, "%02X", uid->namehash[i]);
es_fprintf (es_stdout, "::");
if (uid->attrib_data)
es_fprintf (es_stdout, "%u %lu", uid->numattribs, uid->attrib_len);
else
es_write_sanitized (es_stdout, uid->name, uid->len, ":", NULL);
es_fputs (":::::::::", es_stdout);
if (uid->keyupdate)
es_fputs (colon_strtime (uid->keyupdate), es_stdout);
es_putc (':', es_stdout); /* End of field 19 (last_update). */
es_fprintf (es_stdout, "%d%s", uid->keyorg, uid->updateurl? " ":"");
if (uid->updateurl)
es_write_sanitized (es_stdout,
uid->updateurl, strlen (uid->updateurl),
":", NULL);
es_putc (':', es_stdout); /* End of field 20 (origin). */
es_putc ('\n', es_stdout);
#ifdef USE_TOFU
if (!uid->attrib_data && opt.with_tofu_info
&& (opt.trust_model == TM_TOFU || opt.trust_model == TM_TOFU_PGP))
{
/* Print a "tfs" record. */
tofu_write_tfs_record (ctrl, es_stdout, pk, uid->name);
}
#endif /*USE_TOFU*/
}
else if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
u32 keyid2[2];
PKT_public_key *pk2;
int need_hexgrip = !!hexgrip;
pk2 = node->pkt->pkt.public_key;
xfree (hexgrip_buffer); hexgrip_buffer = NULL; hexgrip = NULL;
xfree (serialno); serialno = NULL;
if (need_hexgrip
|| secret || has_secret || opt.with_keygrip || opt.with_key_data)
{
rc = hexkeygrip_from_pk (pk2, &hexgrip_buffer);
if (rc)
log_error ("error computing a keygrip: %s\n",
gpg_strerror (rc));
hexgrip = hexgrip_buffer? hexgrip_buffer : "";
}
stubkey = 0;
if ((secret||has_secret)
&& agent_get_keyinfo (NULL, hexgrip, &serialno, NULL))
stubkey = 1; /* Key not found. */
keyid_from_pk (pk2, keyid2);
es_fputs (secret? "ssb:":"sub:", es_stdout);
if (!pk2->flags.valid)
es_putc ('i', es_stdout);
else if (pk2->flags.revoked)
es_putc ('r', es_stdout);
else if (pk2->has_expired)
es_putc ('e', es_stdout);
else if (opt.fast_list_mode || opt.no_expensive_trust_checks)
;
else
{
/* TRUSTLETTER should always be defined here. */
if (trustletter)
es_fprintf (es_stdout, "%c", trustletter);
}
keylength = nbits_from_pk (pk2);
es_fprintf (es_stdout, ":%u:%d:%08lX%08lX:%s:%s:::::",
keylength,
pk2->pubkey_algo,
(ulong) keyid2[0], (ulong) keyid2[1],
colon_datestr_from_pk (pk2),
colon_strtime (pk2->expiredate));
print_capabilities (ctrl, pk2, NULL);
es_putc (':', es_stdout); /* End of field 13. */
es_putc (':', es_stdout); /* End of field 14. */
if (secret || has_secret)
{
if (stubkey)
es_putc ('#', es_stdout);
else if (serialno)
es_fputs (serialno, es_stdout);
else if (has_secret)
es_putc ('+', es_stdout);
}
es_putc (':', es_stdout); /* End of field 15. */
es_putc (':', es_stdout); /* End of field 16. */
if (pk2->pubkey_algo == PUBKEY_ALGO_ECDSA
|| pk2->pubkey_algo == PUBKEY_ALGO_EDDSA
|| pk2->pubkey_algo == PUBKEY_ALGO_ECDH)
{
xfree (curve);
curve = openpgp_oid_to_str (pk2->pkey[0]);
curvename = openpgp_oid_to_curve (curve, 0);
if (!curvename)
curvename = curve;
es_fputs (curvename, es_stdout);
}
es_putc (':', es_stdout); /* End of field 17. */
print_compliance_flags (pk2, keylength, curvename);
es_putc (':', es_stdout); /* End of field 18. */
es_putc ('\n', es_stdout);
print_fingerprint (ctrl, NULL, pk2, 0);
if (hexgrip)
es_fprintf (es_stdout, "grp:::::::::%s:\n", hexgrip);
if (opt.with_key_data)
print_key_data (pk2);
}
else if (opt.list_sigs && node->pkt->pkttype == PKT_SIGNATURE)
{
PKT_signature *sig = node->pkt->pkt.signature;
int sigrc, fprokay = 0;
char *sigstr;
size_t fplen;
byte fparray[MAX_FINGERPRINT_LEN];
char *siguid;
size_t siguidlen;
char *issuer_fpr = NULL;
char *reason_text = NULL;
char *reason_comment = NULL;
size_t reason_commentlen;
int reason_code;
if (sig->sig_class == 0x20 || sig->sig_class == 0x28
|| sig->sig_class == 0x30)
{
sigstr = "rev";
reason_code = get_revocation_reason (sig, &reason_text,
&reason_comment,
&reason_commentlen);
}
else if ((sig->sig_class & ~3) == 0x10)
sigstr = "sig";
else if (sig->sig_class == 0x18)
sigstr = "sig";
else if (sig->sig_class == 0x1F)
sigstr = "sig";
else
{
es_fprintf (es_stdout, "sig::::::::::%02x%c:\n",
sig->sig_class, sig->flags.exportable ? 'x' : 'l');
continue;
}
if (opt.check_sigs)
{
PKT_public_key *signer_pk = NULL;
es_fflush (es_stdout);
if (opt.no_sig_cache)
signer_pk = xmalloc_clear (sizeof (PKT_public_key));
rc = check_key_signature2 (ctrl, keyblock, node, NULL, signer_pk,
NULL, NULL, NULL);
switch (gpg_err_code (rc))
{
case 0:
sigrc = '!';
break;
case GPG_ERR_BAD_SIGNATURE:
sigrc = '-';
break;
case GPG_ERR_NO_PUBKEY:
case GPG_ERR_UNUSABLE_PUBKEY:
sigrc = '?';
break;
default:
sigrc = '%';
break;
}
if (opt.no_sig_cache)
{
if (!rc)
{
fingerprint_from_pk (signer_pk, fparray, &fplen);
fprokay = 1;
}
free_public_key (signer_pk);
}
}
else
{
rc = 0;
sigrc = ' '; /* Note the fix-up below in --list-sigs mode. */
}
if (sigrc != '%' && sigrc != '?' && !opt.fast_list_mode)
{
int nouid;
siguid = get_user_id (ctrl, sig->keyid, &siguidlen, &nouid);
if (!opt.check_sigs && nouid)
sigrc = '?'; /* No key in local keyring. */
}
else
{
siguid = NULL;
siguidlen = 0;
}
es_fputs (sigstr, es_stdout);
es_putc (':', es_stdout);
if (sigrc != ' ')
es_putc (sigrc, es_stdout);
es_fprintf (es_stdout, "::%d:%08lX%08lX:%s:%s:", sig->pubkey_algo,
(ulong) sig->keyid[0], (ulong) sig->keyid[1],
colon_datestr_from_sig (sig),
colon_expirestr_from_sig (sig));
if (sig->trust_depth || sig->trust_value)
es_fprintf (es_stdout, "%d %d", sig->trust_depth, sig->trust_value);
es_fprintf (es_stdout, ":");
if (sig->trust_regexp)
es_write_sanitized (es_stdout, sig->trust_regexp,
strlen (sig->trust_regexp), ":", NULL);
es_fprintf (es_stdout, ":");
if (sigrc == '%')
es_fprintf (es_stdout, "[%s] ", gpg_strerror (rc));
else if (siguid)
es_write_sanitized (es_stdout, siguid, siguidlen, ":", NULL);
es_fprintf (es_stdout, ":%02x%c", sig->sig_class,
sig->flags.exportable ? 'x' : 'l');
if (reason_text)
es_fprintf (es_stdout, ",%02x", reason_code);
es_fputs ("::", es_stdout);
if (opt.no_sig_cache && opt.check_sigs && fprokay)
{
for (i = 0; i < fplen; i++)
es_fprintf (es_stdout, "%02X", fparray[i]);
}
else if ((issuer_fpr = issuer_fpr_string (sig)))
es_fputs (issuer_fpr, es_stdout);
es_fprintf (es_stdout, ":::%d:", sig->digest_algo);
if (reason_comment)
{
es_fputs ("::::", es_stdout);
es_write_sanitized (es_stdout, reason_comment, reason_commentlen,
":", NULL);
es_putc (':', es_stdout);
}
es_putc ('\n', es_stdout);
if (opt.show_subpackets)
print_subpackets_colon (sig);
/* fixme: check or list other sigs here */
xfree (reason_text);
xfree (reason_comment);
xfree (siguid);
xfree (issuer_fpr);
}
}
xfree (curve);
xfree (hexgrip_buffer);
xfree (serialno);
}
/*
* Reorder the keyblock so that the primary user ID (and not attribute
* packet) comes first. Fixme: Replace this by a generic sort
* function. */
static void
do_reorder_keyblock (KBNODE keyblock, int attr)
{
KBNODE primary = NULL, primary0 = NULL, primary2 = NULL;
KBNODE last, node;
for (node = keyblock; node; primary0 = node, node = node->next)
{
if (node->pkt->pkttype == PKT_USER_ID &&
((attr && node->pkt->pkt.user_id->attrib_data) ||
(!attr && !node->pkt->pkt.user_id->attrib_data)) &&
node->pkt->pkt.user_id->flags.primary)
{
primary = primary2 = node;
for (node = node->next; node; primary2 = node, node = node->next)
{
if (node->pkt->pkttype == PKT_USER_ID
|| node->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| node->pkt->pkttype == PKT_SECRET_SUBKEY)
{
break;
}
}
break;
}
}
if (!primary)
return; /* No primary key flag found (should not happen). */
for (last = NULL, node = keyblock; node; last = node, node = node->next)
{
if (node->pkt->pkttype == PKT_USER_ID)
break;
}
log_assert (node);
log_assert (last); /* The user ID is never the first packet. */
log_assert (primary0); /* Ditto (this is the node before primary). */
if (node == primary)
return; /* Already the first one. */
last->next = primary;
primary0->next = primary2->next;
primary2->next = node;
}
void
reorder_keyblock (KBNODE keyblock)
{
do_reorder_keyblock (keyblock, 1);
do_reorder_keyblock (keyblock, 0);
}
static void
list_keyblock (ctrl_t ctrl,
KBNODE keyblock, int secret, int has_secret, int fpr,
struct keylist_context *listctx)
{
reorder_keyblock (keyblock);
if (opt.with_colons)
list_keyblock_colon (ctrl, keyblock, secret, has_secret);
else if ((opt.list_options & LIST_SHOW_ONLY_FPR_MBOX))
{
if (!listctx->no_validity)
check_trustdb_stale (ctrl);
list_keyblock_simple (ctrl, keyblock);
}
else
list_keyblock_print (ctrl, keyblock, secret, fpr, listctx);
if (secret)
es_fflush (es_stdout);
}
/* Public function used by keygen to list a keyblock. If NO_VALIDITY
* is set the validity of a key is never shown. */
void
list_keyblock_direct (ctrl_t ctrl,
kbnode_t keyblock, int secret, int has_secret, int fpr,
int no_validity)
{
struct keylist_context listctx;
memset (&listctx, 0, sizeof (listctx));
listctx.no_validity = !!no_validity;
list_keyblock (ctrl, keyblock, secret, has_secret, fpr, &listctx);
keylist_context_release (&listctx);
}
/* Print an hex digit in ICAO spelling. */
static void
print_icao_hexdigit (estream_t fp, int c)
{
static const char *list[16] = {
"Zero", "One", "Two", "Three", "Four", "Five", "Six", "Seven",
"Eight", "Niner", "Alfa", "Bravo", "Charlie", "Delta", "Echo", "Foxtrot"
};
tty_fprintf (fp, "%s", list[c&15]);
}
/*
* Function to print the finperprint.
* mode 0: as used in key listings, opt.with_colons is honored
* 1: print using log_info ()
* 2: direct use of tty
* 3: direct use of tty but only primary key.
* 4: direct use of tty but only subkey.
* 10: Same as 0 but with_colons etc is ignored.
* 20: Same as 0 but using a compact format.
*
* Modes 1 and 2 will try and print both subkey and primary key
* fingerprints. A MODE with bit 7 set is used internally. If
* OVERRIDE_FP is not NULL that stream will be used in 0 instead
* of es_stdout or instead of the TTY in modes 2 and 3.
*/
void
print_fingerprint (ctrl_t ctrl, estream_t override_fp,
PKT_public_key *pk, int mode)
{
char hexfpr[2*MAX_FINGERPRINT_LEN+1];
char *p;
size_t i;
estream_t fp;
const char *text;
int primary = 0;
int with_colons = opt.with_colons;
int with_icao = opt.with_icao_spelling;
int compact = 0;
if (mode == 10)
{
mode = 0;
with_colons = 0;
with_icao = 0;
}
else if (mode == 20)
{
mode = 0;
with_colons = 0;
compact = 1;
}
if (!opt.fingerprint && !opt.with_fingerprint
&& opt.with_subkey_fingerprint)
compact = 1;
if (pk->main_keyid[0] == pk->keyid[0]
&& pk->main_keyid[1] == pk->keyid[1])
primary = 1;
/* Just to be safe */
if ((mode & 0x80) && !primary)
{
log_error ("primary key is not really primary!\n");
return;
}
mode &= ~0x80;
if (!primary && (mode == 1 || mode == 2))
{
PKT_public_key *primary_pk = xmalloc_clear (sizeof (*primary_pk));
get_pubkey (ctrl, primary_pk, pk->main_keyid);
print_fingerprint (ctrl, override_fp, primary_pk, (mode | 0x80));
free_public_key (primary_pk);
}
if (mode == 1)
{
fp = log_get_stream ();
if (primary)
text = _("Primary key fingerprint:");
else
text = _(" Subkey fingerprint:");
}
else if (mode == 2)
{
fp = override_fp; /* Use tty or given stream. */
if (primary)
/* TRANSLATORS: this should fit into 24 bytes so that the
* fingerprint data is properly aligned with the user ID */
text = _(" Primary key fingerprint:");
else
text = _(" Subkey fingerprint:");
}
else if (mode == 3)
{
fp = override_fp; /* Use tty or given stream. */
text = _(" Key fingerprint =");
}
else if (mode == 4)
{
fp = override_fp; /* Use tty or given stream. */
text = _(" Subkey fingerprint:");
}
else
{
fp = override_fp? override_fp : es_stdout;
if (opt.keyid_format == KF_NONE)
{
text = " "; /* To indent ICAO spelling. */
compact = 1;
}
else
text = _(" Key fingerprint =");
}
hexfingerprint (pk, hexfpr, sizeof hexfpr);
if (with_colons && !mode)
{
es_fprintf (fp, "fpr:::::::::%s:", hexfpr);
}
else if (compact && !opt.fingerprint && !opt.with_fingerprint)
{
tty_fprintf (fp, "%*s%s", 6, "", hexfpr);
}
else
{
char fmtfpr[MAX_FORMATTED_FINGERPRINT_LEN + 1];
format_hexfingerprint (hexfpr, fmtfpr, sizeof fmtfpr);
if (compact)
tty_fprintf (fp, "%*s%s", 6, "", fmtfpr);
else
tty_fprintf (fp, "%s %s", text, fmtfpr);
}
tty_fprintf (fp, "\n");
if (!with_colons && with_icao)
{
;
tty_fprintf (fp, "%*s\"", (int)strlen(text)+1, "");
for (i = 0, p = hexfpr; *p; i++, p++)
{
if (!i)
;
else if (!(i%8))
tty_fprintf (fp, "\n%*s ", (int)strlen(text)+1, "");
else if (!(i%4))
tty_fprintf (fp, " ");
else
tty_fprintf (fp, " ");
print_icao_hexdigit (fp, xtoi_1 (p));
}
tty_fprintf (fp, "\"\n");
}
}
/* Print the serial number of an OpenPGP card if available. */
static void
print_card_serialno (const char *serialno)
{
if (!serialno)
return;
if (opt.with_colons)
return; /* Handled elsewhere. */
es_fputs (_(" Card serial no. ="), es_stdout);
es_putc (' ', es_stdout);
if (strlen (serialno) == 32 && !strncmp (serialno, "D27600012401", 12))
{
/* This is an OpenPGP card. Print the relevant part. */
/* Example: D2760001240101010001000003470000 */
/* xxxxyyyyyyyy */
es_fprintf (es_stdout, "%.*s %.*s", 4, serialno+16, 8, serialno+20);
}
else
es_fputs (serialno, es_stdout);
es_putc ('\n', es_stdout);
}
/* Print a public or secret (sub)key line. Example:
*
* pub dsa2048 2007-12-31 [SC] [expires: 2018-12-31]
* 80615870F5BAD690333686D0F2AD85AC1E42B367
*
* pub rsa2048 2017-12-31 [SC] [expires: 2028-12-31]
* 80615870F5BAD690333686D0F2AD85AC1E42B3671122334455
*
* Some global options may result in a different output format. If
* SECRET is set, "sec" or "ssb" is used instead of "pub" or "sub" and
* depending on the value a flag character is shown:
*
* 1 := ' ' Regular secret key
* 2 := '#' Stub secret key
* 3 := '>' Secret key is on a token.
*/
void
print_key_line (ctrl_t ctrl, estream_t fp, PKT_public_key *pk, int secret)
{
char pkstrbuf[PUBKEY_STRING_SIZE];
tty_fprintf (fp, "%s%c %s",
pk->flags.primary? (secret? "sec":"pub")
/**/ : (secret? "ssb":"sub"),
secret == 2? '#' : secret == 3? '>' : ' ',
pubkey_string (pk, pkstrbuf, sizeof pkstrbuf));
if (opt.keyid_format != KF_NONE)
tty_fprintf (fp, "/%s", keystr_from_pk (pk));
tty_fprintf (fp, " %s", datestr_from_pk (pk));
if (pk->flags.primary
&& !(openpgp_pk_algo_usage (pk->pubkey_algo)
& (PUBKEY_USAGE_CERT| PUBKEY_USAGE_SIG|PUBKEY_USAGE_AUTH)))
{
/* A primary key which is really not capable to sign. */
tty_fprintf (fp, " [INVALID_ALGO]");
}
else if ((opt.list_options & LIST_SHOW_USAGE))
{
tty_fprintf (fp, " [%s]", usagestr_from_pk (pk, 0));
}
if (pk->flags.revoked)
{
tty_fprintf (fp, " [");
tty_fprintf (fp, _("revoked: %s"), revokestr_from_pk (pk));
tty_fprintf (fp, "]");
}
else if (pk->has_expired)
{
tty_fprintf (fp, " [");
tty_fprintf (fp, _("expired: %s"), expirestr_from_pk (pk));
tty_fprintf (fp, "]");
}
else if (pk->expiredate)
{
tty_fprintf (fp, " [");
tty_fprintf (fp, _("expires: %s"), expirestr_from_pk (pk));
tty_fprintf (fp, "]");
}
#if 0
/* I need to think about this some more. It's easy enough to
include, but it looks sort of confusing in the listing... */
if (opt.list_options & LIST_SHOW_VALIDITY)
{
int validity = get_validity (ctrl, pk, NULL, NULL, 0);
tty_fprintf (fp, " [%s]", trust_value_to_string (validity));
}
#endif
if (pk->pubkey_algo >= 100)
tty_fprintf (fp, " [experimental algorithm %d]", pk->pubkey_algo);
tty_fprintf (fp, "\n");
/* if the user hasn't explicitly asked for human-readable
fingerprints, show compact fpr of primary key: */
if (pk->flags.primary &&
!opt.fingerprint && !opt.with_fingerprint)
print_fingerprint (ctrl, fp, pk, 20);
}
void
set_attrib_fd (int fd)
{
static int last_fd = -1;
if (fd != -1 && last_fd == fd)
return;
/* Fixme: Do we need to check for the log stream here? */
if (attrib_fp && attrib_fp != log_get_stream ())
es_fclose (attrib_fp);
attrib_fp = NULL;
if (fd == -1)
return;
if (! gnupg_fd_valid (fd))
log_fatal ("attribute-fd is invalid: %s\n", strerror (errno));
#ifdef HAVE_DOSISH_SYSTEM
setmode (fd, O_BINARY);
#endif
if (fd == 1)
attrib_fp = es_stdout;
else if (fd == 2)
attrib_fp = es_stderr;
else
attrib_fp = es_fdopen (fd, "wb");
if (!attrib_fp)
{
log_fatal ("can't open fd %d for attribute output: %s\n",
fd, strerror (errno));
}
last_fd = fd;
}
diff --git a/g10/keyring.c b/g10/keyring.c
index 21791a6ac..5fa499759 100644
--- a/g10/keyring.c
+++ b/g10/keyring.c
@@ -1,1736 +1,1739 @@
/* keyring.c - keyring file handling
* Copyright (C) 1998-2010 Free Software Foundation, Inc.
* Copyright (C) 1997-2015 Werner Koch
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "gpg.h"
#include "../common/util.h"
#include "keyring.h"
#include "packet.h"
#include "keydb.h"
#include "options.h"
#include "main.h" /*for check_key_signature()*/
#include "../common/i18n.h"
#include "../kbx/keybox.h"
typedef struct keyring_resource *KR_RESOURCE;
struct keyring_resource
{
struct keyring_resource *next;
int read_only;
dotlock_t lockhd;
int is_locked;
int did_full_scan;
char fname[1];
};
typedef struct keyring_resource const * CONST_KR_RESOURCE;
static KR_RESOURCE kr_resources;
struct keyring_handle
{
CONST_KR_RESOURCE resource;
struct {
CONST_KR_RESOURCE kr;
IOBUF iobuf;
int eof;
int error;
} current;
struct {
CONST_KR_RESOURCE kr;
off_t offset;
size_t pk_no;
size_t uid_no;
unsigned int n_packets; /*used for delete and update*/
} found, saved_found;
struct {
char *name;
char *pattern;
} word_match;
};
/* The number of extant handles. */
static int active_handles;
static int do_copy (int mode, const char *fname, KBNODE root,
off_t start_offset, unsigned int n_packets );
/* We keep a cache of entries that we have entered in the DB. This
includes not only public keys, but also subkeys.
Note: we'd like to keep the offset of the items that are present,
however, this doesn't work, because another concurrent GnuPG
process could modify the keyring. */
struct key_present {
struct key_present *next;
u32 kid[2];
};
/* For the hash table, we use separate chaining with linked lists.
This means that we have an array of N linked lists (buckets), which
is indexed by KEYID[1] mod N. Elements present in the keyring will
be on the list; elements not present in the keyring will not be on
the list.
Note: since the hash table stores both present and not present
information, it cannot be used until we complete a full scan of the
keyring. This is indicated by key_present_hash_ready. */
typedef struct key_present **key_present_hash_t;
static key_present_hash_t key_present_hash;
static int key_present_hash_ready;
#define KEY_PRESENT_HASH_BUCKETS 2048
/* Allocate a new value for a key present hash table. */
static struct key_present *
key_present_value_new (void)
{
struct key_present *k;
k = xmalloc_clear (sizeof *k);
return k;
}
/* Allocate a new key present hash table. */
static key_present_hash_t
key_present_hash_new (void)
{
struct key_present **tbl;
tbl = xmalloc_clear (KEY_PRESENT_HASH_BUCKETS * sizeof *tbl);
return tbl;
}
/* Return whether the value described by KID if it is in the hash
table. Otherwise, return NULL. */
static struct key_present *
key_present_hash_lookup (key_present_hash_t tbl, u32 *kid)
{
struct key_present *k;
for (k = tbl[(kid[1] % (KEY_PRESENT_HASH_BUCKETS - 1))]; k; k = k->next)
if (k->kid[0] == kid[0] && k->kid[1] == kid[1])
return k;
return NULL;
}
/* Add the key to the hash table TBL if it is not already present. */
static void
key_present_hash_update (key_present_hash_t tbl, u32 *kid)
{
struct key_present *k;
for (k = tbl[(kid[1] % (KEY_PRESENT_HASH_BUCKETS - 1))]; k; k = k->next)
{
if (k->kid[0] == kid[0] && k->kid[1] == kid[1])
return;
}
k = key_present_value_new ();
k->kid[0] = kid[0];
k->kid[1] = kid[1];
k->next = tbl[(kid[1] % (KEY_PRESENT_HASH_BUCKETS - 1))];
tbl[(kid[1] % (KEY_PRESENT_HASH_BUCKETS - 1))] = k;
}
/* Add all the keys (public and subkeys) present in the keyblock to
the hash TBL. */
static void
key_present_hash_update_from_kb (key_present_hash_t tbl, KBNODE node)
{
for (; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_KEY
|| node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
u32 aki[2];
keyid_from_pk (node->pkt->pkt.public_key, aki);
key_present_hash_update (tbl, aki);
}
}
}
/*
* Register a filename for plain keyring files. ptr is set to a
* pointer to be used to create a handles etc, or the already-issued
* pointer if it has already been registered. The function returns 1
* if a new keyring was registered.
*/
int
keyring_register_filename (const char *fname, int read_only, void **ptr)
{
KR_RESOURCE kr;
if (active_handles)
/* There are open handles. */
BUG ();
for (kr=kr_resources; kr; kr = kr->next)
{
if (same_file_p (kr->fname, fname))
{
/* Already registered. */
if (read_only)
kr->read_only = 1;
*ptr=kr;
return 0;
}
}
kr = xmalloc (sizeof *kr + strlen (fname));
strcpy (kr->fname, fname);
kr->read_only = read_only;
kr->lockhd = NULL;
kr->is_locked = 0;
kr->did_full_scan = 0;
/* keep a list of all issued pointers */
kr->next = kr_resources;
kr_resources = kr;
/* create the offset table the first time a function here is used */
if (!key_present_hash)
key_present_hash = key_present_hash_new ();
*ptr=kr;
return 1;
}
int
keyring_is_writable (void *token)
{
KR_RESOURCE r = token;
return r? (r->read_only || !access (r->fname, W_OK)) : 0;
}
/* Create a new handle for the resource associated with TOKEN.
On error NULL is returned and ERRNO is set.
The returned handle must be released using keyring_release (). */
KEYRING_HANDLE
keyring_new (void *token)
{
KEYRING_HANDLE hd;
KR_RESOURCE resource = token;
log_assert (resource);
hd = xtrycalloc (1, sizeof *hd);
if (!hd)
return hd;
hd->resource = resource;
active_handles++;
return hd;
}
void
keyring_release (KEYRING_HANDLE hd)
{
if (!hd)
return;
log_assert (active_handles > 0);
active_handles--;
xfree (hd->word_match.name);
xfree (hd->word_match.pattern);
iobuf_close (hd->current.iobuf);
xfree (hd);
}
/* Save the current found state in HD for later retrieval by
keybox_pop_found_state. Only one state may be saved. */
void
keyring_push_found_state (KEYRING_HANDLE hd)
{
hd->saved_found = hd->found;
hd->found.kr = NULL;
}
/* Restore the saved found state in HD. */
void
keyring_pop_found_state (KEYRING_HANDLE hd)
{
hd->found = hd->saved_found;
hd->saved_found.kr = NULL;
}
const char *
keyring_get_resource_name (KEYRING_HANDLE hd)
{
if (!hd || !hd->resource)
return NULL;
return hd->resource->fname;
}
/*
* Lock the keyring with the given handle, or unlock if YES is false.
* We ignore the handle and lock all registered files.
*/
int
keyring_lock (KEYRING_HANDLE hd, int yes)
{
KR_RESOURCE kr;
int rc = 0;
(void)hd;
if (yes) {
/* first make sure the lock handles are created */
for (kr=kr_resources; kr; kr = kr->next) {
if (!keyring_is_writable(kr))
continue;
if (!kr->lockhd) {
kr->lockhd = dotlock_create (kr->fname, 0);
if (!kr->lockhd) {
log_info ("can't allocate lock for '%s'\n", kr->fname );
rc = GPG_ERR_GENERAL;
}
}
}
if (rc)
return rc;
/* and now set the locks */
for (kr=kr_resources; kr; kr = kr->next) {
if (!keyring_is_writable(kr))
continue;
if (kr->is_locked)
continue;
#ifdef HAVE_W32_SYSTEM
/* Under Windows we need to CloseHandle the file before we
* try to lock it. This is because another process might
* have taken the lock and is using keybox_file_rename to
* rename the base file. How if our dotlock_take below is
* waiting for the lock but we have the base file still
* open, keybox_file_rename will never succeed as we are
* in a deadlock. */
iobuf_ioctl (NULL, IOBUF_IOCTL_INVALIDATE_CACHE, 0,
(char*)kr->fname);
#endif /*HAVE_W32_SYSTEM*/
if (dotlock_take (kr->lockhd, -1) ) {
log_info ("can't lock '%s'\n", kr->fname );
rc = GPG_ERR_GENERAL;
}
else
kr->is_locked = 1;
}
}
if (rc || !yes) {
for (kr=kr_resources; kr; kr = kr->next) {
if (!keyring_is_writable(kr))
continue;
if (!kr->is_locked)
continue;
if (dotlock_release (kr->lockhd))
log_info ("can't unlock '%s'\n", kr->fname );
else
kr->is_locked = 0;
}
}
return rc;
}
/*
* Return the last found keyblock. 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
keyring_get_keyblock (KEYRING_HANDLE hd, KBNODE *ret_kb)
{
PACKET *pkt;
struct parse_packet_ctx_s parsectx;
int rc;
KBNODE keyblock = NULL, node, lastnode;
IOBUF a;
int in_cert = 0;
int pk_no = 0;
int uid_no = 0;
int save_mode;
if (ret_kb)
*ret_kb = NULL;
if (!hd->found.kr)
return -1; /* no successful search */
a = iobuf_open (hd->found.kr->fname);
if (!a)
{
log_error(_("can't open '%s'\n"), hd->found.kr->fname);
return GPG_ERR_KEYRING_OPEN;
}
if (iobuf_seek (a, hd->found.offset) ) {
log_error ("can't seek '%s'\n", hd->found.kr->fname);
iobuf_close(a);
return GPG_ERR_KEYRING_OPEN;
}
pkt = xmalloc (sizeof *pkt);
init_packet (pkt);
init_parse_packet (&parsectx, a);
hd->found.n_packets = 0;
lastnode = NULL;
save_mode = set_packet_list_mode(0);
while ((rc=parse_packet (&parsectx, pkt)) != -1) {
hd->found.n_packets = parsectx.n_parsed_packets;
if (gpg_err_code (rc) == GPG_ERR_UNKNOWN_PACKET) {
free_packet (pkt, &parsectx);
init_packet (pkt);
continue;
}
if (gpg_err_code (rc) == GPG_ERR_LEGACY_KEY)
{
if (in_cert)
/* It is not this key that is problematic, but the
following key. */
{
rc = 0;
hd->found.n_packets --;
}
else
/* Upper layer needs to handle this. */
{
}
break;
}
if (rc) {
log_error ("keyring_get_keyblock: read error: %s\n",
gpg_strerror (rc) );
rc = GPG_ERR_INV_KEYRING;
break;
}
/* Filter allowed packets. */
switch (pkt->pkttype)
{
case PKT_PUBLIC_KEY:
case PKT_PUBLIC_SUBKEY:
case PKT_SECRET_KEY:
case PKT_SECRET_SUBKEY:
case PKT_USER_ID:
case PKT_ATTRIBUTE:
case PKT_SIGNATURE:
break; /* Allowed per RFC. */
case PKT_RING_TRUST:
case PKT_OLD_COMMENT:
case PKT_COMMENT:
case PKT_GPG_CONTROL:
break; /* Allowed by us. */
default:
log_info ("skipped packet of type %d in keyring\n",
(int)pkt->pkttype);
free_packet(pkt, &parsectx);
init_packet(pkt);
continue;
}
if (in_cert && (pkt->pkttype == PKT_PUBLIC_KEY
|| pkt->pkttype == PKT_SECRET_KEY)) {
hd->found.n_packets--; /* fix counter */
break; /* ready */
}
in_cert = 1;
- node = lastnode = new_kbnode (pkt);
+ node = new_kbnode (pkt);
if (!keyblock)
- keyblock = node;
+ keyblock = lastnode = node;
else
- add_kbnode (keyblock, node);
+ {
+ lastnode->next = node;
+ lastnode = node;
+ }
switch (pkt->pkttype)
{
case PKT_PUBLIC_KEY:
case PKT_PUBLIC_SUBKEY:
case PKT_SECRET_KEY:
case PKT_SECRET_SUBKEY:
if (++pk_no == hd->found.pk_no)
node->flag |= 1;
break;
case PKT_USER_ID:
if (++uid_no == hd->found.uid_no)
node->flag |= 2;
break;
default:
break;
}
pkt = xmalloc (sizeof *pkt);
init_packet(pkt);
}
set_packet_list_mode(save_mode);
if (rc == -1 && keyblock)
rc = 0; /* got the entire keyblock */
if (rc || !ret_kb)
release_kbnode (keyblock);
else {
*ret_kb = keyblock;
}
free_packet (pkt, &parsectx);
deinit_parse_packet (&parsectx);
xfree (pkt);
iobuf_close(a);
/* Make sure that future search operations fail immediately when
* we know that we are working on a invalid keyring
*/
if (gpg_err_code (rc) == GPG_ERR_INV_KEYRING)
hd->current.error = rc;
return rc;
}
int
keyring_update_keyblock (KEYRING_HANDLE hd, KBNODE kb)
{
int rc;
if (!hd->found.kr)
return -1; /* no successful prior search */
if (hd->found.kr->read_only)
return gpg_error (GPG_ERR_EACCES);
if (!hd->found.n_packets) {
/* need to know the number of packets - do a dummy get_keyblock*/
rc = keyring_get_keyblock (hd, NULL);
if (rc) {
log_error ("re-reading keyblock failed: %s\n", gpg_strerror (rc));
return rc;
}
if (!hd->found.n_packets)
BUG ();
}
/* The open iobuf isn't needed anymore and in fact is a problem when
it comes to renaming the keyring files on some operating systems,
so close it here */
iobuf_close(hd->current.iobuf);
hd->current.iobuf = NULL;
/* do the update */
rc = do_copy (3, hd->found.kr->fname, kb,
hd->found.offset, hd->found.n_packets );
if (!rc) {
if (key_present_hash)
{
key_present_hash_update_from_kb (key_present_hash, kb);
}
/* better reset the found info */
hd->found.kr = NULL;
hd->found.offset = 0;
}
return rc;
}
int
keyring_insert_keyblock (KEYRING_HANDLE hd, KBNODE kb)
{
int rc;
const char *fname;
if (!hd)
fname = NULL;
else if (hd->found.kr)
{
fname = hd->found.kr->fname;
if (hd->found.kr->read_only)
return gpg_error (GPG_ERR_EACCES);
}
else if (hd->current.kr)
{
fname = hd->current.kr->fname;
if (hd->current.kr->read_only)
return gpg_error (GPG_ERR_EACCES);
}
else
fname = hd->resource? hd->resource->fname:NULL;
if (!fname)
return GPG_ERR_GENERAL;
/* Close this one otherwise we will lose the position for
* a next search. Fixme: it would be better to adjust the position
* after the write opertions.
*/
iobuf_close (hd->current.iobuf);
hd->current.iobuf = NULL;
/* do the insert */
rc = do_copy (1, fname, kb, 0, 0 );
if (!rc && key_present_hash)
{
key_present_hash_update_from_kb (key_present_hash, kb);
}
return rc;
}
int
keyring_delete_keyblock (KEYRING_HANDLE hd)
{
int rc;
if (!hd->found.kr)
return -1; /* no successful prior search */
if (hd->found.kr->read_only)
return gpg_error (GPG_ERR_EACCES);
if (!hd->found.n_packets) {
/* need to know the number of packets - do a dummy get_keyblock*/
rc = keyring_get_keyblock (hd, NULL);
if (rc) {
log_error ("re-reading keyblock failed: %s\n", gpg_strerror (rc));
return rc;
}
if (!hd->found.n_packets)
BUG ();
}
/* close this one otherwise we will lose the position for
* a next search. Fixme: it would be better to adjust the position
* after the write opertions.
*/
iobuf_close (hd->current.iobuf);
hd->current.iobuf = NULL;
/* do the delete */
rc = do_copy (2, hd->found.kr->fname, NULL,
hd->found.offset, hd->found.n_packets );
if (!rc) {
/* better reset the found info */
hd->found.kr = NULL;
hd->found.offset = 0;
/* Delete is a rare operations, so we don't remove the keys
* from the offset table */
}
return rc;
}
/*
* Start the next search on this handle right at the beginning
*/
int
keyring_search_reset (KEYRING_HANDLE hd)
{
log_assert (hd);
iobuf_close (hd->current.iobuf);
hd->current.iobuf = NULL;
hd->current.eof = 0;
hd->current.error = 0;
hd->found.kr = NULL;
hd->found.offset = 0;
if (hd->current.kr)
iobuf_ioctl (NULL, IOBUF_IOCTL_INVALIDATE_CACHE, 0,
(char*)hd->current.kr->fname);
hd->current.kr = NULL;
return 0;
}
static int
prepare_search (KEYRING_HANDLE hd)
{
if (hd->current.error) {
/* If the last key was a legacy key, we simply ignore the error so that
we can easily use search_next. */
if (gpg_err_code (hd->current.error) == GPG_ERR_LEGACY_KEY)
{
if (DBG_LOOKUP)
log_debug ("%s: last error was GPG_ERR_LEGACY_KEY, clearing\n",
__func__);
hd->current.error = 0;
}
else
{
if (DBG_LOOKUP)
log_debug ("%s: returning last error: %s\n",
__func__, gpg_strerror (hd->current.error));
return hd->current.error; /* still in error state */
}
}
if (hd->current.kr && !hd->current.eof) {
if ( !hd->current.iobuf )
{
if (DBG_LOOKUP)
log_debug ("%s: missing iobuf!\n", __func__);
return GPG_ERR_GENERAL; /* Position invalid after a modify. */
}
return 0; /* okay */
}
if (!hd->current.kr && hd->current.eof)
{
if (DBG_LOOKUP)
log_debug ("%s: EOF!\n", __func__);
return -1; /* still EOF */
}
if (!hd->current.kr) { /* start search with first keyring */
hd->current.kr = hd->resource;
if (!hd->current.kr) {
if (DBG_LOOKUP)
log_debug ("%s: keyring not available!\n", __func__);
hd->current.eof = 1;
return -1; /* keyring not available */
}
log_assert (!hd->current.iobuf);
}
else { /* EOF */
if (DBG_LOOKUP)
log_debug ("%s: EOF\n", __func__);
iobuf_close (hd->current.iobuf);
hd->current.iobuf = NULL;
hd->current.kr = NULL;
hd->current.eof = 1;
return -1;
}
hd->current.eof = 0;
hd->current.iobuf = iobuf_open (hd->current.kr->fname);
if (!hd->current.iobuf)
{
hd->current.error = gpg_error_from_syserror ();
log_error(_("can't open '%s'\n"), hd->current.kr->fname );
return hd->current.error;
}
return 0;
}
/* A map of the all characters valid used for word_match()
* Valid characters are in this table converted to uppercase.
* because the upper 128 bytes have special meaning, we assume
* that they are all valid.
* Note: We must use numerical values here in case that this program
* will be converted to those little blue HAL9000s with their strange
* EBCDIC character set (user ids are UTF-8).
* wk 2000-04-13: Hmmm, does this really make sense, given the fact that
* we can run gpg now on a S/390 running GNU/Linux, where the code
* translation is done by the device drivers?
*/
static const byte word_match_chars[256] = {
/* 00 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
/* 08 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
/* 10 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
/* 18 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
/* 20 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
/* 28 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
/* 30 */ 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
/* 38 */ 0x38, 0x39, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
/* 40 */ 0x00, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
/* 48 */ 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
/* 50 */ 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,
/* 58 */ 0x58, 0x59, 0x5a, 0x00, 0x00, 0x00, 0x00, 0x00,
/* 60 */ 0x00, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
/* 68 */ 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
/* 70 */ 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,
/* 78 */ 0x58, 0x59, 0x5a, 0x00, 0x00, 0x00, 0x00, 0x00,
/* 80 */ 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
/* 88 */ 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f,
/* 90 */ 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97,
/* 98 */ 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f,
/* a0 */ 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7,
/* a8 */ 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf,
/* b0 */ 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7,
/* b8 */ 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf,
/* c0 */ 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7,
/* c8 */ 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf,
/* d0 */ 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7,
/* d8 */ 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf,
/* e0 */ 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7,
/* e8 */ 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef,
/* f0 */ 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7,
/* f8 */ 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff
};
/****************
* Do a word match (original user id starts with a '+').
* The pattern is already tokenized to a more suitable format:
* There are only the real words in it delimited by one space
* and all converted to uppercase.
*
* Returns: 0 if all words match.
*
* Note: This algorithm is a straightforward one and not very
* fast. It works for UTF-8 strings. The uidlen should
* be removed but due to the fact that old versions of
* pgp don't use UTF-8 we still use the length; this should
* be fixed in parse-packet (and replace \0 by some special
* UTF-8 encoding)
*/
static int
word_match( const byte *uid, size_t uidlen, const byte *pattern )
{
size_t wlen, n;
const byte *p;
const byte *s;
for( s=pattern; *s; ) {
do {
/* skip leading delimiters */
while( uidlen && !word_match_chars[*uid] )
uid++, uidlen--;
/* get length of the word */
n = uidlen; p = uid;
while( n && word_match_chars[*p] )
p++, n--;
wlen = p - uid;
/* and compare against the current word from pattern */
for(n=0, p=uid; n < wlen && s[n] != ' ' && s[n] ; n++, p++ ) {
if( word_match_chars[*p] != s[n] )
break;
}
if( n == wlen && (s[n] == ' ' || !s[n]) )
break; /* found */
uid += wlen;
uidlen -= wlen;
} while( uidlen );
if( !uidlen )
return -1; /* not found */
/* advance to next word in pattern */
for(; *s != ' ' && *s ; s++ )
;
if( *s )
s++ ;
}
return 0; /* found */
}
/****************
* prepare word word_match; that is parse the name and
* build the pattern.
* caller has to free the returned pattern
*/
static char*
prepare_word_match (const byte *name)
{
byte *pattern, *p;
int c;
/* the original length is always enough for the pattern */
p = pattern = xmalloc(strlen(name)+1);
do {
/* skip leading delimiters */
while( *name && !word_match_chars[*name] )
name++;
/* copy as long as we don't have a delimiter and convert
* to uppercase.
* fixme: how can we handle utf8 uppercasing */
for( ; *name && (c=word_match_chars[*name]); name++ )
*p++ = c;
*p++ = ' '; /* append pattern delimiter */
} while( *name );
p[-1] = 0; /* replace last pattern delimiter by EOS */
return pattern;
}
static int
compare_name (int mode, const char *name, const char *uid, size_t uidlen)
{
int i;
const char *s, *se;
if (mode == KEYDB_SEARCH_MODE_EXACT) {
for (i=0; name[i] && uidlen; i++, uidlen--)
if (uid[i] != name[i])
break;
if (!uidlen && !name[i])
return 0; /* found */
}
else if (mode == KEYDB_SEARCH_MODE_SUBSTR) {
if (ascii_memistr( uid, uidlen, name ))
return 0;
}
else if ( mode == KEYDB_SEARCH_MODE_MAIL
|| mode == KEYDB_SEARCH_MODE_MAILSUB
|| mode == KEYDB_SEARCH_MODE_MAILEND) {
int have_angles = 1;
for (i=0, s= uid; i < uidlen && *s != '<'; s++, i++)
;
if (i == uidlen)
{
/* The UID is a plain addr-spec (cf. RFC2822 section 4.3). */
have_angles = 0;
s = uid;
i = 0;
}
if (i < uidlen) {
if (have_angles)
{
/* skip opening delim and one char and look for the closing one*/
s++; i++;
for (se=s+1, i++; i < uidlen && *se != '>'; se++, i++)
;
}
else
se = s + uidlen;
if (i < uidlen) {
i = se - s;
if (mode == KEYDB_SEARCH_MODE_MAIL) {
if( strlen(name)-2 == i
&& !ascii_memcasecmp( s, name+1, i) )
return 0;
}
else if (mode == KEYDB_SEARCH_MODE_MAILSUB) {
if( ascii_memistr( s, i, name ) )
return 0;
}
else { /* email from end */
/* nyi */
}
}
}
}
else if (mode == KEYDB_SEARCH_MODE_WORDS)
return word_match (uid, uidlen, name);
else
BUG();
return -1; /* not found */
}
/*
* Search through the keyring(s), starting at the current position,
* for a keyblock which contains one of the keys described in the DESC array.
*/
int
keyring_search (KEYRING_HANDLE hd, KEYDB_SEARCH_DESC *desc,
size_t ndesc, size_t *descindex, int ignore_legacy)
{
int rc;
PACKET pkt;
struct parse_packet_ctx_s parsectx;
int save_mode;
off_t offset, main_offset;
size_t n;
int need_uid, need_words, need_keyid, need_fpr, any_skip;
int pk_no, uid_no;
int initial_skip;
int scanned_from_start;
int use_key_present_hash;
PKT_user_id *uid = NULL;
PKT_public_key *pk = NULL;
u32 aki[2];
/* figure out what information we need */
need_uid = need_words = need_keyid = need_fpr = any_skip = 0;
for (n=0; n < ndesc; n++)
{
switch (desc[n].mode)
{
case KEYDB_SEARCH_MODE_EXACT:
case KEYDB_SEARCH_MODE_SUBSTR:
case KEYDB_SEARCH_MODE_MAIL:
case KEYDB_SEARCH_MODE_MAILSUB:
case KEYDB_SEARCH_MODE_MAILEND:
need_uid = 1;
break;
case KEYDB_SEARCH_MODE_WORDS:
need_uid = 1;
need_words = 1;
break;
case KEYDB_SEARCH_MODE_SHORT_KID:
case KEYDB_SEARCH_MODE_LONG_KID:
need_keyid = 1;
break;
case KEYDB_SEARCH_MODE_FPR:
need_fpr = 1;
break;
case KEYDB_SEARCH_MODE_FIRST:
/* always restart the search in this mode */
keyring_search_reset (hd);
break;
default: break;
}
if (desc[n].skipfnc)
{
any_skip = 1;
need_keyid = 1;
}
}
if (DBG_LOOKUP)
log_debug ("%s: need_uid = %d; need_words = %d; need_keyid = %d; need_fpr = %d; any_skip = %d\n",
__func__, need_uid, need_words, need_keyid, need_fpr, any_skip);
rc = prepare_search (hd);
if (rc)
{
if (DBG_LOOKUP)
log_debug ("%s: prepare_search failed: %s (%d)\n",
__func__, gpg_strerror (rc), gpg_err_code (rc));
return rc;
}
use_key_present_hash = !!key_present_hash;
if (!use_key_present_hash)
{
if (DBG_LOOKUP)
log_debug ("%s: no offset table.\n", __func__);
}
else if (!key_present_hash_ready)
{
if (DBG_LOOKUP)
log_debug ("%s: initializing offset table. (need_keyid: %d => 1)\n",
__func__, need_keyid);
need_keyid = 1;
}
else if (ndesc == 1 && desc[0].mode == KEYDB_SEARCH_MODE_LONG_KID)
{
struct key_present *oi;
if (DBG_LOOKUP)
log_debug ("%s: look up by long key id, checking cache\n", __func__);
oi = key_present_hash_lookup (key_present_hash, desc[0].u.kid);
if (!oi)
{ /* We know that we don't have this key */
if (DBG_LOOKUP)
log_debug ("%s: cache says not present\n", __func__);
hd->found.kr = NULL;
hd->current.eof = 1;
return -1;
}
/* We could now create a positive search status and return.
* However the problem is that another instance of gpg may
* have changed the keyring so that the offsets are not valid
* anymore - therefore we don't do it
*/
}
if (need_words)
{
const char *name = NULL;
log_debug ("word search mode does not yet work\n");
/* FIXME: here is a long standing bug in our function and in addition we
just use the first search description */
for (n=0; n < ndesc && !name; n++)
{
if (desc[n].mode == KEYDB_SEARCH_MODE_WORDS)
name = desc[n].u.name;
}
log_assert (name);
if ( !hd->word_match.name || strcmp (hd->word_match.name, name) )
{
/* name changed */
xfree (hd->word_match.name);
xfree (hd->word_match.pattern);
hd->word_match.name = xstrdup (name);
hd->word_match.pattern = prepare_word_match (name);
}
/* name = hd->word_match.pattern; */
}
init_packet(&pkt);
save_mode = set_packet_list_mode(0);
hd->found.kr = NULL;
main_offset = 0;
pk_no = uid_no = 0;
initial_skip = 1; /* skip until we see the start of a keyblock */
scanned_from_start = iobuf_tell (hd->current.iobuf) == 0;
if (DBG_LOOKUP)
log_debug ("%s: %ssearching from start of resource.\n",
__func__, scanned_from_start ? "" : "not ");
init_parse_packet (&parsectx, hd->current.iobuf);
while (1)
{
byte afp[MAX_FINGERPRINT_LEN];
size_t an;
rc = search_packet (&parsectx, &pkt, &offset, need_uid);
if (ignore_legacy && gpg_err_code (rc) == GPG_ERR_LEGACY_KEY)
{
free_packet (&pkt, &parsectx);
continue;
}
if (rc)
break;
if (pkt.pkttype == PKT_PUBLIC_KEY || pkt.pkttype == PKT_SECRET_KEY)
{
main_offset = offset;
pk_no = uid_no = 0;
initial_skip = 0;
}
if (initial_skip)
{
free_packet (&pkt, &parsectx);
continue;
}
pk = NULL;
uid = NULL;
if ( pkt.pkttype == PKT_PUBLIC_KEY
|| pkt.pkttype == PKT_PUBLIC_SUBKEY
|| pkt.pkttype == PKT_SECRET_KEY
|| pkt.pkttype == PKT_SECRET_SUBKEY)
{
pk = pkt.pkt.public_key;
++pk_no;
if (need_fpr)
{
fingerprint_from_pk (pk, afp, &an);
while (an < 32) /* fill up to 32 bytes */
afp[an++] = 0;
}
if (need_keyid)
keyid_from_pk (pk, aki);
if (use_key_present_hash
&& !key_present_hash_ready
&& scanned_from_start)
key_present_hash_update (key_present_hash, aki);
}
else if (pkt.pkttype == PKT_USER_ID)
{
uid = pkt.pkt.user_id;
++uid_no;
}
for (n=0; n < ndesc; n++)
{
switch (desc[n].mode) {
case KEYDB_SEARCH_MODE_NONE:
BUG ();
break;
case KEYDB_SEARCH_MODE_EXACT:
case KEYDB_SEARCH_MODE_SUBSTR:
case KEYDB_SEARCH_MODE_MAIL:
case KEYDB_SEARCH_MODE_MAILSUB:
case KEYDB_SEARCH_MODE_MAILEND:
case KEYDB_SEARCH_MODE_WORDS:
if ( uid && !compare_name (desc[n].mode,
desc[n].u.name,
uid->name, uid->len))
goto found;
break;
case KEYDB_SEARCH_MODE_SHORT_KID:
if (pk && desc[n].u.kid[1] == aki[1])
goto found;
break;
case KEYDB_SEARCH_MODE_LONG_KID:
if (pk && desc[n].u.kid[0] == aki[0]
&& desc[n].u.kid[1] == aki[1])
goto found;
break;
case KEYDB_SEARCH_MODE_FPR:
if (pk && desc[n].fprlen >= 16 && desc[n].fprlen <= 32
&& !memcmp (desc[n].u.fpr, afp, desc[n].fprlen))
goto found;
break;
case KEYDB_SEARCH_MODE_FIRST:
if (pk)
goto found;
break;
case KEYDB_SEARCH_MODE_NEXT:
if (pk)
goto found;
break;
default:
rc = GPG_ERR_INV_ARG;
goto found;
}
}
free_packet (&pkt, &parsectx);
continue;
found:
if (rc)
goto real_found;
if (DBG_LOOKUP)
log_debug ("%s: packet starting at offset %lld matched descriptor %zu\n"
, __func__, (long long)offset, n);
/* Record which desc we matched on. Note this value is only
meaningful if this function returns with no errors. */
if(descindex)
*descindex=n;
for (n=any_skip?0:ndesc; n < ndesc; n++)
{
if (desc[n].skipfnc
&& desc[n].skipfnc (desc[n].skipfncvalue, aki, uid_no))
{
if (DBG_LOOKUP)
log_debug ("%s: skipping match: desc %zd's skip function returned TRUE\n",
__func__, n);
break;
}
}
if (n == ndesc)
goto real_found;
free_packet (&pkt, &parsectx);
}
real_found:
if (!rc)
{
if (DBG_LOOKUP)
log_debug ("%s: returning success\n", __func__);
hd->found.offset = main_offset;
hd->found.kr = hd->current.kr;
hd->found.pk_no = pk? pk_no : 0;
hd->found.uid_no = uid? uid_no : 0;
}
else if (rc == -1)
{
if (DBG_LOOKUP)
log_debug ("%s: no matches (EOF)\n", __func__);
hd->current.eof = 1;
/* if we scanned all keyrings, we are sure that
* all known key IDs are in our offtbl, mark that. */
if (use_key_present_hash
&& !key_present_hash_ready
&& scanned_from_start)
{
KR_RESOURCE kr;
/* First set the did_full_scan flag for this keyring. */
for (kr=kr_resources; kr; kr = kr->next)
{
if (hd->resource == kr)
{
kr->did_full_scan = 1;
break;
}
}
/* Then check whether all flags are set and if so, mark the
offtbl ready */
for (kr=kr_resources; kr; kr = kr->next)
{
if (!kr->did_full_scan)
break;
}
if (!kr)
key_present_hash_ready = 1;
}
}
else
{
if (DBG_LOOKUP)
log_debug ("%s: error encountered during search: %s (%d)\n",
__func__, gpg_strerror (rc), rc);
hd->current.error = rc;
}
free_packet (&pkt, &parsectx);
deinit_parse_packet (&parsectx);
set_packet_list_mode(save_mode);
return rc;
}
static int
create_tmp_file (const char *template,
char **r_bakfname, char **r_tmpfname, IOBUF *r_fp)
{
gpg_error_t err;
mode_t oldmask;
err = keybox_tmp_names (template, 1, r_bakfname, r_tmpfname);
if (err)
return err;
/* Create the temp file with limited access. Note that the umask
call is not anymore needed because iobuf_create now takes care of
it. However, it does not harm and thus we keep it. */
oldmask = umask (077);
if (is_secured_filename (*r_tmpfname))
{
*r_fp = NULL;
gpg_err_set_errno (EPERM);
}
else
*r_fp = iobuf_create (*r_tmpfname, 1);
umask (oldmask);
if (!*r_fp)
{
err = gpg_error_from_syserror ();
log_error (_("can't create '%s': %s\n"), *r_tmpfname, gpg_strerror (err));
xfree (*r_tmpfname);
*r_tmpfname = NULL;
xfree (*r_bakfname);
*r_bakfname = NULL;
}
return err;
}
static int
rename_tmp_file (const char *bakfname, const char *tmpfname, const char *fname)
{
int rc = 0;
int block = 0;
/* Invalidate close caches. */
if (iobuf_ioctl (NULL, IOBUF_IOCTL_INVALIDATE_CACHE, 0, (char*)tmpfname ))
{
rc = gpg_error_from_syserror ();
goto fail;
}
iobuf_ioctl (NULL, IOBUF_IOCTL_INVALIDATE_CACHE, 0, (char*)bakfname );
iobuf_ioctl (NULL, IOBUF_IOCTL_INVALIDATE_CACHE, 0, (char*)fname );
/* First make a backup file. */
block = 1;
rc = gnupg_rename_file (fname, bakfname, &block);
if (rc)
goto fail;
/* then rename the file */
rc = gnupg_rename_file (tmpfname, fname, NULL);
if (block)
{
gnupg_unblock_all_signals ();
block = 0;
}
if (rc)
{
register_secured_file (fname);
goto fail;
}
/* Now make sure the file has the same permissions as the original */
#ifndef HAVE_DOSISH_SYSTEM
{
struct stat statbuf;
statbuf.st_mode=S_IRUSR | S_IWUSR;
if (!stat (bakfname, &statbuf) && !chmod (fname, statbuf.st_mode))
;
else
log_error ("WARNING: unable to restore permissions to '%s': %s",
fname, strerror(errno));
}
#endif
return 0;
fail:
if (block)
gnupg_unblock_all_signals ();
return rc;
}
static int
write_keyblock (IOBUF fp, KBNODE keyblock)
{
KBNODE kbctx = NULL, node;
int rc;
while ( (node = walk_kbnode (keyblock, &kbctx, 0)) )
{
if ( (rc = build_packet_and_meta (fp, node->pkt) ))
{
log_error ("build_packet(%d) failed: %s\n",
node->pkt->pkttype, gpg_strerror (rc) );
return rc;
}
}
return 0;
}
/*
* Walk over all public keyrings, check the signatures and replace the
* keyring with a new one where the signature cache is then updated.
* This is only done for the public keyrings.
*/
int
keyring_rebuild_cache (ctrl_t ctrl, void *token, int noisy)
{
KEYRING_HANDLE hd;
KEYDB_SEARCH_DESC desc;
KBNODE keyblock = NULL, node;
const char *lastresname = NULL, *resname;
IOBUF tmpfp = NULL;
char *tmpfilename = NULL;
char *bakfilename = NULL;
int rc;
ulong count = 0, sigcount = 0;
hd = keyring_new (token);
if (!hd)
return gpg_error_from_syserror ();
memset (&desc, 0, sizeof desc);
desc.mode = KEYDB_SEARCH_MODE_FIRST;
rc=keyring_lock (hd, 1);
if(rc)
goto leave;
for (;;)
{
rc = keyring_search (hd, &desc, 1, NULL, 1 /* ignore_legacy */);
if (rc)
break; /* ready. */
desc.mode = KEYDB_SEARCH_MODE_NEXT;
resname = keyring_get_resource_name (hd);
if (lastresname != resname )
{ /* we have switched to a new keyring - commit changes */
if (tmpfp)
{
if (iobuf_close (tmpfp))
{
rc = gpg_error_from_syserror ();
log_error ("error closing '%s': %s\n",
tmpfilename, strerror (errno));
goto leave;
}
/* because we have switched resources, we can be sure that
* the original file is closed */
tmpfp = NULL;
}
/* Static analyzer note: BAKFILENAME is never NULL here
because it is controlled by LASTRESNAME. */
rc = lastresname? rename_tmp_file (bakfilename, tmpfilename,
lastresname) : 0;
xfree (tmpfilename); tmpfilename = NULL;
xfree (bakfilename); bakfilename = NULL;
if (rc)
goto leave;
lastresname = resname;
if (noisy && !opt.quiet)
log_info (_("caching keyring '%s'\n"), resname);
rc = create_tmp_file (resname, &bakfilename, &tmpfilename, &tmpfp);
if (rc)
goto leave;
}
release_kbnode (keyblock);
rc = keyring_get_keyblock (hd, &keyblock);
if (rc)
{
if (gpg_err_code (rc) == GPG_ERR_LEGACY_KEY)
continue; /* Skip legacy keys. */
log_error ("keyring_get_keyblock failed: %s\n", gpg_strerror (rc));
goto leave;
}
if ( keyblock->pkt->pkttype != PKT_PUBLIC_KEY)
{
/* We had a few reports about corrupted keyrings; if we have
been called directly from the command line we delete such
a keyblock instead of bailing out. */
log_error ("unexpected keyblock found (pkttype=%d)%s\n",
keyblock->pkt->pkttype, noisy? " - deleted":"");
if (noisy)
continue;
log_info ("Hint: backup your keys and try running '%s'\n",
"gpg --rebuild-keydb-caches");
rc = gpg_error (GPG_ERR_INV_KEYRING);
goto leave;
}
if (keyblock->pkt->pkt.public_key->version < 4)
{
/* We do not copy/cache v3 keys or any other unknown
packets. It is better to remove them from the keyring.
The code required to keep them in the keyring would be
too complicated. Given that we do not touch the old
secring.gpg a suitable backup for decryption of v3 stuff
using an older gpg version will always be available.
Note: This test is actually superfluous because we
already acted upon GPG_ERR_LEGACY_KEY. */
}
else
{
/* Check all signature to set the signature's cache flags. */
for (node=keyblock; node; node=node->next)
{
/* Note that this doesn't cache the result of a
revocation issued by a designated revoker. This is
because the pk in question does not carry the revkeys
as we haven't merged the key and selfsigs. It is
questionable whether this matters very much since
there are very very few designated revoker revocation
packets out there. */
if (node->pkt->pkttype == PKT_SIGNATURE)
{
PKT_signature *sig=node->pkt->pkt.signature;
if(!opt.no_sig_cache && sig->flags.checked && sig->flags.valid
&& (openpgp_md_test_algo(sig->digest_algo)
|| openpgp_pk_test_algo(sig->pubkey_algo)))
sig->flags.checked=sig->flags.valid=0;
else
check_key_signature (ctrl, keyblock, node, NULL);
sigcount++;
}
}
/* Write the keyblock to the temporary file. */
rc = write_keyblock (tmpfp, keyblock);
if (rc)
goto leave;
if ( !(++count % 50) && noisy && !opt.quiet)
log_info (ngettext("%lu keys cached so far (%lu signature)\n",
"%lu keys cached so far (%lu signatures)\n",
sigcount),
count, sigcount);
}
} /* end main loop */
if (rc == -1)
rc = 0;
if (rc)
{
log_error ("keyring_search failed: %s\n", gpg_strerror (rc));
goto leave;
}
if (noisy || opt.verbose)
{
log_info (ngettext("%lu key cached",
"%lu keys cached", count), count);
log_printf (ngettext(" (%lu signature)\n",
" (%lu signatures)\n", sigcount), sigcount);
}
if (tmpfp)
{
if (iobuf_close (tmpfp))
{
rc = gpg_error_from_syserror ();
log_error ("error closing '%s': %s\n",
tmpfilename, strerror (errno));
goto leave;
}
/* because we have switched resources, we can be sure that
* the original file is closed */
tmpfp = NULL;
}
rc = lastresname? rename_tmp_file (bakfilename, tmpfilename,
lastresname) : 0;
xfree (tmpfilename); tmpfilename = NULL;
xfree (bakfilename); bakfilename = NULL;
leave:
if (tmpfp)
iobuf_cancel (tmpfp);
xfree (tmpfilename);
xfree (bakfilename);
release_kbnode (keyblock);
keyring_lock (hd, 0);
keyring_release (hd);
return rc;
}
/****************
* Perform insert/delete/update operation.
* mode 1 = insert
* 2 = delete
* 3 = update
*/
static int
do_copy (int mode, const char *fname, KBNODE root,
off_t start_offset, unsigned int n_packets )
{
IOBUF fp, newfp;
int rc=0;
char *bakfname = NULL;
char *tmpfname = NULL;
/* Open the source file. Because we do a rename, we have to check the
permissions of the file */
if (access (fname, W_OK))
return gpg_error_from_syserror ();
fp = iobuf_open (fname);
if (mode == 1 && !fp && errno == ENOENT) {
/* insert mode but file does not exist: create a new file */
KBNODE kbctx, node;
mode_t oldmask;
oldmask=umask(077);
if (is_secured_filename (fname)) {
newfp = NULL;
gpg_err_set_errno (EPERM);
}
else
newfp = iobuf_create (fname, 1);
umask(oldmask);
if( !newfp )
{
rc = gpg_error_from_syserror ();
log_error (_("can't create '%s': %s\n"), fname, strerror(errno));
return rc;
}
if( !opt.quiet )
log_info(_("%s: keyring created\n"), fname );
kbctx=NULL;
while ( (node = walk_kbnode( root, &kbctx, 0 )) ) {
if( (rc = build_packet( newfp, node->pkt )) ) {
log_error("build_packet(%d) failed: %s\n",
node->pkt->pkttype, gpg_strerror (rc) );
iobuf_cancel(newfp);
return rc;
}
}
if( iobuf_close(newfp) ) {
rc = gpg_error_from_syserror ();
log_error ("%s: close failed: %s\n", fname, strerror(errno));
return rc;
}
return 0; /* ready */
}
if( !fp )
{
rc = gpg_error_from_syserror ();
log_error(_("can't open '%s': %s\n"), fname, strerror(errno) );
goto leave;
}
/* Create the new file. */
rc = create_tmp_file (fname, &bakfname, &tmpfname, &newfp);
if (rc) {
iobuf_close(fp);
goto leave;
}
if( mode == 1 ) { /* insert */
/* copy everything to the new file */
rc = copy_all_packets (fp, newfp);
if( rc != -1 ) {
log_error("%s: copy to '%s' failed: %s\n",
fname, tmpfname, gpg_strerror (rc) );
iobuf_close(fp);
iobuf_cancel(newfp);
goto leave;
}
}
if( mode == 2 || mode == 3 ) { /* delete or update */
/* copy first part to the new file */
rc = copy_some_packets( fp, newfp, start_offset );
if( rc ) { /* should never get EOF here */
log_error ("%s: copy to '%s' failed: %s\n",
fname, tmpfname, gpg_strerror (rc) );
iobuf_close(fp);
iobuf_cancel(newfp);
goto leave;
}
/* skip this keyblock */
log_assert( n_packets );
rc = skip_some_packets( fp, n_packets );
if( rc ) {
log_error("%s: skipping %u packets failed: %s\n",
fname, n_packets, gpg_strerror (rc));
iobuf_close(fp);
iobuf_cancel(newfp);
goto leave;
}
}
if( mode == 1 || mode == 3 ) { /* insert or update */
rc = write_keyblock (newfp, root);
if (rc) {
iobuf_close(fp);
iobuf_cancel(newfp);
goto leave;
}
}
if( mode == 2 || mode == 3 ) { /* delete or update */
/* copy the rest */
rc = copy_all_packets( fp, newfp );
if( rc != -1 ) {
log_error("%s: copy to '%s' failed: %s\n",
fname, tmpfname, gpg_strerror (rc) );
iobuf_close(fp);
iobuf_cancel(newfp);
goto leave;
}
}
/* close both files */
if( iobuf_close(fp) ) {
rc = gpg_error_from_syserror ();
log_error("%s: close failed: %s\n", fname, strerror(errno) );
goto leave;
}
if( iobuf_close(newfp) ) {
rc = gpg_error_from_syserror ();
log_error("%s: close failed: %s\n", tmpfname, strerror(errno) );
goto leave;
}
rc = rename_tmp_file (bakfname, tmpfname, fname);
leave:
xfree(bakfname);
xfree(tmpfname);
return rc;
}
diff --git a/g10/keyserver.c b/g10/keyserver.c
index 66900f7a9..b07afb128 100644
--- a/g10/keyserver.c
+++ b/g10/keyserver.c
@@ -1,2165 +1,2164 @@
/* keyserver.c - generic keyserver code
* Copyright (C) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008,
* 2009, 2011, 2012 Free Software Foundation, Inc.
* Copyright (C) 2014 Werner Koch
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <ctype.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include "gpg.h"
#include "../common/iobuf.h"
#include "filter.h"
#include "keydb.h"
#include "../common/status.h"
#include "exec.h"
#include "main.h"
#include "../common/i18n.h"
#include "../common/ttyio.h"
#include "options.h"
#include "packet.h"
#include "trustdb.h"
#include "keyserver-internal.h"
#include "../common/util.h"
#include "../common/membuf.h"
#include "../common/mbox-util.h"
#include "call-dirmngr.h"
#ifdef HAVE_W32_SYSTEM
/* It seems Vista doesn't grok X_OK and so fails access() tests.
Previous versions interpreted X_OK as F_OK anyway, so we'll just
use F_OK directly. */
#undef X_OK
#define X_OK F_OK
#endif /* HAVE_W32_SYSTEM */
struct keyrec
{
KEYDB_SEARCH_DESC desc;
u32 createtime,expiretime;
int size,flags;
byte type;
IOBUF uidbuf;
unsigned int lines;
};
/* Parameters for the search line handler. */
struct search_line_handler_parm_s
{
ctrl_t ctrl; /* The session control structure. */
char *searchstr_disp; /* Native encoded search string or NULL. */
KEYDB_SEARCH_DESC *desc; /* Array with search descriptions. */
int count; /* Number of keys we are currently prepared to
handle. This is the size of the DESC array. If
it is too small, it will grow safely. */
int validcount; /* Enable the "Key x-y of z" messages. */
int nkeys; /* Number of processed records. */
int any_lines; /* At least one line has been processed. */
unsigned int numlines; /* Counter for displayed lines. */
int eof_seen; /* EOF encountered. */
int not_found; /* Set if no keys have been found. */
};
enum ks_action {KS_UNKNOWN=0,KS_GET,KS_GETNAME,KS_SEND,KS_SEARCH};
static struct parse_options keyserver_opts[]=
{
/* some of these options are not real - just for the help
message */
{"max-cert-size",0,NULL,NULL}, /* MUST be the first in this array! */
{"http-proxy", KEYSERVER_HTTP_PROXY, NULL, /* MUST be the second! */
N_("override proxy options set for dirmngr")},
{"include-revoked",0,NULL,N_("include revoked keys in search results")},
{"include-subkeys",0,NULL,N_("include subkeys when searching by key ID")},
{"timeout", KEYSERVER_TIMEOUT, NULL,
N_("override timeout options set for dirmngr")},
{"refresh-add-fake-v3-keyids",KEYSERVER_ADD_FAKE_V3,NULL,
NULL},
{"auto-key-retrieve",KEYSERVER_AUTO_KEY_RETRIEVE,NULL,
N_("automatically retrieve keys when verifying signatures")},
{"honor-keyserver-url",KEYSERVER_HONOR_KEYSERVER_URL,NULL,
N_("honor the preferred keyserver URL set on the key")},
{"honor-pka-record",KEYSERVER_HONOR_PKA_RECORD,NULL,
N_("honor the PKA record set on a key when retrieving keys")},
{NULL,0,NULL,NULL}
};
static gpg_error_t keyserver_get (ctrl_t ctrl,
KEYDB_SEARCH_DESC *desc, int ndesc,
struct keyserver_spec *override_keyserver,
int quick,
unsigned char **r_fpr, size_t *r_fprlen);
static gpg_error_t keyserver_put (ctrl_t ctrl, strlist_t keyspecs);
/* Reasonable guess. The commonly used test key simon.josefsson.org
is larger than 32k, thus we need at least this value. */
#define DEFAULT_MAX_CERT_SIZE 65536
static size_t max_cert_size=DEFAULT_MAX_CERT_SIZE;
static void
warn_kshelper_option(char *option, int noisy)
{
char *p;
if ((p=strchr (option, '=')))
*p = 0;
if (!strcmp (option, "ca-cert-file"))
log_info ("keyserver option '%s' is obsolete; please use "
"'%s' in dirmngr.conf\n",
"ca-cert-file", "hkp-cacert");
else if (!strcmp (option, "check-cert")
|| !strcmp (option, "broken-http-proxy"))
log_info ("keyserver option '%s' is obsolete\n", option);
else if (noisy || opt.verbose)
log_info ("keyserver option '%s' is unknown\n", option);
}
/* Called from main to parse the args for --keyserver-options. */
int
parse_keyserver_options(char *options)
{
int ret=1;
char *tok;
char *max_cert=NULL;
keyserver_opts[0].value=&max_cert;
keyserver_opts[1].value=&opt.keyserver_options.http_proxy;
while((tok=optsep(&options)))
{
if(tok[0]=='\0')
continue;
/* We accept quite a few possible options here - some options to
handle specially, the keyserver_options list, and import and
export options that pertain to keyserver operations. */
if (!parse_options (tok,&opt.keyserver_options.options, keyserver_opts,0)
&& !parse_import_options(tok,&opt.keyserver_options.import_options,0)
&& !parse_export_options(tok,&opt.keyserver_options.export_options,0))
{
/* All of the standard options have failed, so the option was
destined for a keyserver plugin as used by GnuPG < 2.1 */
warn_kshelper_option (tok, 1);
}
}
if(max_cert)
{
max_cert_size=strtoul(max_cert,(char **)NULL,10);
if(max_cert_size==0)
max_cert_size=DEFAULT_MAX_CERT_SIZE;
}
return ret;
}
void
free_keyserver_spec(struct keyserver_spec *keyserver)
{
xfree(keyserver->uri);
xfree(keyserver->scheme);
xfree(keyserver->auth);
xfree(keyserver->host);
xfree(keyserver->port);
xfree(keyserver->path);
xfree(keyserver->opaque);
free_strlist(keyserver->options);
xfree(keyserver);
}
/* Return 0 for match */
static int
cmp_keyserver_spec(struct keyserver_spec *one,struct keyserver_spec *two)
{
if(ascii_strcasecmp(one->scheme,two->scheme)==0)
{
if(one->host && two->host && ascii_strcasecmp(one->host,two->host)==0)
{
if((one->port && two->port
&& ascii_strcasecmp(one->port,two->port)==0)
|| (!one->port && !two->port))
return 0;
}
else if(one->opaque && two->opaque
&& ascii_strcasecmp(one->opaque,two->opaque)==0)
return 0;
}
return 1;
}
/* Try and match one of our keyservers. If we can, return that. If
we can't, return our input. */
struct keyserver_spec *
keyserver_match(struct keyserver_spec *spec)
{
struct keyserver_spec *ks;
for(ks=opt.keyserver;ks;ks=ks->next)
if(cmp_keyserver_spec(spec,ks)==0)
return ks;
return spec;
}
/* TODO: once we cut over to an all-curl world, we don't need this
parser any longer so it can be removed, or at least moved to
keyserver/ksutil.c for limited use in gpgkeys_ldap or the like. */
keyserver_spec_t
parse_keyserver_uri (const char *string,int require_scheme)
{
int assume_hkp=0;
struct keyserver_spec *keyserver;
const char *idx;
int count;
char *uri, *duped_uri, *options;
log_assert (string);
keyserver=xmalloc_clear(sizeof(struct keyserver_spec));
duped_uri = uri = xstrdup (string);
options=strchr(uri,' ');
if(options)
{
char *tok;
*options='\0';
options++;
while((tok=optsep(&options)))
warn_kshelper_option (tok, 0);
}
/* Get the scheme */
for(idx=uri,count=0;*idx && *idx!=':';idx++)
{
count++;
/* Do we see the start of an RFC-2732 ipv6 address here? If so,
there clearly isn't a scheme so get out early. */
if(*idx=='[')
{
/* Was the '[' the first thing in the string? If not, we
have a mangled scheme with a [ in it so fail. */
if(count==1)
break;
else
goto fail;
}
}
if(count==0)
goto fail;
if(*idx=='\0' || *idx=='[')
{
if(require_scheme)
return NULL;
/* Assume HKP if there is no scheme */
assume_hkp=1;
keyserver->scheme=xstrdup("hkp");
keyserver->uri=xmalloc(strlen(keyserver->scheme)+3+strlen(uri)+1);
strcpy(keyserver->uri,keyserver->scheme);
strcat(keyserver->uri,"://");
strcat(keyserver->uri,uri);
}
else
{
int i;
keyserver->uri=xstrdup(uri);
keyserver->scheme=xmalloc(count+1);
/* Force to lowercase */
for(i=0;i<count;i++)
keyserver->scheme[i]=ascii_tolower(uri[i]);
keyserver->scheme[i]='\0';
/* Skip past the scheme and colon */
uri+=count+1;
}
if(ascii_strcasecmp(keyserver->scheme,"x-broken-hkp")==0)
{
log_info ("keyserver option '%s' is obsolete\n",
"x-broken-hkp");
}
else if(ascii_strcasecmp(keyserver->scheme,"x-hkp")==0)
{
/* Canonicalize this to "hkp" so it works with both the internal
and external keyserver interface. */
xfree(keyserver->scheme);
keyserver->scheme=xstrdup("hkp");
}
if (uri[0]=='/' && uri[1]=='/' && uri[2] == '/')
{
/* Three slashes means network path with a default host name.
This is a hack because it does not crok all possible
- combiantions. We should better repalce all code bythe parser
+ combinations. We should better replace all code by the parser
from http.c. */
keyserver->path = xstrdup (uri+2);
}
else if(assume_hkp || (uri[0]=='/' && uri[1]=='/'))
{
/* Two slashes means network path. */
/* Skip over the "//", if any */
if(!assume_hkp)
uri+=2;
/* Do we have userinfo auth data present? */
for(idx=uri,count=0;*idx && *idx!='@' && *idx!='/';idx++)
count++;
/* We found a @ before the slash, so that means everything
before the @ is auth data. */
if(*idx=='@')
{
if(count==0)
goto fail;
keyserver->auth=xmalloc(count+1);
strncpy(keyserver->auth,uri,count);
keyserver->auth[count]='\0';
uri+=count+1;
}
/* Is it an RFC-2732 ipv6 [literal address] ? */
if(*uri=='[')
{
for(idx=uri+1,count=1;*idx
&& ((isascii (*idx) && isxdigit(*idx))
|| *idx==':' || *idx=='.');idx++)
count++;
/* Is the ipv6 literal address terminated? */
if(*idx==']')
count++;
else
goto fail;
}
else
for(idx=uri,count=0;*idx && *idx!=':' && *idx!='/';idx++)
count++;
if(count==0)
goto fail;
keyserver->host=xmalloc(count+1);
strncpy(keyserver->host,uri,count);
keyserver->host[count]='\0';
/* Skip past the host */
uri+=count;
if(*uri==':')
{
/* It would seem to be reasonable to limit the range of the
ports to values between 1-65535, but RFC 1738 and 1808
imply there is no limit. Of course, the real world has
limits. */
for(idx=uri+1,count=0;*idx && *idx!='/';idx++)
{
count++;
/* Ports are digits only */
if(!digitp(idx))
goto fail;
}
keyserver->port=xmalloc(count+1);
strncpy(keyserver->port,uri+1,count);
keyserver->port[count]='\0';
/* Skip past the colon and port number */
uri+=1+count;
}
/* Everything else is the path */
if(*uri)
keyserver->path=xstrdup(uri);
else
keyserver->path=xstrdup("/");
if(keyserver->path[1])
keyserver->flags.direct_uri=1;
}
else if(uri[0]!='/')
{
/* No slash means opaque. Just record the opaque blob and get
out. */
keyserver->opaque=xstrdup(uri);
}
else
{
/* One slash means absolute path. We don't need to support that
yet. */
goto fail;
}
xfree (duped_uri);
return keyserver;
fail:
free_keyserver_spec(keyserver);
xfree (duped_uri);
return NULL;
}
struct keyserver_spec *
parse_preferred_keyserver(PKT_signature *sig)
{
struct keyserver_spec *spec=NULL;
const byte *p;
size_t plen;
p=parse_sig_subpkt(sig->hashed,SIGSUBPKT_PREF_KS,&plen);
if(p && plen)
{
byte *dupe=xmalloc(plen+1);
memcpy(dupe,p,plen);
dupe[plen]='\0';
spec = parse_keyserver_uri (dupe, 1);
xfree(dupe);
}
return spec;
}
static void
print_keyrec (ctrl_t ctrl, int number,struct keyrec *keyrec)
{
iobuf_writebyte(keyrec->uidbuf,0);
iobuf_flush_temp(keyrec->uidbuf);
es_printf ("(%d)\t%s ", number, iobuf_get_temp_buffer (keyrec->uidbuf));
if (keyrec->size>0)
es_printf ("%d bit ", keyrec->size);
if(keyrec->type)
{
const char *str;
str = openpgp_pk_algo_name (keyrec->type);
if (str && strcmp (str, "?"))
es_printf ("%s ",str);
else
es_printf ("unknown ");
}
switch(keyrec->desc.mode)
{
/* If the keyserver helper gave us a short keyid, we have no
choice but to use it. Do check --keyid-format to add a 0x if
needed. */
case KEYDB_SEARCH_MODE_SHORT_KID:
es_printf ("key %s%08lX",
(opt.keyid_format==KF_0xSHORT
|| opt.keyid_format==KF_0xLONG)?"0x":"",
(ulong)keyrec->desc.u.kid[1]);
break;
/* However, if it gave us a long keyid, we can honor
--keyid-format via keystr(). */
case KEYDB_SEARCH_MODE_LONG_KID:
es_printf ("key %s",keystr(keyrec->desc.u.kid));
break;
case KEYDB_SEARCH_MODE_FPR:
{
u32 kid[2];
keyid_from_fingerprint (ctrl, keyrec->desc.u.fpr, keyrec->desc.fprlen,
kid);
es_printf("key %s",keystr(kid));
}
break;
default:
BUG();
break;
}
if(keyrec->createtime>0)
{
es_printf (", ");
es_printf (_("created: %s"), strtimestamp(keyrec->createtime));
}
if(keyrec->expiretime>0)
{
es_printf (", ");
es_printf (_("expires: %s"), strtimestamp(keyrec->expiretime));
}
if (keyrec->flags&1)
es_printf (" (%s)", _("revoked"));
if(keyrec->flags&2)
es_printf (" (%s)", _("disabled"));
if(keyrec->flags&4)
es_printf (" (%s)", _("expired"));
es_printf ("\n");
}
/* Returns a keyrec (which must be freed) once a key is complete, and
NULL otherwise. Call with a NULL keystring once key parsing is
complete to return any unfinished keys. */
static struct keyrec *
parse_keyrec(char *keystring)
{
/* FIXME: Remove the static and put the data into the parms we use
for the caller anyway. */
static struct keyrec *work=NULL;
struct keyrec *ret=NULL;
char *record;
int i;
if(keystring==NULL)
{
if(work==NULL)
return NULL;
else if(work->desc.mode==KEYDB_SEARCH_MODE_NONE)
{
xfree(work);
return NULL;
}
else
{
ret=work;
work=NULL;
return ret;
}
}
if(work==NULL)
{
work=xmalloc_clear(sizeof(struct keyrec));
work->uidbuf=iobuf_temp();
}
trim_trailing_ws (keystring, strlen (keystring));
if((record=strsep(&keystring,":"))==NULL)
return ret;
if(ascii_strcasecmp("pub",record)==0)
{
char *tok;
gpg_error_t err;
if(work->desc.mode)
{
ret=work;
work=xmalloc_clear(sizeof(struct keyrec));
work->uidbuf=iobuf_temp();
}
if((tok=strsep(&keystring,":"))==NULL)
return ret;
err = classify_user_id (tok, &work->desc, 1);
if (err || (work->desc.mode != KEYDB_SEARCH_MODE_SHORT_KID
&& work->desc.mode != KEYDB_SEARCH_MODE_LONG_KID
&& work->desc.mode != KEYDB_SEARCH_MODE_FPR))
{
work->desc.mode=KEYDB_SEARCH_MODE_NONE;
return ret;
}
/* Note all items after this are optional. This allows us to
have a pub line as simple as pub:keyid and nothing else. */
work->lines++;
if((tok=strsep(&keystring,":"))==NULL)
return ret;
work->type=atoi(tok);
if((tok=strsep(&keystring,":"))==NULL)
return ret;
work->size=atoi(tok);
if((tok=strsep(&keystring,":"))==NULL)
return ret;
if(atoi(tok)<=0)
work->createtime=0;
else
work->createtime=atoi(tok);
if((tok=strsep(&keystring,":"))==NULL)
return ret;
if(atoi(tok)<=0)
work->expiretime=0;
else
{
work->expiretime=atoi(tok);
/* Force the 'e' flag on if this key is expired. */
if(work->expiretime<=make_timestamp())
work->flags|=4;
}
if((tok=strsep(&keystring,":"))==NULL)
return ret;
while(*tok)
switch(*tok++)
{
case 'r':
case 'R':
work->flags|=1;
break;
case 'd':
case 'D':
work->flags|=2;
break;
case 'e':
case 'E':
work->flags|=4;
break;
}
}
else if(ascii_strcasecmp("uid",record)==0 && work->desc.mode)
{
char *userid,*tok,*decoded;
if((tok=strsep(&keystring,":"))==NULL)
return ret;
if(strlen(tok)==0)
return ret;
userid=tok;
/* By definition, de-%-encoding is always smaller than the
original string so we can decode in place. */
i=0;
while(*tok)
if(tok[0]=='%' && tok[1] && tok[2])
{
int c;
userid[i] = (c=hextobyte(&tok[1])) == -1 ? '?' : c;
i++;
tok+=3;
}
else
userid[i++]=*tok++;
/* We don't care about the other info provided in the uid: line
since no keyserver supports marking userids with timestamps
or revoked/expired/disabled yet. */
/* No need to check for control characters, as utf8_to_native
does this for us. */
decoded=utf8_to_native(userid,i,0);
if(strlen(decoded)>opt.screen_columns-10)
decoded[opt.screen_columns-10]='\0';
iobuf_writestr(work->uidbuf,decoded);
xfree(decoded);
iobuf_writestr(work->uidbuf,"\n\t");
work->lines++;
}
/* Ignore any records other than "pri" and "uid" for easy future
growth. */
return ret;
}
/* Show a prompt and allow the user to select keys for retrieval. */
static gpg_error_t
show_prompt (ctrl_t ctrl, KEYDB_SEARCH_DESC *desc, int numdesc,
int count, const char *search)
{
gpg_error_t err;
char *answer = NULL;
es_fflush (es_stdout);
if (count && opt.command_fd == -1)
{
static int from = 1;
tty_printf ("Keys %d-%d of %d for \"%s\". ",
from, numdesc, count, search);
from = numdesc + 1;
}
again:
err = 0;
xfree (answer);
answer = cpr_get_no_help ("keysearch.prompt",
_("Enter number(s), N)ext, or Q)uit > "));
/* control-d */
if (answer[0]=='\x04')
{
tty_printf ("Q\n");
answer[0] = 'q';
}
if (answer[0]=='q' || answer[0]=='Q')
err = gpg_error (GPG_ERR_CANCELED);
else if (atoi (answer) >= 1 && atoi (answer) <= numdesc)
{
char *split = answer;
char *num;
int numarray[50];
int numidx = 0;
int idx;
while ((num = strsep (&split, " ,")))
if (atoi (num) >= 1 && atoi (num) <= numdesc)
{
if (numidx >= DIM (numarray))
{
tty_printf ("Too many keys selected\n");
goto again;
}
numarray[numidx++] = atoi (num);
}
if (!numidx)
goto again;
{
KEYDB_SEARCH_DESC *selarray;
selarray = xtrymalloc (numidx * sizeof *selarray);
if (!selarray)
{
err = gpg_error_from_syserror ();
goto leave;
}
for (idx = 0; idx < numidx; idx++)
selarray[idx] = desc[numarray[idx]-1];
err = keyserver_get (ctrl, selarray, numidx, NULL, 0, NULL, NULL);
xfree (selarray);
}
}
leave:
xfree (answer);
return err;
}
/* This is a callback used by call-dirmngr.c to process the result of
KS_SEARCH command. If SPECIAL is 0, LINE is the actual data line
received with all escaping removed and guaranteed to be exactly one
line with stripped LF; an EOF is indicated by LINE passed as NULL.
If special is 1, the line contains the source of the information
(usually an URL). LINE may be modified after return. */
static gpg_error_t
search_line_handler (void *opaque, int special, char *line)
{
struct search_line_handler_parm_s *parm = opaque;
gpg_error_t err = 0;
struct keyrec *keyrec;
if (special == 1)
{
log_info ("data source: %s\n", line);
return 0;
}
else if (special)
{
log_debug ("unknown value %d for special search callback", special);
return 0;
}
if (parm->eof_seen && line)
{
log_debug ("ooops: unexpected data after EOF\n");
line = NULL;
}
/* Print the received line. */
if (opt.with_colons && line)
{
es_printf ("%s\n", line);
}
/* Look for an info: line. The only current info: values defined
are the version and key count. */
if (line && !parm->any_lines && !ascii_strncasecmp ("info:", line, 5))
{
char *str = line + 5;
char *tok;
if ((tok = strsep (&str, ":")))
{
int version;
if (sscanf (tok, "%d", &version) !=1 )
version = 1;
if (version !=1 )
{
log_error (_("invalid keyserver protocol "
"(us %d!=handler %d)\n"), 1, version);
return gpg_error (GPG_ERR_UNSUPPORTED_PROTOCOL);
}
}
if ((tok = strsep (&str, ":"))
&& sscanf (tok, "%d", &parm->count) == 1)
{
if (!parm->count)
parm->not_found = 1;/* Server indicated that no items follow. */
else if (parm->count < 0)
parm->count = 10; /* Bad value - assume something reasonable. */
else
parm->validcount = 1; /* COUNT seems to be okay. */
}
parm->any_lines = 1;
return 0; /* Line processing finished. */
}
again:
if (line)
keyrec = parse_keyrec (line);
else
{
/* Received EOF - flush data */
parm->eof_seen = 1;
keyrec = parse_keyrec (NULL);
if (!keyrec)
{
if (!parm->nkeys)
parm->not_found = 1; /* No keys at all. */
else
{
if (parm->nkeys != parm->count)
parm->validcount = 0;
if (!(opt.with_colons && opt.batch))
{
err = show_prompt (parm->ctrl, parm->desc, parm->nkeys,
parm->validcount? parm->count : 0,
parm->searchstr_disp);
return err;
}
}
}
}
/* Save the key in the key array. */
if (keyrec)
{
/* Allocate or enlarge the key array if needed. */
if (!parm->desc)
{
if (parm->count < 1)
{
parm->count = 10;
parm->validcount = 0;
}
parm->desc = xtrymalloc (parm->count * sizeof *parm->desc);
if (!parm->desc)
{
err = gpg_error_from_syserror ();
iobuf_close (keyrec->uidbuf);
xfree (keyrec);
return err;
}
}
else if (parm->nkeys == parm->count)
{
/* Keyserver sent more keys than claimed in the info: line. */
KEYDB_SEARCH_DESC *tmp;
int newcount = parm->count + 10;
tmp = xtryrealloc (parm->desc, newcount * sizeof *parm->desc);
if (!tmp)
{
err = gpg_error_from_syserror ();
iobuf_close (keyrec->uidbuf);
xfree (keyrec);
return err;
}
parm->count = newcount;
parm->desc = tmp;
parm->validcount = 0;
}
parm->desc[parm->nkeys] = keyrec->desc;
if (!opt.with_colons)
{
/* SCREEN_LINES - 1 for the prompt. */
if (parm->numlines + keyrec->lines > opt.screen_lines - 1)
{
err = show_prompt (parm->ctrl, parm->desc, parm->nkeys,
parm->validcount ? parm->count:0,
parm->searchstr_disp);
if (err)
return err;
parm->numlines = 0;
}
print_keyrec (parm->ctrl, parm->nkeys+1, keyrec);
}
parm->numlines += keyrec->lines;
iobuf_close (keyrec->uidbuf);
xfree (keyrec);
parm->any_lines = 1;
parm->nkeys++;
/* If we are here due to a flush after the EOF, run again for
the last prompt. Fixme: Make this code better readable. */
if (parm->eof_seen)
goto again;
}
return 0;
}
int
keyserver_export (ctrl_t ctrl, strlist_t users)
{
gpg_error_t err;
strlist_t sl=NULL;
KEYDB_SEARCH_DESC desc;
int rc=0;
/* Weed out descriptors that we don't support sending */
for(;users;users=users->next)
{
err = classify_user_id (users->d, &desc, 1);
if (err || (desc.mode != KEYDB_SEARCH_MODE_SHORT_KID
&& desc.mode != KEYDB_SEARCH_MODE_LONG_KID
&& desc.mode != KEYDB_SEARCH_MODE_FPR))
{
log_error(_("\"%s\" not a key ID: skipping\n"),users->d);
continue;
}
else
append_to_strlist(&sl,users->d);
}
if(sl)
{
rc = keyserver_put (ctrl, sl);
free_strlist(sl);
}
return rc;
}
/* Structure to convey the arg to keyserver_retrieval_screener. */
struct ks_retrieval_screener_arg_s
{
KEYDB_SEARCH_DESC *desc;
int ndesc;
};
/* Check whether a key matches the search description. The function
returns 0 if the key shall be imported. */
static gpg_error_t
keyserver_retrieval_screener (kbnode_t keyblock, void *opaque)
{
struct ks_retrieval_screener_arg_s *arg = opaque;
KEYDB_SEARCH_DESC *desc = arg->desc;
int ndesc = arg->ndesc;
kbnode_t node;
PKT_public_key *pk;
int n;
u32 keyid[2];
byte fpr[MAX_FINGERPRINT_LEN];
size_t fpr_len = 0;
/* Secret keys are not expected from a keyserver. We do not
care about secret subkeys because the import code takes care
of skipping them. Not allowing an import of a public key
with a secret subkey would make it too easy to inhibit the
downloading of a public key. Recall that keyservers do only
limited checks. */
node = find_kbnode (keyblock, PKT_SECRET_KEY);
if (node)
return gpg_error (GPG_ERR_GENERAL); /* Do not import. */
if (!ndesc)
return 0; /* Okay if no description given. */
/* Loop over all key packets. */
for (node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype != PKT_PUBLIC_KEY
&& node->pkt->pkttype != PKT_PUBLIC_SUBKEY)
continue;
pk = node->pkt->pkt.public_key;
fingerprint_from_pk (pk, fpr, &fpr_len);
keyid_from_pk (pk, keyid);
/* Compare requested and returned fingerprints if available. */
for (n = 0; n < ndesc; n++)
{
if (desc[n].mode == KEYDB_SEARCH_MODE_FPR)
{
if (fpr_len == desc[n].fprlen && !memcmp (fpr, desc[n].u.fpr, 32))
return 0;
}
else if (desc[n].mode == KEYDB_SEARCH_MODE_LONG_KID)
{
if (keyid[0] == desc[n].u.kid[0] && keyid[1] == desc[n].u.kid[1])
return 0;
}
else if (desc[n].mode == KEYDB_SEARCH_MODE_SHORT_KID)
{
if (keyid[1] == desc[n].u.kid[1])
return 0;
}
else /* No keyid or fingerprint - can't check. */
return 0; /* allow import. */
}
}
return gpg_error (GPG_ERR_GENERAL);
}
int
keyserver_import (ctrl_t ctrl, strlist_t users)
{
gpg_error_t err;
KEYDB_SEARCH_DESC *desc;
int num=100,count=0;
int rc=0;
/* Build a list of key ids */
desc=xmalloc(sizeof(KEYDB_SEARCH_DESC)*num);
for(;users;users=users->next)
{
err = classify_user_id (users->d, &desc[count], 1);
if (err || (desc[count].mode != KEYDB_SEARCH_MODE_SHORT_KID
&& desc[count].mode != KEYDB_SEARCH_MODE_LONG_KID
&& desc[count].mode != KEYDB_SEARCH_MODE_FPR))
{
log_error (_("\"%s\" not a key ID: skipping\n"), users->d);
continue;
}
count++;
if(count==num)
{
num+=100;
desc=xrealloc(desc,sizeof(KEYDB_SEARCH_DESC)*num);
}
}
if(count>0)
rc = keyserver_get (ctrl, desc, count, NULL, 0, NULL, NULL);
xfree(desc);
return rc;
}
/* Return true if any keyserver has been configured. */
int
keyserver_any_configured (ctrl_t ctrl)
{
return !gpg_dirmngr_ks_list (ctrl, NULL);
}
/* Import all keys that exactly match NAME */
int
keyserver_import_name (ctrl_t ctrl, const char *name,
unsigned char **fpr, size_t *fprlen,
struct keyserver_spec *keyserver)
{
KEYDB_SEARCH_DESC desc;
memset (&desc, 0, sizeof desc);
desc.mode = KEYDB_SEARCH_MODE_EXACT;
desc.u.name = name;
return keyserver_get (ctrl, &desc, 1, keyserver, 0, fpr, fprlen);
}
int
keyserver_import_fprint (ctrl_t ctrl, const byte *fprint,size_t fprint_len,
struct keyserver_spec *keyserver, int quick)
{
KEYDB_SEARCH_DESC desc;
memset(&desc,0,sizeof(desc));
if (fprint_len == 16 || fprint_len == 20 || fprint_len == 32)
desc.mode = KEYDB_SEARCH_MODE_FPR;
else
return -1;
memcpy(desc.u.fpr,fprint,fprint_len);
desc.fprlen = fprint_len;
/* TODO: Warn here if the fingerprint we got doesn't match the one
we asked for? */
return keyserver_get (ctrl, &desc, 1, keyserver, quick, NULL, NULL);
}
int
keyserver_import_keyid (ctrl_t ctrl,
u32 *keyid,struct keyserver_spec *keyserver, int quick)
{
KEYDB_SEARCH_DESC desc;
memset(&desc,0,sizeof(desc));
desc.mode=KEYDB_SEARCH_MODE_LONG_KID;
desc.u.kid[0]=keyid[0];
desc.u.kid[1]=keyid[1];
return keyserver_get (ctrl, &desc, 1, keyserver, quick, NULL, NULL);
}
/* code mostly stolen from do_export_stream */
static int
keyidlist (ctrl_t ctrl, strlist_t users, KEYDB_SEARCH_DESC **klist,
int *count, int fakev3)
{
int rc = 0;
int num = 100;
kbnode_t keyblock = NULL;
kbnode_t node;
KEYDB_HANDLE kdbhd;
int ndesc;
KEYDB_SEARCH_DESC *desc = NULL;
strlist_t sl;
*count=0;
*klist=xmalloc(sizeof(KEYDB_SEARCH_DESC)*num);
kdbhd = keydb_new ();
if (!kdbhd)
{
rc = gpg_error_from_syserror ();
goto leave;
}
keydb_disable_caching (kdbhd); /* We are looping the search. */
if(!users)
{
ndesc = 1;
desc = xmalloc_clear ( ndesc * sizeof *desc);
desc[0].mode = KEYDB_SEARCH_MODE_FIRST;
}
else
{
for (ndesc=0, sl=users; sl; sl = sl->next, ndesc++)
;
desc = xmalloc ( ndesc * sizeof *desc);
for (ndesc=0, sl=users; sl; sl = sl->next)
{
gpg_error_t err;
if (!(err = classify_user_id (sl->d, desc+ndesc, 1)))
ndesc++;
else
log_error (_("key \"%s\" not found: %s\n"),
sl->d, gpg_strerror (err));
}
}
for (;;)
{
rc = keydb_search (kdbhd, desc, ndesc, NULL);
if (rc)
break; /* ready. */
if (!users)
desc[0].mode = KEYDB_SEARCH_MODE_NEXT;
/* read the keyblock */
rc = keydb_get_keyblock (kdbhd, &keyblock );
if( rc )
{
log_error (_("error reading keyblock: %s\n"), gpg_strerror (rc) );
goto leave;
}
if((node=find_kbnode(keyblock,PKT_PUBLIC_KEY)))
{
/* This is to work around a bug in some keyservers (pksd and
OKS) that calculate v4 RSA keyids as if they were v3 RSA.
The answer is to refresh both the correct v4 keyid
(e.g. 99242560) and the fake v3 keyid (e.g. 68FDDBC7).
This only happens for key refresh using the HKP scheme
and if the refresh-add-fake-v3-keyids keyserver option is
set. */
if(fakev3 && is_RSA(node->pkt->pkt.public_key->pubkey_algo) &&
node->pkt->pkt.public_key->version>=4)
{
(*klist)[*count].mode=KEYDB_SEARCH_MODE_LONG_KID;
v3_keyid (node->pkt->pkt.public_key->pkey[0],
(*klist)[*count].u.kid);
(*count)++;
if(*count==num)
{
num+=100;
*klist=xrealloc(*klist,sizeof(KEYDB_SEARCH_DESC)*num);
}
}
/* v4 keys get full fingerprints. v3 keys get long keyids.
This is because it's easy to calculate any sort of keyid
from a v4 fingerprint, but not a v3 fingerprint. */
if (node->pkt->pkt.public_key->version < 4)
{
(*klist)[*count].mode=KEYDB_SEARCH_MODE_LONG_KID;
keyid_from_pk(node->pkt->pkt.public_key,
(*klist)[*count].u.kid);
}
else
{
size_t fprlen;
fingerprint_from_pk (node->pkt->pkt.public_key,
(*klist)[*count].u.fpr, &fprlen);
(*klist)[*count].mode = KEYDB_SEARCH_MODE_FPR;
(*klist)[*count].fprlen = fprlen;
}
/* This is a little hackish, using the skipfncvalue as a
void* pointer to the keyserver spec, but we don't need
the skipfnc here, and it saves having an additional field
for this (which would be wasted space most of the
time). */
(*klist)[*count].skipfncvalue=NULL;
/* Are we honoring preferred keyservers? */
if(opt.keyserver_options.options&KEYSERVER_HONOR_KEYSERVER_URL)
{
PKT_user_id *uid=NULL;
PKT_signature *sig=NULL;
merge_keys_and_selfsig (ctrl, keyblock);
for(node=node->next;node;node=node->next)
{
if(node->pkt->pkttype==PKT_USER_ID
&& node->pkt->pkt.user_id->flags.primary)
uid=node->pkt->pkt.user_id;
else if(node->pkt->pkttype==PKT_SIGNATURE
&& node->pkt->pkt.signature->
flags.chosen_selfsig && uid)
{
sig=node->pkt->pkt.signature;
break;
}
}
/* Try and parse the keyserver URL. If it doesn't work,
then we end up writing NULL which indicates we are
the same as any other key. */
if(sig)
(*klist)[*count].skipfncvalue=parse_preferred_keyserver(sig);
}
(*count)++;
if(*count==num)
{
num+=100;
*klist=xrealloc(*klist,sizeof(KEYDB_SEARCH_DESC)*num);
}
}
}
if (gpg_err_code (rc) == GPG_ERR_NOT_FOUND)
rc = 0;
leave:
if(rc)
{
xfree(*klist);
*klist = NULL;
}
xfree(desc);
keydb_release(kdbhd);
release_kbnode(keyblock);
return rc;
}
/* Note this is different than the original HKP refresh. It allows
usernames to refresh only part of the keyring. */
gpg_error_t
keyserver_refresh (ctrl_t ctrl, strlist_t users)
{
gpg_error_t err;
int count, numdesc;
int fakev3 = 0;
KEYDB_SEARCH_DESC *desc;
unsigned int options=opt.keyserver_options.import_options;
/* We switch merge-only on during a refresh, as 'refresh' should
never import new keys, even if their keyids match. */
opt.keyserver_options.import_options|=IMPORT_MERGE_ONLY;
/* Similarly, we switch on fast-import, since refresh may make
multiple import sets (due to preferred keyserver URLs). We don't
want each set to rebuild the trustdb. Instead we do it once at
the end here. */
opt.keyserver_options.import_options|=IMPORT_FAST;
/* If refresh_add_fake_v3_keyids is on and it's a HKP or MAILTO
scheme, then enable fake v3 keyid generation. Note that this
works only with a keyserver configured. gpg.conf
(i.e. opt.keyserver); however that method of configuring a
keyserver is deprecated and in any case it is questionable
whether we should keep on supporting these ancient and broken
keyservers. */
if((opt.keyserver_options.options&KEYSERVER_ADD_FAKE_V3) && opt.keyserver
&& (ascii_strcasecmp(opt.keyserver->scheme,"hkp")==0 ||
ascii_strcasecmp(opt.keyserver->scheme,"mailto")==0))
fakev3=1;
err = keyidlist (ctrl, users, &desc, &numdesc, fakev3);
if (err)
return err;
count=numdesc;
if(count>0)
{
int i;
/* Try to handle preferred keyserver keys first */
for(i=0;i<numdesc;i++)
{
if(desc[i].skipfncvalue)
{
struct keyserver_spec *keyserver=desc[i].skipfncvalue;
if (!opt.quiet)
log_info (_("refreshing %d key from %s\n"), 1, keyserver->uri);
/* We use the keyserver structure we parsed out before.
Note that a preferred keyserver without a scheme://
will be interpreted as hkp:// */
err = keyserver_get (ctrl, &desc[i], 1, keyserver, 0, NULL, NULL);
if (err)
log_info(_("WARNING: unable to refresh key %s"
" via %s: %s\n"),keystr_from_desc(&desc[i]),
keyserver->uri,gpg_strerror (err));
else
{
/* We got it, so mark it as NONE so we don't try and
get it again from the regular keyserver. */
desc[i].mode=KEYDB_SEARCH_MODE_NONE;
count--;
}
free_keyserver_spec(keyserver);
}
}
}
if(count>0)
{
char *tmpuri;
err = gpg_dirmngr_ks_list (ctrl, &tmpuri);
if (!err)
{
if (!opt.quiet)
{
log_info (ngettext("refreshing %d key from %s\n",
"refreshing %d keys from %s\n",
count), count, tmpuri);
}
xfree (tmpuri);
err = keyserver_get (ctrl, desc, numdesc, NULL, 0, NULL, NULL);
}
}
xfree(desc);
opt.keyserver_options.import_options=options;
/* If the original options didn't have fast import, and the trustdb
is dirty, rebuild. */
if(!(opt.keyserver_options.import_options&IMPORT_FAST))
check_or_update_trustdb (ctrl);
return err;
}
/* Search for keys on the keyservers. The patterns are given in the
string list TOKENS. */
gpg_error_t
keyserver_search (ctrl_t ctrl, strlist_t tokens)
{
gpg_error_t err;
char *searchstr;
struct search_line_handler_parm_s parm;
memset (&parm, 0, sizeof parm);
if (!tokens)
return 0; /* Return success if no patterns are given. */
/* Write global options */
/* for(temp=opt.keyserver_options.other;temp;temp=temp->next) */
/* es_fprintf(spawn->tochild,"OPTION %s\n",temp->d); */
/* Write per-keyserver options */
/* for(temp=keyserver->options;temp;temp=temp->next) */
/* es_fprintf(spawn->tochild,"OPTION %s\n",temp->d); */
{
membuf_t mb;
strlist_t item;
init_membuf (&mb, 1024);
for (item = tokens; item; item = item->next)
{
if (item != tokens)
put_membuf (&mb, " ", 1);
put_membuf_str (&mb, item->d);
}
put_membuf (&mb, "", 1); /* Append Nul. */
searchstr = get_membuf (&mb, NULL);
if (!searchstr)
{
err = gpg_error_from_syserror ();
goto leave;
}
}
/* FIXME: Enable the next line */
/* log_info (_("searching for \"%s\" from %s\n"), searchstr, keyserver->uri); */
parm.ctrl = ctrl;
if (searchstr)
parm.searchstr_disp = utf8_to_native (searchstr, strlen (searchstr), 0);
err = gpg_dirmngr_ks_search (ctrl, searchstr, search_line_handler, &parm);
if (parm.not_found || gpg_err_code (err) == GPG_ERR_NO_DATA)
{
if (parm.searchstr_disp)
log_info (_("key \"%s\" not found on keyserver\n"),
parm.searchstr_disp);
else
log_info (_("key not found on keyserver\n"));
}
- if (gpg_err_code (err) == GPG_ERR_NO_KEYSERVER)
- log_error (_("no keyserver known (use option --keyserver)\n"));
- else if (gpg_err_code (err) == GPG_ERR_NO_DATA)
+ if (gpg_err_code (err) == GPG_ERR_NO_DATA)
err = gpg_error (GPG_ERR_NOT_FOUND);
else if (err)
log_error ("error searching keyserver: %s\n", gpg_strerror (err));
/* switch(ret) */
/* { */
/* case KEYSERVER_SCHEME_NOT_FOUND: */
/* log_error(_("no handler for keyserver scheme '%s'\n"), */
/* opt.keyserver->scheme); */
/* break; */
/* case KEYSERVER_NOT_SUPPORTED: */
/* log_error(_("action '%s' not supported with keyserver " */
/* "scheme '%s'\n"), "search", opt.keyserver->scheme); */
/* break; */
/* case KEYSERVER_TIMEOUT: */
/* log_error(_("keyserver timed out\n")); */
/* break; */
/* case KEYSERVER_INTERNAL_ERROR: */
/* default: */
/* log_error(_("keyserver internal error\n")); */
/* break; */
/* } */
/* return gpg_error (GPG_ERR_KEYSERVER); */
leave:
xfree (parm.desc);
xfree (parm.searchstr_disp);
xfree(searchstr);
return err;
}
/* Helper for keyserver_get. Here we only receive a chunk of the
description to be processed in one batch. This is required due to
the limited number of patterns the dirmngr interface (KS_GET) can
grok and to limit the amount of temporary required memory. */
static gpg_error_t
keyserver_get_chunk (ctrl_t ctrl, KEYDB_SEARCH_DESC *desc, int ndesc,
int *r_ndesc_used,
import_stats_t stats_handle,
struct keyserver_spec *override_keyserver,
int quick,
unsigned char **r_fpr, size_t *r_fprlen)
{
gpg_error_t err = 0;
char **pattern;
int idx, npat, npat_fpr;
estream_t datastream;
char *source = NULL;
size_t linelen; /* Estimated linelen for KS_GET. */
size_t n;
int only_fprs;
#define MAX_KS_GET_LINELEN 950 /* Somewhat lower than the real limit. */
*r_ndesc_used = 0;
/* Create an array filled with a search pattern for each key. The
array is delimited by a NULL entry. */
pattern = xtrycalloc (ndesc+1, sizeof *pattern);
if (!pattern)
return gpg_error_from_syserror ();
/* Note that we break the loop as soon as our estimation of the to
be used line length reaches the limit. But we do this only if we
have processed at least one search requests so that an overlong
single request will be rejected only later by gpg_dirmngr_ks_get
but we are sure that R_NDESC_USED has been updated. This avoids
a possible indefinite loop. */
linelen = 17; /* "KS_GET --quick --" */
for (npat=npat_fpr=0, idx=0; idx < ndesc; idx++)
{
int quiet = 0;
if (desc[idx].mode == KEYDB_SEARCH_MODE_FPR)
{
n = 1+2+2*desc[idx].fprlen;
if (idx && linelen + n > MAX_KS_GET_LINELEN)
break; /* Declare end of this chunk. */
linelen += n;
pattern[npat] = xtrymalloc (n);
if (!pattern[npat])
err = gpg_error_from_syserror ();
else
{
strcpy (pattern[npat], "0x");
bin2hex (desc[idx].u.fpr, desc[idx].fprlen, pattern[npat]+2);
npat++;
if (desc[idx].fprlen == 20 || desc[idx].fprlen == 32)
npat_fpr++;
}
}
else if(desc[idx].mode == KEYDB_SEARCH_MODE_LONG_KID)
{
n = 1+2+16;
if (idx && linelen + n > MAX_KS_GET_LINELEN)
break; /* Declare end of this chunk. */
linelen += n;
pattern[npat] = xtryasprintf ("0x%08lX%08lX",
(ulong)desc[idx].u.kid[0],
(ulong)desc[idx].u.kid[1]);
if (!pattern[npat])
err = gpg_error_from_syserror ();
else
npat++;
}
else if(desc[idx].mode == KEYDB_SEARCH_MODE_SHORT_KID)
{
n = 1+2+8;
if (idx && linelen + n > MAX_KS_GET_LINELEN)
break; /* Declare end of this chunk. */
linelen += n;
pattern[npat] = xtryasprintf ("0x%08lX", (ulong)desc[idx].u.kid[1]);
if (!pattern[npat])
err = gpg_error_from_syserror ();
else
npat++;
}
else if(desc[idx].mode == KEYDB_SEARCH_MODE_EXACT)
{
/* The Dirmngr also uses classify_user_id to detect the type
of the search string. By adding the '=' prefix we force
Dirmngr's KS_GET to consider this an exact search string.
(In gpg 1.4 and gpg 2.0 the keyserver helpers used the
KS_GETNAME command to indicate this.) */
n = 1+1+strlen (desc[idx].u.name);
if (idx && linelen + n > MAX_KS_GET_LINELEN)
break; /* Declare end of this chunk. */
linelen += n;
pattern[npat] = strconcat ("=", desc[idx].u.name, NULL);
if (!pattern[npat])
err = gpg_error_from_syserror ();
else
{
npat++;
quiet = 1;
}
}
else if (desc[idx].mode == KEYDB_SEARCH_MODE_NONE)
continue;
else
BUG();
if (err)
{
for (idx=0; idx < npat; idx++)
xfree (pattern[idx]);
xfree (pattern);
return err;
}
if (!quiet && override_keyserver)
{
if (override_keyserver->host)
log_info (_("requesting key %s from %s server %s\n"),
keystr_from_desc (&desc[idx]),
override_keyserver->scheme, override_keyserver->host);
else
log_info (_("requesting key %s from %s\n"),
keystr_from_desc (&desc[idx]), override_keyserver->uri);
}
}
/* Remember how many of the search items were considered. Note that
this is different from NPAT. */
*r_ndesc_used = idx;
only_fprs = (npat && npat == npat_fpr);
err = gpg_dirmngr_ks_get (ctrl, pattern, override_keyserver, quick,
&datastream, &source);
for (idx=0; idx < npat; idx++)
xfree (pattern[idx]);
xfree (pattern);
if (opt.verbose && source)
log_info ("data source: %s\n", source);
if (!err)
{
struct ks_retrieval_screener_arg_s screenerarg;
/* FIXME: Check whether this comment should be moved to dirmngr.
Slurp up all the key data. In the future, it might be nice
to look for KEY foo OUTOFBAND and FAILED indicators. It's
harmless to ignore them, but ignoring them does make gpg
complain about "no valid OpenPGP data found". One way to do
this could be to continue parsing this line-by-line and make
a temp iobuf for each key. Note that we don't allow the
import of secret keys from a keyserver. Keyservers should
never accept or send them but we better protect against rogue
keyservers. */
screenerarg.desc = desc;
screenerarg.ndesc = *r_ndesc_used;
import_keys_es_stream (ctrl, datastream, stats_handle,
r_fpr, r_fprlen,
(opt.keyserver_options.import_options
| IMPORT_NO_SECKEY),
keyserver_retrieval_screener, &screenerarg,
only_fprs? KEYORG_KS : 0,
source);
}
es_fclose (datastream);
xfree (source);
return err;
}
/* Retrieve a key from a keyserver. The search pattern are in
(DESC,NDESC). Allowed search modes are keyid, fingerprint, and
exact searches. OVERRIDE_KEYSERVER gives an optional override
keyserver. If (R_FPR,R_FPRLEN) are not NULL, they may return the
fingerprint of a single imported key. If QUICK is set, dirmngr is
advised to use a shorter timeout. */
static gpg_error_t
keyserver_get (ctrl_t ctrl, KEYDB_SEARCH_DESC *desc, int ndesc,
struct keyserver_spec *override_keyserver, int quick,
unsigned char **r_fpr, size_t *r_fprlen)
{
gpg_error_t err;
import_stats_t stats_handle;
int ndesc_used;
int any_good = 0;
stats_handle = import_new_stats_handle();
for (;;)
{
err = keyserver_get_chunk (ctrl, desc, ndesc, &ndesc_used, stats_handle,
override_keyserver, quick, r_fpr, r_fprlen);
if (!err)
any_good = 1;
if (err || ndesc_used >= ndesc)
break; /* Error or all processed. */
/* Prepare for the next chunk. */
desc += ndesc_used;
ndesc -= ndesc_used;
}
if (any_good)
import_print_stats (stats_handle);
import_release_stats_handle (stats_handle);
return err;
}
/* Send all keys specified by KEYSPECS to the configured keyserver. */
static gpg_error_t
keyserver_put (ctrl_t ctrl, strlist_t keyspecs)
{
gpg_error_t err;
strlist_t kspec;
char *ksurl;
if (!keyspecs)
return 0; /* Return success if the list is empty. */
if (gpg_dirmngr_ks_list (ctrl, &ksurl))
{
log_error (_("no keyserver known\n"));
return gpg_error (GPG_ERR_NO_KEYSERVER);
}
for (kspec = keyspecs; kspec; kspec = kspec->next)
{
void *data;
size_t datalen;
kbnode_t keyblock;
err = export_pubkey_buffer (ctrl, kspec->d,
opt.keyserver_options.export_options,
NULL,
&keyblock, &data, &datalen);
if (err)
log_error (_("skipped \"%s\": %s\n"), kspec->d, gpg_strerror (err));
else
{
log_info (_("sending key %s to %s\n"),
keystr (keyblock->pkt->pkt.public_key->keyid),
ksurl?ksurl:"[?]");
err = gpg_dirmngr_ks_put (ctrl, data, datalen, keyblock);
release_kbnode (keyblock);
xfree (data);
if (err)
{
write_status_error ("keyserver_send", err);
log_error (_("keyserver send failed: %s\n"), gpg_strerror (err));
}
}
}
xfree (ksurl);
return err;
}
/* Loop over all URLs in STRLIST and fetch the key at that URL. Note
that the fetch operation ignores the configured keyservers and
instead directly retrieves the keys. */
int
keyserver_fetch (ctrl_t ctrl, strlist_t urilist, int origin)
{
gpg_error_t err;
strlist_t sl;
estream_t datastream;
unsigned int save_options = opt.keyserver_options.import_options;
/* Switch on fast-import, since fetch can handle more than one
import and we don't want each set to rebuild the trustdb.
Instead we do it once at the end. */
opt.keyserver_options.import_options |= IMPORT_FAST;
for (sl=urilist; sl; sl=sl->next)
{
if (!opt.quiet)
log_info (_("requesting key from '%s'\n"), sl->d);
err = gpg_dirmngr_ks_fetch (ctrl, sl->d, &datastream);
if (!err)
{
import_stats_t stats_handle;
stats_handle = import_new_stats_handle();
import_keys_es_stream (ctrl, datastream, stats_handle, NULL, NULL,
opt.keyserver_options.import_options,
NULL, NULL, origin, sl->d);
import_print_stats (stats_handle);
import_release_stats_handle (stats_handle);
}
else
log_info (_("WARNING: unable to fetch URI %s: %s\n"),
sl->d, gpg_strerror (err));
es_fclose (datastream);
}
opt.keyserver_options.import_options = save_options;
/* If the original options didn't have fast import, and the trustdb
is dirty, rebuild. */
if (!(opt.keyserver_options.import_options&IMPORT_FAST))
check_or_update_trustdb (ctrl);
return 0;
}
/* Import key in a CERT or pointed to by a CERT. In DANE_MODE fetch
the certificate using the DANE method. */
int
keyserver_import_cert (ctrl_t ctrl, const char *name, int dane_mode,
unsigned char **fpr,size_t *fpr_len)
{
gpg_error_t err;
char *look,*url;
estream_t key;
look = xstrdup(name);
if (!dane_mode)
{
char *domain = strrchr (look,'@');
if (domain)
*domain='.';
}
err = gpg_dirmngr_dns_cert (ctrl, look, dane_mode? NULL : "*",
&key, fpr, fpr_len, &url);
if (err)
;
else if (key)
{
int armor_status=opt.no_armor;
import_filter_t save_filt;
/* CERTs and DANE records are always in binary format */
opt.no_armor=1;
if (dane_mode)
{
save_filt = save_and_clear_import_filter ();
if (!save_filt)
err = gpg_error_from_syserror ();
else
{
char *filtstr = es_bsprintf ("keep-uid=mbox = %s", look);
err = filtstr? 0 : gpg_error_from_syserror ();
if (!err)
err = parse_and_set_import_filter (filtstr);
xfree (filtstr);
if (!err)
err = import_keys_es_stream (ctrl, key, NULL, fpr, fpr_len,
IMPORT_NO_SECKEY,
NULL, NULL, KEYORG_DANE, NULL);
restore_import_filter (save_filt);
}
}
else
{
err = import_keys_es_stream (ctrl, key, NULL, fpr, fpr_len,
(opt.keyserver_options.import_options
| IMPORT_NO_SECKEY),
NULL, NULL, 0, NULL);
}
opt.no_armor=armor_status;
es_fclose (key);
key = NULL;
}
else if (*fpr)
{
/* We only consider the IPGP type if a fingerprint was provided.
This lets us select the right key regardless of what a URL
points to, or get the key from a keyserver. */
if(url)
{
struct keyserver_spec *spec;
spec = parse_keyserver_uri (url, 1);
if(spec)
{
err = keyserver_import_fprint (ctrl, *fpr, *fpr_len, spec, 0);
free_keyserver_spec(spec);
}
}
else if (keyserver_any_configured (ctrl))
{
/* If only a fingerprint is provided, try and fetch it from
the configured keyserver. */
err = keyserver_import_fprint (ctrl,
*fpr, *fpr_len, opt.keyserver, 0);
}
else
log_info(_("no keyserver known\n"));
/* Give a better string here? "CERT fingerprint for \"%s\"
found, but no keyserver" " known (use option
--keyserver)\n" ? */
}
xfree(url);
xfree(look);
return err;
}
/* Import key pointed to by a PKA record. Return the requested
fingerprint in fpr. */
gpg_error_t
keyserver_import_pka (ctrl_t ctrl, const char *name,
unsigned char **fpr, size_t *fpr_len)
{
gpg_error_t err;
char *url;
err = gpg_dirmngr_get_pka (ctrl, name, fpr, fpr_len, &url);
if (url && *url && fpr && fpr_len)
{
/* An URL is available. Lookup the key. */
struct keyserver_spec *spec;
spec = parse_keyserver_uri (url, 1);
if (spec)
{
err = keyserver_import_fprint (ctrl, *fpr, *fpr_len, spec, 0);
free_keyserver_spec (spec);
}
}
xfree (url);
if (err)
{
xfree(*fpr);
*fpr = NULL;
*fpr_len = 0;
}
return err;
}
/* Import a key using the Web Key Directory protocol. */
gpg_error_t
keyserver_import_wkd (ctrl_t ctrl, const char *name, int quick,
unsigned char **fpr, size_t *fpr_len)
{
gpg_error_t err;
char *mbox;
estream_t key;
char *url = NULL;
/* We want to work on the mbox. That is what dirmngr will do anyway
* and we need the mbox for the import filter anyway. */
mbox = mailbox_from_userid (name, 0);
if (!mbox)
{
err = gpg_error_from_syserror ();
if (gpg_err_code (err) == GPG_ERR_EINVAL)
err = gpg_error (GPG_ERR_INV_USER_ID);
return err;
}
err = gpg_dirmngr_wkd_get (ctrl, mbox, quick, &key, &url);
if (err)
;
else if (key)
{
int armor_status = opt.no_armor;
import_filter_t save_filt;
- /* Keys returned via WKD are in binary format. */
- opt.no_armor = 1;
+ /* Keys returned via WKD are in binary format. However, we
+ * relax that requirement and allow also for armored data. */
+ opt.no_armor = 0;
save_filt = save_and_clear_import_filter ();
if (!save_filt)
err = gpg_error_from_syserror ();
else
{
char *filtstr = es_bsprintf ("keep-uid=mbox = %s", mbox);
err = filtstr? 0 : gpg_error_from_syserror ();
if (!err)
err = parse_and_set_import_filter (filtstr);
xfree (filtstr);
if (!err)
err = import_keys_es_stream (ctrl, key, NULL, fpr, fpr_len,
IMPORT_NO_SECKEY,
NULL, NULL, KEYORG_WKD, url);
}
restore_import_filter (save_filt);
opt.no_armor = armor_status;
es_fclose (key);
key = NULL;
}
xfree (url);
xfree (mbox);
return err;
}
/* Import a key by name using LDAP */
int
keyserver_import_ldap (ctrl_t ctrl,
const char *name, unsigned char **fpr, size_t *fprlen)
{
(void)ctrl;
(void)name;
(void)fpr;
(void)fprlen;
return gpg_error (GPG_ERR_NOT_IMPLEMENTED); /*FIXME*/
#if 0
char *domain;
struct keyserver_spec *keyserver;
strlist_t list=NULL;
int rc,hostlen=1;
struct srventry *srvlist=NULL;
int srvcount,i;
char srvname[MAXDNAME];
/* Parse out the domain */
domain=strrchr(name,'@');
if(!domain)
return GPG_ERR_GENERAL;
domain++;
keyserver=xmalloc_clear(sizeof(struct keyserver_spec));
keyserver->scheme=xstrdup("ldap");
keyserver->host=xmalloc(1);
keyserver->host[0]='\0';
snprintf(srvname,MAXDNAME,"_pgpkey-ldap._tcp.%s",domain);
FIXME("network related - move to dirmngr or drop the code");
srvcount=getsrv(srvname,&srvlist);
for(i=0;i<srvcount;i++)
{
hostlen+=strlen(srvlist[i].target)+1;
keyserver->host=xrealloc(keyserver->host,hostlen);
strcat(keyserver->host,srvlist[i].target);
if(srvlist[i].port!=389)
{
char port[7];
hostlen+=6; /* a colon, plus 5 digits (unsigned 16-bit value) */
keyserver->host=xrealloc(keyserver->host,hostlen);
snprintf(port,7,":%u",srvlist[i].port);
strcat(keyserver->host,port);
}
strcat(keyserver->host," ");
}
free(srvlist);
/* If all else fails, do the PGP Universal trick of
ldap://keys.(domain) */
hostlen+=5+strlen(domain);
keyserver->host=xrealloc(keyserver->host,hostlen);
strcat(keyserver->host,"keys.");
strcat(keyserver->host,domain);
append_to_strlist(&list,name);
rc = gpg_error (GPG_ERR_NOT_IMPLEMENTED); /*FIXME*/
/* keyserver_work (ctrl, KS_GETNAME, list, NULL, */
/* 0, fpr, fpr_len, keyserver); */
free_strlist(list);
free_keyserver_spec(keyserver);
return rc;
#endif
}
diff --git a/g10/main.h b/g10/main.h
index 34a932b16..f45b03909 100644
--- a/g10/main.h
+++ b/g10/main.h
@@ -1,515 +1,519 @@
/* main.h
* Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007,
* 2008, 2009, 2010 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#ifndef G10_MAIN_H
#define G10_MAIN_H
#include "../common/types.h"
#include "../common/iobuf.h"
#include "keydb.h"
#include "keyedit.h"
#include "../common/util.h"
/* It could be argued that the default cipher should be 3DES rather
than AES128, and the default compression should be 0
(i.e. uncompressed) rather than 1 (zip). However, the real world
issues of speed and size come into play here. */
#if GPG_USE_AES256
# define DEFAULT_CIPHER_ALGO CIPHER_ALGO_AES256
#elif GPG_USE_AES128
# define DEFAULT_CIPHER_ALGO CIPHER_ALGO_AES
#elif GPG_USE_CAST5
# define DEFAULT_CIPHER_ALGO CIPHER_ALGO_CAST5
#else
# define DEFAULT_CIPHER_ALGO CIPHER_ALGO_3DES
#endif
/* We will start using OCB mode by default only if the yet to be
* released libgcrypt 1.9 is used. */
#if GCRYPT_VERSION_NUMBER < 0x010900
# define DEFAULT_AEAD_ALGO AEAD_ALGO_OCB
#else
# define DEFAULT_AEAD_ALGO AEAD_ALGO_EAX
#endif
#define DEFAULT_DIGEST_ALGO ((GNUPG)? DIGEST_ALGO_SHA256:DIGEST_ALGO_SHA1)
#define DEFAULT_S2K_DIGEST_ALGO DIGEST_ALGO_SHA1
#ifdef HAVE_ZIP
# define DEFAULT_COMPRESS_ALGO COMPRESS_ALGO_ZIP
#else
# define DEFAULT_COMPRESS_ALGO COMPRESS_ALGO_NONE
#endif
#define S2K_DIGEST_ALGO (opt.s2k_digest_algo?opt.s2k_digest_algo:DEFAULT_S2K_DIGEST_ALGO)
/* Various data objects. */
typedef struct
{
ctrl_t ctrl;
int header_okay;
PK_LIST pk_list;
DEK *symkey_dek;
STRING2KEY *symkey_s2k;
cipher_filter_context_t cfx;
} encrypt_filter_context_t;
struct groupitem
{
char *name;
strlist_t values;
struct groupitem *next;
};
struct weakhash
{
enum gcry_md_algos algo;
int rejection_shown;
struct weakhash *next;
};
/*-- gpg.c --*/
extern int g10_errors_seen;
#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 5 )
void g10_exit(int rc) __attribute__ ((noreturn));
#else
void g10_exit(int rc);
#endif
void print_pubkey_algo_note (pubkey_algo_t algo);
void print_cipher_algo_note (cipher_algo_t algo);
void print_digest_algo_note (digest_algo_t algo);
void print_digest_rejected_note (enum gcry_md_algos algo);
void print_reported_error (gpg_error_t err, gpg_err_code_t skip_if_ec);
void print_further_info (const char *format, ...) GPGRT_ATTR_PRINTF(1,2);
void additional_weak_digest (const char* digestname);
/*-- armor.c --*/
char *make_radix64_string( const byte *data, size_t len );
/*-- misc.c --*/
void trap_unaligned(void);
void register_secured_file (const char *fname);
void unregister_secured_file (const char *fname);
int is_secured_file (int fd);
int is_secured_filename (const char *fname);
u16 checksum_u16( unsigned n );
u16 checksum( byte *p, unsigned n );
u16 checksum_mpi( gcry_mpi_t a );
u32 buffer_to_u32( const byte *buffer );
const byte *get_session_marker( size_t *rlen );
enum gcry_cipher_algos map_cipher_openpgp_to_gcry (cipher_algo_t algo);
#define openpgp_cipher_open(_a,_b,_c,_d) \
gcry_cipher_open((_a),map_cipher_openpgp_to_gcry((_b)),(_c),(_d))
#define openpgp_cipher_get_algo_keylen(_a) \
gcry_cipher_get_algo_keylen(map_cipher_openpgp_to_gcry((_a)))
#define openpgp_cipher_get_algo_blklen(_a) \
gcry_cipher_get_algo_blklen(map_cipher_openpgp_to_gcry((_a)))
int openpgp_cipher_blocklen (cipher_algo_t algo);
int openpgp_cipher_test_algo(cipher_algo_t algo);
const char *openpgp_cipher_algo_name (cipher_algo_t algo);
gpg_error_t openpgp_aead_test_algo (aead_algo_t algo);
const char *openpgp_aead_algo_name (aead_algo_t algo);
gpg_error_t openpgp_aead_algo_info (aead_algo_t algo,
enum gcry_cipher_modes *r_mode,
unsigned int *r_noncelen);
pubkey_algo_t map_pk_gcry_to_openpgp (enum gcry_pk_algos algo);
int openpgp_pk_test_algo (pubkey_algo_t algo);
int openpgp_pk_test_algo2 (pubkey_algo_t algo, unsigned int use);
int openpgp_pk_algo_usage ( int algo );
const char *openpgp_pk_algo_name (pubkey_algo_t algo);
enum gcry_md_algos map_md_openpgp_to_gcry (digest_algo_t algo);
int openpgp_md_test_algo (digest_algo_t algo);
const char *openpgp_md_algo_name (int algo);
struct expando_args
{
PKT_public_key *pk;
PKT_public_key *pksk;
byte imagetype;
int validity_info;
const char *validity_string;
const byte *namehash;
};
char *pct_expando(const char *string,struct expando_args *args);
void deprecated_warning(const char *configname,unsigned int configlineno,
const char *option,const char *repl1,const char *repl2);
void deprecated_command (const char *name);
void obsolete_scdaemon_option (const char *configname,
unsigned int configlineno, const char *name);
int string_to_cipher_algo (const char *string);
aead_algo_t string_to_aead_algo (const char *string);
int string_to_digest_algo (const char *string);
const char *compress_algo_to_string(int algo);
int string_to_compress_algo(const char *string);
int check_compress_algo(int algo);
int default_cipher_algo(void);
aead_algo_t default_aead_algo(void);
int default_compress_algo(void);
void compliance_failure(void);
struct parse_options
{
char *name;
unsigned int bit;
char **value;
char *help;
};
char *optsep(char **stringp);
char *argsplit(char *string);
int parse_options(char *str,unsigned int *options,
struct parse_options *opts,int noisy);
const char *get_libexecdir (void);
int path_access(const char *file,int mode);
int pubkey_get_npkey (pubkey_algo_t algo);
int pubkey_get_nskey (pubkey_algo_t algo);
int pubkey_get_nsig (pubkey_algo_t algo);
int pubkey_get_nenc (pubkey_algo_t algo);
/* Temporary helpers. */
unsigned int pubkey_nbits( int algo, gcry_mpi_t *pkey );
int mpi_print (estream_t stream, gcry_mpi_t a, int mode);
unsigned int ecdsa_qbits_from_Q (unsigned int qbits);
/*-- cpr.c --*/
void set_status_fd ( int fd );
int is_status_enabled ( void );
void write_status ( int no );
void write_status_error (const char *where, gpg_error_t err);
void write_status_errcode (const char *where, int errcode);
void write_status_failure (const char *where, gpg_error_t err);
void write_status_text ( int no, const char *text );
void write_status_printf (int no, const char *format,
...) GPGRT_ATTR_PRINTF(2,3);
void write_status_strings (int no, const char *text,
...) GPGRT_ATTR_SENTINEL(0);
void write_status_buffer ( int no,
const char *buffer, size_t len, int wrap );
void write_status_text_and_buffer ( int no, const char *text,
const char *buffer, size_t len, int wrap );
void write_status_begin_signing (gcry_md_hd_t md);
int cpr_enabled(void);
char *cpr_get( const char *keyword, const char *prompt );
char *cpr_get_no_help( const char *keyword, const char *prompt );
char *cpr_get_utf8( const char *keyword, const char *prompt );
char *cpr_get_hidden( const char *keyword, const char *prompt );
void cpr_kill_prompt(void);
int cpr_get_answer_is_yes_def (const char *keyword, const char *prompt,
int def_yes);
int cpr_get_answer_is_yes( const char *keyword, const char *prompt );
int cpr_get_answer_yes_no_quit( const char *keyword, const char *prompt );
int cpr_get_answer_okay_cancel (const char *keyword,
const char *prompt,
int def_answer);
/*-- helptext.c --*/
void display_online_help( const char *keyword );
/*-- encode.c --*/
int setup_symkey (STRING2KEY **symkey_s2k,DEK **symkey_dek);
gpg_error_t encrypt_seskey (DEK *dek, aead_algo_t aead_algo, DEK **r_seskey,
void **r_enckey, size_t *r_enckeylen);
aead_algo_t use_aead (pk_list_t pk_list, int algo);
int use_mdc (pk_list_t pk_list,int algo);
int encrypt_symmetric (const char *filename );
int encrypt_store (const char *filename );
int encrypt_crypt (ctrl_t ctrl, int filefd, const char *filename,
strlist_t remusr, int use_symkey, pk_list_t provided_keys,
int outputfd);
void encrypt_crypt_files (ctrl_t ctrl,
int nfiles, char **files, strlist_t remusr);
int encrypt_filter (void *opaque, int control,
iobuf_t a, byte *buf, size_t *ret_len);
int write_pubkey_enc (ctrl_t ctrl, PKT_public_key *pk, int throw_keyid,
DEK *dek, iobuf_t out);
/*-- sign.c --*/
int sign_file (ctrl_t ctrl, strlist_t filenames, int detached, strlist_t locusr,
int do_encrypt, strlist_t remusr, const char *outfile );
int clearsign_file (ctrl_t ctrl,
const char *fname, strlist_t locusr, const char *outfile);
int sign_symencrypt_file (ctrl_t ctrl, const char *fname, strlist_t locusr);
/*-- sig-check.c --*/
void sig_check_dump_stats (void);
/* SIG is a revocation signature. Check if any of PK's designated
revokers generated it. If so, return 0. Note: this function
(correctly) doesn't care if the designated revoker is revoked. */
int check_revocation_keys (ctrl_t ctrl, PKT_public_key *pk, PKT_signature *sig);
/* Check that the backsig BACKSIG from the subkey SUB_PK to its
primary key MAIN_PK is valid. */
int check_backsig(PKT_public_key *main_pk,PKT_public_key *sub_pk,
PKT_signature *backsig);
/* Check that the signature SIG over a key (e.g., a key binding or a
key revocation) is valid. (To check signatures over data, use
check_signature.) */
int check_key_signature (ctrl_t ctrl, kbnode_t root, kbnode_t sig,
int *is_selfsig );
/* Like check_key_signature, but with the ability to specify some
additional parameters and get back additional information. See the
documentation for the implementation for details. */
int check_key_signature2 (ctrl_t ctrl, kbnode_t root, kbnode_t node,
PKT_public_key *check_pk, PKT_public_key *ret_pk,
int *is_selfsig, u32 *r_expiredate, int *r_expired);
/* Returns whether SIGNER generated the signature SIG over the packet
PACKET, which is a key, subkey or uid, and comes from the key block
KB. If SIGNER is NULL, it is looked up based on the information in
SIG. If not NULL, sets *IS_SELFSIG to indicate whether the
signature is a self-signature and *RET_PK to a copy of the signer's
key. */
gpg_error_t check_signature_over_key_or_uid (ctrl_t ctrl,
PKT_public_key *signer,
PKT_signature *sig,
KBNODE kb, PACKET *packet,
int *is_selfsig,
PKT_public_key *ret_pk);
/*-- delkey.c --*/
gpg_error_t delete_keys (ctrl_t ctrl,
strlist_t names, int secret, int allow_both);
/*-- keygen.c --*/
const char *get_default_pubkey_algo (void);
u32 parse_expire_string(const char *string);
u32 ask_expire_interval(int object,const char *def_expire);
u32 ask_expiredate(void);
unsigned int ask_key_flags (int algo, int subkey, unsigned int current);
const char *ask_curve (int *algo, int *subkey_algo, const char *current);
void quick_generate_keypair (ctrl_t ctrl, const char *uid, const char *algostr,
const char *usagestr, const char *expirestr);
void generate_keypair (ctrl_t ctrl, int full, const char *fname,
const char *card_serialno, int card_backup_key);
int keygen_set_std_prefs (const char *string,int personal);
PKT_user_id *keygen_get_std_prefs (void);
int keygen_add_key_expire( PKT_signature *sig, void *opaque );
int keygen_add_key_flags (PKT_signature *sig, void *opaque);
int keygen_add_std_prefs( PKT_signature *sig, void *opaque );
int keygen_upd_std_prefs( PKT_signature *sig, void *opaque );
int keygen_add_keyserver_url(PKT_signature *sig, void *opaque);
int keygen_add_notations(PKT_signature *sig,void *opaque);
int keygen_add_revkey(PKT_signature *sig, void *opaque);
gpg_error_t make_backsig (ctrl_t ctrl,
PKT_signature *sig, PKT_public_key *pk,
PKT_public_key *sub_pk, PKT_public_key *sub_psk,
u32 timestamp, const char *cache_nonce);
gpg_error_t generate_subkeypair (ctrl_t ctrl, kbnode_t keyblock,
const char *algostr,
const char *usagestr,
const char *expirestr);
#ifdef ENABLE_CARD_SUPPORT
gpg_error_t generate_card_subkeypair (ctrl_t ctrl, kbnode_t pub_keyblock,
int keyno, const char *serialno);
#endif
/*-- openfile.c --*/
int overwrite_filep( const char *fname );
char *make_outfile_name( const char *iname );
char *ask_outfile_name( const char *name, size_t namelen );
int open_outfile (int out_fd, const char *iname, int mode,
int restrictedperm, iobuf_t *a);
char *get_matching_datafile (const char *sigfilename);
iobuf_t open_sigfile (const char *sigfilename, progress_filter_context_t *pfx);
void try_make_homedir( const char *fname );
char *get_openpgp_revocdir (const char *home);
/*-- seskey.c --*/
void make_session_key( DEK *dek );
gcry_mpi_t encode_session_key( int openpgp_pk_algo, DEK *dek, unsigned nbits );
gcry_mpi_t encode_md_value (PKT_public_key *pk,
gcry_md_hd_t md, int hash_algo );
/*-- import.c --*/
struct import_stats_s;
typedef struct import_stats_s *import_stats_t;
struct import_filter_s;
typedef struct import_filter_s *import_filter_t;
typedef gpg_error_t (*import_screener_t)(kbnode_t keyblock, void *arg);
int parse_import_options(char *str,unsigned int *options,int noisy);
gpg_error_t parse_and_set_import_filter (const char *string);
import_filter_t save_and_clear_import_filter (void);
void restore_import_filter (import_filter_t filt);
gpg_error_t read_key_from_file (ctrl_t ctrl, const char *fname,
kbnode_t *r_keyblock);
void import_keys (ctrl_t ctrl, char **fnames, int nnames,
import_stats_t stats_hd, unsigned int options,
int origin, const char *url);
gpg_error_t import_keys_es_stream (ctrl_t ctrl, estream_t fp,
import_stats_t stats_handle,
unsigned char **fpr, size_t *fpr_len,
unsigned int options,
import_screener_t screener, void *screener_arg,
int origin, const char *url);
gpg_error_t import_old_secring (ctrl_t ctrl, const char *fname);
import_stats_t import_new_stats_handle (void);
void import_release_stats_handle (import_stats_t hd);
void import_print_stats (import_stats_t hd);
/* Communication for impex_filter_getval */
struct impex_filter_parm_s
{
ctrl_t ctrl;
kbnode_t node;
};
const char *impex_filter_getval (void *cookie, const char *propname);
gpg_error_t transfer_secret_keys (ctrl_t ctrl, struct import_stats_s *stats,
kbnode_t sec_keyblock, int batch, int force,
int only_marked);
int collapse_uids( KBNODE *keyblock );
int get_revocation_reason (PKT_signature *sig, char **r_reason,
char **r_comment, size_t *r_commentlen);
/*-- export.c --*/
struct export_stats_s;
typedef struct export_stats_s *export_stats_t;
export_stats_t export_new_stats (void);
void export_release_stats (export_stats_t stats);
void export_print_stats (export_stats_t stats);
int parse_export_options(char *str,unsigned int *options,int noisy);
gpg_error_t parse_and_set_export_filter (const char *string);
+int exact_subkey_match_p (KEYDB_SEARCH_DESC *desc, kbnode_t node);
+
int export_pubkeys (ctrl_t ctrl, strlist_t users, unsigned int options,
export_stats_t stats);
int export_seckeys (ctrl_t ctrl, strlist_t users, unsigned int options,
export_stats_t stats);
int export_secsubkeys (ctrl_t ctrl, strlist_t users, unsigned int options,
export_stats_t stats);
gpg_error_t export_pubkey_buffer (ctrl_t ctrl, const char *keyspec,
unsigned int options,
export_stats_t stats,
kbnode_t *r_keyblock,
void **r_data, size_t *r_datalen);
gpg_error_t receive_seckey_from_agent (ctrl_t ctrl, gcry_cipher_hd_t cipherhd,
int cleartext,
char **cache_nonce_addr,
const char *hexgrip,
PKT_public_key *pk);
gpg_error_t write_keyblock_to_output (kbnode_t keyblock,
int with_armor, unsigned int options);
gpg_error_t export_ssh_key (ctrl_t ctrl, const char *userid);
/*-- dearmor.c --*/
int dearmor_file( const char *fname );
int enarmor_file( const char *fname );
/*-- revoke.c --*/
struct revocation_reason_info;
int gen_standard_revoke (ctrl_t ctrl,
PKT_public_key *psk, const char *cache_nonce);
int gen_revoke (ctrl_t ctrl, const char *uname);
int gen_desig_revoke (ctrl_t ctrl, const char *uname, strlist_t locusr);
int revocation_reason_build_cb( PKT_signature *sig, void *opaque );
struct revocation_reason_info *
ask_revocation_reason( int key_rev, int cert_rev, int hint );
struct revocation_reason_info * get_default_uid_revocation_reason(void);
void release_revocation_reason_info( struct revocation_reason_info *reason );
/*-- keylist.c --*/
-void public_key_list (ctrl_t ctrl, strlist_t list, int locate_mode );
+void public_key_list (ctrl_t ctrl, strlist_t list,
+ int locate_mode, int no_local);
void secret_key_list (ctrl_t ctrl, strlist_t list );
void print_subpackets_colon(PKT_signature *sig);
void reorder_keyblock (KBNODE keyblock);
void list_keyblock_direct (ctrl_t ctrl, kbnode_t keyblock, int secret,
int has_secret, int fpr, int no_validity);
void print_fingerprint (ctrl_t ctrl, estream_t fp,
PKT_public_key *pk, int mode);
void print_revokers (estream_t fp, PKT_public_key *pk);
void show_policy_url(PKT_signature *sig,int indent,int mode);
void show_keyserver_url(PKT_signature *sig,int indent,int mode);
void show_notation(PKT_signature *sig,int indent,int mode,int which);
void dump_attribs (const PKT_user_id *uid, PKT_public_key *pk);
void set_attrib_fd(int fd);
-char *format_seckey_info (ctrl_t ctrl, PKT_public_key *pk);
-void print_seckey_info (ctrl_t ctrl, PKT_public_key *pk);
-void print_pubkey_info (ctrl_t ctrl, estream_t fp, PKT_public_key *pk);
+void print_key_info (ctrl_t ctrl, estream_t fp, int indent,
+ PKT_public_key *pk, int secret);
+void print_key_info_log (ctrl_t ctrl, int loglevel, int indent,
+ PKT_public_key *pk, int secret);
void print_card_key_info (estream_t fp, KBNODE keyblock);
void print_key_line (ctrl_t ctrl, estream_t fp, PKT_public_key *pk, int secret);
/*-- verify.c --*/
void print_file_status( int status, const char *name, int what );
int verify_signatures (ctrl_t ctrl, int nfiles, char **files );
int verify_files (ctrl_t ctrl, int nfiles, char **files );
int gpg_verify (ctrl_t ctrl, int sig_fd, int data_fd, estream_t out_fp);
/*-- decrypt.c --*/
int decrypt_message (ctrl_t ctrl, const char *filename );
gpg_error_t decrypt_message_fd (ctrl_t ctrl, int input_fd, int output_fd);
void decrypt_messages (ctrl_t ctrl, int nfiles, char *files[]);
/*-- plaintext.c --*/
int hash_datafiles( gcry_md_hd_t md, gcry_md_hd_t md2,
strlist_t files, const char *sigfilename, int textmode);
int hash_datafile_by_fd ( gcry_md_hd_t md, gcry_md_hd_t md2, int data_fd,
int textmode );
PKT_plaintext *setup_plaintext_name(const char *filename,IOBUF iobuf);
/*-- server.c --*/
int gpg_server (ctrl_t);
gpg_error_t gpg_proxy_pinentry_notify (ctrl_t ctrl,
const unsigned char *line);
#ifdef ENABLE_CARD_SUPPORT
/*-- card-util.c --*/
void change_pin (int no, int allow_admin);
void card_status (ctrl_t ctrl, estream_t fp, const char *serialno);
void card_edit (ctrl_t ctrl, strlist_t commands);
gpg_error_t card_generate_subkey (ctrl_t ctrl, kbnode_t pub_keyblock);
int card_store_subkey (KBNODE node, int use);
#endif
/*-- migrate.c --*/
void migrate_secring (ctrl_t ctrl);
#endif /*G10_MAIN_H*/
diff --git a/g10/mainproc.c b/g10/mainproc.c
index 7acf67b1e..8a9005c21 100644
--- a/g10/mainproc.c
+++ b/g10/mainproc.c
@@ -1,2739 +1,2757 @@
/* mainproc.c - handle packets
* Copyright (C) 1998-2009 Free Software Foundation, Inc.
* Copyright (C) 2013-2014 Werner Koch
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "gpg.h"
#include "../common/util.h"
#include "packet.h"
#include "../common/iobuf.h"
#include "options.h"
#include "keydb.h"
#include "filter.h"
#include "main.h"
#include "../common/status.h"
#include "../common/i18n.h"
#include "trustdb.h"
#include "keyserver-internal.h"
#include "photoid.h"
#include "../common/mbox-util.h"
#include "call-dirmngr.h"
#include "../common/compliance.h"
/* Put an upper limit on nested packets. The 32 is an arbitrary
value, a much lower should actually be sufficient. */
#define MAX_NESTING_DEPTH 32
/*
* Object to hold the processing context.
*/
typedef struct mainproc_context *CTX;
struct mainproc_context
{
ctrl_t ctrl;
struct mainproc_context *anchor; /* May be useful in the future. */
PKT_public_key *last_pubkey;
PKT_user_id *last_user_id;
md_filter_context_t mfx;
int sigs_only; /* Process only signatures and reject all other stuff. */
int encrypt_only; /* Process only encryption messages. */
/* Name of the file with the complete signature or the file with the
detached signature. This is currently only used to deduce the
file name of the data file if that has not been given. */
const char *sigfilename;
/* A structure to describe the signed data in case of a detached
signature. */
struct
{
/* A file descriptor of the signed data. Only used if not -1. */
int data_fd;
/* A list of filenames with the data files or NULL. This is only
used if DATA_FD is -1. */
strlist_t data_names;
/* Flag to indicated that either one of the next previous fields
is used. This is only needed for better readability. */
int used;
} signed_data;
DEK *dek;
int last_was_session_key;
kbnode_t list; /* The current list of packets. */
iobuf_t iobuf; /* Used to get the filename etc. */
int trustletter; /* Temporary usage in list_node. */
ulong symkeys; /* Number of symmetrically encrypted session keys. */
struct pubkey_enc_list *pkenc_list; /* List of encryption packets. */
int seen_pkt_encrypted_aead; /* PKT_ENCRYPTED_AEAD packet seen. */
struct {
unsigned int sig_seen:1; /* Set to true if a signature packet
has been seen. */
unsigned int data:1; /* Any data packet seen */
unsigned int uncompress_failed:1;
} any;
};
/* Counter with the number of literal data packets seen. Note that
* this is also bumped at the end of an encryption. This counter is
* used for a basic consistency check of a received PGP message. */
static int literals_seen;
/*** Local prototypes. ***/
static int do_proc_packets (CTX c, iobuf_t a);
static void list_node (CTX c, kbnode_t node);
static void proc_tree (CTX c, kbnode_t node);
/*** Functions. ***/
/* Reset the literal data counter. This is required to setup a new
* decryption or verification context. */
void
reset_literals_seen(void)
{
literals_seen = 0;
}
static void
release_list( CTX c )
{
proc_tree (c, c->list);
release_kbnode (c->list);
while (c->pkenc_list)
{
struct pubkey_enc_list *tmp = c->pkenc_list->next;
mpi_release (c->pkenc_list->data[0]);
mpi_release (c->pkenc_list->data[1]);
xfree (c->pkenc_list);
c->pkenc_list = tmp;
}
c->pkenc_list = NULL;
c->list = NULL;
c->any.data = 0;
c->any.uncompress_failed = 0;
c->last_was_session_key = 0;
c->seen_pkt_encrypted_aead = 0;
xfree (c->dek);
c->dek = NULL;
}
static int
add_onepass_sig (CTX c, PACKET *pkt)
{
kbnode_t node;
if (c->list) /* Add another packet. */
add_kbnode (c->list, new_kbnode (pkt));
else /* Insert the first one. */
c->list = node = new_kbnode (pkt);
return 1;
}
static int
add_gpg_control (CTX c, PACKET *pkt)
{
if ( pkt->pkt.gpg_control->control == CTRLPKT_CLEARSIGN_START )
{
/* New clear text signature.
* Process the last one and reset everything */
release_list(c);
}
if (c->list) /* Add another packet. */
add_kbnode (c->list, new_kbnode (pkt));
else /* Insert the first one. */
c->list = new_kbnode (pkt);
return 1;
}
static int
add_user_id (CTX c, PACKET *pkt)
{
if (!c->list)
{
log_error ("orphaned user ID\n");
return 0;
}
add_kbnode (c->list, new_kbnode (pkt));
return 1;
}
static int
add_subkey (CTX c, PACKET *pkt)
{
if (!c->list)
{
log_error ("subkey w/o mainkey\n");
return 0;
}
add_kbnode (c->list, new_kbnode (pkt));
return 1;
}
static int
add_ring_trust (CTX c, PACKET *pkt)
{
if (!c->list)
{
log_error ("ring trust w/o key\n");
return 0;
}
add_kbnode (c->list, new_kbnode (pkt));
return 1;
}
static int
add_signature (CTX c, PACKET *pkt)
{
kbnode_t node;
c->any.sig_seen = 1;
if (pkt->pkttype == PKT_SIGNATURE && !c->list)
{
/* This is the first signature for the following datafile.
* GPG does not write such packets; instead it always uses
* onepass-sig packets. The drawback of PGP's method
* of prepending the signature to the data is
* that it is not possible to make a signature from data read
* from stdin. (GPG is able to read PGP stuff anyway.) */
node = new_kbnode (pkt);
c->list = node;
return 1;
}
else if (!c->list)
return 0; /* oops (invalid packet sequence)*/
else if (!c->list->pkt)
BUG(); /* so nicht */
/* Add a new signature node item at the end. */
node = new_kbnode (pkt);
add_kbnode (c->list, node);
return 1;
}
static gpg_error_t
symkey_decrypt_seskey (DEK *dek, byte *seskey, size_t slen)
{
gpg_error_t err;
gcry_cipher_hd_t hd;
unsigned int noncelen, keylen;
enum gcry_cipher_modes ciphermode;
if (dek->use_aead)
{
err = openpgp_aead_algo_info (dek->use_aead, &ciphermode, &noncelen);
if (err)
return err;
}
else
{
ciphermode = GCRY_CIPHER_MODE_CFB;
noncelen = 0;
}
/* Check that the session key has a size of 16 to 32 bytes. */
if ((dek->use_aead && (slen < (noncelen + 16 + 16)
|| slen > (noncelen + 32 + 16)))
|| (!dek->use_aead && (slen < 17 || slen > 33)))
{
log_error ( _("weird size for an encrypted session key (%d)\n"),
(int)slen);
return gpg_error (GPG_ERR_BAD_KEY);
}
err = openpgp_cipher_open (&hd, dek->algo, ciphermode, GCRY_CIPHER_SECURE);
if (!err)
err = gcry_cipher_setkey (hd, dek->key, dek->keylen);
if (!err)
err = gcry_cipher_setiv (hd, noncelen? seskey : NULL, noncelen);
if (err)
goto leave;
if (dek->use_aead)
{
byte ad[4];
ad[0] = (0xc0 | PKT_SYMKEY_ENC);
ad[1] = 5;
ad[2] = dek->algo;
ad[3] = dek->use_aead;
err = gcry_cipher_authenticate (hd, ad, 4);
if (err)
goto leave;
gcry_cipher_final (hd);
keylen = slen - noncelen - 16;
err = gcry_cipher_decrypt (hd, seskey+noncelen, keylen, NULL, 0);
if (err)
goto leave;
err = gcry_cipher_checktag (hd, seskey+noncelen+keylen, 16);
if (err)
goto leave;
/* Now we replace the dek components with the real session key to
* decrypt the contents of the sequencing packet. */
if (keylen > DIM(dek->key))
{
err = gpg_error (GPG_ERR_TOO_LARGE);
goto leave;
}
dek->keylen = keylen;
memcpy (dek->key, seskey + noncelen, dek->keylen);
}
else
{
gcry_cipher_decrypt (hd, seskey, slen, NULL, 0 );
/* Here we can only test whether the algo given in decrypted
* session key is a valid OpenPGP algo. With 11 defined
* symmetric algorithms we will miss 4.3% of wrong passphrases
* here. The actual checking is done later during bulk
* decryption; we can't bring this check forward easily. We
* need to use the GPG_ERR_CHECKSUM so that we won't run into
* the gnupg < 2.2 bug compatible case which would terminate the
* process on GPG_ERR_CIPHER_ALGO. Note that with AEAD (above)
* we will have a reliable test here. */
if (openpgp_cipher_test_algo (seskey[0]))
{
err = gpg_error (GPG_ERR_CHECKSUM);
goto leave;
}
/* Now we replace the dek components with the real session key to
* decrypt the contents of the sequencing packet. */
keylen = slen-1;
if (keylen > DIM(dek->key))
{
err = gpg_error (GPG_ERR_TOO_LARGE);
goto leave;
}
dek->algo = seskey[0];
dek->keylen = keylen;
memcpy (dek->key, seskey + 1, dek->keylen);
}
/*log_hexdump( "thekey", dek->key, dek->keylen );*/
leave:
gcry_cipher_close (hd);
return err;
}
static void
proc_symkey_enc (CTX c, PACKET *pkt)
{
gpg_error_t err;
PKT_symkey_enc *enc;
enc = pkt->pkt.symkey_enc;
if (!enc)
log_error ("invalid symkey encrypted packet\n");
else if(!c->dek)
{
int algo = enc->cipher_algo;
const char *s = openpgp_cipher_algo_name (algo);
const char *a = (enc->aead_algo ? openpgp_aead_algo_name (enc->aead_algo)
/**/ : "CFB");
if (!openpgp_cipher_test_algo (algo))
{
if (!opt.quiet)
{
if (enc->seskeylen)
log_info (_("%s.%s encrypted session key\n"), s, a );
else
log_info (_("%s.%s encrypted data\n"), s, a );
}
}
else
log_error (_("encrypted with unknown algorithm %d.%s\n"), algo, a);
if (openpgp_md_test_algo (enc->s2k.hash_algo))
{
log_error(_("passphrase generated with unknown digest"
" algorithm %d\n"),enc->s2k.hash_algo);
s = NULL;
}
c->last_was_session_key = 2;
if (!s || opt.list_only)
goto leave;
if (opt.override_session_key)
{
c->dek = xmalloc_clear (sizeof *c->dek);
if (get_override_session_key (c->dek, opt.override_session_key))
{
xfree (c->dek);
c->dek = NULL;
}
}
else
{
c->dek = passphrase_to_dek (algo, &enc->s2k, 0, 0, NULL, NULL);
if (c->dek)
{
c->dek->symmetric = 1;
c->dek->use_aead = enc->aead_algo;
/* FIXME: This doesn't work perfectly if a symmetric key
comes before a public key in the message - if the
user doesn't know the passphrase, then there is a
chance that the "decrypted" algorithm will happen to
be a valid one, which will make the returned dek
appear valid, so we won't try any public keys that
come later. */
if (enc->seskeylen)
{
err = symkey_decrypt_seskey (c->dek,
enc->seskey, enc->seskeylen);
if (err)
{
log_info ("decryption of the symmetrically encrypted"
" session key failed: %s\n",
gpg_strerror (err));
if (gpg_err_code (err) != GPG_ERR_BAD_KEY
&& gpg_err_code (err) != GPG_ERR_CHECKSUM)
log_fatal ("process terminated to be bug compatible"
" with GnuPG <= 2.2\n");
if (c->dek->s2k_cacheid[0])
{
if (opt.debug)
log_debug ("cleared passphrase cached with ID:"
" %s\n", c->dek->s2k_cacheid);
passphrase_clear_cache (c->dek->s2k_cacheid);
}
xfree (c->dek);
c->dek = NULL;
}
}
else
c->dek->algo_info_printed = 1;
}
}
}
leave:
c->symkeys++;
free_packet (pkt, NULL);
}
static void
proc_pubkey_enc (CTX c, PACKET *pkt)
{
PKT_pubkey_enc *enc;
/* Check whether the secret key is available and store in this case. */
c->last_was_session_key = 1;
enc = pkt->pkt.pubkey_enc;
/*printf("enc: encrypted by a pubkey with keyid %08lX\n", enc->keyid[1] );*/
/* Hmmm: why do I have this algo check here - anyway there is
* function to check it. */
if (opt.verbose)
log_info (_("public key is %s\n"), keystr (enc->keyid));
if (is_status_enabled ())
{
char buf[50];
snprintf (buf, sizeof buf, "%08lX%08lX %d 0",
(ulong)enc->keyid[0], (ulong)enc->keyid[1], enc->pubkey_algo);
write_status_text (STATUS_ENC_TO, buf);
}
if (!opt.list_only && !opt.override_session_key)
{
struct pubkey_enc_list *x = xmalloc (sizeof *x);
x->keyid[0] = enc->keyid[0];
x->keyid[1] = enc->keyid[1];
x->pubkey_algo = enc->pubkey_algo;
x->result = -1;
x->data[0] = x->data[1] = NULL;
if (enc->data[0])
{
x->data[0] = mpi_copy (enc->data[0]);
x->data[1] = mpi_copy (enc->data[1]);
}
x->next = c->pkenc_list;
c->pkenc_list = x;
}
free_packet(pkt, NULL);
}
/*
* Print the list of public key encrypted packets which we could
* not decrypt.
*/
static void
print_pkenc_list (ctrl_t ctrl, struct pubkey_enc_list *list)
{
for (; list; list = list->next)
{
PKT_public_key *pk;
- const char *algstr;
+ char pkstrbuf[PUBKEY_STRING_SIZE];
+ char *p;
- algstr = openpgp_pk_algo_name (list->pubkey_algo);
pk = xmalloc_clear (sizeof *pk);
- if (!algstr)
- algstr = "[?]";
pk->pubkey_algo = list->pubkey_algo;
if (!get_pubkey (ctrl, pk, list->keyid))
{
- char *p;
- log_info (_("encrypted with %u-bit %s key, ID %s, created %s\n"),
- nbits_from_pk (pk), algstr, keystr_from_pk(pk),
+ pubkey_string (pk, pkstrbuf, sizeof pkstrbuf);
+
+ log_info (_("encrypted with %s key, ID %s, created %s\n"),
+ pkstrbuf, keystr_from_pk (pk),
strtimestamp (pk->timestamp));
p = get_user_id_native (ctrl, list->keyid);
log_printf (_(" \"%s\"\n"), p);
xfree (p);
}
else
log_info (_("encrypted with %s key, ID %s\n"),
- algstr, keystr(list->keyid));
+ openpgp_pk_algo_name (list->pubkey_algo),
+ keystr(list->keyid));
free_public_key (pk);
}
}
static void
proc_encrypted (CTX c, PACKET *pkt)
{
int result = 0;
int early_plaintext = literals_seen;
if (pkt->pkttype == PKT_ENCRYPTED_AEAD)
c->seen_pkt_encrypted_aead = 1;
if (early_plaintext)
{
log_info (_("WARNING: multiple plaintexts seen\n"));
write_status_errcode ("decryption.early_plaintext", GPG_ERR_BAD_DATA);
/* We fail only later so that we can print some more info first. */
}
if (!opt.quiet)
{
if (c->symkeys>1)
log_info (_("encrypted with %lu passphrases\n"), c->symkeys);
else if (c->symkeys == 1)
log_info (_("encrypted with 1 passphrase\n"));
print_pkenc_list (c->ctrl, c->pkenc_list);
}
/* Figure out the session key by looking at all pkenc packets. */
if (opt.list_only || c->dek)
;
else if (opt.override_session_key)
{
c->dek = xmalloc_clear (sizeof *c->dek);
result = get_override_session_key (c->dek, opt.override_session_key);
if (result)
{
xfree (c->dek);
c->dek = NULL;
log_info (_("public key decryption failed: %s\n"),
gpg_strerror (result));
write_status_error ("pkdecrypt_failed", result);
}
}
- else
+ else if (c->pkenc_list)
{
c->dek = xmalloc_secure_clear (sizeof *c->dek);
result = get_session_key (c->ctrl, c->pkenc_list, c->dek);
if (is_status_enabled ())
{
struct pubkey_enc_list *list;
for (list = c->pkenc_list; list; list = list->next)
- if (list->result == GPG_ERR_NO_SECKEY)
+ if (list->result != -1)
{
char buf[20];
snprintf (buf, sizeof buf, "%08lX%08lX",
(ulong)list->keyid[0], (ulong)list->keyid[1]);
write_status_text (STATUS_NO_SECKEY, buf);
}
}
if (result)
{
log_info (_("public key decryption failed: %s\n"),
gpg_strerror (result));
write_status_error ("pkdecrypt_failed", result);
/* Error: Delete the DEK. */
xfree (c->dek);
c->dek = NULL;
}
}
if (c->dek && opt.verbose > 1)
log_info (_("public key encrypted data: good DEK\n"));
write_status (STATUS_BEGIN_DECRYPTION);
/*log_debug("dat: %sencrypted data\n", c->dek?"":"conventional ");*/
if (opt.list_only)
result = -1;
else if (!c->dek && !c->last_was_session_key)
{
int algo;
STRING2KEY s2kbuf;
STRING2KEY *s2k = NULL;
int canceled;
if (opt.override_session_key)
{
c->dek = xmalloc_clear (sizeof *c->dek);
result = get_override_session_key (c->dek, opt.override_session_key);
if (result)
{
xfree (c->dek);
c->dek = NULL;
}
}
else
{
/* Assume this is old style conventional encrypted data. */
algo = opt.def_cipher_algo;
if (algo)
log_info (_("assuming %s encrypted data\n"),
openpgp_cipher_algo_name (algo));
else if (openpgp_cipher_test_algo (CIPHER_ALGO_IDEA))
{
algo = opt.def_cipher_algo;
if (!algo)
algo = opt.s2k_cipher_algo;
log_info (_("IDEA cipher unavailable, "
"optimistically attempting to use %s instead\n"),
openpgp_cipher_algo_name (algo));
}
else
{
algo = CIPHER_ALGO_IDEA;
if (!opt.s2k_digest_algo)
{
/* If no digest is given we assume SHA-1. */
s2kbuf.mode = 0;
s2kbuf.hash_algo = DIGEST_ALGO_SHA1;
s2k = &s2kbuf;
}
log_info (_("assuming %s encrypted data\n"), "IDEA");
}
c->dek = passphrase_to_dek (algo, s2k, 0, 0, NULL, &canceled);
if (c->dek)
c->dek->algo_info_printed = 1;
else if (canceled)
result = gpg_error (GPG_ERR_CANCELED);
else
result = gpg_error (GPG_ERR_INV_PASSPHRASE);
}
}
else if (!c->dek)
- result = GPG_ERR_NO_SECKEY;
+ {
+ if (c->symkeys && !c->pkenc_list)
+ result = gpg_error (GPG_ERR_BAD_KEY);
+
+ if (!result)
+ result = gpg_error (GPG_ERR_NO_SECKEY);
+ }
/* Compute compliance with CO_DE_VS. */
if (!result && is_status_enabled ()
/* Symmetric encryption and asymmetric encryption voids compliance. */
&& (c->symkeys != !!c->pkenc_list )
/* Overriding session key voids compliance. */
&& !opt.override_session_key
/* Check symmetric cipher. */
&& gnupg_cipher_is_compliant (CO_DE_VS, c->dek->algo,
GCRY_CIPHER_MODE_CFB))
{
struct pubkey_enc_list *i;
int compliant = 1;
PKT_public_key *pk = xmalloc (sizeof *pk);
if ( !(c->pkenc_list || c->symkeys) )
log_debug ("%s: where else did the session key come from?\n", __func__);
/* Now check that every key used to encrypt the session key is
* compliant. */
for (i = c->pkenc_list; i && compliant; i = i->next)
{
memset (pk, 0, sizeof *pk);
pk->pubkey_algo = i->pubkey_algo;
if (get_pubkey (c->ctrl, pk, i->keyid) != 0
|| ! gnupg_pk_is_compliant (CO_DE_VS, pk->pubkey_algo, pk->pkey,
nbits_from_pk (pk), NULL))
compliant = 0;
release_public_key_parts (pk);
}
xfree (pk);
if (compliant)
write_status_strings (STATUS_DECRYPTION_COMPLIANCE_MODE,
gnupg_status_compliance_flag (CO_DE_VS),
NULL);
}
if (!result)
result = decrypt_data (c->ctrl, c, pkt->pkt.encrypted, c->dek );
/* Trigger the deferred error. */
if (!result && early_plaintext)
result = gpg_error (GPG_ERR_BAD_DATA);
if (result == -1)
;
else if (!result
&& !opt.ignore_mdc_error
&& !pkt->pkt.encrypted->mdc_method
&& !pkt->pkt.encrypted->aead_algo)
{
/* The message has been decrypted but does not carry an MDC or
* uses AEAD encryption. --ignore-mdc-error has also not been
* used. To avoid attacks changing an MDC message to a non-MDC
* message, we fail here. */
log_error (_("WARNING: message was not integrity protected\n"));
if (!pkt->pkt.encrypted->mdc_method
&& (openpgp_cipher_get_algo_blklen (c->dek->algo) == 8
|| c->dek->algo == CIPHER_ALGO_TWOFISH))
{
/* Before 2.2.8 we did not fail hard for a missing MDC if
* one of the old ciphers where used. Although these cases
* are rare in practice we print a hint on how to decrypt
* such messages. */
log_string
(GPGRT_LOGLVL_INFO,
_("Hint: If this message was created before the year 2003 it is\n"
"likely that this message is legitimate. This is because back\n"
"then integrity protection was not widely used.\n"));
log_info (_("Use the option '%s' to decrypt anyway.\n"),
"--ignore-mdc-error");
write_status_errcode ("nomdc_with_legacy_cipher",
GPG_ERR_DECRYPT_FAILED);
}
log_info (_("decryption forced to fail!\n"));
write_status (STATUS_DECRYPTION_FAILED);
}
else if (!result || (gpg_err_code (result) == GPG_ERR_BAD_SIGNATURE
&& !pkt->pkt.encrypted->aead_algo
&& opt.ignore_mdc_error))
{
/* All is fine or for an MDC message the MDC failed but the
* --ignore-mdc-error option is active. For compatibility
* reasons we issue GOODMDC also for AEAD messages. */
write_status (STATUS_DECRYPTION_OKAY);
if (opt.verbose > 1)
log_info(_("decryption okay\n"));
if (pkt->pkt.encrypted->aead_algo)
write_status (STATUS_GOODMDC);
else if (pkt->pkt.encrypted->mdc_method && !result)
write_status (STATUS_GOODMDC);
else
log_info (_("WARNING: message was not integrity protected\n"));
}
else if (gpg_err_code (result) == GPG_ERR_BAD_SIGNATURE
|| gpg_err_code (result) == GPG_ERR_TRUNCATED)
{
glo_ctrl.lasterr = result;
log_error (_("WARNING: encrypted message has been manipulated!\n"));
write_status (STATUS_BADMDC);
write_status (STATUS_DECRYPTION_FAILED);
}
else
{
if ((gpg_err_code (result) == GPG_ERR_BAD_KEY
|| gpg_err_code (result) == GPG_ERR_CHECKSUM
|| gpg_err_code (result) == GPG_ERR_CIPHER_ALGO)
- && *c->dek->s2k_cacheid != '\0')
+ && c->dek && *c->dek->s2k_cacheid != '\0')
{
if (opt.debug)
log_debug ("cleared passphrase cached with ID: %s\n",
c->dek->s2k_cacheid);
passphrase_clear_cache (c->dek->s2k_cacheid);
}
glo_ctrl.lasterr = result;
write_status (STATUS_DECRYPTION_FAILED);
log_error (_("decryption failed: %s\n"), gpg_strerror (result));
/* Hmmm: does this work when we have encrypted using multiple
* ways to specify the session key (symmmetric and PK). */
}
xfree (c->dek);
c->dek = NULL;
free_packet (pkt, NULL);
c->last_was_session_key = 0;
write_status (STATUS_END_DECRYPTION);
/* Bump the counter even if we have not seen a literal data packet
* inside an encryption container. This acts as a sentinel in case
* a misplace extra literal data packets follows after this
* encrypted packet. */
literals_seen++;
}
static int
have_seen_pkt_encrypted_aead( CTX c )
{
CTX cc;
for (cc = c; cc; cc = cc->anchor)
{
if (cc->seen_pkt_encrypted_aead)
return 1;
}
return 0;
}
static void
proc_plaintext( CTX c, PACKET *pkt )
{
PKT_plaintext *pt = pkt->pkt.plaintext;
int any, clearsig, rc;
kbnode_t n;
unsigned char *extrahash;
size_t extrahashlen;
/* This is a literal data packet. Bump a counter for later checks. */
literals_seen++;
if (pt->namelen == 8 && !memcmp( pt->name, "_CONSOLE", 8))
log_info (_("Note: sender requested \"for-your-eyes-only\"\n"));
else if (opt.verbose)
{
/* We don't use print_utf8_buffer because that would require a
* string change which we don't want in 2.2. It is also not
* clear whether the filename is always utf-8 encoded. */
char *tmp = make_printable_string (pt->name, pt->namelen, 0);
log_info (_("original file name='%.*s'\n"), (int)strlen (tmp), tmp);
xfree (tmp);
}
free_md_filter_context (&c->mfx);
if (gcry_md_open (&c->mfx.md, 0, 0))
BUG ();
/* fixme: we may need to push the textfilter if we have sigclass 1
* and no armoring - Not yet tested
* Hmmm, why don't we need it at all if we have sigclass 1
* Should we assume that plaintext in mode 't' has always sigclass 1??
* See: Russ Allbery's mail 1999-02-09
*/
any = clearsig = 0;
for (n=c->list; n; n = n->next )
{
if (n->pkt->pkttype == PKT_ONEPASS_SIG)
{
/* The onepass signature case. */
if (n->pkt->pkt.onepass_sig->digest_algo)
{
if (!opt.skip_verify)
gcry_md_enable (c->mfx.md,
n->pkt->pkt.onepass_sig->digest_algo);
any = 1;
}
}
else if (n->pkt->pkttype == PKT_GPG_CONTROL
&& n->pkt->pkt.gpg_control->control == CTRLPKT_CLEARSIGN_START)
{
/* The clearsigned message case. */
size_t datalen = n->pkt->pkt.gpg_control->datalen;
const byte *data = n->pkt->pkt.gpg_control->data;
/* Check that we have at least the sigclass and one hash. */
if (datalen < 2)
log_fatal ("invalid control packet CTRLPKT_CLEARSIGN_START\n");
/* Note that we don't set the clearsig flag for not-dash-escaped
* documents. */
clearsig = (*data == 0x01);
for (data++, datalen--; datalen; datalen--, data++)
if (!opt.skip_verify)
gcry_md_enable (c->mfx.md, *data);
any = 1;
break; /* Stop here as one-pass signature packets are not
expected. */
}
else if (n->pkt->pkttype == PKT_SIGNATURE)
{
/* The SIG+LITERAL case that PGP used to use. */
if (!opt.skip_verify)
gcry_md_enable (c->mfx.md, n->pkt->pkt.signature->digest_algo);
any = 1;
}
}
if (!any && !opt.skip_verify && !have_seen_pkt_encrypted_aead(c))
{
/* This is for the old GPG LITERAL+SIG case. It's not legal
according to 2440, so hopefully it won't come up that often.
There is no good way to specify what algorithms to use in
that case, so these there are the historical answer. */
gcry_md_enable (c->mfx.md, DIGEST_ALGO_RMD160);
gcry_md_enable (c->mfx.md, DIGEST_ALGO_SHA1);
}
if (DBG_HASHING)
{
gcry_md_debug (c->mfx.md, "verify");
if (c->mfx.md2)
gcry_md_debug (c->mfx.md2, "verify2");
}
rc=0;
if (literals_seen > 1)
{
log_info (_("WARNING: multiple plaintexts seen\n"));
write_status_text (STATUS_ERROR, "proc_pkt.plaintext 89_BAD_DATA");
log_inc_errorcount ();
rc = gpg_error (GPG_ERR_UNEXPECTED);
}
if (!rc)
{
/* It we are in --verify mode, we do not want to output the
* signed text. However, if --output is also used we do what
* has been requested and write out the signed data. */
rc = handle_plaintext (pt, &c->mfx,
(opt.outfp || opt.outfile)? 0 : c->sigs_only,
clearsig);
if (gpg_err_code (rc) == GPG_ERR_EACCES && !c->sigs_only)
{
/* Can't write output but we hash it anyway to check the
signature. */
rc = handle_plaintext( pt, &c->mfx, 1, clearsig );
}
}
if (rc)
log_error ("handle plaintext failed: %s\n", gpg_strerror (rc));
/* We add a marker control packet instead of the plaintext packet.
* This is so that we can later detect invalid packet sequences.
* The apcket is further used to convey extra data from the
* plaintext packet to the signature verification. */
extrahash = xtrymalloc (6 + pt->namelen);
if (!extrahash)
{
/* No way to return an error. */
rc = gpg_error_from_syserror ();
log_error ("malloc failed in %s: %s\n", __func__, gpg_strerror (rc));
extrahashlen = 0;
}
else
{
extrahash[0] = pt->mode;
extrahash[1] = pt->namelen;
if (pt->namelen)
memcpy (extrahash+2, pt->name, pt->namelen);
extrahashlen = 2 + pt->namelen;
extrahash[extrahashlen++] = pt->timestamp >> 24;
extrahash[extrahashlen++] = pt->timestamp >> 16;
extrahash[extrahashlen++] = pt->timestamp >> 8;
extrahash[extrahashlen++] = pt->timestamp ;
}
free_packet (pkt, NULL);
c->last_was_session_key = 0;
n = new_kbnode (create_gpg_control (CTRLPKT_PLAINTEXT_MARK,
extrahash, extrahashlen));
xfree (extrahash);
if (c->list)
add_kbnode (c->list, n);
else
c->list = n;
}
static int
proc_compressed_cb (iobuf_t a, void *info)
{
if ( ((CTX)info)->signed_data.used
&& ((CTX)info)->signed_data.data_fd != -1)
return proc_signature_packets_by_fd (((CTX)info)->ctrl, info, a,
((CTX)info)->signed_data.data_fd);
else
return proc_signature_packets (((CTX)info)->ctrl, info, a,
((CTX)info)->signed_data.data_names,
((CTX)info)->sigfilename );
}
static int
proc_encrypt_cb (iobuf_t a, void *info )
{
CTX c = info;
return proc_encryption_packets (c->ctrl, info, a );
}
static int
proc_compressed (CTX c, PACKET *pkt)
{
PKT_compressed *zd = pkt->pkt.compressed;
int rc;
/*printf("zip: compressed data packet\n");*/
if (c->sigs_only)
rc = handle_compressed (c->ctrl, c, zd, proc_compressed_cb, c);
else if( c->encrypt_only )
rc = handle_compressed (c->ctrl, c, zd, proc_encrypt_cb, c);
else
rc = handle_compressed (c->ctrl, c, zd, NULL, NULL);
if (gpg_err_code (rc) == GPG_ERR_BAD_DATA)
{
if (!c->any.uncompress_failed)
{
CTX cc;
for (cc=c; cc; cc = cc->anchor)
cc->any.uncompress_failed = 1;
log_error ("uncompressing failed: %s\n", gpg_strerror (rc));
}
}
else if (rc)
log_error ("uncompressing failed: %s\n", gpg_strerror (rc));
free_packet (pkt, NULL);
c->last_was_session_key = 0;
return rc;
}
/*
* Check the signature. If R_PK is not NULL a copy of the public key
* used to verify the signature will be stored there, or NULL if not
* found. Returns: 0 = valid signature or an error code
*/
static int
do_check_sig (CTX c, kbnode_t node, const void *extrahash, size_t extrahashlen,
int *is_selfsig,
int *is_expkey, int *is_revkey, PKT_public_key **r_pk)
{
PKT_signature *sig;
gcry_md_hd_t md = NULL;
gcry_md_hd_t md2 = NULL;
gcry_md_hd_t md_good = NULL;
int algo, rc;
if (r_pk)
*r_pk = NULL;
log_assert (node->pkt->pkttype == PKT_SIGNATURE);
if (is_selfsig)
*is_selfsig = 0;
sig = node->pkt->pkt.signature;
algo = sig->digest_algo;
rc = openpgp_md_test_algo (algo);
if (rc)
return rc;
if (sig->sig_class == 0x00)
{
if (c->mfx.md)
{
if (gcry_md_copy (&md, c->mfx.md ))
BUG ();
}
else /* detached signature */
{
/* check_signature() will enable the md. */
if (gcry_md_open (&md, 0, 0 ))
BUG ();
}
}
else if (sig->sig_class == 0x01)
{
/* How do we know that we have to hash the (already hashed) text
in canonical mode ??? (calculating both modes???) */
if (c->mfx.md)
{
if (gcry_md_copy (&md, c->mfx.md ))
BUG ();
if (c->mfx.md2 && gcry_md_copy (&md2, c->mfx.md2))
BUG ();
}
else /* detached signature */
{
log_debug ("Do we really need this here?");
/* check_signature() will enable the md*/
if (gcry_md_open (&md, 0, 0 ))
BUG ();
if (gcry_md_open (&md2, 0, 0 ))
BUG ();
}
}
else if ((sig->sig_class&~3) == 0x10
|| sig->sig_class == 0x18
|| sig->sig_class == 0x1f
|| sig->sig_class == 0x20
|| sig->sig_class == 0x28
|| sig->sig_class == 0x30)
{
if (c->list->pkt->pkttype == PKT_PUBLIC_KEY
|| c->list->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
return check_key_signature (c->ctrl, c->list, node, is_selfsig);
}
else if (sig->sig_class == 0x20)
{
log_error (_("standalone revocation - "
"use \"gpg --import\" to apply\n"));
return GPG_ERR_NOT_PROCESSED;
}
else
{
log_error ("invalid root packet for sigclass %02x\n", sig->sig_class);
return GPG_ERR_SIG_CLASS;
}
}
else
return GPG_ERR_SIG_CLASS;
/* We only get here if we are checking the signature of a binary
(0x00) or text document (0x01). */
rc = check_signature2 (c->ctrl, sig, md, extrahash, extrahashlen,
NULL, is_expkey, is_revkey, r_pk);
if (! rc)
md_good = md;
else if (gpg_err_code (rc) == GPG_ERR_BAD_SIGNATURE && md2)
{
PKT_public_key *pk2;
rc = check_signature2 (c->ctrl, sig, md2, extrahash, extrahashlen,
NULL, is_expkey, is_revkey,
r_pk? &pk2 : NULL);
if (!rc)
{
md_good = md2;
if (r_pk)
{
free_public_key (*r_pk);
*r_pk = pk2;
}
}
}
if (md_good)
{
unsigned char *buffer = gcry_md_read (md_good, sig->digest_algo);
sig->digest_len = gcry_md_get_algo_dlen (map_md_openpgp_to_gcry (algo));
memcpy (sig->digest, buffer, sig->digest_len);
}
gcry_md_close (md);
gcry_md_close (md2);
return rc;
}
static void
print_userid (PACKET *pkt)
{
if (!pkt)
BUG();
if (pkt->pkttype != PKT_USER_ID)
{
es_printf ("ERROR: unexpected packet type %d", pkt->pkttype );
return;
}
if (opt.with_colons)
{
if (pkt->pkt.user_id->attrib_data)
es_printf("%u %lu",
pkt->pkt.user_id->numattribs,
pkt->pkt.user_id->attrib_len);
else
es_write_sanitized (es_stdout, pkt->pkt.user_id->name,
pkt->pkt.user_id->len, ":", NULL);
}
else
print_utf8_buffer (es_stdout, pkt->pkt.user_id->name,
pkt->pkt.user_id->len );
}
/*
* List the keyblock in a user friendly way
*/
static void
list_node (CTX c, kbnode_t node)
{
if (!node)
;
else if (node->pkt->pkttype == PKT_PUBLIC_KEY
|| node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
PKT_public_key *pk = node->pkt->pkt.public_key;
if (opt.with_colons)
{
u32 keyid[2];
keyid_from_pk( pk, keyid );
if (pk->flags.primary)
c->trustletter = (opt.fast_list_mode
? 0
: get_validity_info
(c->ctrl,
node->pkt->pkttype == PKT_PUBLIC_KEY
? node : NULL,
pk, NULL));
es_printf ("%s:", pk->flags.primary? "pub":"sub" );
if (c->trustletter)
es_putc (c->trustletter, es_stdout);
es_printf (":%u:%d:%08lX%08lX:%s:%s::",
nbits_from_pk( pk ),
pk->pubkey_algo,
(ulong)keyid[0],(ulong)keyid[1],
colon_datestr_from_pk( pk ),
colon_strtime (pk->expiredate) );
if (pk->flags.primary && !opt.fast_list_mode)
es_putc (get_ownertrust_info (c->ctrl, pk, 1), es_stdout);
es_putc (':', es_stdout);
es_putc ('\n', es_stdout);
}
else
{
print_key_line (c->ctrl, es_stdout, pk, 0);
}
if (opt.keyid_format == KF_NONE && !opt.with_colons)
; /* Already printed. */
else if ((pk->flags.primary && opt.fingerprint) || opt.fingerprint > 1)
print_fingerprint (c->ctrl, NULL, pk, 0);
if (pk->flags.primary)
{
int kl = opt.keyid_format == KF_NONE? 0 : keystrlen ();
/* Now list all userids with their signatures. */
for (node = node->next; node; node = node->next)
{
if (node->pkt->pkttype == PKT_SIGNATURE)
{
list_node (c, node );
}
else if (node->pkt->pkttype == PKT_USER_ID)
{
if (opt.with_colons)
es_printf ("%s:::::::::",
node->pkt->pkt.user_id->attrib_data?"uat":"uid");
else
es_printf ("uid%*s",
kl + (opt.legacy_list_mode? 9:11),
"" );
print_userid (node->pkt);
if (opt.with_colons)
es_putc (':', es_stdout);
es_putc ('\n', es_stdout);
}
else if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
list_node(c, node );
}
}
}
}
else if (node->pkt->pkttype == PKT_SECRET_KEY
|| node->pkt->pkttype == PKT_SECRET_SUBKEY)
{
log_debug ("FIXME: No way to print secret key packets here\n");
/* fixme: We may use a function to turn a secret key packet into
a public key one and use that here. */
}
else if (node->pkt->pkttype == PKT_SIGNATURE)
{
PKT_signature *sig = node->pkt->pkt.signature;
int is_selfsig = 0;
int rc2 = 0;
size_t n;
char *p;
int sigrc = ' ';
if (!opt.verbose)
return;
if (sig->sig_class == 0x20 || sig->sig_class == 0x30)
es_fputs ("rev", es_stdout);
else
es_fputs ("sig", es_stdout);
if (opt.check_sigs)
{
fflush (stdout);
rc2 = do_check_sig (c, node, NULL, 0, &is_selfsig, NULL, NULL, NULL);
switch (gpg_err_code (rc2))
{
case 0: sigrc = '!'; break;
case GPG_ERR_BAD_SIGNATURE: sigrc = '-'; break;
case GPG_ERR_NO_PUBKEY:
case GPG_ERR_UNUSABLE_PUBKEY: sigrc = '?'; break;
default: sigrc = '%'; break;
}
}
else /* Check whether this is a self signature. */
{
u32 keyid[2];
if (c->list->pkt->pkttype == PKT_PUBLIC_KEY
|| c->list->pkt->pkttype == PKT_SECRET_KEY )
{
keyid_from_pk (c->list->pkt->pkt.public_key, keyid);
if (keyid[0] == sig->keyid[0] && keyid[1] == sig->keyid[1])
is_selfsig = 1;
}
}
if (opt.with_colons)
{
es_putc (':', es_stdout);
if (sigrc != ' ')
es_putc (sigrc, es_stdout);
es_printf ("::%d:%08lX%08lX:%s:%s:", sig->pubkey_algo,
(ulong)sig->keyid[0], (ulong)sig->keyid[1],
colon_datestr_from_sig (sig),
colon_expirestr_from_sig (sig));
if (sig->trust_depth || sig->trust_value)
es_printf ("%d %d",sig->trust_depth,sig->trust_value);
es_putc (':', es_stdout);
if (sig->trust_regexp)
es_write_sanitized (es_stdout, sig->trust_regexp,
strlen (sig->trust_regexp), ":", NULL);
es_putc (':', es_stdout);
}
else
es_printf ("%c %s %s ",
sigrc, keystr (sig->keyid), datestr_from_sig(sig));
if (sigrc == '%')
es_printf ("[%s] ", gpg_strerror (rc2) );
else if (sigrc == '?')
;
else if (is_selfsig)
{
if (opt.with_colons)
es_putc (':', es_stdout);
es_fputs (sig->sig_class == 0x18? "[keybind]":"[selfsig]", es_stdout);
if (opt.with_colons)
es_putc (':', es_stdout);
}
else if (!opt.fast_list_mode)
{
p = get_user_id (c->ctrl, sig->keyid, &n, NULL);
es_write_sanitized (es_stdout, p, n,
opt.with_colons?":":NULL, NULL );
xfree (p);
}
if (opt.with_colons)
es_printf (":%02x%c:", sig->sig_class, sig->flags.exportable?'x':'l');
es_putc ('\n', es_stdout);
}
else
log_error ("invalid node with packet of type %d\n", node->pkt->pkttype);
}
int
proc_packets (ctrl_t ctrl, void *anchor, iobuf_t a )
{
int rc;
CTX c = xmalloc_clear (sizeof *c);
c->ctrl = ctrl;
c->anchor = anchor;
rc = do_proc_packets (c, a);
xfree (c);
return rc;
}
int
proc_signature_packets (ctrl_t ctrl, void *anchor, iobuf_t a,
strlist_t signedfiles, const char *sigfilename )
{
CTX c = xmalloc_clear (sizeof *c);
int rc;
c->ctrl = ctrl;
c->anchor = anchor;
c->sigs_only = 1;
c->signed_data.data_fd = -1;
c->signed_data.data_names = signedfiles;
c->signed_data.used = !!signedfiles;
c->sigfilename = sigfilename;
rc = do_proc_packets (c, a);
/* If we have not encountered any signature we print an error
messages, send a NODATA status back and return an error code.
Using log_error is required because verify_files does not check
error codes for each file but we want to terminate the process
with an error. */
if (!rc && !c->any.sig_seen)
{
write_status_text (STATUS_NODATA, "4");
log_error (_("no signature found\n"));
rc = GPG_ERR_NO_DATA;
}
/* Propagate the signature seen flag upward. Do this only on success
so that we won't issue the nodata status several times. */
if (!rc && c->anchor && c->any.sig_seen)
c->anchor->any.sig_seen = 1;
xfree (c);
return rc;
}
int
proc_signature_packets_by_fd (ctrl_t ctrl,
void *anchor, iobuf_t a, int signed_data_fd )
{
int rc;
CTX c;
c = xtrycalloc (1, sizeof *c);
if (!c)
return gpg_error_from_syserror ();
c->ctrl = ctrl;
c->anchor = anchor;
c->sigs_only = 1;
c->signed_data.data_fd = signed_data_fd;
c->signed_data.data_names = NULL;
c->signed_data.used = (signed_data_fd != -1);
rc = do_proc_packets (c, a);
/* If we have not encountered any signature we print an error
messages, send a NODATA status back and return an error code.
Using log_error is required because verify_files does not check
error codes for each file but we want to terminate the process
with an error. */
if (!rc && !c->any.sig_seen)
{
write_status_text (STATUS_NODATA, "4");
log_error (_("no signature found\n"));
rc = gpg_error (GPG_ERR_NO_DATA);
}
/* Propagate the signature seen flag upward. Do this only on success
so that we won't issue the nodata status several times. */
if (!rc && c->anchor && c->any.sig_seen)
c->anchor->any.sig_seen = 1;
xfree ( c );
return rc;
}
int
proc_encryption_packets (ctrl_t ctrl, void *anchor, iobuf_t a )
{
CTX c = xmalloc_clear (sizeof *c);
int rc;
c->ctrl = ctrl;
c->anchor = anchor;
c->encrypt_only = 1;
rc = do_proc_packets (c, a);
xfree (c);
return rc;
}
static int
check_nesting (CTX c)
{
int level;
for (level=0; c; c = c->anchor)
level++;
if (level > MAX_NESTING_DEPTH)
{
log_error ("input data with too deeply nested packets\n");
write_status_text (STATUS_UNEXPECTED, "1");
return GPG_ERR_BAD_DATA;
}
return 0;
}
static int
do_proc_packets (CTX c, iobuf_t a)
{
PACKET *pkt;
struct parse_packet_ctx_s parsectx;
int rc = 0;
int any_data = 0;
int newpkt;
rc = check_nesting (c);
if (rc)
return rc;
pkt = xmalloc( sizeof *pkt );
c->iobuf = a;
init_packet(pkt);
init_parse_packet (&parsectx, a);
while ((rc=parse_packet (&parsectx, pkt)) != -1)
{
any_data = 1;
if (rc)
{
free_packet (pkt, &parsectx);
/* Stop processing when an invalid packet has been encountered
* but don't do so when we are doing a --list-packets. */
if (gpg_err_code (rc) == GPG_ERR_INV_PACKET
&& opt.list_packets == 0)
break;
continue;
}
newpkt = -1;
if (opt.list_packets)
{
switch (pkt->pkttype)
{
case PKT_PUBKEY_ENC: proc_pubkey_enc (c, pkt); break;
case PKT_SYMKEY_ENC: proc_symkey_enc (c, pkt); break;
case PKT_ENCRYPTED:
case PKT_ENCRYPTED_MDC:
case PKT_ENCRYPTED_AEAD:proc_encrypted (c, pkt); break;
case PKT_COMPRESSED: rc = proc_compressed (c, pkt); break;
default: newpkt = 0; break;
}
}
else if (c->sigs_only)
{
switch (pkt->pkttype)
{
case PKT_PUBLIC_KEY:
case PKT_SECRET_KEY:
case PKT_USER_ID:
case PKT_SYMKEY_ENC:
case PKT_PUBKEY_ENC:
case PKT_ENCRYPTED:
case PKT_ENCRYPTED_MDC:
case PKT_ENCRYPTED_AEAD:
write_status_text( STATUS_UNEXPECTED, "0" );
rc = GPG_ERR_UNEXPECTED;
goto leave;
case PKT_SIGNATURE: newpkt = add_signature (c, pkt); break;
case PKT_PLAINTEXT: proc_plaintext (c, pkt); break;
case PKT_COMPRESSED: rc = proc_compressed (c, pkt); break;
case PKT_ONEPASS_SIG: newpkt = add_onepass_sig (c, pkt); break;
case PKT_GPG_CONTROL: newpkt = add_gpg_control (c, pkt); break;
default: newpkt = 0; break;
}
}
else if (c->encrypt_only)
{
switch (pkt->pkttype)
{
case PKT_PUBLIC_KEY:
case PKT_SECRET_KEY:
case PKT_USER_ID:
write_status_text (STATUS_UNEXPECTED, "0");
rc = GPG_ERR_UNEXPECTED;
goto leave;
case PKT_SIGNATURE: newpkt = add_signature (c, pkt); break;
case PKT_SYMKEY_ENC: proc_symkey_enc (c, pkt); break;
case PKT_PUBKEY_ENC: proc_pubkey_enc (c, pkt); break;
case PKT_ENCRYPTED:
case PKT_ENCRYPTED_MDC:
case PKT_ENCRYPTED_AEAD: proc_encrypted (c, pkt); break;
case PKT_PLAINTEXT: proc_plaintext (c, pkt); break;
case PKT_COMPRESSED: rc = proc_compressed (c, pkt); break;
case PKT_ONEPASS_SIG: newpkt = add_onepass_sig (c, pkt); break;
case PKT_GPG_CONTROL: newpkt = add_gpg_control (c, pkt); break;
default: newpkt = 0; break;
}
}
else
{
switch (pkt->pkttype)
{
case PKT_PUBLIC_KEY:
case PKT_SECRET_KEY:
release_list (c);
c->list = new_kbnode (pkt);
newpkt = 1;
break;
case PKT_PUBLIC_SUBKEY:
case PKT_SECRET_SUBKEY:
newpkt = add_subkey (c, pkt);
break;
case PKT_USER_ID: newpkt = add_user_id (c, pkt); break;
case PKT_SIGNATURE: newpkt = add_signature (c, pkt); break;
case PKT_PUBKEY_ENC: proc_pubkey_enc (c, pkt); break;
case PKT_SYMKEY_ENC: proc_symkey_enc (c, pkt); break;
case PKT_ENCRYPTED:
case PKT_ENCRYPTED_MDC:
case PKT_ENCRYPTED_AEAD: proc_encrypted (c, pkt); break;
case PKT_PLAINTEXT: proc_plaintext (c, pkt); break;
case PKT_COMPRESSED: rc = proc_compressed (c, pkt); break;
case PKT_ONEPASS_SIG: newpkt = add_onepass_sig (c, pkt); break;
case PKT_GPG_CONTROL: newpkt = add_gpg_control(c, pkt); break;
case PKT_RING_TRUST: newpkt = add_ring_trust (c, pkt); break;
default: newpkt = 0; break;
}
}
if (rc)
goto leave;
/* This is a very ugly construct and frankly, I don't remember why
* I used it. Adding the MDC check here is a hack.
* The right solution is to initiate another context for encrypted
* packet and not to reuse the current one ... It works right
* when there is a compression packet between which adds just
* an extra layer.
* Hmmm: Rewrite this whole module here??
*/
if (pkt->pkttype != PKT_SIGNATURE && pkt->pkttype != PKT_MDC)
c->any.data = (pkt->pkttype == PKT_PLAINTEXT);
if (newpkt == -1)
;
else if (newpkt)
{
pkt = xmalloc (sizeof *pkt);
init_packet (pkt);
}
else
free_packet (pkt, &parsectx);
}
if (rc == GPG_ERR_INV_PACKET)
write_status_text (STATUS_NODATA, "3");
if (any_data)
rc = 0;
else if (rc == -1)
write_status_text (STATUS_NODATA, "2");
leave:
release_list (c);
xfree(c->dek);
free_packet (pkt, &parsectx);
deinit_parse_packet (&parsectx);
xfree (pkt);
free_md_filter_context (&c->mfx);
return rc;
}
/* Helper for pka_uri_from_sig to parse the to-be-verified address out
of the notation data. */
static pka_info_t *
get_pka_address (PKT_signature *sig)
{
pka_info_t *pka = NULL;
struct notation *nd,*notation;
notation=sig_to_notation(sig);
for(nd=notation;nd;nd=nd->next)
{
if(strcmp(nd->name,"pka-address@gnupg.org")!=0)
continue; /* Not the notation we want. */
/* For now we only use the first valid PKA notation. In future
we might want to keep additional PKA notations in a linked
list. */
if (is_valid_mailbox (nd->value))
{
pka = xmalloc (sizeof *pka + strlen(nd->value));
pka->valid = 0;
pka->checked = 0;
pka->uri = NULL;
strcpy (pka->email, nd->value);
break;
}
}
free_notation(notation);
return pka;
}
/* Return the URI from a DNS PKA record. If this record has already
be retrieved for the signature we merely return it; if not we go
out and try to get that DNS record. */
static const char *
pka_uri_from_sig (CTX c, PKT_signature *sig)
{
if (!sig->flags.pka_tried)
{
log_assert (!sig->pka_info);
sig->flags.pka_tried = 1;
sig->pka_info = get_pka_address (sig);
if (sig->pka_info)
{
char *url;
unsigned char *fpr;
size_t fprlen;
if (!gpg_dirmngr_get_pka (c->ctrl, sig->pka_info->email,
&fpr, &fprlen, &url))
{
if (fpr && fprlen == sizeof sig->pka_info->fpr)
{
memcpy (sig->pka_info->fpr, fpr, fprlen);
if (url)
{
sig->pka_info->valid = 1;
if (!*url)
xfree (url);
else
sig->pka_info->uri = url;
url = NULL;
}
}
xfree (fpr);
xfree (url);
}
}
}
return sig->pka_info? sig->pka_info->uri : NULL;
}
/* Return true if the AKL has the WKD method specified. */
static int
akl_has_wkd_method (void)
{
struct akl *akl;
for (akl = opt.auto_key_locate; akl; akl = akl->next)
if (akl->type == AKL_WKD)
return 1;
return 0;
}
/* Return the ISSUER fingerprint buffer and its length at R_LEN.
* Returns NULL if not available. The returned buffer is valid as
* long as SIG is not modified. */
const byte *
issuer_fpr_raw (PKT_signature *sig, size_t *r_len)
{
const byte *p;
size_t n;
p = parse_sig_subpkt (sig->hashed, SIGSUBPKT_ISSUER_FPR, &n);
if (p && ((n == 21 && p[0] == 4) || (n == 33 && p[0] == 5)))
{
*r_len = n - 1;
return p+1;
}
*r_len = 0;
return NULL;
}
/* Return the ISSUER fingerprint string in human readable format if
* available. Caller must release the string. */
/* FIXME: Move to another file. */
char *
issuer_fpr_string (PKT_signature *sig)
{
const byte *p;
size_t n;
p = issuer_fpr_raw (sig, &n);
return p? bin2hex (p, n, NULL) : NULL;
}
static void
print_good_bad_signature (int statno, const char *keyid_str, kbnode_t un,
PKT_signature *sig, int rc)
{
char *p;
write_status_text_and_buffer (statno, keyid_str,
un? un->pkt->pkt.user_id->name:"[?]",
un? un->pkt->pkt.user_id->len:3,
-1);
if (un)
p = utf8_to_native (un->pkt->pkt.user_id->name,
un->pkt->pkt.user_id->len, 0);
else
p = xstrdup ("[?]");
if (rc)
log_info (_("BAD signature from \"%s\""), p);
else if (sig->flags.expired)
log_info (_("Expired signature from \"%s\""), p);
else
log_info (_("Good signature from \"%s\""), p);
xfree (p);
}
static int
check_sig_and_print (CTX c, kbnode_t node)
{
PKT_signature *sig = node->pkt->pkt.signature;
const char *astr;
int rc;
int is_expkey = 0;
int is_revkey = 0;
char *issuer_fpr = NULL;
PKT_public_key *pk = NULL; /* The public key for the signature or NULL. */
- int tried_ks_by_fpr;
const void *extrahash = NULL;
size_t extrahashlen = 0;
if (opt.skip_verify)
{
log_info(_("signature verification suppressed\n"));
return 0;
}
/* Check that the message composition is valid.
*
* Per RFC-2440bis (-15) allowed:
*
* S{1,n} -- detached signature.
* S{1,n} P -- old style PGP2 signature
* O{1,n} P S{1,n} -- standard OpenPGP signature.
* C P S{1,n} -- cleartext signature.
*
*
* O = One-Pass Signature packet.
* S = Signature packet.
* P = OpenPGP Message packet (Encrypted | Compressed | Literal)
* (Note that the current rfc2440bis draft also allows
* for a signed message but that does not work as it
* introduces ambiguities.)
* We keep track of these packages using the marker packet
* CTRLPKT_PLAINTEXT_MARK.
* C = Marker packet for cleartext signatures.
*
* We reject all other messages.
*
* Actually we are calling this too often, i.e. for verification of
* each message but better have some duplicate work than to silently
* introduce a bug here.
*/
{
kbnode_t n;
int n_onepass, n_sig;
/* log_debug ("checking signature packet composition\n"); */
/* dump_kbnode (c->list); */
n = c->list;
log_assert (n);
if ( n->pkt->pkttype == PKT_SIGNATURE )
{
/* This is either "S{1,n}" case (detached signature) or
"S{1,n} P" (old style PGP2 signature). */
for (n = n->next; n; n = n->next)
if (n->pkt->pkttype != PKT_SIGNATURE)
break;
if (!n)
; /* Okay, this is a detached signature. */
else if (n->pkt->pkttype == PKT_GPG_CONTROL
&& (n->pkt->pkt.gpg_control->control
== CTRLPKT_PLAINTEXT_MARK) )
{
if (n->next)
goto ambiguous; /* We only allow one P packet. */
extrahash = n->pkt->pkt.gpg_control->data;
extrahashlen = n->pkt->pkt.gpg_control->datalen;
}
else
goto ambiguous;
}
else if (n->pkt->pkttype == PKT_ONEPASS_SIG)
{
/* This is the "O{1,n} P S{1,n}" case (standard signature). */
for (n_onepass=1, n = n->next;
n && n->pkt->pkttype == PKT_ONEPASS_SIG; n = n->next)
n_onepass++;
if (!n || !(n->pkt->pkttype == PKT_GPG_CONTROL
&& (n->pkt->pkt.gpg_control->control
== CTRLPKT_PLAINTEXT_MARK)))
goto ambiguous;
extrahash = n->pkt->pkt.gpg_control->data;
extrahashlen = n->pkt->pkt.gpg_control->datalen;
for (n_sig=0, n = n->next;
n && n->pkt->pkttype == PKT_SIGNATURE; n = n->next)
n_sig++;
if (!n_sig)
goto ambiguous;
/* If we wanted to disallow multiple sig verification, we'd do
* something like this:
*
* if (n)
* goto ambiguous;
*
* However, this can stay allowable as we can't get here. */
if (n_onepass != n_sig)
{
log_info ("number of one-pass packets does not match "
"number of signature packets\n");
goto ambiguous;
}
}
else if (n->pkt->pkttype == PKT_GPG_CONTROL
&& n->pkt->pkt.gpg_control->control == CTRLPKT_CLEARSIGN_START )
{
/* This is the "C P S{1,n}" case (clear text signature). */
n = n->next;
if (!n || !(n->pkt->pkttype == PKT_GPG_CONTROL
&& (n->pkt->pkt.gpg_control->control
== CTRLPKT_PLAINTEXT_MARK)))
goto ambiguous;
extrahash = n->pkt->pkt.gpg_control->data;
extrahashlen = n->pkt->pkt.gpg_control->datalen;
for (n_sig=0, n = n->next;
n && n->pkt->pkttype == PKT_SIGNATURE; n = n->next)
n_sig++;
if (n || !n_sig)
goto ambiguous;
}
else
{
ambiguous:
log_error(_("can't handle this ambiguous signature data\n"));
return 0;
}
}
if (sig->signers_uid)
write_status_buffer (STATUS_NEWSIG,
sig->signers_uid, strlen (sig->signers_uid), 0);
else
write_status_text (STATUS_NEWSIG, NULL);
astr = openpgp_pk_algo_name ( sig->pubkey_algo );
issuer_fpr = issuer_fpr_string (sig);
if (issuer_fpr)
{
log_info (_("Signature made %s\n"), asctimestamp(sig->timestamp));
log_info (_(" using %s key %s\n"),
astr? astr: "?", issuer_fpr);
}
else if (!keystrlen () || keystrlen () > 8)
{
log_info (_("Signature made %s\n"), asctimestamp(sig->timestamp));
log_info (_(" using %s key %s\n"),
astr? astr: "?", keystr(sig->keyid));
}
else /* Legacy format. */
log_info (_("Signature made %s using %s key ID %s\n"),
asctimestamp(sig->timestamp), astr? astr: "?",
keystr(sig->keyid));
/* In verbose mode print the signers UID. */
if (sig->signers_uid)
log_info (_(" issuer \"%s\"\n"), sig->signers_uid);
rc = do_check_sig (c, node, extrahash, extrahashlen,
NULL, &is_expkey, &is_revkey, &pk);
- /* If the key isn't found, check for a preferred keyserver. */
- if (gpg_err_code (rc) == GPG_ERR_NO_PUBKEY && sig->flags.pref_ks)
+ /* If the key isn't found, check for a preferred keyserver. Note
+ * that this is only done if honor-keyserver-url has been set. We
+ * test for this in the loop so that we can show info about the
+ * preferred keyservers. */
+ if (gpg_err_code (rc) == GPG_ERR_NO_PUBKEY
+ && sig->flags.pref_ks)
{
const byte *p;
int seq = 0;
size_t n;
+ int any_pref_ks = 0;
while ((p=enum_sig_subpkt (sig->hashed,SIGSUBPKT_PREF_KS,&n,&seq,NULL)))
{
/* According to my favorite copy editor, in English grammar,
you say "at" if the key is located on a web page, but
"from" if it is located on a keyserver. I'm not going to
even try to make two strings here :) */
log_info(_("Key available at: ") );
print_utf8_buffer (log_get_stream(), p, n);
log_printf ("\n");
+ any_pref_ks = 1;
- if (opt.keyserver_options.options&KEYSERVER_AUTO_KEY_RETRIEVE
- && opt.keyserver_options.options&KEYSERVER_HONOR_KEYSERVER_URL)
+ if ((opt.keyserver_options.options&KEYSERVER_AUTO_KEY_RETRIEVE)
+ && (opt.keyserver_options.options&KEYSERVER_HONOR_KEYSERVER_URL))
{
struct keyserver_spec *spec;
spec = parse_preferred_keyserver (sig);
if (spec)
{
int res;
+ if (DBG_LOOKUP)
+ log_debug ("trying auto-key-retrieve method %s\n",
+ "Pref-KS");
+
free_public_key (pk);
pk = NULL;
glo_ctrl.in_auto_key_retrieve++;
res = keyserver_import_keyid (c->ctrl, sig->keyid,spec, 1);
glo_ctrl.in_auto_key_retrieve--;
if (!res)
rc = do_check_sig (c, node, extrahash, extrahashlen,
NULL, &is_expkey, &is_revkey, &pk);
+ else if (DBG_LOOKUP)
+ log_debug ("lookup via %s failed: %s\n", "Pref-KS",
+ gpg_strerror (res));
free_keyserver_spec (spec);
if (!rc)
break;
}
}
}
+
+ if (any_pref_ks
+ && (opt.keyserver_options.options&KEYSERVER_AUTO_KEY_RETRIEVE)
+ && !(opt.keyserver_options.options&KEYSERVER_HONOR_KEYSERVER_URL))
+ log_info (_("Note: Use '%s' to make use of this info\n"),
+ "--keyserver-option honor-keyserver-url");
+ }
+
+ /* If the above methods didn't work, our next try is to retrieve the
+ * key from the WKD. This requires that WKD is in the AKL and the
+ * Signer's UID is in the signature. */
+ if (gpg_err_code (rc) == GPG_ERR_NO_PUBKEY
+ && (opt.keyserver_options.options & KEYSERVER_AUTO_KEY_RETRIEVE)
+ && !opt.flags.disable_signer_uid
+ && akl_has_wkd_method ()
+ && sig->signers_uid)
+ {
+ int res;
+
+ if (DBG_LOOKUP)
+ log_debug ("trying auto-key-retrieve method %s\n", "WKD");
+ free_public_key (pk);
+ pk = NULL;
+ glo_ctrl.in_auto_key_retrieve++;
+ res = keyserver_import_wkd (c->ctrl, sig->signers_uid, 1, NULL, NULL);
+ glo_ctrl.in_auto_key_retrieve--;
+ /* Fixme: If the fingerprint is embedded in the signature,
+ * compare it to the fingerprint of the returned key. */
+ if (!res)
+ rc = do_check_sig (c, node, extrahash, extrahashlen,
+ NULL, &is_expkey, &is_revkey, &pk);
+ else if (DBG_LOOKUP)
+ log_debug ("lookup via %s failed: %s\n", "WKD", gpg_strerror (res));
}
/* If the avove methods didn't work, our next try is to use the URI
- * from a DNS PKA record. */
+ * from a DNS PKA record. This is a legacy method which will
+ * eventually be removed. */
if (gpg_err_code (rc) == GPG_ERR_NO_PUBKEY
&& (opt.keyserver_options.options & KEYSERVER_AUTO_KEY_RETRIEVE)
&& (opt.keyserver_options.options & KEYSERVER_HONOR_PKA_RECORD))
{
const char *uri = pka_uri_from_sig (c, sig);
if (uri)
{
/* FIXME: We might want to locate the key using the
fingerprint instead of the keyid. */
int res;
struct keyserver_spec *spec;
spec = parse_keyserver_uri (uri, 1);
if (spec)
{
+ if (DBG_LOOKUP)
+ log_debug ("trying auto-key-retrieve method %s\n", "PKA");
+
free_public_key (pk);
pk = NULL;
glo_ctrl.in_auto_key_retrieve++;
res = keyserver_import_keyid (c->ctrl, sig->keyid, spec, 1);
glo_ctrl.in_auto_key_retrieve--;
free_keyserver_spec (spec);
if (!res)
rc = do_check_sig (c, node, extrahash, extrahashlen,
NULL, &is_expkey, &is_revkey, &pk);
+ else if (DBG_LOOKUP)
+ log_debug ("lookup via %s failed: %s\n", "PKA",
+ gpg_strerror (res));
}
}
}
/* If the above methods didn't work, our next try is to locate
* the key via its fingerprint from a keyserver. This requires
- * that the signers fingerprint is encoded in the signature. We
- * favor this over the WKD method (to be tried next), because an
- * arbitrary keyserver is less subject to web bug like monitoring. */
- tried_ks_by_fpr = 0;
+ * that the signers fingerprint is encoded in the signature. */
if (gpg_err_code (rc) == GPG_ERR_NO_PUBKEY
&& (opt.keyserver_options.options&KEYSERVER_AUTO_KEY_RETRIEVE)
&& keyserver_any_configured (c->ctrl))
{
int res;
const byte *p;
size_t n;
p = issuer_fpr_raw (sig, &n);
if (p)
{
+ if (DBG_LOOKUP)
+ log_debug ("trying auto-key-retrieve method %s\n", "KS");
+
/* v4 or v5 packet with a SHA-1/256 fingerprint. */
free_public_key (pk);
pk = NULL;
glo_ctrl.in_auto_key_retrieve++;
res = keyserver_import_fprint (c->ctrl, p, n, opt.keyserver, 1);
- tried_ks_by_fpr = 1;
glo_ctrl.in_auto_key_retrieve--;
if (!res)
rc = do_check_sig (c, node, extrahash, extrahashlen,
NULL, &is_expkey, &is_revkey, &pk);
+ else if (DBG_LOOKUP)
+ log_debug ("lookup via %s failed: %s\n", "KS", gpg_strerror (res));
}
}
- /* If the above methods didn't work, our next try is to retrieve the
- * key from the WKD. */
- if (gpg_err_code (rc) == GPG_ERR_NO_PUBKEY
- && (opt.keyserver_options.options & KEYSERVER_AUTO_KEY_RETRIEVE)
- && !opt.flags.disable_signer_uid
- && akl_has_wkd_method ()
- && sig->signers_uid)
- {
- int res;
-
- free_public_key (pk);
- pk = NULL;
- glo_ctrl.in_auto_key_retrieve++;
- res = keyserver_import_wkd (c->ctrl, sig->signers_uid, 1, NULL, NULL);
- glo_ctrl.in_auto_key_retrieve--;
- /* Fixme: If the fingerprint is embedded in the signature,
- * compare it to the fingerprint of the returned key. */
- if (!res)
- rc = do_check_sig (c, node, extrahash, extrahashlen,
- NULL, &is_expkey, &is_revkey, &pk);
- }
-
- /* If the above methods did't work, our next try is to use a
- * keyserver. */
- if (gpg_err_code (rc) == GPG_ERR_NO_PUBKEY
- && (opt.keyserver_options.options&KEYSERVER_AUTO_KEY_RETRIEVE)
- && !tried_ks_by_fpr
- && keyserver_any_configured (c->ctrl))
- {
- int res;
-
- free_public_key (pk);
- pk = NULL;
- glo_ctrl.in_auto_key_retrieve++;
- res = keyserver_import_keyid (c->ctrl, sig->keyid, opt.keyserver, 1);
- glo_ctrl.in_auto_key_retrieve--;
- if (!res)
- rc = do_check_sig (c, node, extrahash, extrahashlen,
- NULL, &is_expkey, &is_revkey, &pk);
- }
-
if (!rc || gpg_err_code (rc) == GPG_ERR_BAD_SIGNATURE)
{
kbnode_t un, keyblock;
int count = 0;
int statno;
char keyid_str[50];
PKT_public_key *mainpk = NULL;
if (rc)
statno = STATUS_BADSIG;
else if (sig->flags.expired)
statno = STATUS_EXPSIG;
else if (is_expkey)
statno = STATUS_EXPKEYSIG;
else if(is_revkey)
statno = STATUS_REVKEYSIG;
else
statno = STATUS_GOODSIG;
/* FIXME: We should have the public key in PK and thus the
* keyblock has already been fetched. Thus we could use the
* fingerprint or PK itself to lookup the entire keyblock. That
* would best be done with a cache. */
keyblock = get_pubkeyblock_for_sig (c->ctrl, sig);
snprintf (keyid_str, sizeof keyid_str, "%08lX%08lX [uncertain] ",
(ulong)sig->keyid[0], (ulong)sig->keyid[1]);
/* Find and print the primary user ID along with the
"Good|Expired|Bad signature" line. */
for (un=keyblock; un; un = un->next)
{
int valid;
if (un->pkt->pkttype==PKT_PUBLIC_KEY)
{
mainpk = un->pkt->pkt.public_key;
continue;
}
if (un->pkt->pkttype != PKT_USER_ID)
continue;
if (!un->pkt->pkt.user_id->created)
continue;
if (un->pkt->pkt.user_id->flags.revoked)
continue;
if (un->pkt->pkt.user_id->flags.expired)
continue;
if (!un->pkt->pkt.user_id->flags.primary)
continue;
/* We want the textual primary user ID here */
if (un->pkt->pkt.user_id->attrib_data)
continue;
log_assert (mainpk);
/* Since this is just informational, don't actually ask the
user to update any trust information. (Note: we register
the signature later.) Because print_good_bad_signature
does not print a LF we need to compute the validity
before calling that function. */
if ((opt.verify_options & VERIFY_SHOW_UID_VALIDITY))
valid = get_validity (c->ctrl, keyblock, mainpk,
un->pkt->pkt.user_id, NULL, 0);
else
valid = 0; /* Not used. */
keyid_str[17] = 0; /* cut off the "[uncertain]" part */
print_good_bad_signature (statno, keyid_str, un, sig, rc);
if ((opt.verify_options & VERIFY_SHOW_UID_VALIDITY))
log_printf (" [%s]\n",trust_value_to_string(valid));
else
log_printf ("\n");
count++;
}
log_assert (mainpk);
/* In case we did not found a valid textual userid above
we print the first user id packet or a "[?]" instead along
with the "Good|Expired|Bad signature" line. */
if (!count)
{
/* Try for an invalid textual userid */
for (un=keyblock; un; un = un->next)
{
if (un->pkt->pkttype == PKT_USER_ID
&& !un->pkt->pkt.user_id->attrib_data)
break;
}
/* Try for any userid at all */
if (!un)
{
for (un=keyblock; un; un = un->next)
{
if (un->pkt->pkttype == PKT_USER_ID)
break;
}
}
if (opt.trust_model==TM_ALWAYS || !un)
keyid_str[17] = 0; /* cut off the "[uncertain]" part */
print_good_bad_signature (statno, keyid_str, un, sig, rc);
if (opt.trust_model != TM_ALWAYS && un)
log_printf (" %s",_("[uncertain]") );
log_printf ("\n");
}
/* If we have a good signature and already printed
* the primary user ID, print all the other user IDs */
if (count
&& !rc
&& !(opt.verify_options & VERIFY_SHOW_PRIMARY_UID_ONLY))
{
char *p;
for( un=keyblock; un; un = un->next)
{
if (un->pkt->pkttype != PKT_USER_ID)
continue;
if ((un->pkt->pkt.user_id->flags.revoked
|| un->pkt->pkt.user_id->flags.expired)
&& !(opt.verify_options & VERIFY_SHOW_UNUSABLE_UIDS))
continue;
/* Skip textual primary user ids which we printed above. */
if (un->pkt->pkt.user_id->flags.primary
&& !un->pkt->pkt.user_id->attrib_data )
continue;
/* If this user id has attribute data, print that. */
if (un->pkt->pkt.user_id->attrib_data)
{
dump_attribs (un->pkt->pkt.user_id, mainpk);
if (opt.verify_options&VERIFY_SHOW_PHOTOS)
show_photos (c->ctrl,
un->pkt->pkt.user_id->attribs,
un->pkt->pkt.user_id->numattribs,
mainpk ,un->pkt->pkt.user_id);
}
p = utf8_to_native (un->pkt->pkt.user_id->name,
un->pkt->pkt.user_id->len, 0);
log_info (_(" aka \"%s\""), p);
xfree (p);
if ((opt.verify_options & VERIFY_SHOW_UID_VALIDITY))
{
const char *valid;
if (un->pkt->pkt.user_id->flags.revoked)
valid = _("revoked");
else if (un->pkt->pkt.user_id->flags.expired)
valid = _("expired");
else
/* Since this is just informational, don't
actually ask the user to update any trust
information. */
valid = (trust_value_to_string
(get_validity (c->ctrl, keyblock, mainpk,
un->pkt->pkt.user_id, NULL, 0)));
log_printf (" [%s]\n",valid);
}
else
log_printf ("\n");
}
}
/* For good signatures print notation data. */
if (!rc)
{
if ((opt.verify_options & VERIFY_SHOW_POLICY_URLS))
show_policy_url (sig, 0, 1);
else
show_policy_url (sig, 0, 2);
if ((opt.verify_options & VERIFY_SHOW_KEYSERVER_URLS))
show_keyserver_url (sig, 0, 1);
else
show_keyserver_url (sig, 0, 2);
if ((opt.verify_options & VERIFY_SHOW_NOTATIONS))
show_notation
(sig, 0, 1,
(((opt.verify_options&VERIFY_SHOW_STD_NOTATIONS)?1:0)
+ ((opt.verify_options&VERIFY_SHOW_USER_NOTATIONS)?2:0)));
else
show_notation (sig, 0, 2, 0);
}
/* For good signatures print the VALIDSIG status line. */
if (!rc && is_status_enabled () && pk)
{
char pkhex[MAX_FINGERPRINT_LEN*2+1];
char mainpkhex[MAX_FINGERPRINT_LEN*2+1];
hexfingerprint (pk, pkhex, sizeof pkhex);
hexfingerprint (mainpk, mainpkhex, sizeof mainpkhex);
/* TODO: Replace the reserved '0' in the field below with
bits for status flags (policy url, notation, etc.). */
write_status_printf (STATUS_VALIDSIG,
"%s %s %lu %lu %d 0 %d %d %02X %s",
pkhex,
strtimestamp (sig->timestamp),
(ulong)sig->timestamp,
(ulong)sig->expiredate,
sig->version, sig->pubkey_algo,
sig->digest_algo,
sig->sig_class,
mainpkhex);
}
/* Print compliance warning for Good signatures. */
if (!rc && pk && !opt.quiet
&& !gnupg_pk_is_compliant (opt.compliance, pk->pubkey_algo,
pk->pkey, nbits_from_pk (pk), NULL))
{
log_info (_("WARNING: This key is not suitable for signing"
" in %s mode\n"),
gnupg_compliance_option_string (opt.compliance));
}
/* For good signatures compute and print the trust information.
Note that in the Tofu trust model this may ask the user on
how to resolve a conflict. */
if (!rc)
{
if ((opt.verify_options & VERIFY_PKA_LOOKUPS))
pka_uri_from_sig (c, sig); /* Make sure PKA info is available. */
rc = check_signatures_trust (c->ctrl, sig);
}
/* Print extra information about the signature. */
if (sig->flags.expired)
{
log_info (_("Signature expired %s\n"), asctimestamp(sig->expiredate));
rc = GPG_ERR_GENERAL; /* Need a better error here? */
}
else if (sig->expiredate)
log_info (_("Signature expires %s\n"), asctimestamp(sig->expiredate));
if (opt.verbose)
{
char pkstrbuf[PUBKEY_STRING_SIZE];
if (pk)
pubkey_string (pk, pkstrbuf, sizeof pkstrbuf);
else
*pkstrbuf = 0;
log_info (_("%s signature, digest algorithm %s%s%s\n"),
sig->sig_class==0x00?_("binary"):
sig->sig_class==0x01?_("textmode"):_("unknown"),
gcry_md_algo_name (sig->digest_algo),
*pkstrbuf?_(", key algorithm "):"", pkstrbuf);
}
/* Print final warnings. */
if (!rc && !c->signed_data.used)
{
/* Signature is basically good but we test whether the
deprecated command
gpg --verify FILE.sig
was used instead of
gpg --verify FILE.sig FILE
to verify a detached signature. If we figure out that a
data file with a matching name exists, we print a warning.
The problem is that the first form would also verify a
standard signature. This behavior could be used to
create a made up .sig file for a tarball by creating a
standard signature from a valid detached signature packet
(for example from a signed git tag). Then replace the
sig file on the FTP server along with a changed tarball.
Using the first form the verify command would correctly
verify the signature but don't even consider the tarball. */
kbnode_t n;
char *dfile;
dfile = get_matching_datafile (c->sigfilename);
if (dfile)
{
for (n = c->list; n; n = n->next)
if (n->pkt->pkttype != PKT_SIGNATURE)
break;
if (n)
{
/* Not only signature packets in the tree thus this
is not a detached signature. */
log_info (_("WARNING: not a detached signature; "
"file '%s' was NOT verified!\n"), dfile);
}
xfree (dfile);
}
}
/* Compute compliance with CO_DE_VS. */
if (pk && is_status_enabled ()
&& gnupg_pk_is_compliant (CO_DE_VS, pk->pubkey_algo, pk->pkey,
nbits_from_pk (pk), NULL)
&& gnupg_digest_is_compliant (CO_DE_VS, sig->digest_algo))
write_status_strings (STATUS_VERIFICATION_COMPLIANCE_MODE,
gnupg_status_compliance_flag (CO_DE_VS),
NULL);
free_public_key (pk);
pk = NULL;
release_kbnode( keyblock );
if (rc)
g10_errors_seen = 1;
if (opt.batch && rc)
g10_exit (1);
}
else
{
write_status_printf (STATUS_ERRSIG, "%08lX%08lX %d %d %02x %lu %d %s",
(ulong)sig->keyid[0], (ulong)sig->keyid[1],
sig->pubkey_algo, sig->digest_algo,
sig->sig_class, (ulong)sig->timestamp,
gpg_err_code (rc),
issuer_fpr? issuer_fpr:"-");
if (gpg_err_code (rc) == GPG_ERR_NO_PUBKEY)
{
write_status_printf (STATUS_NO_PUBKEY, "%08lX%08lX",
(ulong)sig->keyid[0], (ulong)sig->keyid[1]);
}
if (gpg_err_code (rc) != GPG_ERR_NOT_PROCESSED)
log_error (_("Can't check signature: %s\n"), gpg_strerror (rc));
}
free_public_key (pk);
xfree (issuer_fpr);
return rc;
}
/*
* Process the tree which starts at node
*/
static void
proc_tree (CTX c, kbnode_t node)
{
kbnode_t n1;
int rc;
if (opt.list_packets || opt.list_only)
return;
/* We must skip our special plaintext marker packets here because
they may be the root packet. These packets are only used in
additional checks and skipping them here doesn't matter. */
while (node
&& node->pkt->pkttype == PKT_GPG_CONTROL
&& node->pkt->pkt.gpg_control->control == CTRLPKT_PLAINTEXT_MARK)
{
node = node->next;
}
if (!node)
return;
c->trustletter = ' ';
if (node->pkt->pkttype == PKT_PUBLIC_KEY
|| node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
merge_keys_and_selfsig (c->ctrl, node);
list_node (c, node);
}
else if (node->pkt->pkttype == PKT_SECRET_KEY)
{
merge_keys_and_selfsig (c->ctrl, node);
list_node (c, node);
}
else if (node->pkt->pkttype == PKT_ONEPASS_SIG)
{
/* Check all signatures. */
if (!c->any.data)
{
int use_textmode = 0;
free_md_filter_context (&c->mfx);
/* Prepare to create all requested message digests. */
rc = gcry_md_open (&c->mfx.md, 0, 0);
if (rc)
goto hash_err;
/* Fixme: why looking for the signature packet and not the
one-pass packet? */
for (n1 = node; (n1 = find_next_kbnode (n1, PKT_SIGNATURE));)
gcry_md_enable (c->mfx.md, n1->pkt->pkt.signature->digest_algo);
if (n1 && n1->pkt->pkt.onepass_sig->sig_class == 0x01)
use_textmode = 1;
/* Ask for file and hash it. */
if (c->sigs_only)
{
if (c->signed_data.used && c->signed_data.data_fd != -1)
rc = hash_datafile_by_fd (c->mfx.md, NULL,
c->signed_data.data_fd,
use_textmode);
else
rc = hash_datafiles (c->mfx.md, NULL,
c->signed_data.data_names,
c->sigfilename,
use_textmode);
}
else
{
rc = ask_for_detached_datafile (c->mfx.md, c->mfx.md2,
iobuf_get_real_fname (c->iobuf),
use_textmode);
}
hash_err:
if (rc)
{
log_error ("can't hash datafile: %s\n", gpg_strerror (rc));
return;
}
}
else if (c->signed_data.used)
{
log_error (_("not a detached signature\n"));
return;
}
for (n1 = node; (n1 = find_next_kbnode (n1, PKT_SIGNATURE));)
check_sig_and_print (c, n1);
}
else if (node->pkt->pkttype == PKT_GPG_CONTROL
&& node->pkt->pkt.gpg_control->control == CTRLPKT_CLEARSIGN_START)
{
/* Clear text signed message. */
if (!c->any.data)
{
log_error ("cleartext signature without data\n");
return;
}
else if (c->signed_data.used)
{
log_error (_("not a detached signature\n"));
return;
}
for (n1 = node; (n1 = find_next_kbnode (n1, PKT_SIGNATURE));)
check_sig_and_print (c, n1);
}
else if (node->pkt->pkttype == PKT_SIGNATURE)
{
PKT_signature *sig = node->pkt->pkt.signature;
int multiple_ok = 1;
n1 = find_next_kbnode (node, PKT_SIGNATURE);
if (n1)
{
byte class = sig->sig_class;
byte hash = sig->digest_algo;
for (; n1; (n1 = find_next_kbnode(n1, PKT_SIGNATURE)))
{
/* We can't currently handle multiple signatures of
* different classes (we'd pretty much have to run a
* different hash context for each), but if they are all
* the same and it is detached signature, we make an
* exception. Note that the old code also disallowed
* multiple signatures if the digest algorithms are
* different. We softened this restriction only for
* detached signatures, to be on the safe side. */
if (n1->pkt->pkt.signature->sig_class != class
|| (c->any.data
&& n1->pkt->pkt.signature->digest_algo != hash))
{
multiple_ok = 0;
log_info (_("WARNING: multiple signatures detected. "
"Only the first will be checked.\n"));
break;
}
}
}
if (sig->sig_class != 0x00 && sig->sig_class != 0x01)
{
log_info(_("standalone signature of class 0x%02x\n"), sig->sig_class);
}
else if (!c->any.data)
{
/* Detached signature */
free_md_filter_context (&c->mfx);
rc = gcry_md_open (&c->mfx.md, sig->digest_algo, 0);
if (rc)
goto detached_hash_err;
if (multiple_ok)
{
/* If we have and want to handle multiple signatures we
* need to enable all hash algorithms for the context. */
for (n1 = node; (n1 = find_next_kbnode (n1, PKT_SIGNATURE)); )
if (!openpgp_md_test_algo (n1->pkt->pkt.signature->digest_algo))
gcry_md_enable (c->mfx.md,
map_md_openpgp_to_gcry
(n1->pkt->pkt.signature->digest_algo));
}
if (RFC2440 || RFC4880)
; /* Strict RFC mode. */
else if (sig->digest_algo == DIGEST_ALGO_SHA1
&& sig->pubkey_algo == PUBKEY_ALGO_DSA
&& sig->sig_class == 0x01)
{
/* Enable a workaround for a pgp5 bug when the detached
* signature has been created in textmode. Note that we
* do not implement this for multiple signatures with
* different hash algorithms. */
rc = gcry_md_open (&c->mfx.md2, sig->digest_algo, 0);
if (rc)
goto detached_hash_err;
}
/* Here we used to have another hack to work around a pgp
* 2 bug: It worked by not using the textmode for detached
* signatures; this would let the first signature check
* (on md) fail but the second one (on md2), which adds an
* extra CR would then have produced the "correct" hash.
* This is very, very ugly hack but it may haved help in
* some cases (and break others).
* c->mfx.md2? 0 :(sig->sig_class == 0x01)
*/
if (DBG_HASHING)
{
gcry_md_debug (c->mfx.md, "verify");
if (c->mfx.md2)
gcry_md_debug (c->mfx.md2, "verify2");
}
if (c->sigs_only)
{
if (c->signed_data.used && c->signed_data.data_fd != -1)
rc = hash_datafile_by_fd (c->mfx.md, c->mfx.md2,
c->signed_data.data_fd,
(sig->sig_class == 0x01));
else
rc = hash_datafiles (c->mfx.md, c->mfx.md2,
c->signed_data.data_names,
c->sigfilename,
(sig->sig_class == 0x01));
}
else
{
rc = ask_for_detached_datafile (c->mfx.md, c->mfx.md2,
iobuf_get_real_fname(c->iobuf),
(sig->sig_class == 0x01));
}
detached_hash_err:
if (rc)
{
log_error ("can't hash datafile: %s\n", gpg_strerror (rc));
return;
}
}
else if (c->signed_data.used)
{
log_error (_("not a detached signature\n"));
return;
}
else if (!opt.quiet)
log_info (_("old style (PGP 2.x) signature\n"));
if (multiple_ok)
{
for (n1 = node; n1; (n1 = find_next_kbnode(n1, PKT_SIGNATURE)))
check_sig_and_print (c, n1);
}
else
check_sig_and_print (c, node);
}
else
{
dump_kbnode (c->list);
log_error ("invalid root packet detected in proc_tree()\n");
dump_kbnode (node);
}
}
diff --git a/g10/objcache.c b/g10/objcache.c
new file mode 100644
index 000000000..adb0717d7
--- /dev/null
+++ b/g10/objcache.c
@@ -0,0 +1,689 @@
+/* objcache.c - Caching functions for keys and user ids.
+ * Copyright (C) 2019 g10 Code GmbH
+ *
+ * This file is part of GnuPG.
+ *
+ * GnuPG is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GnuPG is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses/>.
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "gpg.h"
+#include "../common/util.h"
+#include "packet.h"
+#include "keydb.h"
+#include "options.h"
+#include "objcache.h"
+
+/* Note that max value for uid_items is actually a the threshold when
+ * we start to look for ietms which can be removed. */
+#define NO_OF_UID_ITEM_BUCKETS 107
+#define MAX_UID_ITEMS_PER_BUCKET 20
+
+#define NO_OF_KEY_ITEM_BUCKETS 383
+#define MAX_KEY_ITEMS_PER_BUCKET 20
+
+
+/* An object to store a user id. This describes an item in the linked
+ * lists of a bucket in hash table. The reference count will
+ * eventually be used to remove items from the table. */
+typedef struct uid_item_s
+{
+ struct uid_item_s *next;
+ unsigned int refcount; /* The reference count for this item. */
+ unsigned int namelen; /* The length of the UID sans the nul. */
+ char name[1];
+} *uid_item_t;
+
+static uid_item_t *uid_table; /* Hash table for with user ids. */
+static size_t uid_table_size; /* Number of allocated buckets. */
+static unsigned int uid_table_max; /* Max. # of items in a bucket. */
+static unsigned int uid_table_added; /* # of items added. */
+static unsigned int uid_table_dropped;/* # of items dropped. */
+
+
+/* An object to store properties of a key. Note that this can be used
+ * for a primary or a subkey. The key is linked to a user if that
+ * exists. */
+typedef struct key_item_s
+{
+ struct key_item_s *next;
+ unsigned int usecount;
+ byte fprlen;
+ char fpr[MAX_FINGERPRINT_LEN];
+ u32 keyid[2];
+ uid_item_t ui; /* NULL of a ref'ed user id item. */
+} *key_item_t;
+
+static key_item_t *key_table; /* Hash table with the keys. */
+static size_t key_table_size; /* Number of allocated buckents. */
+static unsigned int key_table_max; /* Max. # of items in a bucket. */
+static unsigned int key_table_added; /* # of items added. */
+static unsigned int key_table_dropped;/* # of items dropped. */
+static key_item_t key_item_attic; /* List of freed items. */
+
+
+
+/* Dump stats. */
+void
+objcache_dump_stats (void)
+{
+ unsigned int idx;
+ int len, minlen, maxlen;
+ unsigned int count, attic, empty;
+ key_item_t ki;
+ uid_item_t ui;
+
+ count = empty = 0;
+ minlen = -1;
+ maxlen = 0;
+ for (idx = 0; idx < key_table_size; idx++)
+ {
+ len = 0;
+ for (ki = key_table[idx]; ki; ki = ki->next)
+ {
+ count++;
+ len++;
+ /* log_debug ("key bucket %u: kid=%08lX used=%u ui=%p\n", */
+ /* idx, (ulong)ki->keyid[0], ki->usecount, ki->ui); */
+ }
+ if (len > maxlen)
+ maxlen = len;
+
+ if (!len)
+ empty++;
+ else if (minlen == -1 || len < minlen)
+ minlen = len;
+ }
+ for (attic=0, ki = key_item_attic; ki; ki = ki->next)
+ attic++;
+ log_info ("objcache: keys=%u/%u/%u chains=%u,%d..%d buckets=%zu/%u"
+ " attic=%u\n",
+ count, key_table_added, key_table_dropped,
+ empty, minlen > 0? minlen : 0, maxlen,
+ key_table_size, key_table_max, attic);
+
+ count = empty = 0;
+ minlen = -1;
+ maxlen = 0;
+ for (idx = 0; idx < uid_table_size; idx++)
+ {
+ len = 0;
+ for (ui = uid_table[idx]; ui; ui = ui->next)
+ {
+ count++;
+ len++;
+ /* log_debug ("uid bucket %u: %p ref=%u l=%u (%.20s)\n", */
+ /* idx, ui, ui->refcount, ui->namelen, ui->name); */
+ }
+ if (len > maxlen)
+ maxlen = len;
+
+ if (!len)
+ empty++;
+ else if (minlen == -1 || len < minlen)
+ minlen = len;
+ }
+ log_info ("objcache: uids=%u/%u/%u chains=%u,%d..%d buckets=%zu/%u\n",
+ count, uid_table_added, uid_table_dropped,
+ empty, minlen > 0? minlen : 0, maxlen,
+ uid_table_size, uid_table_max);
+}
+
+
+
+/* The hash function we use for the uid_table. Must not call a system
+ * function. */
+static inline unsigned int
+uid_table_hasher (const char *name, unsigned namelen)
+{
+ const unsigned char *s = (const unsigned char*)name;
+ unsigned int hashval = 0;
+ unsigned int carry;
+
+ for (; namelen; namelen--, s++)
+ {
+ hashval = (hashval << 4) + *s;
+ if ((carry = (hashval & 0xf0000000)))
+ {
+ hashval ^= (carry >> 24);
+ hashval ^= carry;
+ }
+ }
+
+ return hashval % uid_table_size;
+}
+
+
+/* Run time allocation of the uid table. This allows us to eventually
+ * add an option to gpg to control the size. */
+static void
+uid_table_init (void)
+{
+ if (uid_table)
+ return;
+ uid_table_size = NO_OF_UID_ITEM_BUCKETS;
+ uid_table_max = MAX_UID_ITEMS_PER_BUCKET;
+ uid_table = xcalloc (uid_table_size, sizeof *uid_table);
+}
+
+
+static uid_item_t
+uid_item_ref (uid_item_t ui)
+{
+ if (ui)
+ ui->refcount++;
+ return ui;
+}
+
+static void
+uid_item_unref (uid_item_t uid)
+{
+ if (!uid)
+ return;
+ if (!uid->refcount)
+ log_fatal ("too many unrefs for uid_item\n");
+
+ uid->refcount--;
+ /* We do not release the item here because that would require that
+ * we locate the head of the list which has this item. This will
+ * take too long and thus the item is removed when we need to purge
+ * some items for the list during uid_item_put. */
+}
+
+
+/* Put (NAME,NAMELEN) into the UID_TABLE and return the item. The
+ * reference count for that item is incremented. NULL is return on an
+ * allocation error. The caller should release the returned item
+ * using uid_item_unref. */
+static uid_item_t
+uid_table_put (const char *name, unsigned int namelen)
+{
+ unsigned int hash;
+ uid_item_t ui;
+ unsigned int count;
+
+ if (!uid_table)
+ uid_table_init ();
+
+ hash = uid_table_hasher (name, namelen);
+ for (ui = uid_table[hash], count = 0; ui; ui = ui->next, count++)
+ if (ui->namelen == namelen && !memcmp (ui->name, name, namelen))
+ return uid_item_ref (ui); /* Found. */
+
+ /* If the bucket is full remove all unrefed items. */
+ if (count >= uid_table_max)
+ {
+ uid_item_t ui_next, ui_prev, list_head, drop_head;
+
+ /* No syscalls from here .. */
+ list_head = uid_table[hash];
+ drop_head = NULL;
+ while (list_head && !list_head->refcount)
+ {
+ ui = list_head;
+ list_head = ui->next;
+ ui->next = drop_head;
+ drop_head = ui;
+ }
+ if ((ui_prev = list_head))
+ for (ui = ui_prev->next; ui; ui = ui_next)
+ {
+ ui_next = ui->next;
+ if (!ui->refcount)
+ {
+ ui->next = drop_head;
+ drop_head = ui;
+ ui_prev->next = ui_next;
+ }
+ else
+ ui_prev = ui;
+ }
+ uid_table[hash] = list_head;
+ /* ... to here */
+
+ for (ui = drop_head; ui; ui = ui_next)
+ {
+ ui_next = ui->next;
+ xfree (ui);
+ uid_table_dropped++;
+ }
+ }
+
+ count = uid_table_added + uid_table_dropped;
+ ui = xtrycalloc (1, sizeof *ui + namelen);
+ if (!ui)
+ return NULL; /* Out of core. */
+ if (count != uid_table_added + uid_table_dropped)
+ {
+ /* During the malloc another thread added an item. Thus we need
+ * to check again. */
+ uid_item_t ui_new = ui;
+ for (ui = uid_table[hash]; ui; ui = ui->next)
+ if (ui->namelen == namelen && !memcmp (ui->name, name, namelen))
+ {
+ /* Found. */
+ xfree (ui_new);
+ return uid_item_ref (ui);
+ }
+ ui = ui_new;
+ }
+
+ memcpy (ui->name, name, namelen);
+ ui->name[namelen] = 0; /* Extra Nul so we can use it as a string. */
+ ui->namelen = namelen;
+ ui->refcount = 1;
+ ui->next = uid_table[hash];
+ uid_table[hash] = ui;
+ uid_table_added++;
+ return ui;
+}
+
+
+
+/* The hash function we use for the key_table. Must not call a system
+ * function. */
+static inline unsigned int
+key_table_hasher (u32 *keyid)
+{
+ /* A fingerprint could be used directly as a hash value. However,
+ * we use the keyid here because it is used in encrypted packets and
+ * older signatures to identify a key. Since v4 keys the keyid is
+ * anyway a part of the fingerprint so it quickly extracted from a
+ * fingerprint. Note that v3 keys are not supported by gpg. */
+ return keyid[0] % key_table_size;
+}
+
+
+/* Run time allocation of the key table. This allows us to eventually
+ * add an option to gpg to control the size. */
+static void
+key_table_init (void)
+{
+ if (key_table)
+ return;
+ key_table_size = NO_OF_KEY_ITEM_BUCKETS;
+ key_table_max = MAX_KEY_ITEMS_PER_BUCKET;
+ key_table = xcalloc (key_table_size, sizeof *key_table);
+}
+
+
+static void
+key_item_free (key_item_t ki)
+{
+ if (!ki)
+ return;
+ uid_item_unref (ki->ui);
+ ki->ui = NULL;
+ ki->next = key_item_attic;
+ key_item_attic = ki;
+}
+
+
+/* Get a key item from PK or if that is NULL from KEYID. The
+ * reference count for that item is incremented. NULL is return if it
+ * was not found. */
+static key_item_t
+key_table_get (PKT_public_key *pk, u32 *keyid)
+{
+ unsigned int hash;
+ key_item_t ki, ki2;
+
+ if (!key_table)
+ key_table_init ();
+
+ if (pk)
+ {
+ byte fpr[MAX_FINGERPRINT_LEN];
+ size_t fprlen;
+ u32 tmpkeyid[2];
+
+ fingerprint_from_pk (pk, fpr, &fprlen);
+ keyid_from_pk (pk, tmpkeyid);
+ hash = key_table_hasher (tmpkeyid);
+ for (ki = key_table[hash]; ki; ki = ki->next)
+ if (ki->fprlen == fprlen && !memcmp (ki->fpr, fpr, fprlen))
+ return ki; /* Found */
+ }
+ else if (keyid)
+ {
+ hash = key_table_hasher (keyid);
+ for (ki = key_table[hash]; ki; ki = ki->next)
+ if (ki->keyid[0] == keyid[0] && ki->keyid[1] == keyid[1])
+ {
+ /* Found. We need to check for dups. */
+ for (ki2 = ki->next; ki2; ki2 = ki2->next)
+ if (ki2->keyid[0] == keyid[0] && ki2->keyid[1] == keyid[1])
+ return NULL; /* Duplicated keyid - retrun NULL. */
+
+ /* This is the only one - return it. */
+ return ki;
+ }
+ }
+ return NULL;
+}
+
+
+/* Helper for the qsort in key_table_put. */
+static int
+compare_key_items (const void *arg_a, const void *arg_b)
+{
+ const key_item_t a = *(const key_item_t *)arg_a;
+ const key_item_t b = *(const key_item_t *)arg_b;
+
+ /* Reverse sort on the usecount. */
+ if (a->usecount > b->usecount)
+ return -1;
+ else if (a->usecount == b->usecount)
+ return 0;
+ else
+ return 1;
+}
+
+
+/* Put PK into the KEY_TABLE and return a key item. The reference
+ * count for that item is incremented. If UI is given it is put into
+ * the entry. NULL is return on an allocation error. */
+static key_item_t
+key_table_put (PKT_public_key *pk, uid_item_t ui)
+{
+ unsigned int hash;
+ key_item_t ki;
+ u32 keyid[2];
+ byte fpr[MAX_FINGERPRINT_LEN];
+ size_t fprlen;
+ unsigned int count, n;
+
+ if (!key_table)
+ key_table_init ();
+
+ fingerprint_from_pk (pk, fpr, &fprlen);
+ keyid_from_pk (pk, keyid);
+ hash = key_table_hasher (keyid);
+ for (ki = key_table[hash], count=0; ki; ki = ki->next, count++)
+ if (ki->fprlen == fprlen && !memcmp (ki->fpr, fpr, fprlen))
+ return ki; /* Found */
+
+ /* If the bucket is full remove a couple of items. */
+ if (count >= key_table_max)
+ {
+ key_item_t list_head, *list_tailp, ki_next;
+ key_item_t *array;
+ int narray, idx;
+
+ /* Unlink from the global list so that other threads don't
+ * disturb us. If another thread adds or removes something only
+ * one will be the winner. Bad luck for the drooped cache items
+ * but after all it is just a cache. */
+ list_head = key_table[hash];
+ key_table[hash] = NULL;
+
+ /* Put all items into an array for sorting. */
+ array = xtrycalloc (count, sizeof *array);
+ if (!array)
+ {
+ /* That's bad; give up all items of the bucket. */
+ log_info ("Note: malloc failed while purging from the key_tabe: %s\n",
+ gpg_strerror (gpg_error_from_syserror ()));
+ goto leave_drop;
+ }
+ narray = 0;
+ for (ki = list_head; ki; ki = ki_next)
+ {
+ ki_next = ki->next;
+ array[narray++] = ki;
+ ki->next = NULL;
+ }
+ log_assert (narray == count);
+
+ /* Sort the array and put half of it onto a new list. */
+ qsort (array, narray, sizeof *array, compare_key_items);
+ list_head = NULL;
+ list_tailp = &list_head;
+ for (idx=0; idx < narray/2; idx++)
+ {
+ *list_tailp = array[idx];
+ list_tailp = &array[idx]->next;
+ }
+
+ /* Put the new list into the bucket. */
+ ki = key_table[hash];
+ key_table[hash] = list_head;
+ list_head = ki;
+
+ /* Free the remaining items and the array. */
+ for (; idx < narray; idx++)
+ {
+ key_item_free (array[idx]);
+ key_table_dropped++;
+ }
+ xfree (array);
+
+ leave_drop:
+ /* Free any items added in the meantime by other threads. This
+ * is also used in case of a malloc problem (which won't update
+ * the counters, though). */
+ for ( ; list_head; list_head = ki_next)
+ {
+ ki_next = list_head->next;
+ key_item_free (list_head);
+ }
+ }
+
+ /* Add an item to the bucket. We allocate a whole block of items
+ * for cache performace reasons. */
+ if (!key_item_attic)
+ {
+ key_item_t kiblock;
+ int kiblocksize = 256;
+
+ kiblock = xtrymalloc (kiblocksize * sizeof *kiblock);
+ if (!kiblock)
+ return NULL; /* Out of core. */
+ for (n = 0; n < kiblocksize; n++)
+ {
+ ki = kiblock + n;
+ ki->next = key_item_attic;
+ key_item_attic = ki;
+ }
+
+ /* During the malloc another thread may have changed the bucket.
+ * Thus we need to check again. */
+ for (ki = key_table[hash]; ki; ki = ki->next)
+ if (ki->fprlen == fprlen && !memcmp (ki->fpr, fpr, fprlen))
+ return ki; /* Found */
+ }
+
+ /* We now know that there is an item in the attic. */
+ ki = key_item_attic;
+ key_item_attic = ki->next;
+ ki->next = NULL;
+
+ memcpy (ki->fpr, fpr, fprlen);
+ ki->fprlen = fprlen;
+ ki->keyid[0] = keyid[0];
+ ki->keyid[1] = keyid[1];
+ ki->ui = uid_item_ref (ui);
+ ki->usecount = 0;
+ ki->next = key_table[hash];
+ key_table[hash] = ki;
+ key_table_added++;
+ return ki;
+}
+
+
+
+/* Return the user ID from the given keyblock. We use the primary uid
+ * flag which should have already been set. The returned value is
+ * only valid as long as the given keyblock is not changed. */
+static const char *
+primary_uid_from_keyblock (kbnode_t keyblock, size_t *uidlen)
+{
+ kbnode_t k;
+
+ for (k = keyblock; k; k = k->next)
+ {
+ if (k->pkt->pkttype == PKT_USER_ID
+ && !k->pkt->pkt.user_id->attrib_data
+ && k->pkt->pkt.user_id->flags.primary)
+ {
+ *uidlen = k->pkt->pkt.user_id->len;
+ return k->pkt->pkt.user_id->name;
+ }
+ }
+ return NULL;
+}
+
+
+/* Store the associations of keyid/fingerprint and userid. Only
+ * public keys should be fed to this function. */
+void
+cache_put_keyblock (kbnode_t keyblock)
+{
+ uid_item_t ui = NULL;
+ kbnode_t k;
+
+ restart:
+ for (k = keyblock; k; k = k->next)
+ {
+ if (k->pkt->pkttype == PKT_PUBLIC_KEY
+ || k->pkt->pkttype == PKT_PUBLIC_SUBKEY)
+ {
+ if (!ui)
+ {
+ /* Initially we just test for an entry to avoid the need
+ * to create a user id item for a put. Only if we miss
+ * key in the cache we create a user id and restart. */
+ if (!key_table_get (k->pkt->pkt.public_key, NULL))
+ {
+ const char *uid;
+ size_t uidlen;
+
+ uid = primary_uid_from_keyblock (keyblock, &uidlen);
+ if (uid)
+ {
+ ui = uid_table_put (uid, uidlen);
+ if (!ui)
+ {
+ log_info ("Note: failed to cache a user id: %s\n",
+ gpg_strerror (gpg_error_from_syserror ()));
+ goto leave;
+ }
+ goto restart;
+ }
+ }
+ }
+ else /* With a UID we use the update cache mode. */
+ {
+ if (!key_table_put (k->pkt->pkt.public_key, ui))
+ {
+ log_info ("Note: failed to cache a key: %s\n",
+ gpg_strerror (gpg_error_from_syserror ()));
+ goto leave;
+ }
+ }
+ }
+ }
+
+ leave:
+ uid_item_unref (ui);
+}
+
+
+/* Return the user id string for KEYID. If a user id is not found (or
+ * on malloc error) NULL is returned. If R_LENGTH is not NULL the
+ * length of the user id is stored there; this does not included the
+ * always appended nul. Note that a user id may include an internal
+ * nul which can be detected by the caller by comparing to the
+ * returned length. */
+char *
+cache_get_uid_bykid (u32 *keyid, unsigned int *r_length)
+{
+ key_item_t ki;
+ char *p;
+
+ if (r_length)
+ *r_length = 0;
+
+ ki = key_table_get (NULL, keyid);
+ if (!ki)
+ return NULL; /* Not found or duplicate keyid. */
+
+ if (!ki->ui)
+ p = NULL; /* No user id known for key. */
+ else
+ {
+ p = xtrymalloc (ki->ui->namelen + 1);
+ if (p)
+ {
+ memcpy (p, ki->ui->name, ki->ui->namelen + 1);
+ if (r_length)
+ *r_length = ki->ui->namelen;
+ ki->usecount++;
+ }
+ }
+
+ return p;
+}
+
+
+/* Return the user id string for FPR with FPRLEN. If a user id is not
+ * found (or on malloc error) NULL is returned. If R_LENGTH is not
+ * NULL the length of the user id is stored there; this does not
+ * included the always appended nul. Note that a user id may include
+ * an internal nul which can be detected by the caller by comparing to
+ * the returned length. */
+char *
+cache_get_uid_byfpr (const byte *fpr, size_t fprlen, size_t *r_length)
+{
+ char *p;
+ unsigned int hash;
+ u32 keyid[2];
+ key_item_t ki;
+
+ if (r_length)
+ *r_length = 0;
+
+ if (!key_table)
+ return NULL;
+
+ keyid_from_fingerprint (NULL, fpr, fprlen, keyid);
+ hash = key_table_hasher (keyid);
+ for (ki = key_table[hash]; ki; ki = ki->next)
+ if (ki->fprlen == fprlen && !memcmp (ki->fpr, fpr, fprlen))
+ break; /* Found */
+
+ if (!ki)
+ return NULL; /* Not found. */
+
+ if (!ki->ui)
+ p = NULL; /* No user id known for key. */
+ else
+ {
+ p = xtrymalloc (ki->ui->namelen + 1);
+ if (p)
+ {
+ memcpy (p, ki->ui->name, ki->ui->namelen + 1);
+ if (r_length)
+ *r_length = ki->ui->namelen;
+ ki->usecount++;
+ }
+ }
+
+ return p;
+}
diff --git a/agent/trans.c b/g10/objcache.h
similarity index 53%
copy from agent/trans.c
copy to g10/objcache.h
index ff1a34e68..edf129525 100644
--- a/agent/trans.c
+++ b/g10/objcache.h
@@ -1,41 +1,29 @@
-/* trans.c - translatable strings
- * Copyright (C) 2001 Free Software Foundation, Inc.
+/* objcache.h - Caching functions for keys and user ids.
+ * Copyright (C) 2019 g10 Code GmbH
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
+ * SPDX-License-Identifier: GPL-3.0-or-later
*/
-/* To avoid any problems with the gettext implementation (there used
- to be some vulnerabilities in the last years and the use of
- external files is a minor security problem in itself), we use our
- own simple translation stuff */
+#ifndef GNUPG_G10_OBJCACHE_H
+#define GNUPG_G10_OBJCACHE_H
-#include <config.h>
-#include <errno.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <ctype.h>
-#include <assert.h>
-#include <unistd.h>
-#include <sys/stat.h>
+void objcache_dump_stats (void);
+void cache_put_keyblock (kbnode_t keyblock);
+char *cache_get_uid_bykid (u32 *keyid, unsigned int *r_length);
+char *cache_get_uid_byfpr (const byte *fpr, size_t fprlen, size_t *r_length);
-#include "agent.h"
-
-const char *
-trans (const char *text)
-{
- return text;
-}
+#endif /*GNUPG_G10_OBJCACHE_H*/
diff --git a/g10/options.h b/g10/options.h
index 8adf09f08..234929b15 100644
--- a/g10/options.h
+++ b/g10/options.h
@@ -1,411 +1,414 @@
/* options.h
* Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006,
* 2007, 2010, 2011 Free Software Foundation, Inc.
* Copyright (C) 2015 g10 Code GmbH
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#ifndef G10_OPTIONS_H
#define G10_OPTIONS_H
#include <sys/types.h>
#include "../common/types.h"
#include <stdint.h>
#include "main.h"
#include "packet.h"
#include "tofu.h"
#include "../common/session-env.h"
#include "../common/compliance.h"
#ifndef EXTERN_UNLESS_MAIN_MODULE
/* Norcraft can't cope with common symbols */
#if defined (__riscos__) && !defined (INCLUDED_BY_MAIN_MODULE)
#define EXTERN_UNLESS_MAIN_MODULE extern
#else
#define EXTERN_UNLESS_MAIN_MODULE
#endif
#endif
/* Declaration of a keyserver spec type. The definition is found in
../common/keyserver.h. */
struct keyserver_spec;
typedef struct keyserver_spec *keyserver_spec_t;
/* Global options for GPG. */
EXTERN_UNLESS_MAIN_MODULE
struct
{
int verbose;
int quiet;
unsigned debug;
int armor;
char *outfile;
estream_t outfp; /* Hack, sometimes used in place of outfile. */
off_t max_output;
/* If > 0 a hint with the expected number of input data bytes. This
* is not necessary an exact number but intended to be used for
* progress info and to decide on how to allocate buffers. */
uint64_t input_size_hint;
/* The AEAD chunk size expressed as a power of 2. */
int chunk_size;
int dry_run;
int autostart;
int list_only;
int mimemode;
int textmode;
int expert;
const char *def_sig_expire;
int ask_sig_expire;
const char *def_cert_expire;
int ask_cert_expire;
int batch; /* run in batch mode */
int answer_yes; /* answer yes on most questions */
int answer_no; /* answer no on most questions */
int check_sigs; /* check key signatures */
int with_colons;
int with_key_data;
int with_icao_spelling; /* Print ICAO spelling with fingerprints. */
int with_fingerprint; /* Option --with-fingerprint active. */
int with_subkey_fingerprint; /* Option --with-subkey-fingerprint active. */
int with_keygrip; /* Option --with-keygrip active. */
int with_key_screening;/* Option --with-key-screening active. */
int with_tofu_info; /* Option --with-tofu_info active. */
int with_secret; /* Option --with-secret active. */
int with_wkd_hash; /* Option --with-wkd-hash. */
int with_key_origin; /* Option --with-key-origin. */
int fingerprint; /* list fingerprints */
int list_sigs; /* list signatures */
int no_armor;
int list_packets; /* Option --list-packets active. */
int def_cipher_algo;
int def_aead_algo;
int force_mdc;
int disable_mdc;
int force_aead;
int def_digest_algo;
int cert_digest_algo;
int compress_algo;
int compress_level;
int bz2_compress_level;
int bz2_decompress_lowmem;
strlist_t def_secret_key;
char *def_recipient;
int def_recipient_self;
strlist_t secret_keys_to_try;
/* A list of mail addresses (addr-spec) provided by the user with
* the option --sender. */
strlist_t sender_list;
int def_cert_level;
int min_cert_level;
int ask_cert_level;
int emit_version; /* 0 = none,
1 = major only,
2 = major and minor,
3 = full version,
4 = full version plus OS string. */
int marginals_needed;
int completes_needed;
int max_cert_depth;
const char *agent_program;
const char *dirmngr_program;
int disable_dirmngr;
const char *def_new_key_algo;
/* Options to be passed to the gpg-agent */
session_env_t session_env;
char *lc_ctype;
char *lc_messages;
int skip_verify;
int skip_hidden_recipients;
/* TM_CLASSIC must be zero to accommodate trustdbsg generated before
we started storing the trust model inside the trustdb. */
enum
{
TM_CLASSIC=0, TM_PGP=1, TM_EXTERNAL=2,
TM_ALWAYS, TM_DIRECT, TM_AUTO, TM_TOFU, TM_TOFU_PGP
} trust_model;
enum tofu_policy tofu_default_policy;
int force_ownertrust;
enum gnupg_compliance_mode compliance;
enum
{
KF_DEFAULT, KF_NONE, KF_SHORT, KF_LONG, KF_0xSHORT, KF_0xLONG
} keyid_format;
const char *set_filename;
strlist_t comments;
int throw_keyids;
const char *photo_viewer;
int s2k_mode;
int s2k_digest_algo;
int s2k_cipher_algo;
unsigned char s2k_count; /* This is the encoded form, not the raw
count */
int not_dash_escaped;
int escape_from;
int lock_once;
keyserver_spec_t keyserver; /* The list of configured keyservers. */
struct
{
unsigned int options;
unsigned int import_options;
unsigned int export_options;
char *http_proxy;
} keyserver_options;
int exec_disable;
int exec_path_set;
unsigned int import_options;
unsigned int export_options;
unsigned int list_options;
unsigned int verify_options;
const char *def_preference_list;
const char *def_keyserver_url;
prefitem_t *personal_cipher_prefs;
prefitem_t *personal_aead_prefs;
prefitem_t *personal_digest_prefs;
prefitem_t *personal_compress_prefs;
struct weakhash *weak_digests;
int no_perm_warn;
char *temp_dir;
int no_encrypt_to;
int encrypt_to_default_key;
int interactive;
struct notation *sig_notations;
struct notation *cert_notations;
strlist_t sig_policy_url;
strlist_t cert_policy_url;
strlist_t sig_keyserver_url;
strlist_t cert_subpackets;
strlist_t sig_subpackets;
int allow_non_selfsigned_uid;
int allow_freeform_uid;
int no_literal;
ulong set_filesize;
int fast_list_mode;
int legacy_list_mode;
int ignore_time_conflict;
int ignore_valid_from;
int ignore_crc_error;
int ignore_mdc_error;
int command_fd;
const char *override_session_key;
int show_session_key;
const char *gpg_agent_info;
int try_all_secrets;
int no_expensive_trust_checks;
int no_sig_cache;
int no_auto_check_trustdb;
int preserve_permissions;
int no_homedir_creation;
struct groupitem *grouplist;
int mangle_dos_filenames;
int enable_progress_filter;
unsigned int screen_columns;
unsigned int screen_lines;
byte *show_subpackets;
int rfc2440_text;
/* If true, let write failures on the status-fd exit the process. */
int exit_on_status_write_error;
/* If > 0, limit the number of card insertion prompts to this
value. */
int limit_card_insert_tries;
struct
{
/* If set, require an 0x19 backsig to be present on signatures
made by signing subkeys. If not set, a missing backsig is not
an error (but an invalid backsig still is). */
unsigned int require_cross_cert:1;
unsigned int use_embedded_filename:1;
unsigned int utf8_filename:1;
unsigned int dsa2:1;
unsigned int allow_weak_digest_algos:1;
unsigned int large_rsa:1;
unsigned int disable_signer_uid:1;
/* Flag to enable experimental features from RFC4880bis. */
unsigned int rfc4880bis:1;
+ /* Hack: --output is not given but OUTFILE was temporary set to "-". */
+ unsigned int dummy_outfile:1;
} flags;
/* Linked list of ways to find a key if the key isn't on the local
keyring. */
struct akl
{
enum {
AKL_NODEFAULT,
AKL_LOCAL,
AKL_CERT,
AKL_PKA,
AKL_DANE,
AKL_WKD,
AKL_LDAP,
AKL_KEYSERVER,
AKL_SPEC
} type;
keyserver_spec_t spec;
struct akl *next;
} *auto_key_locate;
/* The value of --key-origin. See parse_key_origin(). */
int key_origin;
char *key_origin_url;
int passphrase_repeat;
int pinentry_mode;
int request_origin;
int unwrap_encryption;
int only_sign_text_ids;
int no_symkey_cache; /* Disable the cache used for --symmetric. */
} opt;
/* CTRL is used to keep some global variables we currently can't
avoid. Future concurrent versions of gpg will put it into a per
request structure CTRL. */
EXTERN_UNLESS_MAIN_MODULE
struct {
int in_auto_key_retrieve; /* True if we are doing an
auto_key_retrieve. */
/* Hack to store the last error. We currently need it because the
proc_packet machinery is not able to reliabale return error
codes. Thus for the --server purposes we store some of the error
codes here. FIXME! */
gpg_error_t lasterr;
} glo_ctrl;
#define DBG_PACKET_VALUE 1 /* debug packet reading/writing */
#define DBG_MPI_VALUE 2 /* debug mpi details */
#define DBG_CRYPTO_VALUE 4 /* debug crypto handling */
/* (may reveal sensitive data) */
#define DBG_FILTER_VALUE 8 /* debug internal filter handling */
#define DBG_IOBUF_VALUE 16 /* debug iobuf stuff */
#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_TRUST_VALUE 256 /* debug the trustdb */
#define DBG_HASHING_VALUE 512 /* debug hashing operations */
#define DBG_IPC_VALUE 1024 /* debug assuan communication */
#define DBG_CLOCK_VALUE 4096
#define DBG_LOOKUP_VALUE 8192 /* debug the key lookup */
#define DBG_EXTPROG_VALUE 16384 /* debug external program calls */
/* Tests for the debugging flags. */
#define DBG_PACKET (opt.debug & DBG_PACKET_VALUE)
#define DBG_MPI (opt.debug & DBG_MPI_VALUE)
#define DBG_CRYPTO (opt.debug & DBG_CRYPTO_VALUE)
#define DBG_FILTER (opt.debug & DBG_FILTER_VALUE)
#define DBG_CACHE (opt.debug & DBG_CACHE_VALUE)
#define DBG_TRUST (opt.debug & DBG_TRUST_VALUE)
#define DBG_HASHING (opt.debug & DBG_HASHING_VALUE)
#define DBG_IPC (opt.debug & DBG_IPC_VALUE)
#define DBG_CLOCK (opt.debug & DBG_CLOCK_VALUE)
#define DBG_LOOKUP (opt.debug & DBG_LOOKUP_VALUE)
#define DBG_EXTPROG (opt.debug & DBG_EXTPROG_VALUE)
/* FIXME: We need to check why we did not put this into opt. */
#define DBG_MEMORY memory_debug_mode
#define DBG_MEMSTAT memory_stat_debug_mode
EXTERN_UNLESS_MAIN_MODULE int memory_debug_mode;
EXTERN_UNLESS_MAIN_MODULE int memory_stat_debug_mode;
/* Compatibility flags. */
#define GNUPG (opt.compliance==CO_GNUPG || opt.compliance==CO_DE_VS)
#define RFC2440 (opt.compliance==CO_RFC2440)
#define RFC4880 (opt.compliance==CO_RFC4880)
#define PGP7 (opt.compliance==CO_PGP7)
#define PGP8 (opt.compliance==CO_PGP8)
#define PGPX (PGP7 || PGP8)
/* Various option flags. Note that there should be no common string
names between the IMPORT_ and EXPORT_ flags as they can be mixed in
the keyserver-options option. */
#define IMPORT_LOCAL_SIGS (1<<0)
#define IMPORT_REPAIR_PKS_SUBKEY_BUG (1<<1)
#define IMPORT_FAST (1<<2)
#define IMPORT_SHOW (1<<3)
#define IMPORT_MERGE_ONLY (1<<4)
#define IMPORT_MINIMAL (1<<5)
#define IMPORT_CLEAN (1<<6)
#define IMPORT_NO_SECKEY (1<<7)
#define IMPORT_KEEP_OWNERTTRUST (1<<8)
#define IMPORT_EXPORT (1<<9)
#define IMPORT_RESTORE (1<<10)
#define IMPORT_REPAIR_KEYS (1<<11)
#define IMPORT_DRY_RUN (1<<12)
#define IMPORT_DROP_UIDS (1<<13)
+#define IMPORT_SELF_SIGS_ONLY (1<<14)
#define EXPORT_LOCAL_SIGS (1<<0)
#define EXPORT_ATTRIBUTES (1<<1)
#define EXPORT_SENSITIVE_REVKEYS (1<<2)
#define EXPORT_RESET_SUBKEY_PASSWD (1<<3)
#define EXPORT_MINIMAL (1<<4)
#define EXPORT_CLEAN (1<<5)
#define EXPORT_PKA_FORMAT (1<<6)
#define EXPORT_DANE_FORMAT (1<<7)
#define EXPORT_BACKUP (1<<10)
#define EXPORT_DROP_UIDS (1<<13)
#define LIST_SHOW_PHOTOS (1<<0)
#define LIST_SHOW_POLICY_URLS (1<<1)
#define LIST_SHOW_STD_NOTATIONS (1<<2)
#define LIST_SHOW_USER_NOTATIONS (1<<3)
#define LIST_SHOW_NOTATIONS (LIST_SHOW_STD_NOTATIONS|LIST_SHOW_USER_NOTATIONS)
#define LIST_SHOW_KEYSERVER_URLS (1<<4)
#define LIST_SHOW_UID_VALIDITY (1<<5)
#define LIST_SHOW_UNUSABLE_UIDS (1<<6)
#define LIST_SHOW_UNUSABLE_SUBKEYS (1<<7)
#define LIST_SHOW_KEYRING (1<<8)
#define LIST_SHOW_SIG_EXPIRE (1<<9)
#define LIST_SHOW_SIG_SUBPACKETS (1<<10)
#define LIST_SHOW_USAGE (1<<11)
#define LIST_SHOW_ONLY_FPR_MBOX (1<<12)
#define VERIFY_SHOW_PHOTOS (1<<0)
#define VERIFY_SHOW_POLICY_URLS (1<<1)
#define VERIFY_SHOW_STD_NOTATIONS (1<<2)
#define VERIFY_SHOW_USER_NOTATIONS (1<<3)
#define VERIFY_SHOW_NOTATIONS (VERIFY_SHOW_STD_NOTATIONS|VERIFY_SHOW_USER_NOTATIONS)
#define VERIFY_SHOW_KEYSERVER_URLS (1<<4)
#define VERIFY_SHOW_UID_VALIDITY (1<<5)
#define VERIFY_SHOW_UNUSABLE_UIDS (1<<6)
#define VERIFY_PKA_LOOKUPS (1<<7)
#define VERIFY_PKA_TRUST_INCREASE (1<<8)
#define VERIFY_SHOW_PRIMARY_UID_ONLY (1<<9)
#define KEYSERVER_HTTP_PROXY (1<<0)
#define KEYSERVER_TIMEOUT (1<<1)
#define KEYSERVER_ADD_FAKE_V3 (1<<2)
#define KEYSERVER_AUTO_KEY_RETRIEVE (1<<3)
#define KEYSERVER_HONOR_KEYSERVER_URL (1<<4)
#define KEYSERVER_HONOR_PKA_RECORD (1<<5)
#endif /*G10_OPTIONS_H*/
diff --git a/g10/packet.h b/g10/packet.h
index 41dd1a95a..479f25044 100644
--- a/g10/packet.h
+++ b/g10/packet.h
@@ -1,949 +1,957 @@
/* packet.h - OpenPGP packet definitions
* Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006,
* 2007 Free Software Foundation, Inc.
* Copyright (C) 2015 g10 Code GmbH
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#ifndef G10_PACKET_H
#define G10_PACKET_H
#include "../common/types.h"
#include "../common/iobuf.h"
#include "../common/strlist.h"
#include "dek.h"
#include "filter.h"
#include "../common/openpgpdefs.h"
#include "../common/userids.h"
#include "../common/util.h"
#define DEBUG_PARSE_PACKET 1
+/* Maximum length of packets to avoid excessive memory allocation. */
+#define MAX_KEY_PACKET_LENGTH (256 * 1024)
+#define MAX_UID_PACKET_LENGTH ( 2 * 1024)
+#define MAX_COMMENT_PACKET_LENGTH ( 64 * 1024)
+#define MAX_ATTR_PACKET_LENGTH ( 16 * 1024*1024)
/* Constants to allocate static MPI arrays. */
#define PUBKEY_MAX_NPKEY OPENPGP_MAX_NPKEY
#define PUBKEY_MAX_NSKEY OPENPGP_MAX_NSKEY
#define PUBKEY_MAX_NSIG OPENPGP_MAX_NSIG
#define PUBKEY_MAX_NENC OPENPGP_MAX_NENC
/* Usage flags */
#define PUBKEY_USAGE_SIG GCRY_PK_USAGE_SIGN /* Good for signatures. */
#define PUBKEY_USAGE_ENC GCRY_PK_USAGE_ENCR /* Good for encryption. */
#define PUBKEY_USAGE_CERT GCRY_PK_USAGE_CERT /* Also good to certify keys.*/
#define PUBKEY_USAGE_AUTH GCRY_PK_USAGE_AUTH /* Good for authentication. */
#define PUBKEY_USAGE_UNKNOWN GCRY_PK_USAGE_UNKN /* Unknown usage flag. */
#define PUBKEY_USAGE_NONE 256 /* No usage given. */
#if (GCRY_PK_USAGE_SIGN | GCRY_PK_USAGE_ENCR | GCRY_PK_USAGE_CERT \
| GCRY_PK_USAGE_AUTH | GCRY_PK_USAGE_UNKN) >= 256
# error Please choose another value for PUBKEY_USAGE_NONE
#endif
/* Helper macros. */
#define is_RSA(a) ((a)==PUBKEY_ALGO_RSA || (a)==PUBKEY_ALGO_RSA_E \
|| (a)==PUBKEY_ALGO_RSA_S )
#define is_ELGAMAL(a) ((a)==PUBKEY_ALGO_ELGAMAL_E)
#define is_DSA(a) ((a)==PUBKEY_ALGO_DSA)
/* A pointer to the packet object. */
typedef struct packet_struct PACKET;
/* PKT_GPG_CONTROL types */
typedef enum {
CTRLPKT_CLEARSIGN_START = 1,
CTRLPKT_PIPEMODE = 2,
CTRLPKT_PLAINTEXT_MARK =3
} ctrlpkttype_t;
typedef enum {
PREFTYPE_NONE = 0,
PREFTYPE_SYM = 1,
PREFTYPE_HASH = 2,
PREFTYPE_ZIP = 3,
PREFTYPE_AEAD = 4
} preftype_t;
typedef struct {
byte type;
byte value;
} prefitem_t;
/* A string-to-key specifier as defined in RFC 4880, Section 3.7. */
typedef struct
{
int mode; /* Must be an integer due to the GNU modes 1001 et al. */
byte hash_algo;
byte salt[8];
/* The *coded* (i.e., the serialized version) iteration count. */
u32 count;
} STRING2KEY;
/* A symmetric-key encrypted session key packet as defined in RFC
4880, Section 5.3. All fields are serialized. */
typedef struct {
/* We support version 4 (rfc4880) and 5 (rfc4880bis). */
byte version;
/* The cipher algorithm used to encrypt the session key. Note that
* this may be different from the algorithm that is used to encrypt
* bulk data. */
byte cipher_algo;
/* The AEAD algorithm or 0 for CFB encryption. */
byte aead_algo;
/* The string-to-key specifier. */
STRING2KEY s2k;
/* The length of SESKEY in bytes or 0 if this packet does not
encrypt a session key. (In the latter case, the results of the
S2K function on the password is the session key. See RFC 4880,
Section 5.3.) */
byte seskeylen;
/* The session key as encrypted by the S2K specifier. For AEAD this
* includes the nonce and the authentication tag. */
byte seskey[1];
} PKT_symkey_enc;
/* A public-key encrypted session key packet as defined in RFC 4880,
Section 5.1. All fields are serialized. */
typedef struct {
/* The 64-bit keyid. */
u32 keyid[2];
/* The packet's version. Currently, only version 3 is defined. */
byte version;
/* The algorithm used for the public key encryption scheme. */
byte pubkey_algo;
/* Whether to hide the key id. This value is not directly
serialized. */
byte throw_keyid;
/* The session key. */
gcry_mpi_t data[PUBKEY_MAX_NENC];
} PKT_pubkey_enc;
/* An object to build a list of public-key encrypted session key. */
struct pubkey_enc_list
{
struct pubkey_enc_list *next;
u32 keyid[2];
int pubkey_algo;
int result;
gcry_mpi_t data[PUBKEY_MAX_NENC];
};
/* A one-pass signature packet as defined in RFC 4880, Section
5.4. All fields are serialized. */
typedef struct {
u32 keyid[2]; /* The 64-bit keyid */
/* The signature's classification (RFC 4880, Section 5.2.1). */
byte sig_class;
byte digest_algo; /* algorithm used for digest */
byte pubkey_algo; /* algorithm used for public key scheme */
/* A message can be signed by multiple keys. In this case, there
are n one-pass signature packets before the message to sign and
n signatures packets after the message. It is conceivable that
someone wants to not only sign the message, but all of the
signatures. Now we need to distinguish between signing the
message and signing the message plus the surrounding
signatures. This is the point of this flag. If set, it means:
I sign all of the data starting at the next packet. */
byte last;
} PKT_onepass_sig;
/* A v4 OpenPGP signature has a hashed and unhashed area containing
co-called signature subpackets (RFC 4880, Section 5.2.3). These
areas are described by this data structure. Use enum_sig_subpkt to
parse this area. */
typedef struct {
size_t size; /* allocated */
size_t len; /* used (serialized) */
byte data[1]; /* the serialized subpackes (serialized) */
} subpktarea_t;
/* The in-memory representation of a designated revoker signature
subpacket (RFC 4880, Section 5.2.3.15). */
struct revocation_key {
/* A bit field. 0x80 must be set. 0x40 means this information is
sensitive (and should not be uploaded to a keyserver by
default). */
byte class;
/* The public-key algorithm ID. */
byte algid;
/* The length of the fingerprint. */
byte fprlen;
/* The fingerprint of the authorized key. */
byte fpr[MAX_FINGERPRINT_LEN];
};
/* Object to keep information about a PKA DNS record. */
typedef struct
{
int valid; /* An actual PKA record exists for EMAIL. */
int checked; /* Set to true if the FPR has been checked against the
actual key. */
char *uri; /* Malloced string with the URI. NULL if the URI is
not available.*/
unsigned char fpr[20]; /* The fingerprint as stored in the PKA RR. */
char email[1];/* The email address from the notation data. */
} pka_info_t;
/* A signature packet (RFC 4880, Section 5.2). Only a subset of these
fields are directly serialized (these are marked as such); the rest
are read from the subpackets, which are not synthesized when
serializing this data structure (i.e., when using build_packet()).
Instead, the subpackets must be created by hand. */
typedef struct
{
struct
{
unsigned checked:1; /* Signature has been checked. */
unsigned valid:1; /* Signature is good (if checked is set). */
unsigned chosen_selfsig:1; /* A selfsig that is the chosen one. */
unsigned unknown_critical:1;
unsigned exportable:1;
unsigned revocable:1;
unsigned policy_url:1; /* At least one policy URL is present */
unsigned notation:1; /* At least one notation is present */
unsigned pref_ks:1; /* At least one preferred keyserver is present */
unsigned expired:1;
unsigned pka_tried:1; /* Set if we tried to retrieve the PKA record. */
} flags;
/* The key that allegedly generated this signature. (Directly
serialized in v3 sigs; for v4 sigs, this must be explicitly added
as an issuer subpacket (5.2.3.5.) */
u32 keyid[2];
/* When the signature was made (seconds since the Epoch). (Directly
serialized in v3 sigs; for v4 sigs, this must be explicitly added
as a signature creation time subpacket (5.2.3.4).) */
u32 timestamp;
u32 expiredate; /* Expires at this date or 0 if not at all. */
/* The serialization format used / to use. If 0, then defaults to
version 3. (Serialized.) */
byte version;
/* The signature type. (See RFC 4880, Section 5.2.1.) */
byte sig_class;
/* Algorithm used for public key scheme (e.g., PUBKEY_ALGO_RSA).
(Serialized.) */
byte pubkey_algo;
/* Algorithm used for digest (e.g., DIGEST_ALGO_SHA1).
(Serialized.) */
byte digest_algo;
byte trust_depth;
byte trust_value;
const byte *trust_regexp;
struct revocation_key *revkey;
int numrevkeys;
int help_counter; /* Used internally bu some functions. */
pka_info_t *pka_info; /* Malloced PKA data or NULL if not
available. See also flags.pka_tried. */
char *signers_uid; /* Malloced value of the SIGNERS_UID
* subpacket or NULL. This string has
* already been sanitized. */
subpktarea_t *hashed; /* All subpackets with hashed data (v4 only). */
subpktarea_t *unhashed; /* Ditto for unhashed data. */
/* First 2 bytes of the digest. (Serialized. Note: this is not
automatically filled in when serializing a signature!) */
byte digest_start[2];
/* The signature. (Serialized.) */
gcry_mpi_t data[PUBKEY_MAX_NSIG];
/* The message digest and its length (in bytes). Note the maximum
digest length is 512 bits (64 bytes). If DIGEST_LEN is 0, then
the digest's value has not been saved here. */
byte digest[512 / 8];
int digest_len;
} PKT_signature;
#define ATTRIB_IMAGE 1
/* This is the cooked form of attributes. */
struct user_attribute {
byte type;
const byte *data;
u32 len;
};
/* A user id (RFC 4880, Section 5.11) or a user attribute packet (RFC
4880, Section 5.12). Only a subset of these fields are directly
serialized (these are marked as such); the rest are read from the
self-signatures in merge_keys_and_selfsig()). */
typedef struct
{
int ref; /* reference counter */
/* The length of NAME. */
int len;
struct user_attribute *attribs;
int numattribs;
/* If this is not NULL, the packet is a user attribute rather than a
user id (See RFC 4880 5.12). (Serialized.) */
byte *attrib_data;
/* The length of ATTRIB_DATA. */
unsigned long attrib_len;
byte *namehash;
int help_key_usage;
u32 help_key_expire;
int help_full_count;
int help_marginal_count;
u32 expiredate; /* expires at this date or 0 if not at all */
prefitem_t *prefs; /* list of preferences (may be NULL)*/
u32 created; /* according to the self-signature */
u32 keyupdate; /* From the ring trust packet. */
char *updateurl; /* NULL or the URL of the last update origin. */
byte keyorg; /* From the ring trust packet. */
byte selfsigversion;
struct
{
unsigned int mdc:1;
unsigned int aead:1;
unsigned int ks_modify:1;
unsigned int compacted:1;
unsigned int primary:2; /* 2 if set via the primary flag, 1 if calculated */
unsigned int revoked:1;
unsigned int expired:1;
} flags;
char *mbox; /* NULL or the result of mailbox_from_userid. */
/* The text contained in the user id packet, which is normally the
* name and email address of the key holder (See RFC 4880 5.11).
* (Serialized.). For convenience an extra Nul is always appended. */
char name[1];
} PKT_user_id;
struct revoke_info
{
/* revoked at this date */
u32 date;
/* the keyid of the revoking key (selfsig or designated revoker) */
u32 keyid[2];
/* the algo of the revoking key */
byte algo;
};
/* Information pertaining to secret keys. */
struct seckey_info
{
int is_protected:1; /* The secret info is protected and must */
/* be decrypted before use, the protected */
/* MPIs are simply (void*) pointers to memory */
/* and should never be passed to a mpi_xxx() */
int sha1chk:1; /* SHA1 is used instead of a 16 bit checksum */
u16 csum; /* Checksum for old protection modes. */
byte algo; /* Cipher used to protect the secret information. */
STRING2KEY s2k; /* S2K parameter. */
byte ivlen; /* Used length of the IV. */
byte iv[16]; /* Initialization vector for CFB mode. */
};
/****************
* The in-memory representation of a public key (RFC 4880, Section
* 5.5). Note: this structure contains significantly more information
* than is contained in an OpenPGP public key packet. This
* information is derived from the self-signed signatures (by
* merge_keys_and_selfsig()) and is ignored when serializing the
* packet. The fields that are actually written out when serializing
* this packet are marked as accordingly.
*
* We assume that secret keys have the same number of parameters as
* the public key and that the public parameters are the first items
* in the PKEY array. Thus NPKEY is always less than NSKEY and it is
* possible to compare the secret and public keys by comparing the
* first NPKEY elements of the PKEY array. Note that since GnuPG 2.1
* we don't use secret keys anymore directly because they are managed
* by gpg-agent. However for parsing OpenPGP key files we need a way
* to temporary store those secret keys. We do this by putting them
* into the public key structure and extending the PKEY field to NSKEY
* elements; the extra secret key information are stored in the
* SECKEY_INFO field.
*/
typedef struct
{
/* When the key was created. (Serialized.) */
u32 timestamp;
u32 expiredate; /* expires at this date or 0 if not at all */
u32 max_expiredate; /* must not expire past this date */
struct revoke_info revoked;
/* An OpenPGP packet consists of a header and a body. This is the
size of the header. If this is 0, an appropriate size is
automatically chosen based on the size of the body.
(Serialized.) */
byte hdrbytes;
/* The serialization format. If 0, the default version (4) is used
when serializing. (Serialized.) */
byte version;
byte selfsigversion; /* highest version of all of the self-sigs */
/* The public key algorithm. (Serialized.) */
byte pubkey_algo;
byte pubkey_usage; /* for now only used to pass it to getkey() */
byte req_usage; /* hack to pass a request to getkey() */
+ byte fprlen; /* 0 or length of FPR. */
u32 has_expired; /* set to the expiration date if expired */
/* keyid of the primary key. Never access this value directly.
Instead, use pk_main_keyid(). */
u32 main_keyid[2];
/* keyid of this key. Never access this value directly! Instead,
use pk_keyid(). */
u32 keyid[2];
+ /* Fingerprint of the key. Only valid if FPRLEN is not 0. */
+ byte fpr[MAX_FINGERPRINT_LEN];
prefitem_t *prefs; /* list of preferences (may be NULL) */
struct
{
unsigned int mdc:1; /* MDC feature set. */
unsigned int aead:1; /* AEAD feature set. */
unsigned int disabled_valid:1;/* The next flag is valid. */
unsigned int disabled:1; /* The key has been disabled. */
unsigned int primary:1; /* This is a primary key. */
unsigned int revoked:2; /* Key has been revoked.
1 = revoked by the owner
2 = revoked by designated revoker. */
unsigned int maybe_revoked:1; /* A designated revocation is
present, but without the key to
check it. */
unsigned int valid:1; /* Key (especially subkey) is valid. */
unsigned int dont_cache:1; /* Do not cache this key. */
unsigned int backsig:2; /* 0=none, 1=bad, 2=good. */
unsigned int serialno_valid:1;/* SERIALNO below is valid. */
unsigned int exact:1; /* Found via exact (!) search. */
} flags;
PKT_user_id *user_id; /* If != NULL: found by that uid. */
struct revocation_key *revkey;
int numrevkeys;
u32 trust_timestamp;
byte trust_depth;
byte trust_value;
byte keyorg; /* From the ring trust packet. */
u32 keyupdate; /* From the ring trust packet. */
char *updateurl; /* NULL or the URL of the last update origin. */
const byte *trust_regexp;
char *serialno; /* Malloced hex string or NULL if it is
likely not on a card. See also
flags.serialno_valid. */
/* If not NULL this malloced structure describes a secret key.
(Serialized.) */
struct seckey_info *seckey_info;
/* The public key. Contains pubkey_get_npkey (pubkey_algo) +
pubkey_get_nskey (pubkey_algo) MPIs. (If pubkey_get_npkey
returns 0, then the algorithm is not understood and the PKEY
contains a single opaque MPI.) (Serialized.) */
gcry_mpi_t pkey[PUBKEY_MAX_NSKEY]; /* Right, NSKEY elements. */
} PKT_public_key;
/* Evaluates as true if the pk is disabled, and false if it isn't. If
there is no disable value cached, fill one in. */
#define pk_is_disabled(a) \
(((a)->flags.disabled_valid)? \
((a)->flags.disabled):(cache_disabled_value(ctrl,(a))))
typedef struct {
int len; /* length of data */
char data[1];
} PKT_comment;
/* A compression packet (RFC 4880, Section 5.6). */
typedef struct {
/* Not used. */
u32 len;
/* Whether the serialized version of the packet used / should use
the new format. */
byte new_ctb;
/* The compression algorithm. */
byte algorithm;
/* An iobuf holding the data to be decompressed. (This is not used
for compression!) */
iobuf_t buf;
} PKT_compressed;
/* A symmetrically encrypted data packet (RFC 4880, Section 5.7) or a
symmetrically encrypted integrity protected data packet (Section
5.13) */
typedef struct {
/* Remaining length of encrypted data. */
u32 len;
/* When encrypting in CFB mode, the first block size bytes of data
* are random data and the following 2 bytes are copies of the last
* two bytes of the random data (RFC 4880, Section 5.7). This
* provides a simple check that the key is correct. EXTRALEN is the
* size of this extra data or, in AEAD mode, the length of the
* headers and the tags. This is used by build_packet when writing
* out the packet's header. */
int extralen;
/* Whether the serialized version of the packet used / should use
the new format. */
byte new_ctb;
/* Whether the packet has an indeterminate length (old format) or
was encoded using partial body length headers (new format).
Note: this is ignored when encrypting. */
byte is_partial;
/* If 0, MDC is disabled. Otherwise, the MDC method that was used
(only DIGEST_ALGO_SHA1 has ever been defined). */
byte mdc_method;
/* If 0, AEAD is not used. Otherwise, the used AEAD algorithm.
* MDC_METHOD (above) shall be zero if AEAD is used. */
byte aead_algo;
/* The cipher algo for/from the AEAD packet. 0 for other encryption
* packets. */
byte cipher_algo;
/* The chunk byte from the AEAD packet. */
byte chunkbyte;
/* An iobuf holding the data to be decrypted. (This is not used for
encryption!) */
iobuf_t buf;
} PKT_encrypted;
typedef struct {
byte hash[20];
} PKT_mdc;
/* Subtypes for the ring trust packet. */
#define RING_TRUST_SIG 0 /* The classical signature cache. */
#define RING_TRUST_KEY 1 /* A KEYORG on a primary key. */
#define RING_TRUST_UID 2 /* A KEYORG on a user id. */
/* The local only ring trust packet which OpenPGP declares as
* implementation defined. GnuPG uses this to cache signature
* verification status and since 2.1.18 also to convey information
* about the origin of a key. Note that this packet is not part
* struct packet_struct because we use it only local in the packet
* parser and builder. */
typedef struct {
unsigned int trustval;
unsigned int sigcache;
unsigned char subtype; /* The subtype of this ring trust packet. */
unsigned char keyorg; /* The origin of the key (KEYORG_*). */
u32 keyupdate; /* The wall time the key was last updated. */
char *url; /* NULL or the URL of the source. */
} PKT_ring_trust;
/* A plaintext packet (see RFC 4880, 5.9). */
typedef struct {
/* The length of data in BUF or 0 if unknown. */
u32 len;
/* A buffer containing the data stored in the packet's body. */
iobuf_t buf;
byte new_ctb;
byte is_partial; /* partial length encoded */
/* The data's formatting. This is either 'b', 't', 'u', 'l' or '1'
(however, the last two are deprecated). */
int mode;
u32 timestamp;
/* The name of the file. This can be at most 255 characters long,
since namelen is just a byte in the serialized format. */
int namelen;
char name[1];
} PKT_plaintext;
typedef struct {
int control;
size_t datalen;
char data[1];
} PKT_gpg_control;
/* combine all packets into a union */
struct packet_struct {
pkttype_t pkttype;
union {
void *generic;
PKT_symkey_enc *symkey_enc; /* PKT_SYMKEY_ENC */
PKT_pubkey_enc *pubkey_enc; /* PKT_PUBKEY_ENC */
PKT_onepass_sig *onepass_sig; /* PKT_ONEPASS_SIG */
PKT_signature *signature; /* PKT_SIGNATURE */
PKT_public_key *public_key; /* PKT_PUBLIC_[SUB]KEY */
PKT_public_key *secret_key; /* PKT_SECRET_[SUB]KEY */
PKT_comment *comment; /* PKT_COMMENT */
PKT_user_id *user_id; /* PKT_USER_ID */
PKT_compressed *compressed; /* PKT_COMPRESSED */
PKT_encrypted *encrypted; /* PKT_ENCRYPTED[_MDC] */
PKT_mdc *mdc; /* PKT_MDC */
PKT_plaintext *plaintext; /* PKT_PLAINTEXT */
PKT_gpg_control *gpg_control; /* PKT_GPG_CONTROL */
} pkt;
};
#define init_packet(a) do { (a)->pkttype = 0; \
(a)->pkt.generic = NULL; \
} while(0)
/* A notation. See RFC 4880, Section 5.2.3.16. */
struct notation
{
/* The notation's name. */
char *name;
/* If the notation is human readable, then the value is stored here
as a NUL-terminated string. If it is not human readable a human
readable approximation of the binary value _may_ be stored
here. */
char *value;
/* Sometimes we want to %-expand the value. In these cases, we save
that transformed value here. */
char *altvalue;
/* If the notation is not human readable, then the value is stored
here. */
unsigned char *bdat;
/* The amount of data stored in BDAT.
Note: if this is 0 and BDAT is NULL, this does not necessarily
mean that the value is human readable. It could be that we have
a 0-length value. To determine whether the notation is human
readable, always check if VALUE is not NULL. This works, because
if a human-readable value has a length of 0, we will still
allocate space for the NUL byte. */
size_t blen;
struct
{
/* The notation is critical. */
unsigned int critical:1;
/* The notation is human readable. */
unsigned int human:1;
/* The notation should be deleted. */
unsigned int ignore:1;
} flags;
/* A field to facilitate creating a list of notations. */
struct notation *next;
};
typedef struct notation *notation_t;
/*-- mainproc.c --*/
void reset_literals_seen(void);
int proc_packets (ctrl_t ctrl, void *ctx, iobuf_t a );
int proc_signature_packets (ctrl_t ctrl, void *ctx, iobuf_t a,
strlist_t signedfiles, const char *sigfile );
int proc_signature_packets_by_fd (ctrl_t ctrl,
void *anchor, IOBUF a, int signed_data_fd );
int proc_encryption_packets (ctrl_t ctrl, void *ctx, iobuf_t a);
int list_packets( iobuf_t a );
const byte *issuer_fpr_raw (PKT_signature *sig, size_t *r_len);
char *issuer_fpr_string (PKT_signature *sig);
/*-- parse-packet.c --*/
void register_known_notation (const char *string);
/* Sets the packet list mode to MODE (i.e., whether we are dumping a
packet or not). Returns the current mode. This allows for
temporarily suspending dumping by doing the following:
int saved_mode = set_packet_list_mode (0);
...
set_packet_list_mode (saved_mode);
*/
int set_packet_list_mode( int mode );
/* A context used with parse_packet. */
struct parse_packet_ctx_s
{
iobuf_t inp; /* The input stream with the packets. */
struct packet_struct last_pkt; /* The last parsed packet. */
int free_last_pkt; /* Indicates that LAST_PKT must be freed. */
int skip_meta; /* Skip ring trust packets. */
unsigned int n_parsed_packets; /* Number of parsed packets. */
};
typedef struct parse_packet_ctx_s *parse_packet_ctx_t;
#define init_parse_packet(a,i) do { \
(a)->inp = (i); \
(a)->last_pkt.pkttype = 0; \
(a)->last_pkt.pkt.generic= NULL;\
(a)->free_last_pkt = 0; \
(a)->skip_meta = 0; \
(a)->n_parsed_packets = 0; \
} while (0)
#define deinit_parse_packet(a) do { \
if ((a)->free_last_pkt) \
free_packet (NULL, (a)); \
} while (0)
#if DEBUG_PARSE_PACKET
/* There are debug functions and should not be used directly. */
int dbg_search_packet (parse_packet_ctx_t ctx, PACKET *pkt,
off_t *retpos, int with_uid,
const char* file, int lineno );
int dbg_parse_packet (parse_packet_ctx_t ctx, PACKET *ret_pkt,
const char *file, int lineno);
int dbg_copy_all_packets( iobuf_t inp, iobuf_t out,
const char* file, int lineno );
int dbg_copy_some_packets( iobuf_t inp, iobuf_t out, off_t stopoff,
const char* file, int lineno );
int dbg_skip_some_packets( iobuf_t inp, unsigned n,
const char* file, int lineno );
#define search_packet( a,b,c,d ) \
dbg_search_packet( (a), (b), (c), (d), __FILE__, __LINE__ )
#define parse_packet( a, b ) \
dbg_parse_packet( (a), (b), __FILE__, __LINE__ )
#define copy_all_packets( a,b ) \
dbg_copy_all_packets((a),(b), __FILE__, __LINE__ )
#define copy_some_packets( a,b,c ) \
dbg_copy_some_packets((a),(b),(c), __FILE__, __LINE__ )
#define skip_some_packets( a,b ) \
dbg_skip_some_packets((a),(b), __FILE__, __LINE__ )
#else
/* Return the next valid OpenPGP packet in *PKT. (This function will
* skip any packets whose type is 0.) CTX must have been setup prior to
* calling this function.
*
* Returns 0 on success, -1 if EOF is reached, and an error code
* otherwise. In the case of an error, the packet in *PKT may be
* partially constructed. As such, even if there is an error, it is
* necessary to free *PKT to avoid a resource leak. To detect what
* has been allocated, clear *PKT before calling this function. */
int parse_packet (parse_packet_ctx_t ctx, PACKET *pkt);
/* Return the first OpenPGP packet in *PKT that contains a key (either
* a public subkey, a public key, a secret subkey or a secret key) or,
* if WITH_UID is set, a user id.
*
* Saves the position in the pipeline of the start of the returned
* packet (according to iobuf_tell) in RETPOS, if it is not NULL.
*
* The return semantics are the same as parse_packet. */
int search_packet (parse_packet_ctx_t ctx, PACKET *pkt,
off_t *retpos, int with_uid);
/* Copy all packets (except invalid packets, i.e., those with a type
* of 0) from INP to OUT until either an error occurs or EOF is
* reached.
*
* Returns -1 when end of file is reached or an error code, if an
* error occurred. (Note: this function never returns 0, because it
* effectively keeps going until it gets an EOF.) */
int copy_all_packets (iobuf_t inp, iobuf_t out );
/* Like copy_all_packets, but stops at the first packet that starts at
* or after STOPOFF (as indicated by iobuf_tell).
*
* Example: if STOPOFF is 100, the first packet in INP goes from
* 0 to 110 and the next packet starts at offset 111, then the packet
* starting at offset 0 will be completely processed (even though it
* extends beyond STOPOFF) and the packet starting at offset 111 will
* not be processed at all. */
int copy_some_packets (iobuf_t inp, iobuf_t out, off_t stopoff);
/* Skips the next N packets from INP.
*
* If parsing a packet returns an error code, then the function stops
* immediately and returns the error code. Note: in the case of an
* error, this function does not indicate how many packets were
* successfully processed. */
int skip_some_packets (iobuf_t inp, unsigned int n);
#endif
/* Parse a signature packet and store it in *SIG.
The signature packet is read from INP. The OpenPGP header (the tag
and the packet's length) have already been read; the next byte read
from INP should be the first byte of the packet's contents. The
packet's type (as extract from the tag) must be passed as PKTTYPE
and the packet's length must be passed as PKTLEN. This is used as
the upper bound on the amount of data read from INP. If the packet
is shorter than PKTLEN, the data at the end will be silently
skipped. If an error occurs, an error code will be returned. -1
means the EOF was encountered. 0 means parsing was successful. */
int parse_signature( iobuf_t inp, int pkttype, unsigned long pktlen,
PKT_signature *sig );
/* Given a subpacket area (typically either PKT_signature.hashed or
PKT_signature.unhashed), either:
- test whether there are any subpackets with the critical bit set
that we don't understand,
- list the subpackets, or,
- find a subpacket with a specific type.
REQTYPE indicates the type of operation.
If REQTYPE is SIGSUBPKT_TEST_CRITICAL, then this function checks
whether there are any subpackets that have the critical bit and
which GnuPG cannot handle. If GnuPG understands all subpackets
whose critical bit is set, then this function returns simply
returns SUBPKTS. If there is a subpacket whose critical bit is set
and which GnuPG does not understand, then this function returns
NULL and, if START is not NULL, sets *START to the 1-based index of
the subpacket that violates the constraint.
If REQTYPE is SIGSUBPKT_LIST_HASHED or SIGSUBPKT_LIST_UNHASHED, the
packets are dumped. Note: if REQTYPE is SIGSUBPKT_LIST_HASHED,
this function does not check whether the hash is correct; this is
merely an indication of the section that the subpackets came from.
If REQTYPE is anything else, then this function interprets the
values as a subpacket type and looks for the first subpacket with
that type. If such a packet is found, *CRITICAL (if not NULL) is
set if the critical bit was set, *RET_N is set to the offset of the
subpacket's content within the SUBPKTS buffer, *START is set to the
1-based index of the subpacket within the buffer, and returns
&SUBPKTS[*RET_N].
*START is the number of initial subpackets to not consider. Thus,
if *START is 2, then the first 2 subpackets are ignored. */
const byte *enum_sig_subpkt ( const subpktarea_t *subpkts,
sigsubpkttype_t reqtype,
size_t *ret_n, int *start, int *critical );
/* Shorthand for:
enum_sig_subpkt (buffer, reqtype, ret_n, NULL, NULL); */
const byte *parse_sig_subpkt ( const subpktarea_t *buffer,
sigsubpkttype_t reqtype,
size_t *ret_n );
/* This calls parse_sig_subpkt first on the hashed signature area in
SIG and then, if that returns NULL, calls parse_sig_subpkt on the
unhashed subpacket area in SIG. */
const byte *parse_sig_subpkt2 ( PKT_signature *sig,
sigsubpkttype_t reqtype);
/* Returns whether the N byte large buffer BUFFER is sufficient to
hold a subpacket of type TYPE. Note: the buffer refers to the
contents of the subpacket (not the header) and it must already be
initialized: for some subpackets, it checks some internal
constraints.
Returns 0 if the size is acceptable. Returns -2 if the buffer is
definitely too short. To check for an error, check whether the
return value is less than 0. */
int parse_one_sig_subpkt( const byte *buffer, size_t n, int type );
/* Looks for revocation key subpackets (see RFC 4880 5.2.3.15) in the
hashed area of the signature packet. Any that are found are added
to SIG->REVKEY and SIG->NUMREVKEYS is updated appropriately. */
void parse_revkeys(PKT_signature *sig);
/* Extract the attributes from the buffer at UID->ATTRIB_DATA and
update UID->ATTRIBS and UID->NUMATTRIBS accordingly. */
int parse_attribute_subpkts(PKT_user_id *uid);
/* Set the UID->NAME field according to the attributes. MAX_NAMELEN
must be at least 71. */
void make_attribute_uidname(PKT_user_id *uid, size_t max_namelen);
/* Allocate and initialize a new GPG control packet. DATA is the data
to save in the packet. */
PACKET *create_gpg_control ( ctrlpkttype_t type,
const byte *data,
size_t datalen );
/*-- build-packet.c --*/
int build_packet (iobuf_t out, PACKET *pkt);
gpg_error_t build_packet_and_meta (iobuf_t out, PACKET *pkt);
gpg_error_t gpg_mpi_write (iobuf_t out, gcry_mpi_t a, unsigned int *t_nwritten);
gpg_error_t gpg_mpi_write_nohdr (iobuf_t out, gcry_mpi_t a);
u32 calc_packet_length( PACKET *pkt );
void build_sig_subpkt( PKT_signature *sig, sigsubpkttype_t type,
const byte *buffer, size_t buflen );
void build_sig_subpkt_from_sig (PKT_signature *sig, PKT_public_key *pksk);
int delete_sig_subpkt(subpktarea_t *buffer, sigsubpkttype_t type );
void build_attribute_subpkt(PKT_user_id *uid,byte type,
const void *buf,u32 buflen,
const void *header,u32 headerlen);
struct notation *string_to_notation(const char *string,int is_utf8);
struct notation *blob_to_notation(const char *name,
const char *data, size_t len);
struct notation *sig_to_notation(PKT_signature *sig);
void free_notation(struct notation *notation);
/*-- free-packet.c --*/
void free_symkey_enc( PKT_symkey_enc *enc );
void free_pubkey_enc( PKT_pubkey_enc *enc );
void free_seckey_enc( PKT_signature *enc );
void release_public_key_parts( PKT_public_key *pk );
void free_public_key( PKT_public_key *key );
void free_attributes(PKT_user_id *uid);
void free_user_id( PKT_user_id *uid );
void free_comment( PKT_comment *rem );
void free_packet (PACKET *pkt, parse_packet_ctx_t parsectx);
prefitem_t *copy_prefs (const prefitem_t *prefs);
PKT_public_key *copy_public_key( PKT_public_key *d, PKT_public_key *s );
PKT_signature *copy_signature( PKT_signature *d, PKT_signature *s );
PKT_user_id *scopy_user_id (PKT_user_id *sd );
int cmp_public_keys( PKT_public_key *a, PKT_public_key *b );
int cmp_signatures( PKT_signature *a, PKT_signature *b );
int cmp_user_ids( PKT_user_id *a, PKT_user_id *b );
/*-- sig-check.c --*/
/* Check a signature. This is shorthand for check_signature2 with
the unnamed arguments passed as NULL. */
int check_signature (ctrl_t ctrl, PKT_signature *sig, gcry_md_hd_t digest);
/* Check a signature. Looks up the public key from the key db. (If
* R_PK is not NULL, it is stored at RET_PK.) DIGEST contains a
* valid hash context that already includes the signed data. This
* function adds the relevant meta-data to the hash before finalizing
* it and verifying the signature. */
gpg_error_t check_signature2 (ctrl_t ctrl,
PKT_signature *sig, gcry_md_hd_t digest,
const void *extrahash, size_t extrahashlen,
u32 *r_expiredate, int *r_expired, int *r_revoked,
PKT_public_key **r_pk);
/*-- pubkey-enc.c --*/
gpg_error_t get_session_key (ctrl_t ctrl, struct pubkey_enc_list *k, DEK *dek);
gpg_error_t get_override_session_key (DEK *dek, const char *string);
/*-- compress.c --*/
int handle_compressed (ctrl_t ctrl, void *ctx, PKT_compressed *cd,
int (*callback)(iobuf_t, void *), void *passthru );
/*-- encr-data.c --*/
int decrypt_data (ctrl_t ctrl, void *ctx, PKT_encrypted *ed, DEK *dek );
/*-- plaintext.c --*/
gpg_error_t get_output_file (const byte *embedded_name, int embedded_namelen,
iobuf_t data, char **fnamep, estream_t *fpp);
int handle_plaintext( PKT_plaintext *pt, md_filter_context_t *mfx,
int nooutput, int clearsig );
int ask_for_detached_datafile( gcry_md_hd_t md, gcry_md_hd_t md2,
const char *inname, int textmode );
/*-- sign.c --*/
int make_keysig_packet (ctrl_t ctrl,
PKT_signature **ret_sig, PKT_public_key *pk,
PKT_user_id *uid, PKT_public_key *subpk,
- PKT_public_key *pksk, int sigclass, int digest_algo,
+ PKT_public_key *pksk, int sigclass,
u32 timestamp, u32 duration,
int (*mksubpkt)(PKT_signature *, void *),
void *opaque,
const char *cache_nonce);
gpg_error_t update_keysig_packet (ctrl_t ctrl,
PKT_signature **ret_sig,
PKT_signature *orig_sig,
PKT_public_key *pk,
PKT_user_id *uid,
PKT_public_key *subpk,
PKT_public_key *pksk,
int (*mksubpkt)(PKT_signature *, void *),
void *opaque );
/*-- keygen.c --*/
PKT_user_id *generate_user_id (kbnode_t keyblock, const char *uidstr);
#endif /*G10_PACKET_H*/
diff --git a/g10/parse-packet.c b/g10/parse-packet.c
index 5b4b1c900..ab82d475a 100644
--- a/g10/parse-packet.c
+++ b/g10/parse-packet.c
@@ -1,3591 +1,3594 @@
/* parse-packet.c - read packets
* Copyright (C) 1998-2007, 2009-2010 Free Software Foundation, Inc.
* Copyright (C) 2014, 2018 Werner Koch
* Copyright (C) 2015 g10 Code GmbH
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
* SPDX-License-Identifier: GPL-3.0+
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "gpg.h"
#include "../common/util.h"
#include "packet.h"
#include "../common/iobuf.h"
#include "filter.h"
#include "photoid.h"
#include "options.h"
#include "main.h"
#include "../common/i18n.h"
#include "../common/host2net.h"
+#include "../common/mbox-util.h"
-/* Maximum length of packets to avoid excessive memory allocation. */
-#define MAX_KEY_PACKET_LENGTH (256 * 1024)
-#define MAX_UID_PACKET_LENGTH ( 2 * 1024)
-#define MAX_COMMENT_PACKET_LENGTH ( 64 * 1024)
-#define MAX_ATTR_PACKET_LENGTH ( 16 * 1024*1024)
-
static int mpi_print_mode;
static int list_mode;
static estream_t listfp;
/* A linked list of known notation names. Note that the FLAG is used
* to store the length of the name to speed up the check. */
static strlist_t known_notations_list;
static int parse (parse_packet_ctx_t ctx, PACKET *pkt, int onlykeypkts,
off_t * retpos, int *skip, IOBUF out, int do_skip
#if DEBUG_PARSE_PACKET
, const char *dbg_w, const char *dbg_f, int dbg_l
#endif
);
static int copy_packet (IOBUF inp, IOBUF out, int pkttype,
unsigned long pktlen, int partial);
static void skip_packet (IOBUF inp, int pkttype,
unsigned long pktlen, int partial);
static void *read_rest (IOBUF inp, size_t pktlen);
static int parse_marker (IOBUF inp, int pkttype, unsigned long pktlen);
static int parse_symkeyenc (IOBUF inp, int pkttype, unsigned long pktlen,
PACKET * packet);
static int parse_pubkeyenc (IOBUF inp, int pkttype, unsigned long pktlen,
PACKET * packet);
static int parse_onepass_sig (IOBUF inp, int pkttype, unsigned long pktlen,
PKT_onepass_sig * ops);
static int parse_key (IOBUF inp, int pkttype, unsigned long pktlen,
byte * hdr, int hdrlen, PACKET * packet);
static int parse_user_id (IOBUF inp, int pkttype, unsigned long pktlen,
PACKET * packet);
static int parse_attribute (IOBUF inp, int pkttype, unsigned long pktlen,
PACKET * packet);
static int parse_comment (IOBUF inp, int pkttype, unsigned long pktlen,
PACKET * packet);
static gpg_error_t parse_ring_trust (parse_packet_ctx_t ctx,
unsigned long pktlen);
static int parse_plaintext (IOBUF inp, int pkttype, unsigned long pktlen,
PACKET * packet, int new_ctb, int partial);
static int parse_compressed (IOBUF inp, int pkttype, unsigned long pktlen,
PACKET * packet, int new_ctb);
static int parse_encrypted (IOBUF inp, int pkttype, unsigned long pktlen,
PACKET * packet, int new_ctb, int partial);
static gpg_error_t parse_encrypted_aead (IOBUF inp, int pkttype,
unsigned long pktlen, PACKET *packet,
int partial);
static int parse_mdc (IOBUF inp, int pkttype, unsigned long pktlen,
PACKET * packet, int new_ctb);
static int parse_gpg_control (IOBUF inp, int pkttype, unsigned long pktlen,
PACKET * packet, int partial);
/* Read a 16-bit value in MSB order (big endian) from an iobuf. */
static unsigned short
read_16 (IOBUF inp)
{
unsigned short a;
a = (unsigned short)iobuf_get_noeof (inp) << 8;
a |= iobuf_get_noeof (inp);
return a;
}
/* Read a 32-bit value in MSB order (big endian) from an iobuf. */
static unsigned long
read_32 (IOBUF inp)
{
unsigned long a;
a = (unsigned long)iobuf_get_noeof (inp) << 24;
a |= iobuf_get_noeof (inp) << 16;
a |= iobuf_get_noeof (inp) << 8;
a |= iobuf_get_noeof (inp);
return a;
}
/* Read an external representation of an MPI and return the MPI. The
external format is a 16-bit unsigned value stored in network byte
order giving the number of bits for the following integer. The
integer is stored MSB first and is left padded with zero bits to
align on a byte boundary.
The caller must set *RET_NREAD to the maximum number of bytes to
read from the pipeline INP. This function sets *RET_NREAD to be
the number of bytes actually read from the pipeline.
If SECURE is true, the integer is stored in secure memory
(allocated using gcry_xmalloc_secure). */
static gcry_mpi_t
mpi_read (iobuf_t inp, unsigned int *ret_nread, int secure)
{
int c, c1, c2, i;
unsigned int nmax = *ret_nread;
unsigned int nbits, nbytes;
size_t nread = 0;
gcry_mpi_t a = NULL;
byte *buf = NULL;
byte *p;
if (!nmax)
goto overflow;
if ((c = c1 = iobuf_get (inp)) == -1)
goto leave;
if (++nread == nmax)
goto overflow;
nbits = c << 8;
if ((c = c2 = iobuf_get (inp)) == -1)
goto leave;
++nread;
nbits |= c;
if (nbits > MAX_EXTERN_MPI_BITS)
{
log_error ("mpi too large (%u bits)\n", nbits);
goto leave;
}
nbytes = (nbits + 7) / 8;
buf = secure ? gcry_xmalloc_secure (nbytes + 2) : gcry_xmalloc (nbytes + 2);
p = buf;
p[0] = c1;
p[1] = c2;
for (i = 0; i < nbytes; i++)
{
if (nread == nmax)
goto overflow;
c = iobuf_get (inp);
if (c == -1)
goto leave;
p[i + 2] = c;
nread ++;
}
if (gcry_mpi_scan (&a, GCRYMPI_FMT_PGP, buf, nread, &nread))
a = NULL;
*ret_nread = nread;
gcry_free(buf);
return a;
overflow:
log_error ("mpi larger than indicated length (%u bits)\n", 8*nmax);
leave:
*ret_nread = nread;
gcry_free(buf);
return a;
}
/* Register STRING as a known critical notation name. */
void
register_known_notation (const char *string)
{
strlist_t sl;
if (!known_notations_list)
{
sl = add_to_strlist (&known_notations_list,
"preferred-email-encoding@pgp.com");
sl->flags = 32;
sl = add_to_strlist (&known_notations_list, "pka-address@gnupg.org");
sl->flags = 21;
}
if (!string)
return; /* Only initialized the default known notations. */
/* In --set-notation we use an exclamation mark to indicate a
* critical notation. As a convenience skip this here. */
if (*string == '!')
string++;
if (!*string || strlist_find (known_notations_list, string))
return; /* Empty string or already registered. */
sl = add_to_strlist (&known_notations_list, string);
sl->flags = strlen (string);
}
int
set_packet_list_mode (int mode)
{
int old = list_mode;
list_mode = mode;
/* We use stdout only if invoked by the --list-packets command
but switch to stderr in all other cases. This breaks the
previous behaviour but that seems to be more of a bug than
intentional. I don't believe that any application makes use of
this long standing annoying way of printing to stdout except when
doing a --list-packets. If this assumption fails, it will be easy
to add an option for the listing stream. Note that we initialize
it only once; mainly because there is code which switches
opt.list_mode back to 1 and we want to have all output to the
same stream. The MPI_PRINT_MODE will be enabled if the
corresponding debug flag is set or if we are in --list-packets
and --verbose is given.
Using stderr is not actually very clean because it bypasses the
logging code but it is a special thing anyway. I am not sure
whether using log_stream() would be better. Perhaps we should
enable the list mode only with a special option. */
if (!listfp)
{
if (opt.list_packets)
{
listfp = es_stdout;
if (opt.verbose)
mpi_print_mode = 1;
}
else
listfp = es_stderr;
if (DBG_MPI)
mpi_print_mode = 1;
}
return old;
}
/* If OPT.VERBOSE is set, print a warning that the algorithm ALGO is
not suitable for signing and encryption. */
static void
unknown_pubkey_warning (int algo)
{
static byte unknown_pubkey_algos[256];
/* First check whether the algorithm is usable but not suitable for
encryption/signing. */
if (pubkey_get_npkey (algo))
{
if (opt.verbose)
{
if (!pubkey_get_nsig (algo))
log_info ("public key algorithm %s not suitable for %s\n",
openpgp_pk_algo_name (algo), "signing");
if (!pubkey_get_nenc (algo))
log_info ("public key algorithm %s not suitable for %s\n",
openpgp_pk_algo_name (algo), "encryption");
}
}
else
{
algo &= 0xff;
if (!unknown_pubkey_algos[algo])
{
if (opt.verbose)
log_info (_("can't handle public key algorithm %d\n"), algo);
unknown_pubkey_algos[algo] = 1;
}
}
}
#if DEBUG_PARSE_PACKET
int
dbg_parse_packet (parse_packet_ctx_t ctx, PACKET *pkt,
const char *dbg_f, int dbg_l)
{
int skip, rc;
do
{
rc = parse (ctx, pkt, 0, NULL, &skip, NULL, 0, "parse", dbg_f, dbg_l);
}
while (skip && ! rc);
return rc;
}
#else /*!DEBUG_PARSE_PACKET*/
int
parse_packet (parse_packet_ctx_t ctx, PACKET *pkt)
{
int skip, rc;
do
{
rc = parse (ctx, pkt, 0, NULL, &skip, NULL, 0);
}
while (skip && ! rc);
return rc;
}
#endif /*!DEBUG_PARSE_PACKET*/
/*
* Like parse packet, but only return secret or public (sub)key
* packets.
*/
#if DEBUG_PARSE_PACKET
int
dbg_search_packet (parse_packet_ctx_t ctx, PACKET *pkt,
off_t * retpos, int with_uid,
const char *dbg_f, int dbg_l)
{
int skip, rc;
do
{
rc = parse (ctx, pkt, with_uid ? 2 : 1, retpos, &skip, NULL, 0, "search",
dbg_f, dbg_l);
}
while (skip && ! rc);
return rc;
}
#else /*!DEBUG_PARSE_PACKET*/
int
search_packet (parse_packet_ctx_t ctx, PACKET *pkt,
off_t * retpos, int with_uid)
{
int skip, rc;
do
{
rc = parse (ctx, pkt, with_uid ? 2 : 1, retpos, &skip, NULL, 0);
}
while (skip && ! rc);
return rc;
}
#endif /*!DEBUG_PARSE_PACKET*/
/*
* Copy all packets from INP to OUT, thereby removing unused spaces.
*/
#if DEBUG_PARSE_PACKET
int
dbg_copy_all_packets (iobuf_t inp, iobuf_t out, const char *dbg_f, int dbg_l)
{
PACKET pkt;
struct parse_packet_ctx_s parsectx;
int skip, rc = 0;
if (! out)
log_bug ("copy_all_packets: OUT may not be NULL.\n");
init_parse_packet (&parsectx, inp);
do
{
init_packet (&pkt);
}
while (!
(rc =
parse (&parsectx, &pkt, 0, NULL, &skip, out, 0, "copy",
dbg_f, dbg_l)));
deinit_parse_packet (&parsectx);
return rc;
}
#else /*!DEBUG_PARSE_PACKET*/
int
copy_all_packets (iobuf_t inp, iobuf_t out)
{
PACKET pkt;
struct parse_packet_ctx_s parsectx;
int skip, rc = 0;
if (! out)
log_bug ("copy_all_packets: OUT may not be NULL.\n");
init_parse_packet (&parsectx, inp);
do
{
init_packet (&pkt);
}
while (!(rc = parse (&parsectx, &pkt, 0, NULL, &skip, out, 0)));
deinit_parse_packet (&parsectx);
return rc;
}
#endif /*!DEBUG_PARSE_PACKET*/
/*
* Copy some packets from INP to OUT, thereby removing unused spaces.
* Stop at offset STOPoff (i.e. don't copy packets at this or later
* offsets)
*/
#if DEBUG_PARSE_PACKET
int
dbg_copy_some_packets (iobuf_t inp, iobuf_t out, off_t stopoff,
const char *dbg_f, int dbg_l)
{
int rc = 0;
PACKET pkt;
int skip;
struct parse_packet_ctx_s parsectx;
init_parse_packet (&parsectx, inp);
do
{
if (iobuf_tell (inp) >= stopoff)
{
deinit_parse_packet (&parsectx);
return 0;
}
init_packet (&pkt);
}
while (!(rc = parse (&parsectx, &pkt, 0, NULL, &skip, out, 0,
"some", dbg_f, dbg_l)));
deinit_parse_packet (&parsectx);
return rc;
}
#else /*!DEBUG_PARSE_PACKET*/
int
copy_some_packets (iobuf_t inp, iobuf_t out, off_t stopoff)
{
int rc = 0;
PACKET pkt;
struct parse_packet_ctx_s parsectx;
int skip;
init_parse_packet (&parsectx, inp);
do
{
if (iobuf_tell (inp) >= stopoff)
{
deinit_parse_packet (&parsectx);
return 0;
}
init_packet (&pkt);
}
while (!(rc = parse (&parsectx, &pkt, 0, NULL, &skip, out, 0)));
deinit_parse_packet (&parsectx);
return rc;
}
#endif /*!DEBUG_PARSE_PACKET*/
/*
* Skip over N packets
*/
#if DEBUG_PARSE_PACKET
int
dbg_skip_some_packets (iobuf_t inp, unsigned n, const char *dbg_f, int dbg_l)
{
int rc = 0;
int skip;
PACKET pkt;
struct parse_packet_ctx_s parsectx;
init_parse_packet (&parsectx, inp);
for (; n && !rc; n--)
{
init_packet (&pkt);
rc = parse (&parsectx, &pkt, 0, NULL, &skip, NULL, 1, "skip",
dbg_f, dbg_l);
}
deinit_parse_packet (&parsectx);
return rc;
}
#else /*!DEBUG_PARSE_PACKET*/
int
skip_some_packets (iobuf_t inp, unsigned int n)
{
int rc = 0;
int skip;
PACKET pkt;
struct parse_packet_ctx_s parsectx;
init_parse_packet (&parsectx, inp);
for (; n && !rc; n--)
{
init_packet (&pkt);
rc = parse (&parsectx, &pkt, 0, NULL, &skip, NULL, 1);
}
deinit_parse_packet (&parsectx);
return rc;
}
#endif /*!DEBUG_PARSE_PACKET*/
/* Parse a packet and save it in *PKT.
If OUT is not NULL and the packet is valid (its type is not 0),
then the header, the initial length field and the packet's contents
are written to OUT. In this case, the packet is not saved in *PKT.
ONLYKEYPKTS is a simple packet filter. If ONLYKEYPKTS is set to 1,
then only public subkey packets, public key packets, private subkey
packets and private key packets are parsed. The rest are skipped
(i.e., the header and the contents are read from the pipeline and
discarded). If ONLYKEYPKTS is set to 2, then in addition to the
above 4 types of packets, user id packets are also accepted.
DO_SKIP is a more coarse grained filter. Unless ONLYKEYPKTS is set
to 2 and the packet is a user id packet, all packets are skipped.
Finally, if a packet is invalid (it's type is 0), it is skipped.
If a packet is skipped and SKIP is not NULL, then *SKIP is set to
1.
Note: ONLYKEYPKTS and DO_SKIP are only respected if OUT is NULL,
i.e., the packets are not simply being copied.
If RETPOS is not NULL, then the position of CTX->INP (as returned by
iobuf_tell) is saved there before any data is read from CTX->INP.
*/
static int
parse (parse_packet_ctx_t ctx, PACKET *pkt, int onlykeypkts, off_t * retpos,
int *skip, IOBUF out, int do_skip
#if DEBUG_PARSE_PACKET
, const char *dbg_w, const char *dbg_f, int dbg_l
#endif
)
{
int rc = 0;
iobuf_t inp;
int c, ctb, pkttype, lenbytes;
unsigned long pktlen;
byte hdr[8];
int hdrlen;
int new_ctb = 0, partial = 0;
int with_uid = (onlykeypkts == 2);
off_t pos;
*skip = 0;
inp = ctx->inp;
again:
log_assert (!pkt->pkt.generic);
if (retpos || list_mode)
{
pos = iobuf_tell (inp);
if (retpos)
*retpos = pos;
}
else
pos = 0; /* (silence compiler warning) */
/* The first byte of a packet is the so-called tag. The highest bit
must be set. */
if ((ctb = iobuf_get (inp)) == -1)
{
rc = -1;
goto leave;
}
hdrlen = 0;
hdr[hdrlen++] = ctb;
if (!(ctb & 0x80))
{
log_error ("%s: invalid packet (ctb=%02x)\n", iobuf_where (inp), ctb);
rc = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
/* Immediately following the header is the length. There are two
formats: the old format and the new format. If bit 6 (where the
least significant bit is bit 0) is set in the tag, then we are
dealing with a new format packet. Otherwise, it is an old format
packet. */
pktlen = 0;
new_ctb = !!(ctb & 0x40);
if (new_ctb)
{
/* Get the packet's type. This is encoded in the 6 least
significant bits of the tag. */
pkttype = ctb & 0x3f;
/* Extract the packet's length. New format packets have 4 ways
to encode the packet length. The value of the first byte
determines the encoding and partially determines the length.
See section 4.2.2 of RFC 4880 for details. */
if ((c = iobuf_get (inp)) == -1)
{
log_error ("%s: 1st length byte missing\n", iobuf_where (inp));
rc = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
hdr[hdrlen++] = c;
if (c < 192)
pktlen = c;
else if (c < 224)
{
pktlen = (c - 192) * 256;
if ((c = iobuf_get (inp)) == -1)
{
log_error ("%s: 2nd length byte missing\n",
iobuf_where (inp));
rc = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
hdr[hdrlen++] = c;
pktlen += c + 192;
}
else if (c == 255)
{
int i;
char value[4];
for (i = 0; i < 4; i ++)
{
if ((c = iobuf_get (inp)) == -1)
{
log_error ("%s: 4 byte length invalid\n", iobuf_where (inp));
rc = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
value[i] = hdr[hdrlen++] = c;
}
pktlen = buf32_to_ulong (value);
}
else /* Partial body length. */
{
switch (pkttype)
{
case PKT_PLAINTEXT:
case PKT_ENCRYPTED:
case PKT_ENCRYPTED_MDC:
case PKT_ENCRYPTED_AEAD:
case PKT_COMPRESSED:
iobuf_set_partial_body_length_mode (inp, c & 0xff);
pktlen = 0; /* To indicate partial length. */
partial = 1;
break;
default:
log_error ("%s: partial length invalid for"
" packet type %d\n", iobuf_where (inp), pkttype);
rc = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
}
}
else
/* This is an old format packet. */
{
/* Extract the packet's type. This is encoded in bits 2-5. */
pkttype = (ctb >> 2) & 0xf;
/* The type of length encoding is encoded in bits 0-1 of the
tag. */
lenbytes = ((ctb & 3) == 3) ? 0 : (1 << (ctb & 3));
if (!lenbytes)
{
pktlen = 0; /* Don't know the value. */
/* This isn't really partial, but we can treat it the same
in a "read until the end" sort of way. */
partial = 1;
if (pkttype != PKT_ENCRYPTED && pkttype != PKT_PLAINTEXT
&& pkttype != PKT_COMPRESSED)
{
log_error ("%s: indeterminate length for invalid"
" packet type %d\n", iobuf_where (inp), pkttype);
rc = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
}
else
{
for (; lenbytes; lenbytes--)
{
pktlen <<= 8;
c = iobuf_get (inp);
if (c == -1)
{
log_error ("%s: length invalid\n", iobuf_where (inp));
rc = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
pktlen |= hdr[hdrlen++] = c;
}
}
}
/* Sometimes the decompressing layer enters an error state in which
it simply outputs 0xff for every byte read. If we have a stream
of 0xff bytes, then it will be detected as a new format packet
with type 63 and a 4-byte encoded length that is 4G-1. Since
packets with type 63 are private and we use them as a control
packet, which won't be 4 GB, we reject such packets as
invalid. */
if (pkttype == 63 && pktlen == 0xFFFFFFFF)
{
/* With some probability this is caused by a problem in the
* the uncompressing layer - in some error cases it just loops
* and spits out 0xff bytes. */
log_error ("%s: garbled packet detected\n", iobuf_where (inp));
g10_exit (2);
}
if (out && pkttype)
{
/* This type of copying won't work if the packet uses a partial
body length. (In other words, this only works if HDR is
actually the length.) Currently, no callers require this
functionality so we just log this as an error. */
if (partial)
{
log_error ("parse: Can't copy partial packet. Aborting.\n");
rc = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
rc = iobuf_write (out, hdr, hdrlen);
if (!rc)
rc = copy_packet (inp, out, pkttype, pktlen, partial);
goto leave;
}
if (with_uid && pkttype == PKT_USER_ID)
/* If ONLYKEYPKTS is set to 2, then we never skip user id packets,
even if DO_SKIP is set. */
;
else if (do_skip
/* type==0 is not allowed. This is an invalid packet. */
|| !pkttype
/* When ONLYKEYPKTS is set, we don't skip keys. */
|| (onlykeypkts && pkttype != PKT_PUBLIC_SUBKEY
&& pkttype != PKT_PUBLIC_KEY
&& pkttype != PKT_SECRET_SUBKEY && pkttype != PKT_SECRET_KEY))
{
iobuf_skip_rest (inp, pktlen, partial);
*skip = 1;
rc = 0;
goto leave;
}
if (DBG_PACKET)
{
#if DEBUG_PARSE_PACKET
log_debug ("parse_packet(iob=%d): type=%d length=%lu%s (%s.%s.%d)\n",
iobuf_id (inp), pkttype, pktlen, new_ctb ? " (new_ctb)" : "",
dbg_w, dbg_f, dbg_l);
#else
log_debug ("parse_packet(iob=%d): type=%d length=%lu%s\n",
iobuf_id (inp), pkttype, pktlen,
new_ctb ? " (new_ctb)" : "");
#endif
}
if (list_mode)
es_fprintf (listfp, "# off=%lu ctb=%02x tag=%d hlen=%d plen=%lu%s%s\n",
(unsigned long)pos, ctb, pkttype, hdrlen, pktlen,
partial? (new_ctb ? " partial" : " indeterminate") :"",
new_ctb? " new-ctb":"");
/* Count it. */
ctx->n_parsed_packets++;
pkt->pkttype = pkttype;
rc = GPG_ERR_UNKNOWN_PACKET; /* default error */
switch (pkttype)
{
case PKT_PUBLIC_KEY:
case PKT_PUBLIC_SUBKEY:
case PKT_SECRET_KEY:
case PKT_SECRET_SUBKEY:
pkt->pkt.public_key = xmalloc_clear (sizeof *pkt->pkt.public_key);
rc = parse_key (inp, pkttype, pktlen, hdr, hdrlen, pkt);
break;
case PKT_SYMKEY_ENC:
rc = parse_symkeyenc (inp, pkttype, pktlen, pkt);
break;
case PKT_PUBKEY_ENC:
rc = parse_pubkeyenc (inp, pkttype, pktlen, pkt);
break;
case PKT_SIGNATURE:
pkt->pkt.signature = xmalloc_clear (sizeof *pkt->pkt.signature);
rc = parse_signature (inp, pkttype, pktlen, pkt->pkt.signature);
break;
case PKT_ONEPASS_SIG:
pkt->pkt.onepass_sig = xmalloc_clear (sizeof *pkt->pkt.onepass_sig);
rc = parse_onepass_sig (inp, pkttype, pktlen, pkt->pkt.onepass_sig);
break;
case PKT_USER_ID:
rc = parse_user_id (inp, pkttype, pktlen, pkt);
break;
case PKT_ATTRIBUTE:
pkt->pkttype = pkttype = PKT_USER_ID; /* we store it in the userID */
rc = parse_attribute (inp, pkttype, pktlen, pkt);
break;
case PKT_OLD_COMMENT:
case PKT_COMMENT:
rc = parse_comment (inp, pkttype, pktlen, pkt);
break;
case PKT_RING_TRUST:
{
rc = parse_ring_trust (ctx, pktlen);
if (!rc)
goto again; /* Directly read the next packet. */
}
break;
case PKT_PLAINTEXT:
rc = parse_plaintext (inp, pkttype, pktlen, pkt, new_ctb, partial);
break;
case PKT_COMPRESSED:
rc = parse_compressed (inp, pkttype, pktlen, pkt, new_ctb);
break;
case PKT_ENCRYPTED:
case PKT_ENCRYPTED_MDC:
rc = parse_encrypted (inp, pkttype, pktlen, pkt, new_ctb, partial);
break;
case PKT_MDC:
rc = parse_mdc (inp, pkttype, pktlen, pkt, new_ctb);
break;
case PKT_ENCRYPTED_AEAD:
rc = parse_encrypted_aead (inp, pkttype, pktlen, pkt, partial);
break;
case PKT_GPG_CONTROL:
rc = parse_gpg_control (inp, pkttype, pktlen, pkt, partial);
break;
case PKT_MARKER:
rc = parse_marker (inp, pkttype, pktlen);
break;
default:
/* Unknown packet. Skip it. */
skip_packet (inp, pkttype, pktlen, partial);
break;
}
/* Store a shallow copy of certain packets in the context. */
free_packet (NULL, ctx);
if (!rc && (pkttype == PKT_PUBLIC_KEY
|| pkttype == PKT_SECRET_KEY
|| pkttype == PKT_USER_ID
|| pkttype == PKT_ATTRIBUTE
|| pkttype == PKT_SIGNATURE))
{
ctx->last_pkt = *pkt;
}
leave:
/* FIXME: We leak in case of an error (see the xmalloc's above). */
if (!rc && iobuf_error (inp))
rc = GPG_ERR_INV_KEYRING;
/* FIXME: We use only the error code for now to avoid problems with
callers which have not been checked to always use gpg_err_code()
when comparing error codes. */
return rc == -1? -1 : gpg_err_code (rc);
}
static void
dump_hex_line (int c, int *i)
{
if (*i && !(*i % 8))
{
if (*i && !(*i % 24))
es_fprintf (listfp, "\n%4d:", *i);
else
es_putc (' ', listfp);
}
if (c == -1)
es_fprintf (listfp, " EOF");
else
es_fprintf (listfp, " %02x", c);
++*i;
}
/* Copy the contents of a packet from the pipeline IN to the pipeline
OUT.
The header and length have already been read from INP and the
decoded values are given as PKGTYPE and PKTLEN.
If the packet is a partial body length packet (RFC 4880, Section
4.2.2.4), then iobuf_set_partial_block_modeiobuf_set_partial_block_mode
should already have been called on INP and PARTIAL should be set.
If PARTIAL is set or PKTLEN is 0 and PKTTYPE is PKT_COMPRESSED,
copy until the first EOF is encountered on INP.
Returns 0 on success and an error code if an error occurs. */
static int
copy_packet (IOBUF inp, IOBUF out, int pkttype,
unsigned long pktlen, int partial)
{
int rc;
int n;
char buf[100];
if (partial)
{
while ((n = iobuf_read (inp, buf, sizeof (buf))) != -1)
if ((rc = iobuf_write (out, buf, n)))
return rc; /* write error */
}
else if (!pktlen && pkttype == PKT_COMPRESSED)
{
log_debug ("copy_packet: compressed!\n");
/* compressed packet, copy till EOF */
while ((n = iobuf_read (inp, buf, sizeof (buf))) != -1)
if ((rc = iobuf_write (out, buf, n)))
return rc; /* write error */
}
else
{
for (; pktlen; pktlen -= n)
{
n = pktlen > sizeof (buf) ? sizeof (buf) : pktlen;
n = iobuf_read (inp, buf, n);
if (n == -1)
return gpg_error (GPG_ERR_EOF);
if ((rc = iobuf_write (out, buf, n)))
return rc; /* write error */
}
}
return 0;
}
/* Skip an unknown packet. PKTTYPE is the packet's type, PKTLEN is
the length of the packet's content and PARTIAL is whether partial
body length encoding in used (in this case PKTLEN is ignored). */
static void
skip_packet (IOBUF inp, int pkttype, unsigned long pktlen, int partial)
{
if (list_mode)
{
es_fprintf (listfp, ":unknown packet: type %2d, length %lu\n",
pkttype, pktlen);
if (pkttype)
{
int c, i = 0;
es_fputs ("dump:", listfp);
if (partial)
{
while ((c = iobuf_get (inp)) != -1)
dump_hex_line (c, &i);
}
else
{
for (; pktlen; pktlen--)
{
dump_hex_line ((c = iobuf_get (inp)), &i);
if (c == -1)
break;
}
}
es_putc ('\n', listfp);
return;
}
}
iobuf_skip_rest (inp, pktlen, partial);
}
/* Read PKTLEN bytes from INP and return them in a newly allocated
* buffer. In case of an error (including reading fewer than PKTLEN
* bytes from INP before EOF is returned), NULL is returned and an
* error message is logged. */
static void *
read_rest (IOBUF inp, size_t pktlen)
{
int c;
byte *buf, *p;
buf = xtrymalloc (pktlen);
if (!buf)
{
gpg_error_t err = gpg_error_from_syserror ();
log_error ("error reading rest of packet: %s\n", gpg_strerror (err));
return NULL;
}
for (p = buf; pktlen; pktlen--)
{
c = iobuf_get (inp);
if (c == -1)
{
log_error ("premature eof while reading rest of packet\n");
xfree (buf);
return NULL;
}
*p++ = c;
}
return buf;
}
/* Read a special size+body from INP. On success store an opaque MPI
with it at R_DATA. On error return an error code and store NULL at
R_DATA. Even in the error case store the number of read bytes at
R_NREAD. The caller shall pass the remaining size of the packet in
PKTLEN. */
static gpg_error_t
read_size_body (iobuf_t inp, int pktlen, size_t *r_nread,
gcry_mpi_t *r_data)
{
char buffer[256];
char *tmpbuf;
int i, c, nbytes;
*r_nread = 0;
*r_data = NULL;
if (!pktlen)
return gpg_error (GPG_ERR_INV_PACKET);
c = iobuf_readbyte (inp);
if (c < 0)
return gpg_error (GPG_ERR_INV_PACKET);
pktlen--;
++*r_nread;
nbytes = c;
if (nbytes < 2 || nbytes > 254)
return gpg_error (GPG_ERR_INV_PACKET);
if (nbytes > pktlen)
return gpg_error (GPG_ERR_INV_PACKET);
buffer[0] = nbytes;
for (i = 0; i < nbytes; i++)
{
c = iobuf_get (inp);
if (c < 0)
return gpg_error (GPG_ERR_INV_PACKET);
++*r_nread;
buffer[1+i] = c;
}
tmpbuf = xtrymalloc (1 + nbytes);
if (!tmpbuf)
return gpg_error_from_syserror ();
memcpy (tmpbuf, buffer, 1 + nbytes);
*r_data = gcry_mpi_set_opaque (NULL, tmpbuf, 8 * (1 + nbytes));
if (!*r_data)
{
xfree (tmpbuf);
return gpg_error_from_syserror ();
}
return 0;
}
/* Parse a marker packet. */
static int
parse_marker (IOBUF inp, int pkttype, unsigned long pktlen)
{
(void) pkttype;
if (pktlen != 3)
goto fail;
if (iobuf_get (inp) != 'P')
{
pktlen--;
goto fail;
}
if (iobuf_get (inp) != 'G')
{
pktlen--;
goto fail;
}
if (iobuf_get (inp) != 'P')
{
pktlen--;
goto fail;
}
if (list_mode)
es_fputs (":marker packet: PGP\n", listfp);
return 0;
fail:
log_error ("invalid marker packet\n");
if (list_mode)
es_fputs (":marker packet: [invalid]\n", listfp);
iobuf_skip_rest (inp, pktlen, 0);
return GPG_ERR_INV_PACKET;
}
static int
parse_symkeyenc (IOBUF inp, int pkttype, unsigned long pktlen,
PACKET * packet)
{
PKT_symkey_enc *k;
int rc = 0;
int i, version, s2kmode, cipher_algo, aead_algo, hash_algo, seskeylen, minlen;
if (pktlen < 4)
goto too_short;
version = iobuf_get_noeof (inp);
pktlen--;
if (version == 4)
;
else if (version == 5)
;
else
{
log_error ("packet(%d) with unknown version %d\n", pkttype, version);
if (list_mode)
es_fprintf (listfp, ":symkey enc packet: [unknown version]\n");
rc = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
if (pktlen > 200)
{ /* (we encode the seskeylen in a byte) */
log_error ("packet(%d) too large\n", pkttype);
if (list_mode)
es_fprintf (listfp, ":symkey enc packet: [too large]\n");
rc = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
cipher_algo = iobuf_get_noeof (inp);
pktlen--;
if (version == 5)
{
aead_algo = iobuf_get_noeof (inp);
pktlen--;
}
else
aead_algo = 0;
if (pktlen < 2)
goto too_short;
s2kmode = iobuf_get_noeof (inp);
pktlen--;
hash_algo = iobuf_get_noeof (inp);
pktlen--;
switch (s2kmode)
{
case 0: /* Simple S2K. */
minlen = 0;
break;
case 1: /* Salted S2K. */
minlen = 8;
break;
case 3: /* Iterated+salted S2K. */
minlen = 9;
break;
default:
log_error ("unknown S2K mode %d\n", s2kmode);
if (list_mode)
es_fprintf (listfp, ":symkey enc packet: [unknown S2K mode]\n");
goto leave;
}
if (minlen > pktlen)
{
log_error ("packet with S2K %d too short\n", s2kmode);
if (list_mode)
es_fprintf (listfp, ":symkey enc packet: [too short]\n");
rc = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
seskeylen = pktlen - minlen;
k = packet->pkt.symkey_enc = xmalloc_clear (sizeof *packet->pkt.symkey_enc
+ seskeylen - 1);
k->version = version;
k->cipher_algo = cipher_algo;
k->aead_algo = aead_algo;
k->s2k.mode = s2kmode;
k->s2k.hash_algo = hash_algo;
if (s2kmode == 1 || s2kmode == 3)
{
for (i = 0; i < 8 && pktlen; i++, pktlen--)
k->s2k.salt[i] = iobuf_get_noeof (inp);
}
if (s2kmode == 3)
{
k->s2k.count = iobuf_get_noeof (inp);
pktlen--;
}
k->seskeylen = seskeylen;
if (k->seskeylen)
{
for (i = 0; i < seskeylen && pktlen; i++, pktlen--)
k->seskey[i] = iobuf_get_noeof (inp);
/* What we're watching out for here is a session key decryptor
with no salt. The RFC says that using salt for this is a
MUST. */
if (s2kmode != 1 && s2kmode != 3)
log_info (_("WARNING: potentially insecure symmetrically"
" encrypted session key\n"));
}
log_assert (!pktlen);
if (list_mode)
{
es_fprintf (listfp,
":symkey enc packet: version %d, cipher %d, aead %d,"
" s2k %d, hash %d",
version, cipher_algo, aead_algo, s2kmode, hash_algo);
if (seskeylen)
{
/* To compute the size of the session key we need to know
* the size of the AEAD nonce which we may not know. Thus
* we show only the seize of the entire encrypted session
* key. */
if (aead_algo)
es_fprintf (listfp, ", encrypted seskey %d bytes", seskeylen);
else
es_fprintf (listfp, ", seskey %d bits", (seskeylen - 1) * 8);
}
es_fprintf (listfp, "\n");
if (s2kmode == 1 || s2kmode == 3)
{
es_fprintf (listfp, "\tsalt ");
es_write_hexstring (listfp, k->s2k.salt, 8, 0, NULL);
if (s2kmode == 3)
es_fprintf (listfp, ", count %lu (%lu)",
S2K_DECODE_COUNT ((ulong) k->s2k.count),
(ulong) k->s2k.count);
es_fprintf (listfp, "\n");
}
}
leave:
iobuf_skip_rest (inp, pktlen, 0);
return rc;
too_short:
log_error ("packet(%d) too short\n", pkttype);
if (list_mode)
es_fprintf (listfp, ":symkey enc packet: [too short]\n");
rc = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
static int
parse_pubkeyenc (IOBUF inp, int pkttype, unsigned long pktlen,
PACKET * packet)
{
int rc = 0;
int i, ndata;
PKT_pubkey_enc *k;
k = packet->pkt.pubkey_enc = xmalloc_clear (sizeof *packet->pkt.pubkey_enc);
if (pktlen < 12)
{
log_error ("packet(%d) too short\n", pkttype);
if (list_mode)
es_fputs (":pubkey enc packet: [too short]\n", listfp);
rc = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
k->version = iobuf_get_noeof (inp);
pktlen--;
if (k->version != 2 && k->version != 3)
{
log_error ("packet(%d) with unknown version %d\n", pkttype, k->version);
if (list_mode)
es_fputs (":pubkey enc packet: [unknown version]\n", listfp);
rc = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
k->keyid[0] = read_32 (inp);
pktlen -= 4;
k->keyid[1] = read_32 (inp);
pktlen -= 4;
k->pubkey_algo = iobuf_get_noeof (inp);
pktlen--;
k->throw_keyid = 0; /* Only used as flag for build_packet. */
if (list_mode)
es_fprintf (listfp,
":pubkey enc packet: version %d, algo %d, keyid %08lX%08lX\n",
k->version, k->pubkey_algo, (ulong) k->keyid[0],
(ulong) k->keyid[1]);
ndata = pubkey_get_nenc (k->pubkey_algo);
if (!ndata)
{
if (list_mode)
es_fprintf (listfp, "\tunsupported algorithm %d\n", k->pubkey_algo);
unknown_pubkey_warning (k->pubkey_algo);
k->data[0] = NULL; /* No need to store the encrypted data. */
}
else
{
for (i = 0; i < ndata; i++)
{
if (k->pubkey_algo == PUBKEY_ALGO_ECDH && i == 1)
{
size_t n;
rc = read_size_body (inp, pktlen, &n, k->data+i);
pktlen -= n;
}
else
{
int n = pktlen;
k->data[i] = mpi_read (inp, &n, 0);
pktlen -= n;
if (!k->data[i])
rc = gpg_error (GPG_ERR_INV_PACKET);
}
if (rc)
goto leave;
if (list_mode)
{
es_fprintf (listfp, "\tdata: ");
mpi_print (listfp, k->data[i], mpi_print_mode);
es_putc ('\n', listfp);
}
}
}
leave:
iobuf_skip_rest (inp, pktlen, 0);
return rc;
}
/* Dump a subpacket to LISTFP. BUFFER contains the subpacket in
question and points to the type field in the subpacket header (not
the start of the header). TYPE is the subpacket's type with the
critical bit cleared. CRITICAL is the value of the CRITICAL bit.
BUFLEN is the length of the buffer and LENGTH is the length of the
subpacket according to the subpacket's header. */
static void
dump_sig_subpkt (int hashed, int type, int critical,
const byte * buffer, size_t buflen, size_t length)
{
const char *p = NULL;
int i;
/* The CERT has warning out with explains how to use GNUPG to detect
* the ARRs - we print our old message here when it is a faked ARR
* and add an additional notice. */
if (type == SIGSUBPKT_ARR && !hashed)
{
es_fprintf (listfp,
"\tsubpkt %d len %u (additional recipient request)\n"
"WARNING: PGP versions > 5.0 and < 6.5.8 will automagically "
"encrypt to this key and thereby reveal the plaintext to "
"the owner of this ARR key. Detailed info follows:\n",
type, (unsigned) length);
}
buffer++;
length--;
es_fprintf (listfp, "\t%s%ssubpkt %d len %u (", /*) */
critical ? "critical " : "",
hashed ? "hashed " : "", type, (unsigned) length);
if (length > buflen)
{
es_fprintf (listfp, "too short: buffer is only %u)\n", (unsigned) buflen);
return;
}
switch (type)
{
case SIGSUBPKT_SIG_CREATED:
if (length >= 4)
es_fprintf (listfp, "sig created %s",
strtimestamp (buf32_to_u32 (buffer)));
break;
case SIGSUBPKT_SIG_EXPIRE:
if (length >= 4)
{
if (buf32_to_u32 (buffer))
es_fprintf (listfp, "sig expires after %s",
strtimevalue (buf32_to_u32 (buffer)));
else
es_fprintf (listfp, "sig does not expire");
}
break;
case SIGSUBPKT_EXPORTABLE:
if (length)
es_fprintf (listfp, "%sexportable", *buffer ? "" : "not ");
break;
case SIGSUBPKT_TRUST:
if (length != 2)
p = "[invalid trust subpacket]";
else
es_fprintf (listfp, "trust signature of depth %d, value %d", buffer[0],
buffer[1]);
break;
case SIGSUBPKT_REGEXP:
if (!length)
p = "[invalid regexp subpacket]";
else
{
es_fprintf (listfp, "regular expression: \"");
es_write_sanitized (listfp, buffer, length, "\"", NULL);
p = "\"";
}
break;
case SIGSUBPKT_REVOCABLE:
if (length)
es_fprintf (listfp, "%srevocable", *buffer ? "" : "not ");
break;
case SIGSUBPKT_KEY_EXPIRE:
if (length >= 4)
{
if (buf32_to_u32 (buffer))
es_fprintf (listfp, "key expires after %s",
strtimevalue (buf32_to_u32 (buffer)));
else
es_fprintf (listfp, "key does not expire");
}
break;
case SIGSUBPKT_PREF_SYM:
es_fputs ("pref-sym-algos:", listfp);
for (i = 0; i < length; i++)
es_fprintf (listfp, " %d", buffer[i]);
break;
case SIGSUBPKT_PREF_AEAD:
es_fputs ("pref-aead-algos:", listfp);
for (i = 0; i < length; i++)
es_fprintf (listfp, " %d", buffer[i]);
break;
case SIGSUBPKT_REV_KEY:
es_fputs ("revocation key: ", listfp);
if (length < 22)
p = "[too short]";
else
{
es_fprintf (listfp, "c=%02x a=%d f=", buffer[0], buffer[1]);
for (i = 2; i < length; i++)
es_fprintf (listfp, "%02X", buffer[i]);
}
break;
case SIGSUBPKT_ISSUER:
if (length >= 8)
es_fprintf (listfp, "issuer key ID %08lX%08lX",
(ulong) buf32_to_u32 (buffer),
(ulong) buf32_to_u32 (buffer + 4));
break;
case SIGSUBPKT_ISSUER_FPR:
if (length >= 21)
{
char *tmp;
es_fprintf (listfp, "issuer fpr v%d ", buffer[0]);
tmp = bin2hex (buffer+1, length-1, NULL);
if (tmp)
{
es_fputs (tmp, listfp);
xfree (tmp);
}
}
break;
case SIGSUBPKT_NOTATION:
{
es_fputs ("notation: ", listfp);
if (length < 8)
p = "[too short]";
else
{
const byte *s = buffer;
size_t n1, n2;
n1 = (s[4] << 8) | s[5];
n2 = (s[6] << 8) | s[7];
s += 8;
if (8 + n1 + n2 != length)
p = "[error]";
else
{
es_write_sanitized (listfp, s, n1, ")", NULL);
es_putc ('=', listfp);
if (*buffer & 0x80)
es_write_sanitized (listfp, s + n1, n2, ")", NULL);
else
p = "[not human readable]";
}
}
}
break;
case SIGSUBPKT_PREF_HASH:
es_fputs ("pref-hash-algos:", listfp);
for (i = 0; i < length; i++)
es_fprintf (listfp, " %d", buffer[i]);
break;
case SIGSUBPKT_PREF_COMPR:
es_fputs ("pref-zip-algos:", listfp);
for (i = 0; i < length; i++)
es_fprintf (listfp, " %d", buffer[i]);
break;
case SIGSUBPKT_KS_FLAGS:
es_fputs ("keyserver preferences:", listfp);
for (i = 0; i < length; i++)
es_fprintf (listfp, " %02X", buffer[i]);
break;
case SIGSUBPKT_PREF_KS:
es_fputs ("preferred keyserver: ", listfp);
es_write_sanitized (listfp, buffer, length, ")", NULL);
break;
case SIGSUBPKT_PRIMARY_UID:
p = "primary user ID";
break;
case SIGSUBPKT_POLICY:
es_fputs ("policy: ", listfp);
es_write_sanitized (listfp, buffer, length, ")", NULL);
break;
case SIGSUBPKT_KEY_FLAGS:
es_fputs ("key flags:", listfp);
for (i = 0; i < length; i++)
es_fprintf (listfp, " %02X", buffer[i]);
break;
case SIGSUBPKT_SIGNERS_UID:
p = "signer's user ID";
break;
case SIGSUBPKT_REVOC_REASON:
if (length)
{
es_fprintf (listfp, "revocation reason 0x%02x (", *buffer);
es_write_sanitized (listfp, buffer + 1, length - 1, ")", NULL);
p = ")";
}
break;
case SIGSUBPKT_ARR:
es_fputs ("Big Brother's key (ignored): ", listfp);
if (length < 22)
p = "[too short]";
else
{
es_fprintf (listfp, "c=%02x a=%d f=", buffer[0], buffer[1]);
if (length > 2)
es_write_hexstring (listfp, buffer+2, length-2, 0, NULL);
}
break;
case SIGSUBPKT_FEATURES:
es_fputs ("features:", listfp);
for (i = 0; i < length; i++)
es_fprintf (listfp, " %02x", buffer[i]);
break;
case SIGSUBPKT_SIGNATURE:
es_fputs ("signature: ", listfp);
if (length < 17)
p = "[too short]";
else
es_fprintf (listfp, "v%d, class 0x%02X, algo %d, digest algo %d",
buffer[0],
buffer[0] == 3 ? buffer[2] : buffer[1],
buffer[0] == 3 ? buffer[15] : buffer[2],
buffer[0] == 3 ? buffer[16] : buffer[3]);
break;
default:
if (type >= 100 && type <= 110)
p = "experimental / private subpacket";
else
p = "?";
break;
}
es_fprintf (listfp, "%s)\n", p ? p : "");
}
/*
* Returns: >= 0 use this offset into buffer
* -1 explicitly reject returning this type
* -2 subpacket too short
*/
int
parse_one_sig_subpkt (const byte * buffer, size_t n, int type)
{
switch (type)
{
case SIGSUBPKT_REV_KEY:
if (n < 22)
break;
return 0;
case SIGSUBPKT_SIG_CREATED:
case SIGSUBPKT_SIG_EXPIRE:
case SIGSUBPKT_KEY_EXPIRE:
if (n < 4)
break;
return 0;
case SIGSUBPKT_KEY_FLAGS:
case SIGSUBPKT_KS_FLAGS:
case SIGSUBPKT_PREF_SYM:
case SIGSUBPKT_PREF_AEAD:
case SIGSUBPKT_PREF_HASH:
case SIGSUBPKT_PREF_COMPR:
case SIGSUBPKT_POLICY:
case SIGSUBPKT_PREF_KS:
case SIGSUBPKT_FEATURES:
case SIGSUBPKT_REGEXP:
return 0;
case SIGSUBPKT_SIGNATURE:
case SIGSUBPKT_EXPORTABLE:
case SIGSUBPKT_REVOCABLE:
case SIGSUBPKT_REVOC_REASON:
if (!n)
break;
return 0;
case SIGSUBPKT_ISSUER: /* issuer key ID */
if (n < 8)
break;
return 0;
case SIGSUBPKT_ISSUER_FPR: /* issuer key fingerprint */
if (n < 21)
break;
return 0;
case SIGSUBPKT_NOTATION:
/* minimum length needed, and the subpacket must be well-formed
where the name length and value length all fit inside the
packet. */
if (n < 8
|| 8 + ((buffer[4] << 8) | buffer[5]) +
((buffer[6] << 8) | buffer[7]) != n)
break;
return 0;
case SIGSUBPKT_PRIMARY_UID:
if (n != 1)
break;
return 0;
case SIGSUBPKT_TRUST:
if (n != 2)
break;
return 0;
default:
return 0;
}
return -2;
}
/* Return true if we understand the critical notation. */
static int
can_handle_critical_notation (const byte *name, size_t len)
{
strlist_t sl;
register_known_notation (NULL); /* Make sure it is initialized. */
for (sl = known_notations_list; sl; sl = sl->next)
if (sl->flags == len && !memcmp (sl->d, name, len))
return 1; /* Known */
if (opt.verbose)
{
log_info(_("Unknown critical signature notation: ") );
print_utf8_buffer (log_get_stream(), name, len);
log_printf ("\n");
}
return 0; /* Unknown. */
}
static int
can_handle_critical (const byte * buffer, size_t n, int type)
{
switch (type)
{
case SIGSUBPKT_NOTATION:
if (n >= 8)
{
size_t notation_len = ((buffer[4] << 8) | buffer[5]);
if (n - 8 >= notation_len)
return can_handle_critical_notation (buffer + 8, notation_len);
}
return 0;
case SIGSUBPKT_SIGNATURE:
case SIGSUBPKT_SIG_CREATED:
case SIGSUBPKT_SIG_EXPIRE:
case SIGSUBPKT_KEY_EXPIRE:
case SIGSUBPKT_EXPORTABLE:
case SIGSUBPKT_REVOCABLE:
case SIGSUBPKT_REV_KEY:
case SIGSUBPKT_ISSUER: /* issuer key ID */
case SIGSUBPKT_ISSUER_FPR: /* issuer fingerprint */
case SIGSUBPKT_PREF_SYM:
case SIGSUBPKT_PREF_AEAD:
case SIGSUBPKT_PREF_HASH:
case SIGSUBPKT_PREF_COMPR:
case SIGSUBPKT_KEY_FLAGS:
case SIGSUBPKT_PRIMARY_UID:
case SIGSUBPKT_FEATURES:
case SIGSUBPKT_TRUST:
case SIGSUBPKT_REGEXP:
/* Is it enough to show the policy or keyserver? */
case SIGSUBPKT_POLICY:
case SIGSUBPKT_PREF_KS:
case SIGSUBPKT_REVOC_REASON: /* At least we know about it. */
return 1;
default:
return 0;
}
}
const byte *
enum_sig_subpkt (const subpktarea_t * pktbuf, sigsubpkttype_t reqtype,
size_t * ret_n, int *start, int *critical)
{
const byte *buffer;
int buflen;
int type;
int critical_dummy;
int offset;
size_t n;
int seq = 0;
int reqseq = start ? *start : 0;
if (!critical)
critical = &critical_dummy;
if (!pktbuf || reqseq == -1)
{
static char dummy[] = "x";
/* Return a value different from NULL to indicate that
* there is no critical bit we do not understand. */
return reqtype == SIGSUBPKT_TEST_CRITICAL ? dummy : NULL;
}
buffer = pktbuf->data;
buflen = pktbuf->len;
while (buflen)
{
n = *buffer++;
buflen--;
if (n == 255) /* 4 byte length header. */
{
if (buflen < 4)
goto too_short;
n = buf32_to_size_t (buffer);
buffer += 4;
buflen -= 4;
}
else if (n >= 192) /* 4 byte special encoded length header. */
{
if (buflen < 2)
goto too_short;
n = ((n - 192) << 8) + *buffer + 192;
buffer++;
buflen--;
}
if (buflen < n)
goto too_short;
if (!buflen)
goto no_type_byte;
type = *buffer;
if (type & 0x80)
{
type &= 0x7f;
*critical = 1;
}
else
*critical = 0;
if (!(++seq > reqseq))
;
else if (reqtype == SIGSUBPKT_TEST_CRITICAL)
{
if (*critical)
{
if (n - 1 > buflen + 1)
goto too_short;
if (!can_handle_critical (buffer + 1, n - 1, type))
{
if (opt.verbose)
log_info (_("subpacket of type %d has "
"critical bit set\n"), type);
if (start)
*start = seq;
return NULL; /* This is an error. */
}
}
}
else if (reqtype < 0) /* List packets. */
dump_sig_subpkt (reqtype == SIGSUBPKT_LIST_HASHED,
type, *critical, buffer, buflen, n);
else if (type == reqtype) /* Found. */
{
buffer++;
n--;
if (n > buflen)
goto too_short;
if (ret_n)
*ret_n = n;
offset = parse_one_sig_subpkt (buffer, n, type);
switch (offset)
{
case -2:
log_error ("subpacket of type %d too short\n", type);
return NULL;
case -1:
return NULL;
default:
break;
}
if (start)
*start = seq;
return buffer + offset;
}
buffer += n;
buflen -= n;
}
if (reqtype == SIGSUBPKT_TEST_CRITICAL)
/* Returning NULL means we found a subpacket with the critical bit
set that we don't grok. We've iterated over all the subpackets
and haven't found such a packet so we need to return a non-NULL
value. */
return buffer;
/* Critical bit we don't understand. */
if (start)
*start = -1;
return NULL; /* End of packets; not found. */
too_short:
if (opt.verbose)
log_info ("buffer shorter than subpacket\n");
if (start)
*start = -1;
return NULL;
no_type_byte:
if (opt.verbose)
log_info ("type octet missing in subpacket\n");
if (start)
*start = -1;
return NULL;
}
const byte *
parse_sig_subpkt (const subpktarea_t * buffer, sigsubpkttype_t reqtype,
size_t * ret_n)
{
return enum_sig_subpkt (buffer, reqtype, ret_n, NULL, NULL);
}
const byte *
parse_sig_subpkt2 (PKT_signature * sig, sigsubpkttype_t reqtype)
{
const byte *p;
p = parse_sig_subpkt (sig->hashed, reqtype, NULL);
if (!p)
p = parse_sig_subpkt (sig->unhashed, reqtype, NULL);
return p;
}
/* Find all revocation keys. Look in hashed area only. */
void
parse_revkeys (PKT_signature * sig)
{
const byte *revkey;
int seq = 0;
size_t len;
if (sig->sig_class != 0x1F)
return;
while ((revkey = enum_sig_subpkt (sig->hashed, SIGSUBPKT_REV_KEY,
&len, &seq, NULL)))
{
/* Consider only valid packets. They must have a length of
* either 2+20 or 2+32 octets and bit 7 of the class octet must
* be set. */
if ((len == 22 || len == 34)
&& (revkey[0] & 0x80))
{
sig->revkey = xrealloc (sig->revkey,
sizeof (struct revocation_key) *
(sig->numrevkeys + 1));
sig->revkey[sig->numrevkeys].class = revkey[0];
sig->revkey[sig->numrevkeys].algid = revkey[1];
len -= 2;
sig->revkey[sig->numrevkeys].fprlen = len;
memcpy (sig->revkey[sig->numrevkeys].fpr, revkey+2, len);
memset (sig->revkey[sig->numrevkeys].fpr+len, 0,
sizeof (sig->revkey[sig->numrevkeys].fpr) - len);
sig->numrevkeys++;
}
}
}
int
parse_signature (IOBUF inp, int pkttype, unsigned long pktlen,
PKT_signature * sig)
{
int md5_len = 0;
unsigned n;
int is_v4or5 = 0;
int rc = 0;
int i, ndata;
if (pktlen < 16)
{
log_error ("packet(%d) too short\n", pkttype);
if (list_mode)
es_fputs (":signature packet: [too short]\n", listfp);
goto leave;
}
sig->version = iobuf_get_noeof (inp);
pktlen--;
if (sig->version == 4 || sig->version == 5)
is_v4or5 = 1;
else if (sig->version != 2 && sig->version != 3)
{
log_error ("packet(%d) with unknown version %d\n",
pkttype, sig->version);
if (list_mode)
es_fputs (":signature packet: [unknown version]\n", listfp);
rc = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
if (!is_v4or5)
{
if (pktlen == 0)
goto underflow;
md5_len = iobuf_get_noeof (inp);
pktlen--;
}
if (pktlen == 0)
goto underflow;
sig->sig_class = iobuf_get_noeof (inp);
pktlen--;
if (!is_v4or5)
{
if (pktlen < 12)
goto underflow;
sig->timestamp = read_32 (inp);
pktlen -= 4;
sig->keyid[0] = read_32 (inp);
pktlen -= 4;
sig->keyid[1] = read_32 (inp);
pktlen -= 4;
}
if (pktlen < 2)
goto underflow;
sig->pubkey_algo = iobuf_get_noeof (inp);
pktlen--;
sig->digest_algo = iobuf_get_noeof (inp);
pktlen--;
sig->flags.exportable = 1;
sig->flags.revocable = 1;
if (is_v4or5) /* Read subpackets. */
{
if (pktlen < 2)
goto underflow;
n = read_16 (inp);
pktlen -= 2; /* Length of hashed data. */
if (pktlen < n)
goto underflow;
if (n > 10000)
{
log_error ("signature packet: hashed data too long\n");
if (list_mode)
es_fputs (":signature packet: [hashed data too long]\n", listfp);
rc = GPG_ERR_INV_PACKET;
goto leave;
}
if (n)
{
sig->hashed = xmalloc (sizeof (*sig->hashed) + n - 1);
sig->hashed->size = n;
sig->hashed->len = n;
if (iobuf_read (inp, sig->hashed->data, n) != n)
{
log_error ("premature eof while reading "
"hashed signature data\n");
if (list_mode)
es_fputs (":signature packet: [premature eof]\n", listfp);
rc = -1;
goto leave;
}
pktlen -= n;
}
if (pktlen < 2)
goto underflow;
n = read_16 (inp);
pktlen -= 2; /* Length of unhashed data. */
if (pktlen < n)
goto underflow;
if (n > 10000)
{
log_error ("signature packet: unhashed data too long\n");
if (list_mode)
es_fputs (":signature packet: [unhashed data too long]\n", listfp);
rc = GPG_ERR_INV_PACKET;
goto leave;
}
if (n)
{
sig->unhashed = xmalloc (sizeof (*sig->unhashed) + n - 1);
sig->unhashed->size = n;
sig->unhashed->len = n;
if (iobuf_read (inp, sig->unhashed->data, n) != n)
{
log_error ("premature eof while reading "
"unhashed signature data\n");
if (list_mode)
es_fputs (":signature packet: [premature eof]\n", listfp);
rc = -1;
goto leave;
}
pktlen -= n;
}
}
if (pktlen < 2)
goto underflow;
sig->digest_start[0] = iobuf_get_noeof (inp);
pktlen--;
sig->digest_start[1] = iobuf_get_noeof (inp);
pktlen--;
if (is_v4or5 && sig->pubkey_algo) /* Extract required information. */
{
const byte *p;
size_t len;
/* Set sig->flags.unknown_critical if there is a critical bit
* set for packets which we do not understand. */
if (!parse_sig_subpkt (sig->hashed, SIGSUBPKT_TEST_CRITICAL, NULL)
|| !parse_sig_subpkt (sig->unhashed, SIGSUBPKT_TEST_CRITICAL, NULL))
sig->flags.unknown_critical = 1;
p = parse_sig_subpkt (sig->hashed, SIGSUBPKT_SIG_CREATED, NULL);
if (p)
sig->timestamp = buf32_to_u32 (p);
else if (!(sig->pubkey_algo >= 100 && sig->pubkey_algo <= 110)
&& opt.verbose)
log_info ("signature packet without timestamp\n");
/* Set the key id. We first try the issuer fingerprint and if
* it is a v4 signature the fallback to the issuer. Note that
* only the issuer packet is also searched in the unhashed area. */
p = parse_sig_subpkt (sig->hashed, SIGSUBPKT_ISSUER_FPR, &len);
if (p && len == 21 && p[0] == 4)
{
sig->keyid[0] = buf32_to_u32 (p + 1 + 12);
sig->keyid[1] = buf32_to_u32 (p + 1 + 16);
}
else if (p && len == 33 && p[0] == 5)
{
sig->keyid[0] = buf32_to_u32 (p + 1 );
sig->keyid[1] = buf32_to_u32 (p + 1 + 4);
}
else if ((p = parse_sig_subpkt2 (sig, SIGSUBPKT_ISSUER)))
{
sig->keyid[0] = buf32_to_u32 (p);
sig->keyid[1] = buf32_to_u32 (p + 4);
}
else if (!(sig->pubkey_algo >= 100 && sig->pubkey_algo <= 110)
&& opt.verbose)
log_info ("signature packet without keyid\n");
p = parse_sig_subpkt (sig->hashed, SIGSUBPKT_SIG_EXPIRE, NULL);
if (p && buf32_to_u32 (p))
sig->expiredate = sig->timestamp + buf32_to_u32 (p);
if (sig->expiredate && sig->expiredate <= make_timestamp ())
sig->flags.expired = 1;
p = parse_sig_subpkt (sig->hashed, SIGSUBPKT_POLICY, NULL);
if (p)
sig->flags.policy_url = 1;
p = parse_sig_subpkt (sig->hashed, SIGSUBPKT_PREF_KS, NULL);
if (p)
sig->flags.pref_ks = 1;
p = parse_sig_subpkt (sig->hashed, SIGSUBPKT_SIGNERS_UID, &len);
if (p && len)
{
+ char *mbox;
+
sig->signers_uid = try_make_printable_string (p, len, 0);
if (!sig->signers_uid)
{
rc = gpg_error_from_syserror ();
goto leave;
}
+ mbox = mailbox_from_userid (sig->signers_uid, 0);
+ if (mbox)
+ {
+ xfree (sig->signers_uid);
+ sig->signers_uid = mbox;
+ }
}
p = parse_sig_subpkt (sig->hashed, SIGSUBPKT_NOTATION, NULL);
if (p)
sig->flags.notation = 1;
p = parse_sig_subpkt (sig->hashed, SIGSUBPKT_REVOCABLE, NULL);
if (p && *p == 0)
sig->flags.revocable = 0;
p = parse_sig_subpkt (sig->hashed, SIGSUBPKT_TRUST, &len);
if (p && len == 2)
{
sig->trust_depth = p[0];
sig->trust_value = p[1];
/* Only look for a regexp if there is also a trust
subpacket. */
sig->trust_regexp =
parse_sig_subpkt (sig->hashed, SIGSUBPKT_REGEXP, &len);
/* If the regular expression is of 0 length, there is no
regular expression. */
if (len == 0)
sig->trust_regexp = NULL;
}
/* We accept the exportable subpacket from either the hashed or
unhashed areas as older versions of gpg put it in the
unhashed area. In theory, anyway, we should never see this
packet off of a local keyring. */
p = parse_sig_subpkt2 (sig, SIGSUBPKT_EXPORTABLE);
if (p && *p == 0)
sig->flags.exportable = 0;
/* Find all revocation keys. */
if (sig->sig_class == 0x1F)
parse_revkeys (sig);
}
if (list_mode)
{
es_fprintf (listfp, ":signature packet: algo %d, keyid %08lX%08lX\n"
"\tversion %d, created %lu, md5len %d, sigclass 0x%02x\n"
"\tdigest algo %d, begin of digest %02x %02x\n",
sig->pubkey_algo,
(ulong) sig->keyid[0], (ulong) sig->keyid[1],
sig->version, (ulong) sig->timestamp, md5_len, sig->sig_class,
sig->digest_algo, sig->digest_start[0], sig->digest_start[1]);
if (is_v4or5)
{
parse_sig_subpkt (sig->hashed, SIGSUBPKT_LIST_HASHED, NULL);
parse_sig_subpkt (sig->unhashed, SIGSUBPKT_LIST_UNHASHED, NULL);
}
}
ndata = pubkey_get_nsig (sig->pubkey_algo);
if (!ndata)
{
if (list_mode)
es_fprintf (listfp, "\tunknown algorithm %d\n", sig->pubkey_algo);
unknown_pubkey_warning (sig->pubkey_algo);
/* We store the plain material in data[0], so that we are able
* to write it back with build_packet(). */
if (pktlen > (5 * MAX_EXTERN_MPI_BITS / 8))
{
/* We include a limit to avoid too trivial DoS attacks by
having gpg allocate too much memory. */
log_error ("signature packet: too much data\n");
rc = GPG_ERR_INV_PACKET;
}
else
{
sig->data[0] =
gcry_mpi_set_opaque (NULL, read_rest (inp, pktlen), pktlen * 8);
pktlen = 0;
}
}
else
{
for (i = 0; i < ndata; i++)
{
n = pktlen;
sig->data[i] = mpi_read (inp, &n, 0);
pktlen -= n;
if (list_mode)
{
es_fprintf (listfp, "\tdata: ");
mpi_print (listfp, sig->data[i], mpi_print_mode);
es_putc ('\n', listfp);
}
if (!sig->data[i])
rc = GPG_ERR_INV_PACKET;
}
}
leave:
iobuf_skip_rest (inp, pktlen, 0);
return rc;
underflow:
log_error ("packet(%d) too short\n", pkttype);
if (list_mode)
es_fputs (":signature packet: [too short]\n", listfp);
iobuf_skip_rest (inp, pktlen, 0);
return GPG_ERR_INV_PACKET;
}
static int
parse_onepass_sig (IOBUF inp, int pkttype, unsigned long pktlen,
PKT_onepass_sig * ops)
{
int version;
int rc = 0;
if (pktlen < 13)
{
log_error ("packet(%d) too short\n", pkttype);
if (list_mode)
es_fputs (":onepass_sig packet: [too short]\n", listfp);
rc = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
version = iobuf_get_noeof (inp);
pktlen--;
if (version != 3)
{
log_error ("onepass_sig with unknown version %d\n", version);
if (list_mode)
es_fputs (":onepass_sig packet: [unknown version]\n", listfp);
rc = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
ops->sig_class = iobuf_get_noeof (inp);
pktlen--;
ops->digest_algo = iobuf_get_noeof (inp);
pktlen--;
ops->pubkey_algo = iobuf_get_noeof (inp);
pktlen--;
ops->keyid[0] = read_32 (inp);
pktlen -= 4;
ops->keyid[1] = read_32 (inp);
pktlen -= 4;
ops->last = iobuf_get_noeof (inp);
pktlen--;
if (list_mode)
es_fprintf (listfp,
":onepass_sig packet: keyid %08lX%08lX\n"
"\tversion %d, sigclass 0x%02x, digest %d, pubkey %d, "
"last=%d\n",
(ulong) ops->keyid[0], (ulong) ops->keyid[1],
version, ops->sig_class,
ops->digest_algo, ops->pubkey_algo, ops->last);
leave:
iobuf_skip_rest (inp, pktlen, 0);
return rc;
}
static int
parse_key (IOBUF inp, int pkttype, unsigned long pktlen,
byte * hdr, int hdrlen, PACKET * pkt)
{
gpg_error_t err = 0;
int i, version, algorithm;
unsigned long timestamp, expiredate, max_expiredate;
int npkey, nskey;
u32 keyid[2];
PKT_public_key *pk;
int is_v5;
unsigned int pkbytes; /* For v5 keys: Number of bytes in the public
* key material. For v4 keys: 0. */
(void) hdr;
pk = pkt->pkt.public_key; /* PK has been cleared. */
version = iobuf_get_noeof (inp);
pktlen--;
if (pkttype == PKT_PUBLIC_SUBKEY && version == '#')
{
/* Early versions of G10 used the old PGP comments packets;
* luckily all those comments are started by a hash. */
if (list_mode)
{
es_fprintf (listfp, ":rfc1991 comment packet: \"");
for (; pktlen; pktlen--)
{
int c;
c = iobuf_get (inp);
if (c == -1)
break; /* Ooops: shorter than indicated. */
if (c >= ' ' && c <= 'z')
es_putc (c, listfp);
else
es_fprintf (listfp, "\\x%02x", c);
}
es_fprintf (listfp, "\"\n");
}
iobuf_skip_rest (inp, pktlen, 0);
return 0;
}
else if (version == 4)
is_v5 = 0;
else if (version == 5)
is_v5 = 1;
else if (version == 2 || version == 3)
{
/* Not anymore supported since 2.1. Use an older gpg version
* (i.e. gpg 1.4) to parse v3 packets. */
if (opt.verbose > 1)
log_info ("packet(%d) with obsolete version %d\n", pkttype, version);
if (list_mode)
es_fprintf (listfp, ":key packet: [obsolete version %d]\n", version);
pk->version = version;
err = gpg_error (GPG_ERR_LEGACY_KEY);
goto leave;
}
else
{
log_error ("packet(%d) with unknown version %d\n", pkttype, version);
if (list_mode)
es_fputs (":key packet: [unknown version]\n", listfp);
err = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
if (pktlen < (is_v5? 15:11))
{
log_error ("packet(%d) too short\n", pkttype);
if (list_mode)
es_fputs (":key packet: [too short]\n", listfp);
err = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
else if (pktlen > MAX_KEY_PACKET_LENGTH)
{
log_error ("packet(%d) too large\n", pkttype);
if (list_mode)
es_fputs (":key packet: [too large]\n", listfp);
err = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
timestamp = read_32 (inp);
pktlen -= 4;
expiredate = 0; /* have to get it from the selfsignature */
max_expiredate = 0;
algorithm = iobuf_get_noeof (inp);
pktlen--;
if (is_v5)
{
pkbytes = read_32 (inp);
pktlen -= 4;
}
else
pkbytes = 0;
if (list_mode)
{
es_fprintf (listfp, ":%s key packet:\n"
"\tversion %d, algo %d, created %lu, expires %lu",
pkttype == PKT_PUBLIC_KEY ? "public" :
pkttype == PKT_SECRET_KEY ? "secret" :
pkttype == PKT_PUBLIC_SUBKEY ? "public sub" :
pkttype == PKT_SECRET_SUBKEY ? "secret sub" : "??",
version, algorithm, timestamp, expiredate);
if (is_v5)
es_fprintf (listfp, ", pkbytes %u\n", pkbytes);
else
es_fprintf (listfp, "\n");
}
pk->timestamp = timestamp;
pk->expiredate = expiredate;
pk->max_expiredate = max_expiredate;
pk->hdrbytes = hdrlen;
pk->version = version;
pk->flags.primary = (pkttype == PKT_PUBLIC_KEY || pkttype == PKT_SECRET_KEY);
pk->pubkey_algo = algorithm;
nskey = pubkey_get_nskey (algorithm);
npkey = pubkey_get_npkey (algorithm);
if (!npkey)
{
if (list_mode)
es_fprintf (listfp, "\tunknown algorithm %d\n", algorithm);
unknown_pubkey_warning (algorithm);
}
if (!npkey)
{
/* Unknown algorithm - put data into an opaque MPI. */
pk->pkey[0] = gcry_mpi_set_opaque (NULL,
read_rest (inp, pktlen), pktlen * 8);
pktlen = 0;
goto leave;
}
else
{
for (i = 0; i < npkey; i++)
{
if ( (algorithm == PUBKEY_ALGO_ECDSA && (i == 0))
|| (algorithm == PUBKEY_ALGO_EDDSA && (i == 0))
|| (algorithm == PUBKEY_ALGO_ECDH && (i == 0 || i == 2)))
{
/* Read the OID (i==1) or the KDF params (i==2). */
size_t n;
err = read_size_body (inp, pktlen, &n, pk->pkey+i);
pktlen -= n;
}
else
{
unsigned int n = pktlen;
pk->pkey[i] = mpi_read (inp, &n, 0);
pktlen -= n;
if (!pk->pkey[i])
err = gpg_error (GPG_ERR_INV_PACKET);
}
if (err)
goto leave;
if (list_mode)
{
es_fprintf (listfp, "\tpkey[%d]: ", i);
mpi_print (listfp, pk->pkey[i], mpi_print_mode);
if ((algorithm == PUBKEY_ALGO_ECDSA
|| algorithm == PUBKEY_ALGO_EDDSA
|| algorithm == PUBKEY_ALGO_ECDH) && i==0)
{
char *curve = openpgp_oid_to_str (pk->pkey[0]);
const char *name = openpgp_oid_to_curve (curve, 0);
es_fprintf (listfp, " %s (%s)", name?name:"", curve);
xfree (curve);
}
es_putc ('\n', listfp);
}
}
}
if (list_mode)
keyid_from_pk (pk, keyid);
if (pkttype == PKT_SECRET_KEY || pkttype == PKT_SECRET_SUBKEY)
{
struct seckey_info *ski;
byte temp[16];
size_t snlen = 0;
unsigned int skbytes;
if (pktlen < 1)
{
err = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
pk->seckey_info = ski = xtrycalloc (1, sizeof *ski);
if (!pk->seckey_info)
{
err = gpg_error_from_syserror ();
goto leave;
}
ski->algo = iobuf_get_noeof (inp);
pktlen--;
if (is_v5)
{
unsigned int protcount = 0;
/* Read the one octet count of the following key-protection
* material. Only required in case of unknown values. */
if (!pktlen)
{
err = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
protcount = iobuf_get_noeof (inp);
pktlen--;
if (list_mode)
es_fprintf (listfp, "\tprotbytes: %u\n", protcount);
}
if (ski->algo)
{
ski->is_protected = 1;
ski->s2k.count = 0;
if (ski->algo == 254 || ski->algo == 255)
{
if (pktlen < 3)
{
err = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
ski->sha1chk = (ski->algo == 254);
ski->algo = iobuf_get_noeof (inp);
pktlen--;
/* Note that a ski->algo > 110 is illegal, but I'm not
* erroring out here as otherwise there would be no way
* to delete such a key. */
ski->s2k.mode = iobuf_get_noeof (inp);
pktlen--;
ski->s2k.hash_algo = iobuf_get_noeof (inp);
pktlen--;
/* Check for the special GNU extension. */
if (ski->s2k.mode == 101)
{
for (i = 0; i < 4 && pktlen; i++, pktlen--)
temp[i] = iobuf_get_noeof (inp);
if (i < 4 || memcmp (temp, "GNU", 3))
{
if (list_mode)
es_fprintf (listfp, "\tunknown S2K %d\n",
ski->s2k.mode);
err = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
/* Here we know that it is a GNU extension. What
* follows is the GNU protection mode: All values
* have special meanings and they are mapped to MODE
* with a base of 1000. */
ski->s2k.mode = 1000 + temp[3];
}
/* Read the salt. */
if (ski->s2k.mode == 3 || ski->s2k.mode == 1)
{
for (i = 0; i < 8 && pktlen; i++, pktlen--)
temp[i] = iobuf_get_noeof (inp);
if (i < 8)
{
err = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
memcpy (ski->s2k.salt, temp, 8);
}
/* Check the mode. */
switch (ski->s2k.mode)
{
case 0:
if (list_mode)
es_fprintf (listfp, "\tsimple S2K");
break;
case 1:
if (list_mode)
es_fprintf (listfp, "\tsalted S2K");
break;
case 3:
if (list_mode)
es_fprintf (listfp, "\titer+salt S2K");
break;
case 1001:
if (list_mode)
es_fprintf (listfp, "\tgnu-dummy S2K");
break;
case 1002:
if (list_mode)
es_fprintf (listfp, "\tgnu-divert-to-card S2K");
break;
default:
if (list_mode)
es_fprintf (listfp, "\tunknown %sS2K %d\n",
ski->s2k.mode < 1000 ? "" : "GNU ",
ski->s2k.mode);
err = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
/* Print some info. */
if (list_mode)
{
es_fprintf (listfp, ", algo: %d,%s hash: %d",
ski->algo,
ski->sha1chk ? " SHA1 protection,"
: " simple checksum,", ski->s2k.hash_algo);
if (ski->s2k.mode == 1 || ski->s2k.mode == 3)
{
es_fprintf (listfp, ", salt: ");
es_write_hexstring (listfp, ski->s2k.salt, 8, 0, NULL);
}
es_putc ('\n', listfp);
}
/* Read remaining protection parameters. */
if (ski->s2k.mode == 3)
{
if (pktlen < 1)
{
err = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
ski->s2k.count = iobuf_get_noeof (inp);
pktlen--;
if (list_mode)
es_fprintf (listfp, "\tprotect count: %lu (%lu)\n",
(ulong)S2K_DECODE_COUNT ((ulong)ski->s2k.count),
(ulong) ski->s2k.count);
}
else if (ski->s2k.mode == 1002)
{
/* Read the serial number. */
if (pktlen < 1)
{
err = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
snlen = iobuf_get (inp);
pktlen--;
if (pktlen < snlen || snlen == (size_t)(-1))
{
err = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
}
}
else /* Old version; no S2K, so we set mode to 0, hash MD5. */
{
/* Note that a ski->algo > 110 is illegal, but I'm not
erroring on it here as otherwise there would be no
way to delete such a key. */
ski->s2k.mode = 0;
ski->s2k.hash_algo = DIGEST_ALGO_MD5;
if (list_mode)
es_fprintf (listfp, "\tprotect algo: %d (hash algo: %d)\n",
ski->algo, ski->s2k.hash_algo);
}
/* It is really ugly that we don't know the size
* of the IV here in cases we are not aware of the algorithm.
* so a
* ski->ivlen = cipher_get_blocksize (ski->algo);
* won't work. The only solution I see is to hardwire it.
* NOTE: if you change the ivlen above 16, don't forget to
* enlarge temp.
* FIXME: For v5 keys we can deduce this info!
*/
ski->ivlen = openpgp_cipher_blocklen (ski->algo);
log_assert (ski->ivlen <= sizeof (temp));
if (ski->s2k.mode == 1001)
ski->ivlen = 0;
else if (ski->s2k.mode == 1002)
ski->ivlen = snlen < 16 ? snlen : 16;
if (pktlen < ski->ivlen)
{
err = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
for (i = 0; i < ski->ivlen; i++, pktlen--)
temp[i] = iobuf_get_noeof (inp);
if (list_mode)
{
es_fprintf (listfp,
ski->s2k.mode == 1002 ? "\tserial-number: "
: "\tprotect IV: ");
for (i = 0; i < ski->ivlen; i++)
es_fprintf (listfp, " %02x", temp[i]);
es_putc ('\n', listfp);
}
memcpy (ski->iv, temp, ski->ivlen);
}
/* Skip count of secret key material. */
if (is_v5)
{
if (pktlen < 4)
{
err = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
skbytes = read_32 (inp);
pktlen -= 4;
if (list_mode)
es_fprintf (listfp, "\tskbytes: %u\n", skbytes);
}
/* It does not make sense to read it into secure memory.
* If the user is so careless, not to protect his secret key,
* we can assume, that he operates an open system :=(.
* So we put the key into secure memory when we unprotect it. */
if (ski->s2k.mode == 1001 || ski->s2k.mode == 1002)
{
/* Better set some dummy stuff here. */
pk->pkey[npkey] = gcry_mpi_set_opaque (NULL,
xstrdup ("dummydata"),
10 * 8);
pktlen = 0;
}
else if (ski->is_protected)
{
if (pktlen < 2) /* At least two bytes for the length. */
{
err = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
/* Ugly: The length is encrypted too, so we read all stuff
* up to the end of the packet into the first SKEY
* element.
* FIXME: We can do better for v5 keys. */
pk->pkey[npkey] = gcry_mpi_set_opaque (NULL,
read_rest (inp, pktlen),
pktlen * 8);
/* Mark that MPI as protected - we need this information for
* importing a key. The OPAQUE flag can't be used because
* we also store public EdDSA values in opaque MPIs. */
if (pk->pkey[npkey])
gcry_mpi_set_flag (pk->pkey[npkey], GCRYMPI_FLAG_USER1);
pktlen = 0;
if (list_mode)
es_fprintf (listfp, "\tskey[%d]: [v4 protected]\n", npkey);
}
else
{
/* Not encrypted. */
for (i = npkey; i < nskey; i++)
{
unsigned int n;
if (pktlen < 2) /* At least two bytes for the length. */
{
err = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
n = pktlen;
pk->pkey[i] = mpi_read (inp, &n, 0);
pktlen -= n;
if (list_mode)
{
es_fprintf (listfp, "\tskey[%d]: ", i);
mpi_print (listfp, pk->pkey[i], mpi_print_mode);
es_putc ('\n', listfp);
}
if (!pk->pkey[i])
err = gpg_error (GPG_ERR_INV_PACKET);
}
if (err)
goto leave;
if (pktlen < 2)
{
err = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
ski->csum = read_16 (inp);
pktlen -= 2;
if (list_mode)
es_fprintf (listfp, "\tchecksum: %04hx\n", ski->csum);
}
}
/* Note that KEYID below has been initialized above in list_mode. */
if (list_mode)
es_fprintf (listfp, "\tkeyid: %08lX%08lX\n",
(ulong) keyid[0], (ulong) keyid[1]);
leave:
iobuf_skip_rest (inp, pktlen, 0);
return err;
}
/* Attribute subpackets have the same format as v4 signature
subpackets. This is not part of OpenPGP, but is done in several
versions of PGP nevertheless. */
int
parse_attribute_subpkts (PKT_user_id * uid)
{
size_t n;
int count = 0;
struct user_attribute *attribs = NULL;
const byte *buffer = uid->attrib_data;
int buflen = uid->attrib_len;
byte type;
xfree (uid->attribs);
while (buflen)
{
n = *buffer++;
buflen--;
if (n == 255) /* 4 byte length header. */
{
if (buflen < 4)
goto too_short;
n = buf32_to_size_t (buffer);
buffer += 4;
buflen -= 4;
}
else if (n >= 192) /* 2 byte special encoded length header. */
{
if (buflen < 2)
goto too_short;
n = ((n - 192) << 8) + *buffer + 192;
buffer++;
buflen--;
}
if (buflen < n)
goto too_short;
if (!n)
{
/* Too short to encode the subpacket type. */
if (opt.verbose)
log_info ("attribute subpacket too short\n");
break;
}
attribs = xrealloc (attribs,
(count + 1) * sizeof (struct user_attribute));
memset (&attribs[count], 0, sizeof (struct user_attribute));
type = *buffer;
buffer++;
buflen--;
n--;
attribs[count].type = type;
attribs[count].data = buffer;
attribs[count].len = n;
buffer += n;
buflen -= n;
count++;
}
uid->attribs = attribs;
uid->numattribs = count;
return count;
too_short:
if (opt.verbose)
log_info ("buffer shorter than attribute subpacket\n");
uid->attribs = attribs;
uid->numattribs = count;
return count;
}
static int
parse_user_id (IOBUF inp, int pkttype, unsigned long pktlen, PACKET * packet)
{
byte *p;
/* Cap the size of a user ID at 2k: a value absurdly large enough
that there is no sane user ID string (which is printable text
as of RFC2440bis) that won't fit in it, but yet small enough to
avoid allocation problems. A large pktlen may not be
allocatable, and a very large pktlen could actually cause our
allocation to wrap around in xmalloc to a small number. */
if (pktlen > MAX_UID_PACKET_LENGTH)
{
log_error ("packet(%d) too large\n", pkttype);
if (list_mode)
es_fprintf (listfp, ":user ID packet: [too large]\n");
iobuf_skip_rest (inp, pktlen, 0);
return GPG_ERR_INV_PACKET;
}
packet->pkt.user_id = xmalloc_clear (sizeof *packet->pkt.user_id + pktlen);
packet->pkt.user_id->len = pktlen;
packet->pkt.user_id->ref = 1;
p = packet->pkt.user_id->name;
for (; pktlen; pktlen--, p++)
*p = iobuf_get_noeof (inp);
*p = 0;
if (list_mode)
{
int n = packet->pkt.user_id->len;
es_fprintf (listfp, ":user ID packet: \"");
/* fixme: Hey why don't we replace this with es_write_sanitized?? */
for (p = packet->pkt.user_id->name; n; p++, n--)
{
if (*p >= ' ' && *p <= 'z')
es_putc (*p, listfp);
else
es_fprintf (listfp, "\\x%02x", *p);
}
es_fprintf (listfp, "\"\n");
}
return 0;
}
void
make_attribute_uidname (PKT_user_id * uid, size_t max_namelen)
{
log_assert (max_namelen > 70);
if (uid->numattribs <= 0)
sprintf (uid->name, "[bad attribute packet of size %lu]",
uid->attrib_len);
else if (uid->numattribs > 1)
sprintf (uid->name, "[%d attributes of size %lu]",
uid->numattribs, uid->attrib_len);
else
{
/* Only one attribute, so list it as the "user id" */
if (uid->attribs->type == ATTRIB_IMAGE)
{
u32 len;
byte type;
if (parse_image_header (uid->attribs, &type, &len))
sprintf (uid->name, "[%.20s image of size %lu]",
image_type_to_string (type, 1), (ulong) len);
else
sprintf (uid->name, "[invalid image]");
}
else
sprintf (uid->name, "[unknown attribute of size %lu]",
(ulong) uid->attribs->len);
}
uid->len = strlen (uid->name);
}
static int
parse_attribute (IOBUF inp, int pkttype, unsigned long pktlen,
PACKET * packet)
{
byte *p;
(void) pkttype;
/* We better cap the size of an attribute packet to make DoS not too
easy. 16MB should be more then enough for one attribute packet
(ie. a photo). */
if (pktlen > MAX_ATTR_PACKET_LENGTH)
{
log_error ("packet(%d) too large\n", pkttype);
if (list_mode)
es_fprintf (listfp, ":attribute packet: [too large]\n");
iobuf_skip_rest (inp, pktlen, 0);
return GPG_ERR_INV_PACKET;
}
#define EXTRA_UID_NAME_SPACE 71
packet->pkt.user_id = xmalloc_clear (sizeof *packet->pkt.user_id
+ EXTRA_UID_NAME_SPACE);
packet->pkt.user_id->ref = 1;
packet->pkt.user_id->attrib_data = xmalloc (pktlen? pktlen:1);
packet->pkt.user_id->attrib_len = pktlen;
p = packet->pkt.user_id->attrib_data;
for (; pktlen; pktlen--, p++)
*p = iobuf_get_noeof (inp);
/* Now parse out the individual attribute subpackets. This is
somewhat pointless since there is only one currently defined
attribute type (jpeg), but it is correct by the spec. */
parse_attribute_subpkts (packet->pkt.user_id);
make_attribute_uidname (packet->pkt.user_id, EXTRA_UID_NAME_SPACE);
if (list_mode)
{
es_fprintf (listfp, ":attribute packet: %s\n", packet->pkt.user_id->name);
}
return 0;
}
static int
parse_comment (IOBUF inp, int pkttype, unsigned long pktlen, PACKET * packet)
{
byte *p;
/* Cap comment packet at a reasonable value to avoid an integer
overflow in the malloc below. Comment packets are actually not
anymore define my OpenPGP and we even stopped to use our
private comment packet. */
if (pktlen > MAX_COMMENT_PACKET_LENGTH)
{
log_error ("packet(%d) too large\n", pkttype);
if (list_mode)
es_fprintf (listfp, ":%scomment packet: [too large]\n",
pkttype == PKT_OLD_COMMENT ? "OpenPGP draft " : "");
iobuf_skip_rest (inp, pktlen, 0);
return GPG_ERR_INV_PACKET;
}
packet->pkt.comment = xmalloc (sizeof *packet->pkt.comment + pktlen - 1);
packet->pkt.comment->len = pktlen;
p = packet->pkt.comment->data;
for (; pktlen; pktlen--, p++)
*p = iobuf_get_noeof (inp);
if (list_mode)
{
int n = packet->pkt.comment->len;
es_fprintf (listfp, ":%scomment packet: \"", pkttype == PKT_OLD_COMMENT ?
"OpenPGP draft " : "");
for (p = packet->pkt.comment->data; n; p++, n--)
{
if (*p >= ' ' && *p <= 'z')
es_putc (*p, listfp);
else
es_fprintf (listfp, "\\x%02x", *p);
}
es_fprintf (listfp, "\"\n");
}
return 0;
}
/* Parse a ring trust packet RFC4880 (5.10).
*
* This parser is special in that the packet is not stored as a packet
* but its content is merged into the previous packet. */
static gpg_error_t
parse_ring_trust (parse_packet_ctx_t ctx, unsigned long pktlen)
{
gpg_error_t err;
iobuf_t inp = ctx->inp;
PKT_ring_trust rt = {0};
int c;
int not_gpg = 0;
if (!pktlen)
{
if (list_mode)
es_fprintf (listfp, ":trust packet: empty\n");
err = 0;
goto leave;
}
c = iobuf_get_noeof (inp);
pktlen--;
rt.trustval = c;
if (pktlen)
{
if (!c)
{
c = iobuf_get_noeof (inp);
/* We require that bit 7 of the sigcache is 0 (easier
* eof handling). */
if (!(c & 0x80))
rt.sigcache = c;
}
else
iobuf_get_noeof (inp); /* Dummy read. */
pktlen--;
}
/* Next is the optional subtype. */
if (pktlen > 3)
{
char tmp[4];
tmp[0] = iobuf_get_noeof (inp);
tmp[1] = iobuf_get_noeof (inp);
tmp[2] = iobuf_get_noeof (inp);
tmp[3] = iobuf_get_noeof (inp);
pktlen -= 4;
if (!memcmp (tmp, "gpg", 3))
rt.subtype = tmp[3];
else
not_gpg = 1;
}
/* If it is a key or uid subtype read the remaining data. */
if ((rt.subtype == RING_TRUST_KEY || rt.subtype == RING_TRUST_UID)
&& pktlen >= 6 )
{
int i;
unsigned int namelen;
rt.keyorg = iobuf_get_noeof (inp);
pktlen--;
rt.keyupdate = read_32 (inp);
pktlen -= 4;
namelen = iobuf_get_noeof (inp);
pktlen--;
if (namelen && pktlen)
{
rt.url = xtrymalloc (namelen + 1);
if (!rt.url)
{
err = gpg_error_from_syserror ();
goto leave;
}
for (i = 0; pktlen && i < namelen; pktlen--, i++)
rt.url[i] = iobuf_get_noeof (inp);
rt.url[i] = 0;
}
}
if (list_mode)
{
if (rt.subtype == RING_TRUST_SIG)
es_fprintf (listfp, ":trust packet: sig flag=%02x sigcache=%02x\n",
rt.trustval, rt.sigcache);
else if (rt.subtype == RING_TRUST_UID || rt.subtype == RING_TRUST_KEY)
{
unsigned char *p;
es_fprintf (listfp, ":trust packet: %s upd=%lu src=%d%s",
(rt.subtype == RING_TRUST_UID? "uid" : "key"),
(unsigned long)rt.keyupdate,
rt.keyorg,
(rt.url? " url=":""));
if (rt.url)
{
for (p = rt.url; *p; p++)
{
if (*p >= ' ' && *p <= 'z')
es_putc (*p, listfp);
else
es_fprintf (listfp, "\\x%02x", *p);
}
}
es_putc ('\n', listfp);
}
else if (not_gpg)
es_fprintf (listfp, ":trust packet: not created by gpg\n");
else
es_fprintf (listfp, ":trust packet: subtype=%02x\n",
rt.subtype);
}
/* Now transfer the data to the respective packet. Do not do this
* if SKIP_META is set. */
if (!ctx->last_pkt.pkt.generic || ctx->skip_meta)
;
else if (rt.subtype == RING_TRUST_SIG
&& ctx->last_pkt.pkttype == PKT_SIGNATURE)
{
PKT_signature *sig = ctx->last_pkt.pkt.signature;
if ((rt.sigcache & 1))
{
sig->flags.checked = 1;
sig->flags.valid = !!(rt.sigcache & 2);
}
}
else if (rt.subtype == RING_TRUST_UID
&& (ctx->last_pkt.pkttype == PKT_USER_ID
|| ctx->last_pkt.pkttype == PKT_ATTRIBUTE))
{
PKT_user_id *uid = ctx->last_pkt.pkt.user_id;
uid->keyorg = rt.keyorg;
uid->keyupdate = rt.keyupdate;
uid->updateurl = rt.url;
rt.url = NULL;
}
else if (rt.subtype == RING_TRUST_KEY
&& (ctx->last_pkt.pkttype == PKT_PUBLIC_KEY
|| ctx->last_pkt.pkttype == PKT_SECRET_KEY))
{
PKT_public_key *pk = ctx->last_pkt.pkt.public_key;
pk->keyorg = rt.keyorg;
pk->keyupdate = rt.keyupdate;
pk->updateurl = rt.url;
rt.url = NULL;
}
err = 0;
leave:
xfree (rt.url);
free_packet (NULL, ctx); /* This sets ctx->last_pkt to NULL. */
iobuf_skip_rest (inp, pktlen, 0);
return err;
}
static int
parse_plaintext (IOBUF inp, int pkttype, unsigned long pktlen,
PACKET * pkt, int new_ctb, int partial)
{
int rc = 0;
int mode, namelen;
PKT_plaintext *pt;
byte *p;
int c, i;
if (!partial && pktlen < 6)
{
log_error ("packet(%d) too short (%lu)\n", pkttype, (ulong) pktlen);
if (list_mode)
es_fputs (":literal data packet: [too short]\n", listfp);
rc = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
mode = iobuf_get_noeof (inp);
if (pktlen)
pktlen--;
namelen = iobuf_get_noeof (inp);
if (pktlen)
pktlen--;
/* Note that namelen will never exceed 255 bytes. */
pt = pkt->pkt.plaintext =
xmalloc (sizeof *pkt->pkt.plaintext + namelen - 1);
pt->new_ctb = new_ctb;
pt->mode = mode;
pt->namelen = namelen;
pt->is_partial = partial;
if (pktlen)
{
for (i = 0; pktlen > 4 && i < namelen; pktlen--, i++)
pt->name[i] = iobuf_get_noeof (inp);
}
else
{
for (i = 0; i < namelen; i++)
if ((c = iobuf_get (inp)) == -1)
break;
else
pt->name[i] = c;
}
/* Fill up NAME so that a check with valgrind won't complain about
* reading from uninitialized memory. This case may be triggred by
* corrupted packets. */
for (; i < namelen; i++)
pt->name[i] = 0;
pt->timestamp = read_32 (inp);
if (pktlen)
pktlen -= 4;
pt->len = pktlen;
pt->buf = inp;
if (list_mode)
{
es_fprintf (listfp, ":literal data packet:\n"
"\tmode %c (%X), created %lu, name=\"",
mode >= ' ' && mode < 'z' ? mode : '?', mode,
(ulong) pt->timestamp);
for (p = pt->name, i = 0; i < namelen; p++, i++)
{
if (*p >= ' ' && *p <= 'z')
es_putc (*p, listfp);
else
es_fprintf (listfp, "\\x%02x", *p);
}
es_fprintf (listfp, "\",\n\traw data: ");
if (partial)
es_fprintf (listfp, "unknown length\n");
else
es_fprintf (listfp, "%lu bytes\n", (ulong) pt->len);
}
leave:
return rc;
}
static int
parse_compressed (IOBUF inp, int pkttype, unsigned long pktlen,
PACKET * pkt, int new_ctb)
{
PKT_compressed *zd;
/* PKTLEN is here 0, but data follows (this should be the last
object in a file or the compress algorithm should know the
length). */
(void) pkttype;
(void) pktlen;
zd = pkt->pkt.compressed = xmalloc (sizeof *pkt->pkt.compressed);
zd->algorithm = iobuf_get_noeof (inp);
zd->len = 0; /* not used */
zd->new_ctb = new_ctb;
zd->buf = inp;
if (list_mode)
es_fprintf (listfp, ":compressed packet: algo=%d\n", zd->algorithm);
return 0;
}
static int
parse_encrypted (IOBUF inp, int pkttype, unsigned long pktlen,
PACKET * pkt, int new_ctb, int partial)
{
int rc = 0;
PKT_encrypted *ed;
unsigned long orig_pktlen = pktlen;
ed = pkt->pkt.encrypted = xmalloc (sizeof *pkt->pkt.encrypted);
/* ed->len is set below. */
ed->extralen = 0; /* Unknown here; only used in build_packet. */
ed->buf = NULL;
ed->new_ctb = new_ctb;
ed->is_partial = partial;
ed->aead_algo = 0;
ed->cipher_algo = 0; /* Only used with AEAD. */
ed->chunkbyte = 0; /* Only used with AEAD. */
if (pkttype == PKT_ENCRYPTED_MDC)
{
/* Fixme: add some pktlen sanity checks. */
int version;
version = iobuf_get_noeof (inp);
if (orig_pktlen)
pktlen--;
if (version != 1)
{
log_error ("encrypted_mdc packet with unknown version %d\n",
version);
if (list_mode)
es_fputs (":encrypted data packet: [unknown version]\n", listfp);
/*skip_rest(inp, pktlen); should we really do this? */
rc = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
ed->mdc_method = DIGEST_ALGO_SHA1;
}
else
ed->mdc_method = 0;
/* A basic sanity check. We need at least an 8 byte IV plus the 2
detection bytes. Note that we don't known the algorithm and thus
we may only check against the minimum blocksize. */
if (orig_pktlen && pktlen < 10)
{
/* Actually this is blocksize+2. */
log_error ("packet(%d) too short\n", pkttype);
if (list_mode)
es_fputs (":encrypted data packet: [too short]\n", listfp);
rc = GPG_ERR_INV_PACKET;
iobuf_skip_rest (inp, pktlen, partial);
goto leave;
}
/* Store the remaining length of the encrypted data (i.e. without
the MDC version number but with the IV etc.). This value is
required during decryption. */
ed->len = pktlen;
if (list_mode)
{
if (orig_pktlen)
es_fprintf (listfp, ":encrypted data packet:\n\tlength: %lu\n",
orig_pktlen);
else
es_fprintf (listfp, ":encrypted data packet:\n\tlength: unknown\n");
if (ed->mdc_method)
es_fprintf (listfp, "\tmdc_method: %d\n", ed->mdc_method);
}
ed->buf = inp;
leave:
return rc;
}
/* Note, that this code is not anymore used in real life because the
MDC checking is now done right after the decryption in
decrypt_data. */
static int
parse_mdc (IOBUF inp, int pkttype, unsigned long pktlen,
PACKET * pkt, int new_ctb)
{
int rc = 0;
PKT_mdc *mdc;
byte *p;
(void) pkttype;
mdc = pkt->pkt.mdc = xmalloc (sizeof *pkt->pkt.mdc);
if (list_mode)
es_fprintf (listfp, ":mdc packet: length=%lu\n", pktlen);
if (!new_ctb || pktlen != 20)
{
log_error ("mdc_packet with invalid encoding\n");
rc = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
p = mdc->hash;
for (; pktlen; pktlen--, p++)
*p = iobuf_get_noeof (inp);
leave:
return rc;
}
static gpg_error_t
parse_encrypted_aead (iobuf_t inp, int pkttype, unsigned long pktlen,
PACKET *pkt, int partial)
{
int rc = 0;
PKT_encrypted *ed;
unsigned long orig_pktlen = pktlen;
int version;
ed = pkt->pkt.encrypted = xtrymalloc (sizeof *pkt->pkt.encrypted);
if (!ed)
return gpg_error_from_syserror ();
ed->len = 0;
ed->extralen = 0; /* (only used in build_packet.) */
ed->buf = NULL;
ed->new_ctb = 1; /* (packet number requires a new CTB anyway.) */
ed->is_partial = partial;
ed->mdc_method = 0;
/* A basic sanity check. We need one version byte, one algo byte,
* one aead algo byte, one chunkbyte, at least 15 byte IV. */
if (orig_pktlen && pktlen < 19)
{
log_error ("packet(%d) too short\n", pkttype);
if (list_mode)
es_fputs (":aead encrypted packet: [too short]\n", listfp);
rc = gpg_error (GPG_ERR_INV_PACKET);
iobuf_skip_rest (inp, pktlen, partial);
goto leave;
}
version = iobuf_get_noeof (inp);
if (orig_pktlen)
pktlen--;
if (version != 1)
{
log_error ("aead encrypted packet with unknown version %d\n",
version);
if (list_mode)
es_fputs (":aead encrypted packet: [unknown version]\n", listfp);
/*skip_rest(inp, pktlen); should we really do this? */
rc = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
ed->cipher_algo = iobuf_get_noeof (inp);
if (orig_pktlen)
pktlen--;
ed->aead_algo = iobuf_get_noeof (inp);
if (orig_pktlen)
pktlen--;
ed->chunkbyte = iobuf_get_noeof (inp);
if (orig_pktlen)
pktlen--;
/* Store the remaining length of the encrypted data. We read the
* rest during decryption. */
ed->len = pktlen;
if (list_mode)
{
es_fprintf (listfp, ":aead encrypted packet: cipher=%u aead=%u cb=%u\n",
ed->cipher_algo, ed->aead_algo, ed->chunkbyte);
if (orig_pktlen)
es_fprintf (listfp, "\tlength: %lu\n", orig_pktlen);
else
es_fprintf (listfp, "\tlength: unknown\n");
}
ed->buf = inp;
leave:
return rc;
}
/*
* This packet is internally generated by us (in armor.c) to transfer
* some information to the lower layer. To make sure that this packet
* is really a GPG faked one and not one coming from outside, we
* first check that there is a unique tag in it.
*
* The format of such a control packet is:
* n byte session marker
* 1 byte control type CTRLPKT_xxxxx
* m byte control data
*/
static int
parse_gpg_control (IOBUF inp, int pkttype, unsigned long pktlen,
PACKET * packet, int partial)
{
byte *p;
const byte *sesmark;
size_t sesmarklen;
int i;
(void) pkttype;
if (list_mode)
es_fprintf (listfp, ":packet 63: length %lu ", pktlen);
sesmark = get_session_marker (&sesmarklen);
if (pktlen < sesmarklen + 1) /* 1 is for the control bytes */
goto skipit;
for (i = 0; i < sesmarklen; i++, pktlen--)
{
if (sesmark[i] != iobuf_get_noeof (inp))
goto skipit;
}
if (pktlen > 4096)
goto skipit; /* Definitely too large. We skip it to avoid an
overflow in the malloc. */
if (list_mode)
es_fputs ("- gpg control packet", listfp);
packet->pkt.gpg_control = xmalloc (sizeof *packet->pkt.gpg_control
+ pktlen - 1);
packet->pkt.gpg_control->control = iobuf_get_noeof (inp);
pktlen--;
packet->pkt.gpg_control->datalen = pktlen;
p = packet->pkt.gpg_control->data;
for (; pktlen; pktlen--, p++)
*p = iobuf_get_noeof (inp);
return 0;
skipit:
if (list_mode)
{
int c;
i = 0;
es_fprintf (listfp, "- private (rest length %lu)\n", pktlen);
if (partial)
{
while ((c = iobuf_get (inp)) != -1)
dump_hex_line (c, &i);
}
else
{
for (; pktlen; pktlen--)
{
dump_hex_line ((c = iobuf_get (inp)), &i);
if (c == -1)
break;
}
}
es_putc ('\n', listfp);
}
iobuf_skip_rest (inp, pktlen, 0);
return gpg_error (GPG_ERR_INV_PACKET);
}
/* Create a GPG control packet to be used internally as a placeholder. */
PACKET *
create_gpg_control (ctrlpkttype_t type, const byte * data, size_t datalen)
{
PACKET *packet;
byte *p;
if (!data)
datalen = 0;
packet = xmalloc (sizeof *packet);
init_packet (packet);
packet->pkttype = PKT_GPG_CONTROL;
packet->pkt.gpg_control = xmalloc (sizeof *packet->pkt.gpg_control + datalen);
packet->pkt.gpg_control->control = type;
packet->pkt.gpg_control->datalen = datalen;
p = packet->pkt.gpg_control->data;
for (; datalen; datalen--, p++)
*p = *data++;
return packet;
}
diff --git a/g10/photoid.c b/g10/photoid.c
index bcea64fbf..f9720d329 100644
--- a/g10/photoid.c
+++ b/g10/photoid.c
@@ -1,382 +1,392 @@
/* photoid.c - photo ID handling code
* Copyright (C) 2001, 2002, 2005, 2006, 2008, 2011 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#ifdef _WIN32
# ifdef HAVE_WINSOCK2_H
# include <winsock2.h>
# endif
# include <windows.h>
# ifndef VER_PLATFORM_WIN32_WINDOWS
# define VER_PLATFORM_WIN32_WINDOWS 1
# endif
#endif
#include "gpg.h"
#include "../common/util.h"
#include "packet.h"
#include "../common/status.h"
#include "exec.h"
#include "keydb.h"
#include "../common/i18n.h"
#include "../common/iobuf.h"
#include "options.h"
#include "main.h"
#include "photoid.h"
#include "../common/ttyio.h"
#include "trustdb.h"
/* Generate a new photo id packet, or return NULL if canceled.
FIXME: Should we add a duplicates check similar to generate_user_id? */
PKT_user_id *
generate_photo_id (ctrl_t ctrl, PKT_public_key *pk,const char *photo_name)
{
PKT_user_id *uid;
int error=1,i;
unsigned int len;
char *filename;
byte *photo=NULL;
byte header[16];
IOBUF file;
int overflow;
header[0]=0x10; /* little side of photo header length */
header[1]=0; /* big side of photo header length */
header[2]=1; /* 1 == version of photo header */
header[3]=1; /* 1 == JPEG */
for(i=4;i<16;i++) /* The reserved bytes */
header[i]=0;
#define EXTRA_UID_NAME_SPACE 71
uid=xmalloc_clear(sizeof(*uid)+71);
if(photo_name && *photo_name)
filename=make_filename(photo_name,(void *)NULL);
else
{
tty_printf(_("\nPick an image to use for your photo ID."
" The image must be a JPEG file.\n"
"Remember that the image is stored within your public key."
" If you use a\n"
"very large picture, your key will become very large"
" as well!\n"
"Keeping the image close to 240x288 is a good size"
" to use.\n"));
filename=NULL;
}
while(photo==NULL)
{
if(filename==NULL)
{
char *tempname;
tty_printf("\n");
tty_enable_completion(NULL);
tempname=cpr_get("photoid.jpeg.add",
_("Enter JPEG filename for photo ID: "));
tty_disable_completion();
filename=make_filename(tempname,(void *)NULL);
xfree(tempname);
if(strlen(filename)==0)
goto scram;
}
file=iobuf_open(filename);
if (file && is_secured_file (iobuf_get_fd (file)))
{
iobuf_close (file);
file = NULL;
gpg_err_set_errno (EPERM);
}
if(!file)
{
log_error(_("unable to open JPEG file '%s': %s\n"),
filename,strerror(errno));
xfree(filename);
filename=NULL;
continue;
}
len=iobuf_get_filelength(file, &overflow);
if(len>6144 || overflow)
{
tty_printf( _("This JPEG is really large (%d bytes) !\n"),len);
if(!cpr_get_answer_is_yes("photoid.jpeg.size",
_("Are you sure you want to use it? (y/N) ")))
{
iobuf_close(file);
xfree(filename);
filename=NULL;
continue;
}
}
photo=xmalloc(len);
iobuf_read(file,photo,len);
iobuf_close(file);
/* Is it a JPEG? */
if(photo[0]!=0xFF || photo[1]!=0xD8)
{
log_error(_("'%s' is not a JPEG file\n"),filename);
xfree(photo);
photo=NULL;
xfree(filename);
filename=NULL;
continue;
}
/* Build the packet */
build_attribute_subpkt(uid,1,photo,len,header,16);
parse_attribute_subpkts(uid);
make_attribute_uidname(uid, EXTRA_UID_NAME_SPACE);
/* Showing the photo is not safe when noninteractive since the
"user" may not be able to dismiss a viewer window! */
if(opt.command_fd==-1)
{
show_photos (ctrl, uid->attribs, uid->numattribs, pk, uid);
switch(cpr_get_answer_yes_no_quit("photoid.jpeg.okay",
_("Is this photo correct (y/N/q)? ")))
{
case -1:
goto scram;
case 0:
free_attributes(uid);
xfree(photo);
photo=NULL;
xfree(filename);
filename=NULL;
continue;
}
}
}
error=0;
uid->ref=1;
scram:
xfree(filename);
xfree(photo);
if(error)
{
free_attributes(uid);
xfree(uid);
return NULL;
}
return uid;
}
/* Returns 0 for error, 1 for valid */
int parse_image_header(const struct user_attribute *attr,byte *type,u32 *len)
{
u16 headerlen;
if(attr->len<3)
return 0;
/* For historical reasons (i.e. "oops!"), the header length is
little endian. */
headerlen=(attr->data[1]<<8) | attr->data[0];
if(headerlen>attr->len)
return 0;
if(type && attr->len>=4)
{
if(attr->data[2]==1) /* header version 1 */
*type=attr->data[3];
else
*type=0;
}
*len=attr->len-headerlen;
if(*len==0)
return 0;
return 1;
}
/* style==0 for extension, 1 for name, 2 for MIME type. Remember that
the "name" style string could be used in a user ID name field, so
make sure it is not too big (see parse-packet.c:parse_attribute).
Extensions should be 3 characters long for the best cross-platform
compatibility. */
char *image_type_to_string(byte type,int style)
{
char *string;
switch(type)
{
case 1: /* jpeg */
if(style==0)
string="jpg";
else if(style==1)
string="jpeg";
else
string="image/jpeg";
break;
default:
if(style==0)
string="bin";
else if(style==1)
string="unknown";
else
string="image/x-unknown";
break;
}
return string;
}
#if !defined(FIXED_PHOTO_VIEWER) && !defined(DISABLE_PHOTO_VIEWER)
-static const char *get_default_photo_command(void)
+static const char *
+get_default_photo_command(void)
{
#if defined(_WIN32)
OSVERSIONINFO osvi;
memset(&osvi,0,sizeof(osvi));
osvi.dwOSVersionInfoSize=sizeof(osvi);
GetVersionEx(&osvi);
if(osvi.dwPlatformId==VER_PLATFORM_WIN32_WINDOWS)
return "start /w %i";
else
- return "cmd /c start /w %i";
+ return "!ShellExecute 400 %i";
#elif defined(__APPLE__)
/* OS X. This really needs more than just __APPLE__. */
return "open %I";
#elif defined(__riscos__)
return "Filer_Run %I";
#else
- return "xloadimage -fork -quiet -title 'KeyID 0x%k' stdin";
+ if (!path_access ("xloadimage", X_OK))
+ return "xloadimage -fork -quiet -title 'KeyID 0x%k' stdin";
+ else if (!path_access ("display",X_OK))
+ return "display -title 'KeyID 0x%k' %i";
+ else if (getuid () && !path_access ("xdg-open", X_OK))
+ return "xdg-open %i";
+ else
+ return "/bin/true";
#endif
}
#endif
void
show_photos (ctrl_t ctrl, const struct user_attribute *attrs, int count,
PKT_public_key *pk, PKT_user_id *uid)
{
#ifdef DISABLE_PHOTO_VIEWER
(void)attrs;
(void)count;
(void)pk;
(void)uid;
#else /*!DISABLE_PHOTO_VIEWER*/
int i;
struct expando_args args;
u32 len;
u32 kid[2]={0,0};
memset (&args, 0, sizeof(args));
args.pk = pk;
args.validity_info = get_validity_info (ctrl, NULL, pk, uid);
args.validity_string = get_validity_string (ctrl, pk, uid);
namehash_from_uid (uid);
args.namehash = uid->namehash;
if (pk)
keyid_from_pk (pk, kid);
+ es_fflush (es_stdout);
+
for(i=0;i<count;i++)
if(attrs[i].type==ATTRIB_IMAGE &&
parse_image_header(&attrs[i],&args.imagetype,&len))
{
char *command,*name;
struct exec_info *spawn;
int offset=attrs[i].len-len;
#ifdef FIXED_PHOTO_VIEWER
opt.photo_viewer=FIXED_PHOTO_VIEWER;
#else
if(!opt.photo_viewer)
opt.photo_viewer=get_default_photo_command();
#endif
/* make command grow */
command=pct_expando(opt.photo_viewer,&args);
if(!command)
goto fail;
name=xmalloc(16+strlen(EXTSEP_S)+
strlen(image_type_to_string(args.imagetype,0))+1);
/* Make the filename. Notice we are not using the image
encoding type for more than cosmetics. Most external image
viewers can handle a multitude of types, and even if one
cannot understand a particular type, we have no way to know
which. The spec permits this, by the way. -dms */
#ifdef USE_ONLY_8DOT3
sprintf(name,"%08lX" EXTSEP_S "%s",(ulong)kid[1],
image_type_to_string(args.imagetype,0));
#else
sprintf(name,"%08lX%08lX" EXTSEP_S "%s",(ulong)kid[0],(ulong)kid[1],
image_type_to_string(args.imagetype,0));
#endif
if(exec_write(&spawn,NULL,command,name,1,1)!=0)
{
xfree(name);
goto fail;
}
#ifdef __riscos__
riscos_set_filetype_by_mimetype(spawn->tempfile_in,
image_type_to_string(args.imagetype,2));
#endif
xfree(name);
fwrite(&attrs[i].data[offset],attrs[i].len-offset,1,spawn->tochild);
if(exec_read(spawn)!=0)
{
exec_finish(spawn);
goto fail;
}
if(exec_finish(spawn)!=0)
goto fail;
}
return;
fail:
log_error(_("unable to display photo ID!\n"));
#endif /*!DISABLE_PHOTO_VIEWER*/
}
diff --git a/g10/pkclist.c b/g10/pkclist.c
index 46258bf85..1fd23a3e4 100644
--- a/g10/pkclist.c
+++ b/g10/pkclist.c
@@ -1,1719 +1,1722 @@
/* pkclist.c - create a list of public keys
* Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007,
* 2008, 2009, 2010 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include "gpg.h"
#include "options.h"
#include "packet.h"
#include "../common/status.h"
#include "keydb.h"
#include "../common/util.h"
#include "main.h"
#include "trustdb.h"
#include "../common/ttyio.h"
#include "../common/status.h"
#include "photoid.h"
#include "../common/i18n.h"
#include "tofu.h"
#define CONTROL_D ('D' - 'A' + 1)
static void
send_status_inv_recp (int reason, const char *name)
{
char buf[40];
snprintf (buf, sizeof buf, "%d ", reason);
write_status_text_and_buffer (STATUS_INV_RECP, buf,
name, strlen (name),
-1);
}
/****************
* Show the revocation reason as it is stored with the given signature
*/
static void
do_show_revocation_reason( PKT_signature *sig )
{
size_t n, nn;
const byte *p, *pp;
int seq = 0;
const char *text;
while( (p = enum_sig_subpkt (sig->hashed, SIGSUBPKT_REVOC_REASON,
&n, &seq, NULL )) ) {
if( !n )
continue; /* invalid - just skip it */
if( *p == 0 )
text = _("No reason specified");
else if( *p == 0x01 )
text = _("Key is superseded");
else if( *p == 0x02 )
text = _("Key has been compromised");
else if( *p == 0x03 )
text = _("Key is no longer used");
else if( *p == 0x20 )
text = _("User ID is no longer valid");
else
text = NULL;
log_info ( _("reason for revocation: "));
if (text)
log_printf ("%s\n", text);
else
log_printf ("code=%02x\n", *p );
n--; p++;
pp = NULL;
do {
/* We don't want any empty lines, so skip them */
while( n && *p == '\n' ) {
p++;
n--;
}
if( n ) {
pp = memchr( p, '\n', n );
nn = pp? pp - p : n;
log_info ( _("revocation comment: ") );
es_write_sanitized (log_get_stream(), p, nn, NULL, NULL);
log_printf ("\n");
p += nn; n -= nn;
}
} while( pp );
}
}
/* Mode 0: try and find the revocation based on the pk (i.e. check
subkeys, etc.) Mode 1: use only the revocation on the main pk */
void
show_revocation_reason (ctrl_t ctrl, PKT_public_key *pk, int mode)
{
/* Hmmm, this is not so easy because we have to duplicate the code
* used in the trustdb to calculate the keyflags. We need to find
* a clean way to check revocation certificates on keys and
* signatures. And there should be no duplicate code. Because we
* enter this function only when the trustdb told us that we have
* a revoked key, we could simply look for a revocation cert and
* display this one, when there is only one. Let's try to do this
* until we have a better solution. */
KBNODE node, keyblock = NULL;
byte fingerprint[MAX_FINGERPRINT_LEN];
size_t fingerlen;
int rc;
/* get the keyblock */
fingerprint_from_pk( pk, fingerprint, &fingerlen );
rc = get_pubkey_byfprint (ctrl, NULL, &keyblock, fingerprint, fingerlen);
if( rc ) { /* that should never happen */
log_debug( "failed to get the keyblock\n");
return;
}
for( node=keyblock; node; node = node->next ) {
if( (mode && node->pkt->pkttype == PKT_PUBLIC_KEY) ||
( ( node->pkt->pkttype == PKT_PUBLIC_KEY
|| node->pkt->pkttype == PKT_PUBLIC_SUBKEY )
&& !cmp_public_keys( node->pkt->pkt.public_key, pk ) ) )
break;
}
if( !node ) {
log_debug("Oops, PK not in keyblock\n");
release_kbnode( keyblock );
return;
}
/* now find the revocation certificate */
for( node = node->next; node ; node = node->next ) {
if( node->pkt->pkttype == PKT_PUBLIC_SUBKEY )
break;
if( node->pkt->pkttype == PKT_SIGNATURE
&& (node->pkt->pkt.signature->sig_class == 0x20
|| node->pkt->pkt.signature->sig_class == 0x28 ) ) {
/* FIXME: we should check the signature here */
do_show_revocation_reason ( node->pkt->pkt.signature );
break;
}
}
/* We didn't find it, so check if the whole key is revoked */
if(!node && !mode)
show_revocation_reason (ctrl, pk, 1);
release_kbnode( keyblock );
}
/****************
* mode: 0 = standard
* 1 = Without key info and additional menu option 'm'
* this does also add an option to set the key to ultimately trusted.
* Returns:
* -2 = nothing changed - caller should show some additional info
* -1 = quit operation
* 0 = nothing changed
* 1 = new ownertrust now in new_trust
*/
#ifndef NO_TRUST_MODELS
static int
do_edit_ownertrust (ctrl_t ctrl, PKT_public_key *pk, int mode,
unsigned *new_trust, int defer_help )
{
char *p;
u32 keyid[2];
int changed=0;
int quit=0;
int show=0;
int min_num;
int did_help=defer_help;
unsigned int minimum = tdb_get_min_ownertrust (ctrl, pk, 0);
switch(minimum)
{
default:
case TRUST_UNDEFINED: min_num=1; break;
case TRUST_NEVER: min_num=2; break;
case TRUST_MARGINAL: min_num=3; break;
case TRUST_FULLY: min_num=4; break;
}
keyid_from_pk (pk, keyid);
for(;;) {
/* A string with valid answers.
TRANSLATORS: These are the allowed answers in lower and
uppercase. Below you will find the matching strings which
should be translated accordingly and the letter changed to
match the one in the answer string.
i = please show me more information
m = back to the main menu
s = skip this key
q = quit
*/
const char *ans = _("iImMqQsS");
if( !did_help )
{
if( !mode )
{
KBNODE keyblock, un;
tty_printf (_("No trust value assigned to:\n"));
print_key_line (ctrl, NULL, pk, 0);
p = get_user_id_native (ctrl, keyid);
tty_printf (_(" \"%s\"\n"),p);
xfree (p);
keyblock = get_pubkeyblock (ctrl, keyid);
if (!keyblock)
BUG ();
for (un=keyblock; un; un = un->next)
{
if (un->pkt->pkttype != PKT_USER_ID )
continue;
if (un->pkt->pkt.user_id->flags.revoked)
continue;
if (un->pkt->pkt.user_id->flags.expired)
continue;
/* Only skip textual primaries */
if (un->pkt->pkt.user_id->flags.primary
&& !un->pkt->pkt.user_id->attrib_data )
continue;
if((opt.verify_options&VERIFY_SHOW_PHOTOS)
&& un->pkt->pkt.user_id->attrib_data)
show_photos (ctrl,
un->pkt->pkt.user_id->attribs,
un->pkt->pkt.user_id->numattribs, pk,
un->pkt->pkt.user_id);
p=utf8_to_native(un->pkt->pkt.user_id->name,
un->pkt->pkt.user_id->len,0);
tty_printf(_(" aka \"%s\"\n"),p);
}
print_fingerprint (ctrl, NULL, pk, 2);
tty_printf("\n");
release_kbnode (keyblock);
}
if(opt.trust_model==TM_DIRECT)
{
tty_printf(_("How much do you trust that this key actually "
"belongs to the named user?\n"));
tty_printf("\n");
}
else
{
/* This string also used in keyedit.c:trustsig_prompt */
tty_printf(_("Please decide how far you trust this user to"
" correctly verify other users' keys\n"
"(by looking at passports, checking fingerprints from"
" different sources, etc.)\n"));
tty_printf("\n");
}
if(min_num<=1)
tty_printf (_(" %d = I don't know or won't say\n"), 1);
if(min_num<=2)
tty_printf (_(" %d = I do NOT trust\n"), 2);
if(min_num<=3)
tty_printf (_(" %d = I trust marginally\n"), 3);
if(min_num<=4)
tty_printf (_(" %d = I trust fully\n"), 4);
if (mode)
tty_printf (_(" %d = I trust ultimately\n"), 5);
#if 0
/* not yet implemented */
tty_printf (" i = please show me more information\n");
#endif
if( mode )
tty_printf(_(" m = back to the main menu\n"));
else
{
tty_printf(_(" s = skip this key\n"));
tty_printf(_(" q = quit\n"));
}
tty_printf("\n");
if(minimum)
tty_printf(_("The minimum trust level for this key is: %s\n\n"),
trust_value_to_string(minimum));
did_help = 1;
}
if( strlen(ans) != 8 )
BUG();
p = cpr_get("edit_ownertrust.value",_("Your decision? "));
trim_spaces(p);
cpr_kill_prompt();
if( !*p )
did_help = 0;
else if( *p && p[1] )
;
else if( !p[1] && ((*p >= '0'+min_num) && *p <= (mode?'5':'4')) )
{
unsigned int trust;
switch( *p )
{
case '1': trust = TRUST_UNDEFINED; break;
case '2': trust = TRUST_NEVER ; break;
case '3': trust = TRUST_MARGINAL ; break;
case '4': trust = TRUST_FULLY ; break;
case '5': trust = TRUST_ULTIMATE ; break;
default: BUG();
}
if (trust == TRUST_ULTIMATE
&& !cpr_get_answer_is_yes ("edit_ownertrust.set_ultimate.okay",
_("Do you really want to set this key"
" to ultimate trust? (y/N) ")))
; /* no */
else
{
*new_trust = trust;
changed = 1;
break;
}
}
#if 0
/* not yet implemented */
else if( *p == ans[0] || *p == ans[1] )
{
tty_printf(_("Certificates leading to an ultimately trusted key:\n"));
show = 1;
break;
}
#endif
else if( mode && (*p == ans[2] || *p == ans[3] || *p == CONTROL_D ) )
{
break ; /* back to the menu */
}
else if( !mode && (*p == ans[6] || *p == ans[7] ) )
{
break; /* skip */
}
else if( !mode && (*p == ans[4] || *p == ans[5] ) )
{
quit = 1;
break ; /* back to the menu */
}
xfree(p); p = NULL;
}
xfree(p);
return show? -2: quit? -1 : changed;
}
#endif /*!NO_TRUST_MODELS*/
/*
* Display a menu to change the ownertrust of the key PK (which should
* be a primary key).
* For mode values see do_edit_ownertrust ()
*/
#ifndef NO_TRUST_MODELS
int
edit_ownertrust (ctrl_t ctrl, PKT_public_key *pk, int mode )
{
unsigned int trust = 0;
int no_help = 0;
for(;;)
{
switch ( do_edit_ownertrust (ctrl, pk, mode, &trust, no_help ) )
{
case -1: /* quit */
return -1;
case -2: /* show info */
no_help = 1;
break;
case 1: /* trust value set */
trust &= ~TRUST_FLAG_DISABLED;
trust |= get_ownertrust (ctrl, pk) & TRUST_FLAG_DISABLED;
update_ownertrust (ctrl, pk, trust );
return 1;
default:
return 0;
}
}
}
#endif /*!NO_TRUST_MODELS*/
/****************
* Check whether we can trust this pk which has a trustlevel of TRUSTLEVEL
* Returns: true if we trust.
*/
static int
do_we_trust( PKT_public_key *pk, unsigned int trustlevel )
{
/* We should not be able to get here with a revoked or expired
key */
if(trustlevel & TRUST_FLAG_REVOKED
|| trustlevel & TRUST_FLAG_SUB_REVOKED
|| (trustlevel & TRUST_MASK) == TRUST_EXPIRED)
BUG();
if( opt.trust_model==TM_ALWAYS )
{
if( opt.verbose )
log_info("No trust check due to '--trust-model always' option\n");
return 1;
}
switch(trustlevel & TRUST_MASK)
{
default:
log_error ("invalid trustlevel %u returned from validation layer\n",
trustlevel);
/* fall through */
case TRUST_UNKNOWN:
case TRUST_UNDEFINED:
log_info(_("%s: There is no assurance this key belongs"
" to the named user\n"),keystr_from_pk(pk));
return 0; /* no */
case TRUST_MARGINAL:
log_info(_("%s: There is limited assurance this key belongs"
" to the named user\n"),keystr_from_pk(pk));
return 1; /* yes */
case TRUST_FULLY:
if( opt.verbose )
log_info(_("This key probably belongs to the named user\n"));
return 1; /* yes */
case TRUST_ULTIMATE:
if( opt.verbose )
log_info(_("This key belongs to us\n"));
return 1; /* yes */
case TRUST_NEVER:
/* This can be returned by TOFU, which can return negative
assertions. */
log_info(_("%s: This key is bad! It has been marked as untrusted!\n"),
keystr_from_pk(pk));
return 0; /* no */
}
return 1; /*NOTREACHED*/
}
/****************
* wrapper around do_we_trust, so we can ask whether to use the
* key anyway.
*/
static int
do_we_trust_pre (ctrl_t ctrl, PKT_public_key *pk, unsigned int trustlevel )
{
int rc;
rc = do_we_trust( pk, trustlevel );
if( !opt.batch && !rc )
{
- print_pubkey_info (ctrl, NULL,pk);
+ print_key_info (ctrl, NULL, 0, pk, 0);
print_fingerprint (ctrl, NULL, pk, 2);
tty_printf("\n");
if ((trustlevel & TRUST_MASK) == TRUST_NEVER)
tty_printf(
_("This key is bad! It has been marked as untrusted! If you\n"
"*really* know what you are doing, you may answer the next\n"
"question with yes.\n"));
else
tty_printf(
_("It is NOT certain that the key belongs to the person named\n"
"in the user ID. If you *really* know what you are doing,\n"
"you may answer the next question with yes.\n"));
tty_printf("\n");
if (is_status_enabled ())
{
u32 kid[2];
char *hint_str;
keyid_from_pk (pk, kid);
hint_str = get_long_user_id_string (ctrl, kid);
write_status_text ( STATUS_USERID_HINT, hint_str );
xfree (hint_str);
}
if( cpr_get_answer_is_yes("untrusted_key.override",
_("Use this key anyway? (y/N) ")) )
rc = 1;
/* Hmmm: Should we set a flag to tell the user about
* his decision the next time he encrypts for this recipient?
*/
}
return rc;
}
/* Write a TRUST_foo status line inclduing the validation model. */
static void
write_trust_status (int statuscode, int trustlevel)
{
#ifdef NO_TRUST_MODELS
write_status (statuscode);
#else /* NO_TRUST_MODELS */
int tm;
/* For the combined tofu+pgp method, we return the trust model which
* was responsible for the trustlevel. */
if (opt.trust_model == TM_TOFU_PGP)
tm = (trustlevel & TRUST_FLAG_TOFU_BASED)? TM_TOFU : TM_PGP;
else
tm = opt.trust_model;
write_status_strings (statuscode, "0 ", trust_model_string (tm), NULL);
#endif /* NO_TRUST_MODELS */
}
/****************
* Check whether we can trust this signature.
* Returns an error code if we should not trust this signature.
*/
int
check_signatures_trust (ctrl_t ctrl, PKT_signature *sig)
{
PKT_public_key *pk = xmalloc_clear( sizeof *pk );
unsigned int trustlevel = TRUST_UNKNOWN;
int rc=0;
rc = get_pubkey_for_sig (ctrl, pk, sig);
if (rc)
{ /* this should not happen */
log_error("Ooops; the key vanished - can't check the trust\n");
rc = GPG_ERR_NO_PUBKEY;
goto leave;
}
if ( opt.trust_model==TM_ALWAYS )
{
if( !opt.quiet )
log_info(_("WARNING: Using untrusted key!\n"));
if (opt.with_fingerprint)
print_fingerprint (ctrl, NULL, pk, 1);
goto leave;
}
if(pk->flags.maybe_revoked && !pk->flags.revoked)
log_info(_("WARNING: this key might be revoked (revocation key"
" not present)\n"));
trustlevel = get_validity (ctrl, NULL, pk, NULL, sig, 1);
if ( (trustlevel & TRUST_FLAG_REVOKED) )
{
write_status( STATUS_KEYREVOKED );
if(pk->flags.revoked == 2)
log_info(_("WARNING: This key has been revoked by its"
" designated revoker!\n"));
else
log_info(_("WARNING: This key has been revoked by its owner!\n"));
log_info(_(" This could mean that the signature is forged.\n"));
show_revocation_reason (ctrl, pk, 0);
}
else if ((trustlevel & TRUST_FLAG_SUB_REVOKED) )
{
write_status( STATUS_KEYREVOKED );
log_info(_("WARNING: This subkey has been revoked by its owner!\n"));
show_revocation_reason (ctrl, pk, 0);
}
if ((trustlevel & TRUST_FLAG_DISABLED))
log_info (_("Note: This key has been disabled.\n"));
/* If we have PKA information adjust the trustlevel. */
if (sig->pka_info && sig->pka_info->valid)
{
unsigned char fpr[MAX_FINGERPRINT_LEN];
PKT_public_key *primary_pk;
size_t fprlen;
int okay;
primary_pk = xmalloc_clear (sizeof *primary_pk);
get_pubkey (ctrl, primary_pk, pk->main_keyid);
fingerprint_from_pk (primary_pk, fpr, &fprlen);
free_public_key (primary_pk);
if ( fprlen == 20 && !memcmp (sig->pka_info->fpr, fpr, 20) )
{
okay = 1;
write_status_text (STATUS_PKA_TRUST_GOOD, sig->pka_info->email);
log_info (_("Note: Verified signer's address is '%s'\n"),
sig->pka_info->email);
}
else
{
okay = 0;
write_status_text (STATUS_PKA_TRUST_BAD, sig->pka_info->email);
log_info (_("Note: Signer's address '%s' "
"does not match DNS entry\n"), sig->pka_info->email);
}
switch ( (trustlevel & TRUST_MASK) )
{
case TRUST_UNKNOWN:
case TRUST_UNDEFINED:
case TRUST_MARGINAL:
if (okay && opt.verify_options&VERIFY_PKA_TRUST_INCREASE)
{
trustlevel = ((trustlevel & ~TRUST_MASK) | TRUST_FULLY);
log_info (_("trustlevel adjusted to FULL"
" due to valid PKA info\n"));
}
/* fall through */
case TRUST_FULLY:
if (!okay)
{
trustlevel = ((trustlevel & ~TRUST_MASK) | TRUST_NEVER);
log_info (_("trustlevel adjusted to NEVER"
" due to bad PKA info\n"));
}
break;
}
}
/* Now let the user know what up with the trustlevel. */
switch ( (trustlevel & TRUST_MASK) )
{
case TRUST_EXPIRED:
log_info(_("Note: This key has expired!\n"));
print_fingerprint (ctrl, NULL, pk, 1);
break;
default:
log_error ("invalid trustlevel %u returned from validation layer\n",
trustlevel);
/* fall through */
case TRUST_UNKNOWN:
case TRUST_UNDEFINED:
write_trust_status (STATUS_TRUST_UNDEFINED, trustlevel);
log_info(_("WARNING: This key is not certified with"
" a trusted signature!\n"));
log_info(_(" There is no indication that the "
"signature belongs to the owner.\n" ));
print_fingerprint (ctrl, NULL, pk, 1);
break;
case TRUST_NEVER:
/* This level can be returned by TOFU, which supports negative
* assertions. */
write_trust_status (STATUS_TRUST_NEVER, trustlevel);
log_info(_("WARNING: We do NOT trust this key!\n"));
log_info(_(" The signature is probably a FORGERY.\n"));
if (opt.with_fingerprint)
print_fingerprint (ctrl, NULL, pk, 1);
rc = gpg_error (GPG_ERR_BAD_SIGNATURE);
break;
case TRUST_MARGINAL:
write_trust_status (STATUS_TRUST_MARGINAL, trustlevel);
log_info(_("WARNING: This key is not certified with"
" sufficiently trusted signatures!\n"));
log_info(_(" It is not certain that the"
" signature belongs to the owner.\n" ));
print_fingerprint (ctrl, NULL, pk, 1);
break;
case TRUST_FULLY:
write_trust_status (STATUS_TRUST_FULLY, trustlevel);
if (opt.with_fingerprint)
print_fingerprint (ctrl, NULL, pk, 1);
break;
case TRUST_ULTIMATE:
write_trust_status (STATUS_TRUST_ULTIMATE, trustlevel);
if (opt.with_fingerprint)
print_fingerprint (ctrl, NULL, pk, 1);
break;
}
leave:
free_public_key( pk );
return rc;
}
void
release_pk_list (pk_list_t pk_list)
{
PK_LIST pk_rover;
for ( ; pk_list; pk_list = pk_rover)
{
pk_rover = pk_list->next;
free_public_key ( pk_list->pk );
xfree ( pk_list );
}
}
static int
key_present_in_pk_list(PK_LIST pk_list, PKT_public_key *pk)
{
for( ; pk_list; pk_list = pk_list->next)
if (cmp_public_keys(pk_list->pk, pk) == 0)
return 0;
return -1;
}
/*
* Return a malloced string with a default recipient if there is any
* Fixme: We don't distinguish between malloc failure and no-default-recipient.
*/
static char *
default_recipient (ctrl_t ctrl)
{
PKT_public_key *pk;
char *result;
if (opt.def_recipient)
return xtrystrdup (opt.def_recipient);
if (!opt.def_recipient_self)
return NULL;
pk = xtrycalloc (1, sizeof *pk );
if (!pk)
return NULL;
if (get_seckey_default (ctrl, pk))
{
free_public_key (pk);
return NULL;
}
result = hexfingerprint (pk, NULL, 0);
free_public_key (pk);
return result;
}
static int
expand_id(const char *id,strlist_t *into,unsigned int flags)
{
struct groupitem *groups;
int count=0;
for(groups=opt.grouplist;groups;groups=groups->next)
{
/* need strcasecmp() here, as this should be localized */
if(strcasecmp(groups->name,id)==0)
{
strlist_t each,sl;
/* this maintains the current utf8-ness */
for(each=groups->values;each;each=each->next)
{
sl=add_to_strlist(into,each->d);
sl->flags=flags;
count++;
}
break;
}
}
return count;
}
/* For simplicity, and to avoid potential loops, we only expand once -
* you can't make an alias that points to an alias. */
static strlist_t
expand_group (strlist_t input)
{
strlist_t output = NULL;
strlist_t sl, rover;
for (rover = input; rover; rover = rover->next)
if (!(rover->flags & PK_LIST_FROM_FILE)
&& !expand_id(rover->d,&output,rover->flags))
{
/* Didn't find any groups, so use the existing string */
sl=add_to_strlist(&output,rover->d);
sl->flags=rover->flags;
}
return output;
}
/* Helper for build_pk_list to find and check one key. This helper is
* also used directly in server mode by the RECIPIENTS command. On
* success the new key is added to PK_LIST_ADDR. NAME is the user id
* of the key. USE the requested usage and a set MARK_HIDDEN will
* mark the key in the updated list as a hidden recipient. If
* FROM_FILE is true, NAME is not a user ID but the name of a file
* holding a key. */
gpg_error_t
find_and_check_key (ctrl_t ctrl, const char *name, unsigned int use,
int mark_hidden, int from_file, pk_list_t *pk_list_addr)
{
int rc;
PKT_public_key *pk;
KBNODE keyblock = NULL;
if (!name || !*name)
return gpg_error (GPG_ERR_INV_USER_ID);
pk = xtrycalloc (1, sizeof *pk);
if (!pk)
return gpg_error_from_syserror ();
pk->req_usage = use;
if (from_file)
rc = get_pubkey_fromfile (ctrl, pk, name);
else
- rc = get_best_pubkey_byname (ctrl, NULL, pk, name, &keyblock, 0);
+ rc = get_best_pubkey_byname (ctrl, GET_PUBKEY_NORMAL,
+ NULL, pk, name, &keyblock, 0);
if (rc)
{
int code;
/* Key not found or other error. */
log_error (_("%s: skipped: %s\n"), name, gpg_strerror (rc) );
switch (gpg_err_code (rc))
{
case GPG_ERR_NO_SECKEY:
case GPG_ERR_NO_PUBKEY: code = 1; break;
case GPG_ERR_INV_USER_ID: code = 14; break;
default: code = 0; break;
}
send_status_inv_recp (code, name);
free_public_key (pk);
return rc;
}
rc = openpgp_pk_test_algo2 (pk->pubkey_algo, use);
if (rc)
{
/* Key found but not usable for us (e.g. sign-only key). */
release_kbnode (keyblock);
send_status_inv_recp (3, name); /* Wrong key usage */
log_error (_("%s: skipped: %s\n"), name, gpg_strerror (rc) );
free_public_key (pk);
return rc;
}
/* Key found and usable. Check validity. */
if (!from_file)
{
int trustlevel;
trustlevel = get_validity (ctrl, keyblock, pk, pk->user_id, NULL, 1);
release_kbnode (keyblock);
if ( (trustlevel & TRUST_FLAG_DISABLED) )
{
/* Key has been disabled. */
send_status_inv_recp (13, name);
log_info (_("%s: skipped: public key is disabled\n"), name);
free_public_key (pk);
return GPG_ERR_UNUSABLE_PUBKEY;
}
if ( !do_we_trust_pre (ctrl, pk, trustlevel) )
{
/* We don't trust this key. */
send_status_inv_recp (10, name);
free_public_key (pk);
return GPG_ERR_UNUSABLE_PUBKEY;
}
}
/* Skip the actual key if the key is already present in the
list. */
if (!key_present_in_pk_list (*pk_list_addr, pk))
{
if (!opt.quiet)
log_info (_("%s: skipped: public key already present\n"), name);
free_public_key (pk);
}
else
{
pk_list_t r;
r = xtrymalloc (sizeof *r);
if (!r)
{
rc = gpg_error_from_syserror ();
free_public_key (pk);
return rc;
}
r->pk = pk;
r->next = *pk_list_addr;
r->flags = mark_hidden? 1:0;
*pk_list_addr = r;
}
return 0;
}
/* This is the central function to collect the keys for recipients.
* It is thus used to prepare a public key encryption. encrypt-to
* keys, default keys and the keys for the actual recipients are all
* collected here. When not in batch mode and no recipient has been
* passed on the commandline, the function will also ask for
* recipients.
*
* RCPTS is a string list with the recipients; NULL is an allowed
* value but not very useful. Group expansion is done on these names;
* they may be in any of the user Id formats we can handle. The flags
* bits for each string in the string list are used for:
*
* - PK_LIST_ENCRYPT_TO :: This is an encrypt-to recipient.
* - PK_LIST_HIDDEN :: This is a hidden recipient.
* - PK_LIST_FROM_FILE :: The argument is a file with a key.
*
* On success a list of keys is stored at the address RET_PK_LIST; the
* caller must free this list. On error the value at this address is
* not changed.
*/
int
build_pk_list (ctrl_t ctrl, strlist_t rcpts, PK_LIST *ret_pk_list)
{
PK_LIST pk_list = NULL;
PKT_public_key *pk=NULL;
int rc=0;
int any_recipients=0;
strlist_t rov,remusr;
char *def_rec = NULL;
char pkstrbuf[PUBKEY_STRING_SIZE];
/* Try to expand groups if any have been defined. */
if (opt.grouplist)
remusr = expand_group (rcpts);
else
remusr = rcpts;
/* XXX: Change this function to use get_pubkeys instead of
get_pubkey_byname to detect ambiguous key specifications and warn
about duplicate keyblocks. For ambiguous key specifications on
the command line or provided interactively, prompt the user to
select the best key. If a key specification is ambiguous and we
are in batch mode, die. */
if (opt.encrypt_to_default_key)
{
static int warned;
const char *default_key = parse_def_secret_key (ctrl);
if (default_key)
{
PK_LIST r = xmalloc_clear (sizeof *r);
r->pk = xmalloc_clear (sizeof *r->pk);
r->pk->req_usage = PUBKEY_USAGE_ENC;
- rc = get_pubkey_byname (ctrl, NULL, r->pk, default_key,
- NULL, NULL, 0, 1);
+ rc = get_pubkey_byname (ctrl, GET_PUBKEY_NO_AKL,
+ NULL, r->pk, default_key, NULL, NULL, 0);
if (rc)
{
xfree (r->pk);
xfree (r);
log_error (_("can't encrypt to '%s'\n"), default_key);
if (!opt.quiet)
log_info (_("(check argument of option '%s')\n"),
"--default-key");
}
else
{
r->next = pk_list;
r->flags = 0;
pk_list = r;
}
}
else if (opt.def_secret_key)
{
if (! warned)
log_info (_("option '%s' given, but no valid default keys given\n"),
"--encrypt-to-default-key");
warned = 1;
}
else
{
if (! warned)
log_info (_("option '%s' given, but option '%s' not given\n"),
"--encrypt-to-default-key", "--default-key");
warned = 1;
}
}
/* Check whether there are any recipients in the list and build the
* list of the encrypt-to ones (we always trust them). */
for ( rov = remusr; rov; rov = rov->next )
{
if ( !(rov->flags & PK_LIST_ENCRYPT_TO) )
{
/* This is a regular recipient; i.e. not an encrypt-to
one. */
any_recipients = 1;
/* Hidden recipients are not allowed while in PGP mode,
issue a warning and switch into GnuPG mode. */
if ((rov->flags & PK_LIST_HIDDEN) && (PGP7 || PGP8))
{
log_info(_("option '%s' may not be used in %s mode\n"),
"--hidden-recipient",
gnupg_compliance_option_string (opt.compliance));
compliance_failure();
}
}
else if (!opt.no_encrypt_to)
{
/* --encrypt-to has not been disabled. Check this
encrypt-to key. */
pk = xmalloc_clear( sizeof *pk );
pk->req_usage = PUBKEY_USAGE_ENC;
/* We explicitly allow encrypt-to to an disabled key; thus
we pass 1 for the second last argument and 1 as the last
argument to disable AKL. */
- if ( (rc = get_pubkey_byname (ctrl,
- NULL, pk, rov->d, NULL, NULL, 1, 1)) )
+ if ((rc = get_pubkey_byname (ctrl, GET_PUBKEY_NO_AKL,
+ NULL, pk, rov->d, NULL, NULL, 1)))
{
free_public_key ( pk ); pk = NULL;
log_error (_("%s: skipped: %s\n"), rov->d, gpg_strerror (rc) );
send_status_inv_recp (0, rov->d);
goto fail;
}
else if ( !(rc=openpgp_pk_test_algo2 (pk->pubkey_algo,
PUBKEY_USAGE_ENC)) )
{
/* Skip the actual key if the key is already present
* in the list. Add it to our list if not. */
if (key_present_in_pk_list(pk_list, pk) == 0)
{
free_public_key (pk); pk = NULL;
if (!opt.quiet)
log_info (_("%s: skipped: public key already present\n"),
rov->d);
}
else
{
PK_LIST r;
r = xmalloc( sizeof *r );
r->pk = pk; pk = NULL;
r->next = pk_list;
r->flags = (rov->flags&PK_LIST_HIDDEN)?1:0;
pk_list = r;
/* Hidden encrypt-to recipients are not allowed while
in PGP mode, issue a warning and switch into
GnuPG mode. */
if ((r->flags&PK_LIST_ENCRYPT_TO) && (PGP7 || PGP8))
{
log_info(_("option '%s' may not be used in %s mode\n"),
"--hidden-encrypt-to",
gnupg_compliance_option_string (opt.compliance));
compliance_failure();
}
}
}
else
{
/* The public key is not usable for encryption. */
free_public_key( pk ); pk = NULL;
log_error(_("%s: skipped: %s\n"), rov->d, gpg_strerror (rc) );
send_status_inv_recp (3, rov->d); /* Wrong key usage */
goto fail;
}
}
}
/* If we don't have any recipients yet and we are not in batch mode
drop into interactive selection mode. */
if ( !any_recipients && !opt.batch )
{
int have_def_rec;
char *answer = NULL;
strlist_t backlog = NULL;
if (pk_list)
any_recipients = 1;
def_rec = default_recipient(ctrl);
have_def_rec = !!def_rec;
if ( !have_def_rec )
tty_printf(_("You did not specify a user ID. (you may use \"-r\")\n"));
for (;;)
{
rc = 0;
xfree(answer);
if ( have_def_rec )
{
/* A default recipient is taken as the first entry. */
answer = def_rec;
def_rec = NULL;
}
else if (backlog)
{
/* This is part of our trick to expand and display groups. */
answer = strlist_pop (&backlog);
}
else
{
/* Show the list of already collected recipients and ask
for more. */
PK_LIST iter;
tty_printf("\n");
tty_printf(_("Current recipients:\n"));
for (iter=pk_list;iter;iter=iter->next)
{
u32 keyid[2];
keyid_from_pk(iter->pk,keyid);
tty_printf ("%s/%s %s \"",
pubkey_string (iter->pk,
pkstrbuf, sizeof pkstrbuf),
keystr(keyid),
datestr_from_pk (iter->pk));
if (iter->pk->user_id)
tty_print_utf8_string(iter->pk->user_id->name,
iter->pk->user_id->len);
else
{
size_t n;
char *p = get_user_id (ctrl, keyid, &n, NULL);
tty_print_utf8_string ( p, n );
xfree(p);
}
tty_printf("\"\n");
}
answer = cpr_get_utf8("pklist.user_id.enter",
_("\nEnter the user ID. "
"End with an empty line: "));
trim_spaces(answer);
cpr_kill_prompt();
}
if ( !answer || !*answer )
{
xfree(answer);
break; /* No more recipients entered - get out of loop. */
}
/* Do group expand here too. The trick here is to continue
the loop if any expansion occurred. The code above will
then list all expanded keys. */
if (expand_id(answer,&backlog,0))
continue;
/* Get and check key for the current name. */
free_public_key (pk);
pk = xmalloc_clear( sizeof *pk );
pk->req_usage = PUBKEY_USAGE_ENC;
- rc = get_pubkey_byname (ctrl, NULL, pk, answer, NULL, NULL, 0, 0 );
+ rc = get_pubkey_byname (ctrl, GET_PUBKEY_NORMAL,
+ NULL, pk, answer, NULL, NULL, 0);
if (rc)
tty_printf(_("No such user ID.\n"));
else if ( !(rc=openpgp_pk_test_algo2 (pk->pubkey_algo,
PUBKEY_USAGE_ENC)) )
{
if ( have_def_rec )
{
/* No validation for a default recipient. */
if (!key_present_in_pk_list(pk_list, pk))
{
free_public_key (pk);
pk = NULL;
log_info (_("skipped: public key "
"already set as default recipient\n") );
}
else
{
PK_LIST r = xmalloc (sizeof *r);
r->pk = pk; pk = NULL;
r->next = pk_list;
r->flags = 0; /* No throwing default ids. */
pk_list = r;
}
any_recipients = 1;
continue;
}
else
{ /* Check validity of this key. */
int trustlevel;
trustlevel =
get_validity (ctrl, NULL, pk, pk->user_id, NULL, 1);
if ( (trustlevel & TRUST_FLAG_DISABLED) )
{
tty_printf (_("Public key is disabled.\n") );
}
else if ( do_we_trust_pre (ctrl, pk, trustlevel) )
{
/* Skip the actual key if the key is already
* present in the list */
if (!key_present_in_pk_list(pk_list, pk))
{
free_public_key (pk);
pk = NULL;
log_info(_("skipped: public key already set\n") );
}
else
{
PK_LIST r;
r = xmalloc( sizeof *r );
r->pk = pk; pk = NULL;
r->next = pk_list;
r->flags = 0; /* No throwing interactive ids. */
pk_list = r;
}
any_recipients = 1;
continue;
}
}
}
xfree(def_rec); def_rec = NULL;
have_def_rec = 0;
}
if ( pk )
{
free_public_key( pk );
pk = NULL;
}
}
else if ( !any_recipients && (def_rec = default_recipient(ctrl)) )
{
/* We are in batch mode and have only a default recipient. */
pk = xmalloc_clear( sizeof *pk );
pk->req_usage = PUBKEY_USAGE_ENC;
/* The default recipient is allowed to be disabled; thus pass 1
as second last argument. We also don't want an AKL. */
- rc = get_pubkey_byname (ctrl, NULL, pk, def_rec, NULL, NULL, 1, 1);
+ rc = get_pubkey_byname (ctrl, GET_PUBKEY_NO_AKL,
+ NULL, pk, def_rec, NULL, NULL, 1);
if (rc)
log_error(_("unknown default recipient \"%s\"\n"), def_rec );
else if ( !(rc=openpgp_pk_test_algo2(pk->pubkey_algo,
PUBKEY_USAGE_ENC)) )
{
/* Mark any_recipients here since the default recipient
would have been used if it wasn't already there. It
doesn't really matter if we got this key from the default
recipient or an encrypt-to. */
any_recipients = 1;
if (!key_present_in_pk_list(pk_list, pk))
log_info (_("skipped: public key already set "
"as default recipient\n"));
else
{
PK_LIST r = xmalloc( sizeof *r );
r->pk = pk; pk = NULL;
r->next = pk_list;
r->flags = 0; /* No throwing default ids. */
pk_list = r;
}
}
if ( pk )
{
free_public_key( pk );
pk = NULL;
}
xfree(def_rec); def_rec = NULL;
}
else
{
/* General case: Check all keys. */
any_recipients = 0;
for (; remusr; remusr = remusr->next )
{
if ( (remusr->flags & PK_LIST_ENCRYPT_TO) )
continue; /* encrypt-to keys are already handled. */
rc = find_and_check_key (ctrl, remusr->d, PUBKEY_USAGE_ENC,
!!(remusr->flags&PK_LIST_HIDDEN),
!!(remusr->flags&PK_LIST_FROM_FILE),
&pk_list);
if (rc)
goto fail;
any_recipients = 1;
}
}
if ( !rc && !any_recipients )
{
log_error(_("no valid addressees\n"));
write_status_text (STATUS_NO_RECP, "0");
rc = GPG_ERR_NO_USER_ID;
}
#ifdef USE_TOFU
if (! rc && (opt.trust_model == TM_TOFU_PGP || opt.trust_model == TM_TOFU))
{
PK_LIST iter;
for (iter = pk_list; iter; iter = iter->next)
{
int rc2;
/* Note: we already resolved any conflict when looking up
the key. Don't annoy the user again if she selected
accept once. */
rc2 = tofu_register_encryption (ctrl, iter->pk, NULL, 0);
if (rc2)
log_info ("WARNING: Failed to register encryption to %s"
" with TOFU engine\n",
keystr (pk_main_keyid (iter->pk)));
else if (DBG_TRUST)
log_debug ("Registered encryption to %s with TOFU DB.\n",
keystr (pk_main_keyid (iter->pk)));
}
}
#endif /*USE_TOFU*/
fail:
if ( rc )
release_pk_list( pk_list );
else
*ret_pk_list = pk_list;
if (opt.grouplist)
free_strlist(remusr);
return rc;
}
/* In pgp6 mode, disallow all ciphers except IDEA (1), 3DES (2), and
CAST5 (3), all hashes except MD5 (1), SHA1 (2), and RIPEMD160 (3),
and all compressions except none (0) and ZIP (1). pgp7 and pgp8
mode expands the cipher list to include AES128 (7), AES192 (8),
AES256 (9), and TWOFISH (10). pgp8 adds the SHA-256 hash (8). For
a true PGP key all of this is unneeded as they are the only items
present in the preferences subpacket, but checking here covers the
weird case of encrypting to a key that had preferences from a
different implementation which was then used with PGP. I am not
completely comfortable with this as the right thing to do, as it
slightly alters the list of what the user is supposedly requesting.
It is not against the RFC however, as the preference chosen will
never be one that the user didn't specify somewhere ("The
implementation may use any mechanism to pick an algorithm in the
intersection"), and PGP has no mechanism to fix such a broken
preference list, so I'm including it. -dms */
int
algo_available( preftype_t preftype, int algo, const union pref_hint *hint)
{
if( preftype == PREFTYPE_SYM )
{
if(PGP7 && (algo != CIPHER_ALGO_IDEA
&& algo != CIPHER_ALGO_3DES
&& algo != CIPHER_ALGO_CAST5
&& algo != CIPHER_ALGO_AES
&& algo != CIPHER_ALGO_AES192
&& algo != CIPHER_ALGO_AES256
&& algo != CIPHER_ALGO_TWOFISH))
return 0;
/* PGP8 supports all the ciphers we do.. */
return algo && !openpgp_cipher_test_algo ( algo );
}
else if( preftype == PREFTYPE_HASH )
{
if (hint && hint->digest_length)
{
if (hint->digest_length!=20 || opt.flags.dsa2)
{
/* If --enable-dsa2 is set or the hash isn't 160 bits
(which implies DSA2), then we'll accept a hash that
is larger than we need. Otherwise we won't accept
any hash that isn't exactly the right size. */
if (hint->digest_length > gcry_md_get_algo_dlen (algo))
return 0;
}
else if (hint->digest_length != gcry_md_get_algo_dlen (algo))
return 0;
}
if (PGP7 && (algo != DIGEST_ALGO_MD5
&& algo != DIGEST_ALGO_SHA1
&& algo != DIGEST_ALGO_RMD160))
return 0;
if(PGP8 && (algo != DIGEST_ALGO_MD5
&& algo != DIGEST_ALGO_SHA1
&& algo != DIGEST_ALGO_RMD160
&& algo != DIGEST_ALGO_SHA256))
return 0;
return algo && !openpgp_md_test_algo (algo);
}
else if( preftype == PREFTYPE_ZIP )
{
if (PGP7 && (algo != COMPRESS_ALGO_NONE
&& algo != COMPRESS_ALGO_ZIP))
return 0;
/* PGP8 supports all the compression algos we do */
return !check_compress_algo( algo );
}
else
return 0;
}
/****************
* Return -1 if we could not find an algorithm.
*/
int
select_algo_from_prefs(PK_LIST pk_list, int preftype,
int request, const union pref_hint *hint)
{
PK_LIST pkr;
u32 bits[8];
const prefitem_t *prefs;
int result=-1,i;
u16 scores[256];
if( !pk_list )
return -1;
memset(bits,0xFF,sizeof(bits));
memset(scores,0,sizeof(scores));
for( pkr = pk_list; pkr; pkr = pkr->next )
{
u32 mask[8];
int rank=1,implicit=-1;
memset(mask,0,sizeof(mask));
switch(preftype)
{
case PREFTYPE_SYM:
/* IDEA is implicitly there for v3 keys with v3 selfsigs if
--pgp2 mode is on. This was a 2440 thing that was
dropped from 4880 but is still relevant to GPG's 1991
support. All this doesn't mean IDEA is actually
available, of course. */
implicit=CIPHER_ALGO_3DES;
break;
case PREFTYPE_AEAD:
/* No implicit algo. */
break;
case PREFTYPE_HASH:
/* While I am including this code for completeness, note
that currently --pgp2 mode locks the hash at MD5, so this
code will never even be called. Even if the hash wasn't
locked at MD5, we don't support sign+encrypt in --pgp2
mode, and that's the only time PREFTYPE_HASH is used
anyway. -dms */
implicit=DIGEST_ALGO_SHA1;
break;
case PREFTYPE_ZIP:
/* Uncompressed is always an option. */
implicit=COMPRESS_ALGO_NONE;
}
if (pkr->pk->user_id) /* selected by user ID */
prefs = pkr->pk->user_id->prefs;
else
prefs = pkr->pk->prefs;
if( prefs )
{
for (i=0; prefs[i].type; i++ )
{
if( prefs[i].type == preftype )
{
/* Make sure all scores don't add up past 0xFFFF
(and roll around) */
if(rank+scores[prefs[i].value]<=0xFFFF)
scores[prefs[i].value]+=rank;
else
scores[prefs[i].value]=0xFFFF;
mask[prefs[i].value/32] |= 1<<(prefs[i].value%32);
rank++;
/* We saw the implicit algorithm, so we don't need
tack it on the end ourselves. */
if(implicit==prefs[i].value)
implicit=-1;
}
}
}
if(rank==1 && preftype==PREFTYPE_ZIP)
{
/* If the compression preferences are not present, they are
assumed to be ZIP, Uncompressed (RFC4880:13.3.1) */
scores[1]=1; /* ZIP is first choice */
scores[0]=2; /* Uncompressed is second choice */
mask[0]|=3;
}
/* If the key didn't have the implicit algorithm listed
explicitly, add it here at the tail of the list. */
if(implicit>-1)
{
scores[implicit]+=rank;
mask[implicit/32] |= 1<<(implicit%32);
}
for(i=0;i<8;i++)
bits[i]&=mask[i];
}
/* We've now scored all of the algorithms, and the usable ones have
bits set. Let's pick the winner. */
/* The caller passed us a request. Can we use it? */
if(request>-1 && (bits[request/32] & (1<<(request%32))) &&
algo_available(preftype,request,hint))
result=request;
if(result==-1)
{
/* If we have personal prefs set, use them. */
prefs=NULL;
if(preftype==PREFTYPE_SYM && opt.personal_cipher_prefs)
prefs=opt.personal_cipher_prefs;
else if(preftype==PREFTYPE_AEAD && opt.personal_aead_prefs)
prefs=opt.personal_aead_prefs;
else if(preftype==PREFTYPE_HASH && opt.personal_digest_prefs)
prefs=opt.personal_digest_prefs;
else if(preftype==PREFTYPE_ZIP && opt.personal_compress_prefs)
prefs=opt.personal_compress_prefs;
if( prefs )
for(i=0; prefs[i].type; i++ )
{
if(bits[prefs[i].value/32] & (1<<(prefs[i].value%32))
&& algo_available( preftype, prefs[i].value, hint))
{
result = prefs[i].value;
break;
}
}
}
if(result==-1)
{
unsigned int best=-1;
/* At this point, we have not selected an algorithm due to a
special request or via personal prefs. Pick the highest
ranked algorithm (i.e. the one with the lowest score). */
if(preftype==PREFTYPE_HASH && scores[DIGEST_ALGO_MD5])
{
/* "If you are building an authentication system, the recipient
may specify a preferred signing algorithm. However, the
signer would be foolish to use a weak algorithm simply
because the recipient requests it." (RFC4880:14). If any
other hash algorithm is available, pretend that MD5 isn't.
Note that if the user intentionally chose MD5 by putting it
in their personal prefs, then we do what the user said (as we
never reach this code). */
for(i=DIGEST_ALGO_MD5+1;i<256;i++)
if(scores[i])
{
scores[DIGEST_ALGO_MD5]=0;
break;
}
}
for(i=0;i<256;i++)
{
/* Note the '<' here. This means in case of a tie, we will
favor the lower algorithm number. We have a choice
between the lower number (probably an older algorithm
with more time in use), or the higher number (probably a
newer algorithm with less time in use). Older is
probably safer here, even though the newer algorithms
tend to be "stronger". */
if(scores[i] && scores[i]<best
&& (bits[i/32] & (1<<(i%32)))
&& algo_available(preftype,i,hint))
{
best=scores[i];
result=i;
}
}
}
return result;
}
/*
* Select the MDC flag from the pk_list. We can only use MDC if all
* recipients support this feature.
*/
int
select_mdc_from_pklist (PK_LIST pk_list)
{
PK_LIST pkr;
if ( !pk_list )
return 0;
for (pkr = pk_list; pkr; pkr = pkr->next)
{
int mdc;
if (pkr->pk->user_id) /* selected by user ID */
mdc = pkr->pk->user_id->flags.mdc;
else
mdc = pkr->pk->flags.mdc;
if (!mdc)
return 0; /* At least one recipient does not support it. */
}
return 1; /* Can be used. */
}
/* Select the AEAD flag from the pk_list. We can only use AEAD if all
* recipients support this feature. Returns the AEAD to be used or 0
* if AEAD shall not be used. */
aead_algo_t
select_aead_from_pklist (PK_LIST pk_list)
{
pk_list_t pkr;
int aead;
if (!pk_list)
return 0;
for (pkr = pk_list; pkr; pkr = pkr->next)
{
if (pkr->pk->user_id) /* selected by user ID */
aead = pkr->pk->user_id->flags.aead;
else
aead = pkr->pk->flags.aead;
if (!aead)
return 0; /* At least one recipient does not support it. */
}
return default_aead_algo (); /* Yes, AEAD can be used. */
}
/* Print a warning for all keys in PK_LIST missing the AEAD feature
* flag or AEAD algorithms. */
void
warn_missing_aead_from_pklist (PK_LIST pk_list)
{
PK_LIST pkr;
for (pkr = pk_list; pkr; pkr = pkr->next)
{
int mdc;
if (pkr->pk->user_id) /* selected by user ID */
mdc = pkr->pk->user_id->flags.aead;
else
mdc = pkr->pk->flags.aead;
if (!mdc)
log_info (_("Note: key %s has no %s feature\n"),
keystr_from_pk (pkr->pk), "AEAD");
}
}
void
warn_missing_aes_from_pklist (PK_LIST pk_list)
{
PK_LIST pkr;
for (pkr = pk_list; pkr; pkr = pkr->next)
{
const prefitem_t *prefs;
int i;
int gotit = 0;
prefs = pkr->pk->user_id? pkr->pk->user_id->prefs : pkr->pk->prefs;
if (prefs)
{
for (i=0; !gotit && prefs[i].type; i++ )
if (prefs[i].type == PREFTYPE_SYM
&& prefs[i].value == CIPHER_ALGO_AES)
gotit++;
}
if (!gotit)
log_info (_("Note: key %s has no preference for %s\n"),
keystr_from_pk (pkr->pk), "AES");
}
}
diff --git a/g10/plaintext.c b/g10/plaintext.c
index c5d1ddb7f..f9e0a4296 100644
--- a/g10/plaintext.c
+++ b/g10/plaintext.c
@@ -1,813 +1,814 @@
/* plaintext.c - process plaintext packets
* Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005,
* 2006, 2009, 2010 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#ifdef HAVE_DOSISH_SYSTEM
# include <fcntl.h> /* for setmode() */
#endif
#include "gpg.h"
#include "../common/util.h"
#include "options.h"
#include "packet.h"
#include "../common/ttyio.h"
#include "filter.h"
#include "main.h"
#include "../common/status.h"
#include "../common/i18n.h"
/* Get the output filename. On success, the actual filename that is
used is set in *FNAMEP and a filepointer is returned in *FP.
EMBEDDED_NAME AND EMBEDDED_NAMELEN are normally stored in a
plaintext packet. EMBEDDED_NAMELEN should not include any NUL
terminator (EMBEDDED_NAME does not need to be NUL terminated).
DATA is the iobuf containing the input data. We just use it to get
the input file's filename.
On success, the caller is responsible for calling xfree on *FNAMEP
and calling es_close on *FPP. */
gpg_error_t
get_output_file (const byte *embedded_name, int embedded_namelen,
iobuf_t data, char **fnamep, estream_t *fpp)
{
gpg_error_t err = 0;
char *fname = NULL;
estream_t fp = NULL;
int nooutput = 0;
/* Create the filename as C string. */
if (opt.outfp)
{
fname = xtrystrdup ("[FP]");
if (!fname)
{
err = gpg_error_from_syserror ();
goto leave;
}
}
- else if (opt.outfile)
+ else if (opt.outfile
+ && !(opt.flags.use_embedded_filename && opt.flags.dummy_outfile))
{
fname = xtrystrdup (opt.outfile);
if (!fname)
{
err = gpg_error_from_syserror ();
goto leave;
}
}
else if (embedded_namelen == 8 && !memcmp (embedded_name, "_CONSOLE", 8))
{
log_info (_("data not saved; use option \"--output\" to save it\n"));
nooutput = 1;
}
else if (!opt.flags.use_embedded_filename)
{
if (data)
fname = make_outfile_name (iobuf_get_real_fname (data));
if (!fname)
fname = ask_outfile_name (embedded_name, embedded_namelen);
if (!fname)
{
err = gpg_error (GPG_ERR_GENERAL); /* Can't create file. */
goto leave;
}
}
else
fname = utf8_to_native (embedded_name, embedded_namelen, 0);
if (nooutput)
;
else if (opt.outfp)
{
fp = opt.outfp;
es_set_binary (fp);
}
else if (iobuf_is_pipe_filename (fname) || !*fname)
{
/* Special file name, no filename, or "-" given; write to the
* file descriptor or to stdout. */
int fd;
char xname[64];
fd = check_special_filename (fname, 1, 0);
if (fd == -1)
{
/* Not a special filename, thus we want stdout. */
fp = es_stdout;
es_set_binary (fp);
}
else if (!(fp = es_fdopen_nc (fd, "wb")))
{
err = gpg_error_from_syserror ();
snprintf (xname, sizeof xname, "[fd %d]", fd);
log_error (_("can't open '%s': %s\n"), xname, gpg_strerror (err));
goto leave;
}
}
else
{
while (!overwrite_filep (fname))
{
char *tmp = ask_outfile_name (NULL, 0);
if (!tmp || !*tmp)
{
xfree (tmp);
/* FIXME: Below used to be GPG_ERR_CREATE_FILE */
err = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
xfree (fname);
fname = tmp;
}
}
#ifndef __riscos__
if (opt.outfp && is_secured_file (es_fileno (opt.outfp)))
{
err = gpg_error (GPG_ERR_EPERM);
log_error (_("error creating '%s': %s\n"), fname, gpg_strerror (err));
goto leave;
}
else if (fp || nooutput)
;
else if (is_secured_filename (fname))
{
gpg_err_set_errno (EPERM);
err = gpg_error_from_syserror ();
log_error (_("error creating '%s': %s\n"), fname, gpg_strerror (err));
goto leave;
}
else if (!(fp = es_fopen (fname, "wb")))
{
err = gpg_error_from_syserror ();
log_error (_("error creating '%s': %s\n"), fname, gpg_strerror (err));
goto leave;
}
#else /* __riscos__ */
/* If no output filename was given, i.e. we constructed it, convert
all '.' in fname to '/' but not vice versa as we don't create
directories! */
if (!opt.outfile)
for (c = 0; fname[c]; ++c)
if (fname[c] == '.')
fname[c] = '/';
if (fp || nooutput)
;
else
{
/* Note: riscos stuff is not expected to work anymore. If we
want to port it again to riscos we should do most of the suff
in estream. FIXME: Consider to remove all riscos special
cases. */
fp = fopen (fname, "wb");
if (!fp)
{
log_error (_("error creating '%s': %s\n"), fname, gpg_strerror (err));
err = GPG_ERR_CREATE_FILE;
if (errno == 106)
log_info ("Do output file and input file have the same name?\n");
goto leave;
}
/* If there's a ,xxx extension in the embedded filename,
use that, else check whether the user input (in fname)
has a ,xxx appended, then use that in preference */
if ((c = riscos_get_filetype_from_string (embedded_name,
embedded_namelen)) != -1)
filetype = c;
if ((c = riscos_get_filetype_from_string (fname, strlen (fname))) != -1)
filetype = c;
riscos_set_filetype_by_number (fname, filetype);
}
#endif /* __riscos__ */
leave:
if (err)
{
if (fp && fp != es_stdout && fp != opt.outfp)
es_fclose (fp);
xfree (fname);
return err;
}
*fnamep = fname;
*fpp = fp;
return 0;
}
/* Handle a plaintext packet. If MFX is not NULL, update the MDs
* Note: We should have used the filter stuff here, but we have to add
* some easy mimic to set a read limit, so we calculate only the bytes
* from the plaintext. */
int
handle_plaintext (PKT_plaintext * pt, md_filter_context_t * mfx,
int nooutput, int clearsig)
{
char *fname = NULL;
estream_t fp = NULL;
static off_t count = 0;
int err = 0;
int c;
int convert;
#ifdef __riscos__
int filetype = 0xfff;
#endif
if (pt->mode == 't' || pt->mode == 'u' || pt->mode == 'm')
convert = pt->mode;
else
convert = 0;
/* Let people know what the plaintext info is. This allows the
receiving program to try and do something different based on the
format code (say, recode UTF-8 to local). */
if (!nooutput && is_status_enabled ())
{
char status[50];
/* Better make sure that stdout has been flushed in case the
output will be written to it. This is to make sure that no
not-yet-flushed stuff will be written after the plaintext
status message. */
es_fflush (es_stdout);
snprintf (status, sizeof status,
"%X %lu ", (byte) pt->mode, (ulong) pt->timestamp);
write_status_text_and_buffer (STATUS_PLAINTEXT,
status, pt->name, pt->namelen, 0);
if (!pt->is_partial)
{
snprintf (status, sizeof status, "%lu", (ulong) pt->len);
write_status_text (STATUS_PLAINTEXT_LENGTH, status);
}
}
if (! nooutput)
{
err = get_output_file (pt->name, pt->namelen, pt->buf, &fname, &fp);
if (err)
goto leave;
}
if (!pt->is_partial)
{
/* We have an actual length (which might be zero). */
if (clearsig)
{
log_error ("clearsig encountered while not expected\n");
err = gpg_error (GPG_ERR_UNEXPECTED);
goto leave;
}
if (convert) /* Text mode. */
{
for (; pt->len; pt->len--)
{
if ((c = iobuf_get (pt->buf)) == -1)
{
err = gpg_error_from_syserror ();
log_error ("problem reading source (%u bytes remaining)\n",
(unsigned) pt->len);
goto leave;
}
if (mfx->md)
gcry_md_putc (mfx->md, c);
#ifndef HAVE_DOSISH_SYSTEM
/* Convert to native line ending. */
/* fixme: this hack might be too simple */
if (c == '\r' && convert != 'm')
continue;
#endif
if (fp)
{
if (opt.max_output && (++count) > opt.max_output)
{
log_error ("error writing to '%s': %s\n",
fname, "exceeded --max-output limit\n");
err = gpg_error (GPG_ERR_TOO_LARGE);
goto leave;
}
else if (es_putc (c, fp) == EOF)
{
if (es_ferror (fp))
err = gpg_error_from_syserror ();
else
err = gpg_error (GPG_ERR_EOF);
log_error ("error writing to '%s': %s\n",
fname, gpg_strerror (err));
goto leave;
}
}
}
}
else /* Binary mode. */
{
byte *buffer = xmalloc (32768);
while (pt->len)
{
int len = pt->len > 32768 ? 32768 : pt->len;
len = iobuf_read (pt->buf, buffer, len);
if (len == -1)
{
err = gpg_error_from_syserror ();
log_error ("problem reading source (%u bytes remaining)\n",
(unsigned) pt->len);
xfree (buffer);
goto leave;
}
if (mfx->md)
gcry_md_write (mfx->md, buffer, len);
if (fp)
{
if (opt.max_output && (count += len) > opt.max_output)
{
log_error ("error writing to '%s': %s\n",
fname, "exceeded --max-output limit\n");
err = gpg_error (GPG_ERR_TOO_LARGE);
xfree (buffer);
goto leave;
}
else if (es_fwrite (buffer, 1, len, fp) != len)
{
err = gpg_error_from_syserror ();
log_error ("error writing to '%s': %s\n",
fname, gpg_strerror (err));
xfree (buffer);
goto leave;
}
}
pt->len -= len;
}
xfree (buffer);
}
}
else if (!clearsig)
{
if (convert)
{ /* text mode */
while ((c = iobuf_get (pt->buf)) != -1)
{
if (mfx->md)
gcry_md_putc (mfx->md, c);
#ifndef HAVE_DOSISH_SYSTEM
if (c == '\r' && convert != 'm')
continue; /* fixme: this hack might be too simple */
#endif
if (fp)
{
if (opt.max_output && (++count) > opt.max_output)
{
log_error ("Error writing to '%s': %s\n",
fname, "exceeded --max-output limit\n");
err = gpg_error (GPG_ERR_TOO_LARGE);
goto leave;
}
else if (es_putc (c, fp) == EOF)
{
if (es_ferror (fp))
err = gpg_error_from_syserror ();
else
err = gpg_error (GPG_ERR_EOF);
log_error ("error writing to '%s': %s\n",
fname, gpg_strerror (err));
goto leave;
}
}
}
}
else
{ /* binary mode */
byte *buffer;
int eof_seen = 0;
buffer = xtrymalloc (32768);
if (!buffer)
{
err = gpg_error_from_syserror ();
goto leave;
}
while (!eof_seen)
{
/* Why do we check for len < 32768:
* If we won't, we would practically read 2 EOFs but
* the first one has already popped the block_filter
* off and therefore we don't catch the boundary.
* So, always assume EOF if iobuf_read returns less bytes
* then requested */
int len = iobuf_read (pt->buf, buffer, 32768);
if (len == -1)
break;
if (len < 32768)
eof_seen = 1;
if (mfx->md)
gcry_md_write (mfx->md, buffer, len);
if (fp)
{
if (opt.max_output && (count += len) > opt.max_output)
{
log_error ("error writing to '%s': %s\n",
fname, "exceeded --max-output limit\n");
err = gpg_error (GPG_ERR_TOO_LARGE);
xfree (buffer);
goto leave;
}
else if (es_fwrite (buffer, 1, len, fp) != len)
{
err = gpg_error_from_syserror ();
log_error ("error writing to '%s': %s\n",
fname, gpg_strerror (err));
xfree (buffer);
goto leave;
}
}
}
xfree (buffer);
}
pt->buf = NULL;
}
else /* Clear text signature - don't hash the last CR,LF. */
{
int state = 0;
while ((c = iobuf_get (pt->buf)) != -1)
{
if (fp)
{
if (opt.max_output && (++count) > opt.max_output)
{
log_error ("error writing to '%s': %s\n",
fname, "exceeded --max-output limit\n");
err = gpg_error (GPG_ERR_TOO_LARGE);
goto leave;
}
else if (es_putc (c, fp) == EOF)
{
err = gpg_error_from_syserror ();
log_error ("error writing to '%s': %s\n",
fname, gpg_strerror (err));
goto leave;
}
}
if (!mfx->md)
continue;
if (state == 2)
{
gcry_md_putc (mfx->md, '\r');
gcry_md_putc (mfx->md, '\n');
state = 0;
}
if (!state)
{
if (c == '\r')
state = 1;
else if (c == '\n')
state = 2;
else
gcry_md_putc (mfx->md, c);
}
else if (state == 1)
{
if (c == '\n')
state = 2;
else
{
gcry_md_putc (mfx->md, '\r');
if (c == '\r')
state = 1;
else
{
state = 0;
gcry_md_putc (mfx->md, c);
}
}
}
}
pt->buf = NULL;
}
if (fp && fp != es_stdout && fp != opt.outfp && es_fclose (fp))
{
err = gpg_error_from_syserror ();
log_error ("error closing '%s': %s\n", fname, gpg_strerror (err));
fp = NULL;
goto leave;
}
fp = NULL;
leave:
/* Make sure that stdout gets flushed after the plaintext has been
handled. This is for extra security as we do a flush anyway
before checking the signature. */
if (es_fflush (es_stdout))
{
/* We need to check the return code to detect errors like disk
full for short plaintexts. See bug#1207. Checking return
values is a good idea in any case. */
if (!err)
err = gpg_error_from_syserror ();
log_error ("error flushing '%s': %s\n", "[stdout]",
gpg_strerror (err));
}
if (fp && fp != es_stdout && fp != opt.outfp)
es_fclose (fp);
xfree (fname);
return err;
}
static void
do_hash (gcry_md_hd_t md, gcry_md_hd_t md2, IOBUF fp, int textmode)
{
text_filter_context_t tfx;
int c;
if (textmode)
{
memset (&tfx, 0, sizeof tfx);
iobuf_push_filter (fp, text_filter, &tfx);
}
if (md2)
{ /* work around a strange behaviour in pgp2 */
/* It seems that at least PGP5 converts a single CR to a CR,LF too */
int lc = -1;
while ((c = iobuf_get (fp)) != -1)
{
if (c == '\n' && lc == '\r')
gcry_md_putc (md2, c);
else if (c == '\n')
{
gcry_md_putc (md2, '\r');
gcry_md_putc (md2, c);
}
else if (c != '\n' && lc == '\r')
{
gcry_md_putc (md2, '\n');
gcry_md_putc (md2, c);
}
else
gcry_md_putc (md2, c);
if (md)
gcry_md_putc (md, c);
lc = c;
}
}
else
{
while ((c = iobuf_get (fp)) != -1)
{
if (md)
gcry_md_putc (md, c);
}
}
}
/****************
* Ask for the detached datafile and calculate the digest from it.
* INFILE is the name of the input file.
*/
int
ask_for_detached_datafile (gcry_md_hd_t md, gcry_md_hd_t md2,
const char *inname, int textmode)
{
progress_filter_context_t *pfx;
char *answer = NULL;
IOBUF fp;
int rc = 0;
pfx = new_progress_context ();
fp = open_sigfile (inname, pfx); /* Open default file. */
if (!fp && !opt.batch)
{
int any = 0;
tty_printf (_("Detached signature.\n"));
do
{
char *name;
xfree (answer);
tty_enable_completion (NULL);
name = cpr_get ("detached_signature.filename",
_("Please enter name of data file: "));
tty_disable_completion ();
cpr_kill_prompt ();
answer = make_filename (name, (void *) NULL);
xfree (name);
if (any && !*answer)
{
rc = gpg_error (GPG_ERR_GENERAL); /*G10ERR_READ_FILE */
goto leave;
}
fp = iobuf_open (answer);
if (fp && is_secured_file (iobuf_get_fd (fp)))
{
iobuf_close (fp);
fp = NULL;
gpg_err_set_errno (EPERM);
}
if (!fp && errno == ENOENT)
{
tty_printf ("No such file, try again or hit enter to quit.\n");
any++;
}
else if (!fp)
{
rc = gpg_error_from_syserror ();
log_error (_("can't open '%s': %s\n"), answer,
strerror (errno));
goto leave;
}
}
while (!fp);
}
if (!fp)
{
if (opt.verbose)
log_info (_("reading stdin ...\n"));
fp = iobuf_open (NULL);
log_assert (fp);
}
do_hash (md, md2, fp, textmode);
iobuf_close (fp);
leave:
xfree (answer);
release_progress_context (pfx);
return rc;
}
/* Hash the given files and append the hash to hash contexts MD and
* MD2. If FILES is NULL, stdin is hashed. */
int
hash_datafiles (gcry_md_hd_t md, gcry_md_hd_t md2, strlist_t files,
const char *sigfilename, int textmode)
{
progress_filter_context_t *pfx;
IOBUF fp;
strlist_t sl;
pfx = new_progress_context ();
if (!files)
{
/* Check whether we can open the signed material. We avoid
trying to open a file if run in batch mode. This assumed
data file for a sig file feature is just a convenience thing
for the command line and the user needs to read possible
warning messages. */
if (!opt.batch)
{
fp = open_sigfile (sigfilename, pfx);
if (fp)
{
do_hash (md, md2, fp, textmode);
iobuf_close (fp);
release_progress_context (pfx);
return 0;
}
}
log_error (_("no signed data\n"));
release_progress_context (pfx);
return gpg_error (GPG_ERR_NO_DATA);
}
for (sl = files; sl; sl = sl->next)
{
fp = iobuf_open (sl->d);
if (fp && is_secured_file (iobuf_get_fd (fp)))
{
iobuf_close (fp);
fp = NULL;
gpg_err_set_errno (EPERM);
}
if (!fp)
{
int rc = gpg_error_from_syserror ();
log_error (_("can't open signed data '%s'\n"),
print_fname_stdin (sl->d));
release_progress_context (pfx);
return rc;
}
handle_progress (pfx, fp, sl->d);
do_hash (md, md2, fp, textmode);
iobuf_close (fp);
}
release_progress_context (pfx);
return 0;
}
/* Hash the data from file descriptor DATA_FD and append the hash to hash
contexts MD and MD2. */
int
hash_datafile_by_fd (gcry_md_hd_t md, gcry_md_hd_t md2, int data_fd,
int textmode)
{
progress_filter_context_t *pfx = new_progress_context ();
iobuf_t fp;
if (is_secured_file (data_fd))
{
fp = NULL;
gpg_err_set_errno (EPERM);
}
else
fp = iobuf_fdopen_nc (data_fd, "rb");
if (!fp)
{
int rc = gpg_error_from_syserror ();
log_error (_("can't open signed data fd=%d: %s\n"),
data_fd, strerror (errno));
release_progress_context (pfx);
return rc;
}
handle_progress (pfx, fp, NULL);
do_hash (md, md2, fp, textmode);
iobuf_close (fp);
release_progress_context (pfx);
return 0;
}
/* Set up a plaintext packet with the appropriate filename. If there
is a --set-filename, use it (it's already UTF8). If there is a
regular filename, UTF8-ize it if necessary. If there is no
filenames at all, set the field empty. */
PKT_plaintext *
setup_plaintext_name (const char *filename, IOBUF iobuf)
{
PKT_plaintext *pt;
if ((filename && !iobuf_is_pipe_filename (filename))
|| (opt.set_filename && !iobuf_is_pipe_filename (opt.set_filename)))
{
char *s;
if (opt.set_filename)
s = make_basename (opt.set_filename, iobuf_get_real_fname (iobuf));
else if (filename && !opt.flags.utf8_filename)
{
char *tmp = native_to_utf8 (filename);
s = make_basename (tmp, iobuf_get_real_fname (iobuf));
xfree (tmp);
}
else
s = make_basename (filename, iobuf_get_real_fname (iobuf));
pt = xmalloc (sizeof *pt + strlen (s) - 1);
pt->namelen = strlen (s);
memcpy (pt->name, s, pt->namelen);
xfree (s);
}
else
{
/* no filename */
pt = xmalloc (sizeof *pt - 1);
pt->namelen = 0;
}
return pt;
}
diff --git a/g10/pubkey-enc.c b/g10/pubkey-enc.c
index 055c39b8f..fb1b17143 100644
--- a/g10/pubkey-enc.c
+++ b/g10/pubkey-enc.c
@@ -1,500 +1,514 @@
/* pubkey-enc.c - Process a public key encoded packet.
* Copyright (C) 1998, 1999, 2000, 2001, 2002, 2006, 2009,
* 2010 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "gpg.h"
#include "../common/util.h"
#include "packet.h"
#include "keydb.h"
#include "trustdb.h"
#include "../common/status.h"
#include "options.h"
#include "main.h"
#include "../common/i18n.h"
#include "pkglue.h"
#include "call-agent.h"
#include "../common/host2net.h"
#include "../common/compliance.h"
static gpg_error_t get_it (ctrl_t ctrl, struct pubkey_enc_list *k,
DEK *dek, PKT_public_key *sk, u32 *keyid);
/* Check that the given algo is mentioned in one of the valid user-ids. */
static int
is_algo_in_prefs (kbnode_t keyblock, preftype_t type, int algo)
{
kbnode_t k;
for (k = keyblock; k; k = k->next)
{
if (k->pkt->pkttype == PKT_USER_ID)
{
PKT_user_id *uid = k->pkt->pkt.user_id;
prefitem_t *prefs = uid->prefs;
if (uid->created && prefs && !uid->flags.revoked && !uid->flags.expired)
{
for (; prefs->type; prefs++)
if (prefs->type == type && prefs->value == algo)
return 1;
}
}
}
return 0;
}
/*
* Get the session key from a pubkey enc packet and return it in DEK,
* which should have been allocated in secure memory by the caller.
*/
gpg_error_t
get_session_key (ctrl_t ctrl, struct pubkey_enc_list *list, DEK *dek)
{
PKT_public_key *sk = NULL;
- int rc;
+ gpg_error_t err;
void *enum_context = NULL;
u32 keyid[2];
int search_for_secret_keys = 1;
+ struct pubkey_enc_list *k;
if (DBG_CLOCK)
log_clock ("get_session_key enter");
while (search_for_secret_keys)
{
- struct pubkey_enc_list *k;
-
sk = xmalloc_clear (sizeof *sk);
- rc = enum_secret_keys (ctrl, &enum_context, sk);
- if (rc)
- {
- rc = GPG_ERR_NO_SECKEY;
- break;
- }
+ err = enum_secret_keys (ctrl, &enum_context, sk);
+ if (err)
+ break;
if (!(sk->pubkey_usage & PUBKEY_USAGE_ENC))
continue;
/* Check compliance. */
if (! gnupg_pk_is_allowed (opt.compliance, PK_USE_DECRYPTION,
sk->pubkey_algo,
sk->pkey, nbits_from_pk (sk), NULL))
{
log_info (_("key %s is not suitable for decryption"
" in %s mode\n"),
keystr_from_pk (sk),
gnupg_compliance_option_string (opt.compliance));
continue;
}
/* FIXME: The list needs to be sorted so that we try the keys in
* an appropriate order. For example:
* - On-disk keys w/o protection
* - On-disk keys with a cached passphrase
* - On-card keys of an active card
* - On-disk keys with protection
* - On-card keys from cards which are not plugged it. Here a
* cancel-all button should stop asking for other cards.
* Without any anonymous keys the sorting can be skipped.
*/
for (k = list; k; k = k->next)
{
if (!(k->pubkey_algo == PUBKEY_ALGO_ELGAMAL_E
|| k->pubkey_algo == PUBKEY_ALGO_ECDH
|| k->pubkey_algo == PUBKEY_ALGO_RSA
|| k->pubkey_algo == PUBKEY_ALGO_RSA_E
|| k->pubkey_algo == PUBKEY_ALGO_ELGAMAL))
continue;
if (openpgp_pk_test_algo2 (k->pubkey_algo, PUBKEY_USAGE_ENC))
continue;
- k->result = GPG_ERR_NO_SECKEY;
-
if (sk->pubkey_algo != k->pubkey_algo)
continue;
keyid_from_pk (sk, keyid);
if (!k->keyid[0] && !k->keyid[1])
{
if (opt.skip_hidden_recipients)
continue;
if (!opt.quiet)
log_info (_("anonymous recipient; trying secret key %s ...\n"),
keystr (keyid));
}
else if (opt.try_all_secrets
|| (k->keyid[0] == keyid[0] && k->keyid[1] == keyid[1]))
;
else
continue;
- rc = get_it (ctrl, k, dek, sk, keyid);
- if (!rc)
+ err = get_it (ctrl, k, dek, sk, keyid);
+ k->result = err;
+ if (!err)
{
- k->result = 0;
if (!opt.quiet && !k->keyid[0] && !k->keyid[1])
log_info (_("okay, we are the anonymous recipient.\n"));
search_for_secret_keys = 0;
break;
}
- else if (gpg_err_code (rc) == GPG_ERR_FULLY_CANCELED)
+ else if (gpg_err_code (err) == GPG_ERR_FULLY_CANCELED)
{
search_for_secret_keys = 0;
break; /* Don't try any more secret keys. */
}
}
}
enum_secret_keys (ctrl, &enum_context, NULL); /* free context */
+ if (gpg_err_code (err) == GPG_ERR_EOF)
+ {
+ err = gpg_error (GPG_ERR_NO_SECKEY);
+
+ /* Return the last specific error, if any. */
+ for (k = list; k; k = k->next)
+ if (k->result != -1)
+ err = k->result;
+ }
+
if (DBG_CLOCK)
log_clock ("get_session_key leave");
- return rc;
+ return err;
}
static gpg_error_t
get_it (ctrl_t ctrl,
struct pubkey_enc_list *enc, DEK *dek, PKT_public_key *sk, u32 *keyid)
{
gpg_error_t err;
byte *frame = NULL;
unsigned int n;
size_t nframe;
u16 csum, csum2;
int padding;
gcry_sexp_t s_data;
char *desc;
char *keygrip;
byte fp[MAX_FINGERPRINT_LEN];
size_t fpn;
if (DBG_CLOCK)
log_clock ("decryption start");
/* Get the keygrip. */
err = hexkeygrip_from_pk (sk, &keygrip);
if (err)
goto leave;
/* Convert the data to an S-expression. */
if (sk->pubkey_algo == PUBKEY_ALGO_ELGAMAL
|| sk->pubkey_algo == PUBKEY_ALGO_ELGAMAL_E)
{
if (!enc->data[0] || !enc->data[1])
err = gpg_error (GPG_ERR_BAD_MPI);
else
err = gcry_sexp_build (&s_data, NULL, "(enc-val(elg(a%m)(b%m)))",
enc->data[0], enc->data[1]);
}
else if (sk->pubkey_algo == PUBKEY_ALGO_RSA
|| sk->pubkey_algo == PUBKEY_ALGO_RSA_E)
{
if (!enc->data[0])
err = gpg_error (GPG_ERR_BAD_MPI);
else
err = gcry_sexp_build (&s_data, NULL, "(enc-val(rsa(a%m)))",
enc->data[0]);
}
else if (sk->pubkey_algo == PUBKEY_ALGO_ECDH)
{
if (!enc->data[0] || !enc->data[1])
err = gpg_error (GPG_ERR_BAD_MPI);
else
err = gcry_sexp_build (&s_data, NULL, "(enc-val(ecdh(s%m)(e%m)))",
enc->data[1], enc->data[0]);
}
else
err = gpg_error (GPG_ERR_BUG);
if (err)
goto leave;
if (sk->pubkey_algo == PUBKEY_ALGO_ECDH)
{
fingerprint_from_pk (sk, fp, &fpn);
log_assert (fpn == 20);
}
/* Decrypt. */
desc = gpg_format_keydesc (ctrl, sk, FORMAT_KEYDESC_NORMAL, 1);
err = agent_pkdecrypt (NULL, keygrip,
desc, sk->keyid, sk->main_keyid, sk->pubkey_algo,
s_data, &frame, &nframe, &padding);
xfree (desc);
gcry_sexp_release (s_data);
if (err)
goto leave;
/* Now get the DEK (data encryption key) from the frame
*
* Old versions encode the DEK in this format (msb is left):
*
* 0 1 DEK(16 bytes) CSUM(2 bytes) 0 RND(n bytes) 2
*
* Later versions encode the DEK like this:
*
* 0 2 RND(n bytes) 0 A DEK(k bytes) CSUM(2 bytes)
*
* (mpi_get_buffer already removed the leading zero).
*
* RND are non-zero randow bytes.
* A is the cipher algorithm
* DEK is the encryption key (session key) with length k
* CSUM
*/
if (DBG_CRYPTO)
log_printhex (frame, nframe, "DEK frame:");
n = 0;
if (sk->pubkey_algo == PUBKEY_ALGO_ECDH)
{
gcry_mpi_t shared_mpi;
gcry_mpi_t decoded;
/* At the beginning the frame are the bytes of shared point MPI. */
err = gcry_mpi_scan (&shared_mpi, GCRYMPI_FMT_USG, frame, nframe, NULL);
if (err)
{
err = gpg_error (GPG_ERR_WRONG_SECKEY);
goto leave;
}
err = pk_ecdh_decrypt (&decoded, fp, enc->data[1]/*encr data as an MPI*/,
shared_mpi, sk->pkey);
mpi_release (shared_mpi);
if(err)
goto leave;
xfree (frame);
err = gcry_mpi_aprint (GCRYMPI_FMT_USG, &frame, &nframe, decoded);
mpi_release (decoded);
if (err)
goto leave;
/* Now the frame are the bytes decrypted but padded session key. */
/* Allow double padding for the benefit of DEK size concealment.
Higher than this is wasteful. */
if (!nframe || frame[nframe-1] > 8*2 || nframe <= 8
|| frame[nframe-1] > nframe)
{
err = gpg_error (GPG_ERR_WRONG_SECKEY);
goto leave;
}
nframe -= frame[nframe-1]; /* Remove padding. */
log_assert (!n); /* (used just below) */
}
else
{
if (padding)
{
if (n + 7 > nframe)
{
err = gpg_error (GPG_ERR_WRONG_SECKEY);
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 right way and
+ * therefore we have to skip the zero. This should be fixed
+ * in gpg-agent of course. */
+ if (!frame[n])
+ n++;
+
if (frame[n] == 1 && frame[nframe - 1] == 2)
{
log_info (_("old encoding of the DEK is not supported\n"));
err = gpg_error (GPG_ERR_CIPHER_ALGO);
goto leave;
}
if (frame[n] != 2) /* Something went wrong. */
{
err = gpg_error (GPG_ERR_WRONG_SECKEY);
goto leave;
}
for (n++; n < nframe && frame[n]; n++) /* Skip the random bytes. */
;
n++; /* Skip the zero byte. */
}
}
if (n + 4 > nframe)
{
err = gpg_error (GPG_ERR_WRONG_SECKEY);
goto leave;
}
dek->keylen = nframe - (n + 1) - 2;
dek->algo = frame[n++];
err = openpgp_cipher_test_algo (dek->algo);
if (err)
{
if (!opt.quiet && gpg_err_code (err) == GPG_ERR_CIPHER_ALGO)
{
log_info (_("cipher algorithm %d%s is unknown or disabled\n"),
dek->algo,
dek->algo == CIPHER_ALGO_IDEA ? " (IDEA)" : "");
}
dek->algo = 0;
goto leave;
}
if (dek->keylen != openpgp_cipher_get_algo_keylen (dek->algo))
{
err = gpg_error (GPG_ERR_WRONG_SECKEY);
goto leave;
}
/* Copy the key to DEK and compare the checksum. */
csum = buf16_to_u16 (frame+nframe-2);
memcpy (dek->key, frame + n, dek->keylen);
for (csum2 = 0, n = 0; n < dek->keylen; n++)
csum2 += dek->key[n];
if (csum != csum2)
{
err = gpg_error (GPG_ERR_WRONG_SECKEY);
goto leave;
}
if (DBG_CLOCK)
log_clock ("decryption ready");
if (DBG_CRYPTO)
log_printhex (dek->key, dek->keylen, "DEK is:");
/* Check that the algo is in the preferences and whether it has
* expired. Also print a status line with the key's fingerprint. */
{
PKT_public_key *pk = NULL;
PKT_public_key *mainpk = NULL;
KBNODE pkb = get_pubkeyblock (ctrl, keyid);
if (!pkb)
{
err = -1;
log_error ("oops: public key not found for preference check\n");
}
else if (pkb->pkt->pkt.public_key->selfsigversion > 3
&& dek->algo != CIPHER_ALGO_3DES
&& !opt.quiet
&& !is_algo_in_prefs (pkb, PREFTYPE_SYM, dek->algo))
log_info (_("WARNING: cipher algorithm %s not found in recipient"
" preferences\n"), openpgp_cipher_algo_name (dek->algo));
if (!err)
{
kbnode_t k;
int first = 1;
for (k = pkb; k; k = k->next)
{
if (k->pkt->pkttype == PKT_PUBLIC_KEY
|| k->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
u32 aki[2];
if (first)
{
first = 0;
mainpk = k->pkt->pkt.public_key;
}
keyid_from_pk (k->pkt->pkt.public_key, aki);
if (aki[0] == keyid[0] && aki[1] == keyid[1])
{
pk = k->pkt->pkt.public_key;
break;
}
}
}
if (!pk)
BUG ();
if (pk->expiredate && pk->expiredate <= make_timestamp ())
{
log_info (_("Note: secret key %s expired at %s\n"),
keystr (keyid), asctimestamp (pk->expiredate));
}
}
if (pk && pk->flags.revoked)
{
log_info (_("Note: key has been revoked"));
log_printf ("\n");
show_revocation_reason (ctrl, pk, 1);
}
if (is_status_enabled () && pk && mainpk)
{
char pkhex[MAX_FINGERPRINT_LEN*2+1];
char mainpkhex[MAX_FINGERPRINT_LEN*2+1];
hexfingerprint (pk, pkhex, sizeof pkhex);
hexfingerprint (mainpk, mainpkhex, sizeof mainpkhex);
/* Note that we do not want to create a trustdb just for
* getting the ownertrust: If there is no trustdb there can't
* be ulitmately trusted key anyway and thus the ownertrust
* value is irrelevant. */
write_status_printf (STATUS_DECRYPTION_KEY, "%s %s %c",
pkhex, mainpkhex,
get_ownertrust_info (ctrl, mainpk, 1));
}
release_kbnode (pkb);
err = 0;
}
leave:
xfree (frame);
xfree (keygrip);
return err;
}
/*
* Get the session key from the given string.
* String is supposed to be formatted as this:
* <algo-id>:<even-number-of-hex-digits>
*/
gpg_error_t
get_override_session_key (DEK *dek, const char *string)
{
const char *s;
int i;
if (!string)
return GPG_ERR_BAD_KEY;
dek->algo = atoi (string);
if (dek->algo < 1)
return GPG_ERR_BAD_KEY;
if (!(s = strchr (string, ':')))
return GPG_ERR_BAD_KEY;
s++;
for (i = 0; i < DIM (dek->key) && *s; i++, s += 2)
{
int c = hextobyte (s);
if (c == -1)
return GPG_ERR_BAD_KEY;
dek->key[i] = c;
}
if (*s)
return GPG_ERR_BAD_KEY;
dek->keylen = i;
return 0;
}
diff --git a/g10/revoke.c b/g10/revoke.c
index e8ce3544c..0a93c31f9 100644
--- a/g10/revoke.c
+++ b/g10/revoke.c
@@ -1,904 +1,900 @@
/* revoke.c - Create recovation certificates.
* Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003,
* 2004 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#include "gpg.h"
#include "options.h"
#include "packet.h"
#include "../common/status.h"
#include "keydb.h"
#include "../common/util.h"
#include "main.h"
#include "../common/ttyio.h"
#include "../common/i18n.h"
#include "call-agent.h"
struct revocation_reason_info {
int code;
char *desc;
};
int
revocation_reason_build_cb( PKT_signature *sig, void *opaque )
{
struct revocation_reason_info *reason = opaque;
char *ud = NULL;
byte *buffer;
size_t buflen = 1;
if(!reason)
return 0;
if( reason->desc ) {
ud = native_to_utf8( reason->desc );
buflen += strlen(ud);
}
buffer = xmalloc( buflen );
*buffer = reason->code;
if( ud ) {
memcpy(buffer+1, ud, strlen(ud) );
xfree( ud );
}
build_sig_subpkt( sig, SIGSUBPKT_REVOC_REASON, buffer, buflen );
xfree( buffer );
return 0;
}
/* Outputs a minimal pk (as defined by 2440) from a keyblock. A
minimal pk consists of the public key packet and a user ID. We try
and pick a user ID that has a uid signature, and include it if
possible. */
static int
export_minimal_pk(IOBUF out,KBNODE keyblock,
PKT_signature *revsig,PKT_signature *revkey)
{
KBNODE node;
PACKET pkt;
PKT_user_id *uid=NULL;
PKT_signature *selfsig=NULL;
u32 keyid[2];
int rc;
node=find_kbnode(keyblock,PKT_PUBLIC_KEY);
if(!node)
{
log_error("key incomplete\n");
return GPG_ERR_GENERAL;
}
keyid_from_pk(node->pkt->pkt.public_key,keyid);
pkt=*node->pkt;
rc=build_packet(out,&pkt);
if(rc)
{
log_error(_("build_packet failed: %s\n"), gpg_strerror (rc) );
return rc;
}
init_packet(&pkt);
pkt.pkttype=PKT_SIGNATURE;
/* the revocation itself, if any. 2440 likes this to come first. */
if(revsig)
{
pkt.pkt.signature=revsig;
rc=build_packet(out,&pkt);
if(rc)
{
log_error("build_packet failed: %s\n", gpg_strerror (rc) );
return rc;
}
}
/* If a revkey in a 1F sig is present, include it too */
if(revkey)
{
pkt.pkt.signature=revkey;
rc=build_packet(out,&pkt);
if(rc)
{
log_error(_("build_packet failed: %s\n"), gpg_strerror (rc) );
return rc;
}
}
while(!selfsig)
{
KBNODE signode;
node=find_next_kbnode(node,PKT_USER_ID);
if(!node)
{
/* We're out of user IDs - none were self-signed. */
if(uid)
break;
else
{
log_error(_("key %s has no user IDs\n"),keystr(keyid));
return GPG_ERR_GENERAL;
}
}
if(node->pkt->pkt.user_id->attrib_data)
continue;
uid=node->pkt->pkt.user_id;
signode=node;
while((signode=find_next_kbnode(signode,PKT_SIGNATURE)))
{
if(keyid[0]==signode->pkt->pkt.signature->keyid[0] &&
keyid[1]==signode->pkt->pkt.signature->keyid[1] &&
IS_UID_SIG(signode->pkt->pkt.signature))
{
selfsig=signode->pkt->pkt.signature;
break;
}
}
}
pkt.pkttype=PKT_USER_ID;
pkt.pkt.user_id=uid;
rc=build_packet(out,&pkt);
if(rc)
{
log_error(_("build_packet failed: %s\n"), gpg_strerror (rc) );
return rc;
}
if(selfsig)
{
pkt.pkttype=PKT_SIGNATURE;
pkt.pkt.signature=selfsig;
rc=build_packet(out,&pkt);
if(rc)
{
log_error(_("build_packet failed: %s\n"), gpg_strerror (rc) );
return rc;
}
}
return 0;
}
/****************
* Generate a revocation certificate for UNAME via a designated revoker
*/
int
gen_desig_revoke (ctrl_t ctrl, const char *uname, strlist_t locusr)
{
int rc = 0;
armor_filter_context_t *afx;
PKT_public_key *pk = NULL;
PKT_public_key *pk2 = NULL;
PKT_signature *sig = NULL;
IOBUF out = NULL;
struct revocation_reason_info *reason = NULL;
KEYDB_HANDLE kdbhd;
KEYDB_SEARCH_DESC desc;
KBNODE keyblock=NULL,node;
u32 keyid[2];
int i,any=0;
SK_LIST sk_list=NULL;
if( opt.batch )
{
log_error(_("can't do this in batch mode\n"));
return GPG_ERR_GENERAL;
}
afx = new_armor_context ();
kdbhd = keydb_new ();
if (!kdbhd)
{
rc = gpg_error_from_syserror ();
goto leave;
}
rc = classify_user_id (uname, &desc, 1);
if (!rc)
rc = keydb_search (kdbhd, &desc, 1, NULL);
if (rc) {
log_error (_("key \"%s\" not found: %s\n"),uname, gpg_strerror (rc));
goto leave;
}
rc = keydb_get_keyblock (kdbhd, &keyblock );
if( rc ) {
log_error (_("error reading keyblock: %s\n"), gpg_strerror (rc) );
goto leave;
}
/* To parse the revkeys */
merge_keys_and_selfsig (ctrl, keyblock);
/* get the key from the keyblock */
node = find_kbnode( keyblock, PKT_PUBLIC_KEY );
if( !node )
BUG ();
pk=node->pkt->pkt.public_key;
keyid_from_pk(pk,keyid);
if(locusr)
{
rc = build_sk_list (ctrl, locusr, &sk_list, PUBKEY_USAGE_CERT);
if(rc)
goto leave;
}
/* Are we a designated revoker for this key? */
if(!pk->revkey && pk->numrevkeys)
BUG();
for(i=0;i<pk->numrevkeys;i++)
{
SK_LIST list;
free_public_key (pk2);
pk2 = NULL;
if(sk_list)
{
for(list=sk_list;list;list=list->next)
{
byte fpr[MAX_FINGERPRINT_LEN];
size_t fprlen;
fingerprint_from_pk (list->pk, fpr, &fprlen);
/* Don't get involved with keys that don't have a v4
* or v5 fingerprint */
if (fprlen != 20 && fprlen != 32)
continue;
if (!memcmp(fpr,pk->revkey[i].fpr, fprlen))
break;
}
if (list)
pk2 = copy_public_key (NULL, list->pk);
else
continue;
}
else
{
pk2 = xmalloc_clear (sizeof *pk2);
rc = get_pubkey_byfprint (ctrl, pk2, NULL,
pk->revkey[i].fpr, pk->revkey[i].fprlen);
}
/* We have the revocation key. */
if(!rc)
{
PKT_signature *revkey = NULL;
any = 1;
- print_pubkey_info (ctrl, NULL, pk);
+ print_key_info (ctrl, NULL, 0, pk, 0);
tty_printf ("\n");
tty_printf (_("To be revoked by:\n"));
- print_seckey_info (ctrl, pk2);
+ print_key_info (ctrl, NULL, 0, pk2, 1);
if(pk->revkey[i].class&0x40)
tty_printf(_("(This is a sensitive revocation key)\n"));
tty_printf("\n");
rc = agent_probe_secret_key (ctrl, pk2);
if (rc)
{
tty_printf (_("Secret key is not available.\n"));
continue;
}
if( !cpr_get_answer_is_yes("gen_desig_revoke.okay",
_("Create a designated revocation certificate for this key? (y/N) ")))
continue;
/* get the reason for the revocation (this is always v4) */
reason = ask_revocation_reason( 1, 0, 1 );
if( !reason )
continue;
if( !opt.armor )
tty_printf(_("ASCII armored output forced.\n"));
if( (rc = open_outfile (-1, NULL, 0, 1, &out )) )
goto leave;
afx->what = 1;
afx->hdrlines = "Comment: A designated revocation certificate"
" should follow\n";
push_armor_filter (afx, out);
/* create it */
- rc = make_keysig_packet (ctrl, &sig, pk, NULL, NULL, pk2, 0x20, 0,
+ rc = make_keysig_packet (ctrl, &sig, pk, NULL, NULL, pk2, 0x20,
0, 0,
revocation_reason_build_cb, reason,
NULL);
if( rc ) {
log_error(_("make_keysig_packet failed: %s\n"), gpg_strerror (rc));
goto leave;
}
/* Spit out a minimal pk as well, since otherwise there is
no way to know which key to attach this revocation to.
Also include the direct key signature that contains
this revocation key. We're allowed to include
sensitive revocation keys along with a revocation, as
this may be the only time the recipient has seen it.
Note that this means that if we have multiple different
sensitive revocation keys in a given direct key
signature, we're going to include them all here. This
is annoying, but the good outweighs the bad, since
without including this a sensitive revoker can't really
do their job. People should not include multiple
sensitive revocation keys in one signature: 2440 says
"Note that it may be appropriate to isolate this
subpacket within a separate signature so that it is not
combined with other subpackets that need to be
exported." -dms */
while(!revkey)
{
KBNODE signode;
signode=find_next_kbnode(node,PKT_SIGNATURE);
if(!signode)
break;
node=signode;
if(keyid[0]==signode->pkt->pkt.signature->keyid[0] &&
keyid[1]==signode->pkt->pkt.signature->keyid[1] &&
IS_KEY_SIG(signode->pkt->pkt.signature))
{
int j;
for(j=0;j<signode->pkt->pkt.signature->numrevkeys;j++)
{
if (pk->revkey[i].class
== signode->pkt->pkt.signature->revkey[j].class
&& pk->revkey[i].algid
== signode->pkt->pkt.signature->revkey[j].algid
&& pk->revkey[i].fprlen
== signode->pkt->pkt.signature->revkey[j].fprlen
&& !memcmp
(pk->revkey[i].fpr,
signode->pkt->pkt.signature->revkey[j].fpr,
pk->revkey[i].fprlen))
{
revkey = signode->pkt->pkt.signature;
break;
}
}
}
}
if(!revkey)
BUG();
rc=export_minimal_pk(out,keyblock,sig,revkey);
if(rc)
goto leave;
/* and issue a usage notice */
tty_printf(_("Revocation certificate created.\n"));
break;
}
}
if(!any)
log_error(_("no revocation keys found for \"%s\"\n"),uname);
leave:
free_public_key (pk);
free_public_key (pk2);
if( sig )
free_seckey_enc( sig );
release_sk_list(sk_list);
if( rc )
iobuf_cancel(out);
else
iobuf_close(out);
release_revocation_reason_info( reason );
release_armor_context (afx);
return rc;
}
/* Common core to create the revocation. FILENAME may be NULL to write
to stdout or the filename given by --output. REASON describes the
revocation reason. PSK is the public primary key - we expect that
a corresponding secret key is available. KEYBLOCK is the entire
KEYBLOCK which is used in PGP mode to write a minimal key and not
just the naked revocation signature; it may be NULL. If LEADINTEXT
is not NULL, it is written right before the (armored) output.*/
static int
create_revocation (ctrl_t ctrl,
const char *filename,
struct revocation_reason_info *reason,
PKT_public_key *psk,
kbnode_t keyblock,
const char *leadintext, int suffix,
const char *cache_nonce)
{
int rc;
iobuf_t out = NULL;
armor_filter_context_t *afx;
PKT_signature *sig = NULL;
PACKET pkt;
afx = new_armor_context ();
if ((rc = open_outfile (-1, filename, suffix, 1, &out)))
goto leave;
if (leadintext )
iobuf_writestr (out, leadintext);
afx->what = 1;
afx->hdrlines = "Comment: This is a revocation certificate\n";
push_armor_filter (afx, out);
- rc = make_keysig_packet (ctrl, &sig, psk, NULL, NULL, psk, 0x20, 0,
+ rc = make_keysig_packet (ctrl, &sig, psk, NULL, NULL, psk, 0x20,
0, 0,
revocation_reason_build_cb, reason, cache_nonce);
if (rc)
{
log_error (_("make_keysig_packet failed: %s\n"), gpg_strerror (rc));
goto leave;
}
if (keyblock && (PGP7 || PGP8))
{
/* Use a minimal pk for PGPx mode, since PGP can't import bare
revocation certificates. */
rc = export_minimal_pk (out, keyblock, sig, NULL);
if (rc)
goto leave;
}
else
{
init_packet (&pkt);
pkt.pkttype = PKT_SIGNATURE;
pkt.pkt.signature = sig;
rc = build_packet (out, &pkt);
if (rc)
{
log_error (_("build_packet failed: %s\n"), gpg_strerror (rc));
goto leave;
}
}
leave:
if (sig)
free_seckey_enc (sig);
if (rc)
iobuf_cancel (out);
else
iobuf_close (out);
release_armor_context (afx);
return rc;
}
/* This function is used to generate a standard revocation certificate
by gpg's interactive key generation function. The certificate is
stored at a dedicated place in a slightly modified form to avoid an
accidental import. PSK is the primary key; a corresponding secret
key must be available. CACHE_NONCE is optional but can be used to
help gpg-agent to avoid an extra passphrase prompt. */
int
gen_standard_revoke (ctrl_t ctrl, PKT_public_key *psk, const char *cache_nonce)
{
int rc;
estream_t memfp;
struct revocation_reason_info reason;
char *dir, *tmpstr, *fname;
void *leadin;
size_t len;
u32 keyid[2];
int kl;
char *orig_codeset;
char *old_outfile;
dir = get_openpgp_revocdir (gnupg_homedir ());
tmpstr = hexfingerprint (psk, NULL, 0);
if (!tmpstr)
{
rc = gpg_error_from_syserror ();
xfree (dir);
return rc;
}
fname = strconcat (dir, DIRSEP_S, tmpstr, NULL);
if (!fname)
{
rc = gpg_error_from_syserror ();
xfree (tmpstr);
xfree (dir);
return rc;
}
xfree (tmpstr);
xfree (dir);
keyid_from_pk (psk, keyid);
memfp = es_fopenmem (0, "r+");
if (!memfp)
log_fatal ("error creating memory stream\n");
orig_codeset = i18n_switchto_utf8 ();
es_fprintf (memfp, "%s\n\n",
_("This is a revocation certificate for the OpenPGP key:"));
print_key_line (ctrl, memfp, psk, 0);
if (opt.keyid_format != KF_NONE)
print_fingerprint (ctrl, memfp, psk, 3);
kl = opt.keyid_format == KF_NONE? 0 : keystrlen ();
tmpstr = get_user_id (ctrl, keyid, &len, NULL);
es_fprintf (memfp, "uid%*s%.*s\n\n",
kl + 10, "",
(int)len, tmpstr);
xfree (tmpstr);
es_fprintf (memfp, "%s\n\n%s\n\n%s\n\n:",
_("A revocation certificate is a kind of \"kill switch\" to publicly\n"
"declare that a key shall not anymore be used. It is not possible\n"
"to retract such a revocation certificate once it has been published."),
_("Use it to revoke this key in case of a compromise or loss of\n"
"the secret key. However, if the secret key is still accessible,\n"
"it is better to generate a new revocation certificate and give\n"
"a reason for the revocation. For details see the description of\n"
"of the gpg command \"--generate-revocation\" in the "
"GnuPG manual."),
_("To avoid an accidental use of this file, a colon has been inserted\n"
"before the 5 dashes below. Remove this colon with a text editor\n"
"before importing and publishing this revocation certificate."));
es_putc (0, memfp);
i18n_switchback (orig_codeset);
if (es_fclose_snatch (memfp, &leadin, NULL))
log_fatal ("error snatching memory stream\n");
reason.code = 0x00; /* No particular reason. */
reason.desc = NULL;
old_outfile = opt.outfile;
opt.outfile = NULL;
rc = create_revocation (ctrl,
fname, &reason, psk, NULL, leadin, 3, cache_nonce);
opt.outfile = old_outfile;
if (!rc && !opt.quiet)
log_info (_("revocation certificate stored as '%s.rev'\n"), fname);
xfree (leadin);
xfree (fname);
return rc;
}
/****************
* Generate a revocation certificate for UNAME
*/
int
gen_revoke (ctrl_t ctrl, const char *uname)
{
int rc = 0;
PKT_public_key *psk;
u32 keyid[2];
kbnode_t keyblock = NULL;
kbnode_t node;
KEYDB_HANDLE kdbhd;
struct revocation_reason_info *reason = NULL;
KEYDB_SEARCH_DESC desc;
if( opt.batch )
{
log_error(_("can't do this in batch mode\n"));
return GPG_ERR_GENERAL;
}
/* Search the userid; we don't want the whole getkey stuff here. */
kdbhd = keydb_new ();
if (!kdbhd)
{
rc = gpg_error_from_syserror ();
goto leave;
}
rc = classify_user_id (uname, &desc, 1);
if (!rc)
rc = keydb_search (kdbhd, &desc, 1, NULL);
if (rc)
{
if (gpg_err_code (rc) == GPG_ERR_NOT_FOUND)
log_error (_("secret key \"%s\" not found\n"), uname);
else
log_error (_("secret key \"%s\" not found: %s\n"),
uname, gpg_strerror (rc));
goto leave;
}
rc = keydb_get_keyblock (kdbhd, &keyblock );
if (rc)
{
log_error (_("error reading keyblock: %s\n"), gpg_strerror (rc) );
goto leave;
}
rc = keydb_search (kdbhd, &desc, 1, NULL);
if (gpg_err_code (rc) == GPG_ERR_NOT_FOUND)
- /* Not ambiguous. */
{
+ /* Not ambiguous. */
}
else if (rc == 0)
- /* Ambiguous. */
{
- char *info;
-
+ /* Ambiguous. */
/* TRANSLATORS: The %s prints a key specification which
for example has been given at the command line. Several lines
lines with secret key infos are printed after this message. */
log_error (_("'%s' matches multiple secret keys:\n"), uname);
- info = format_seckey_info (ctrl, keyblock->pkt->pkt.public_key);
- log_error (" %s\n", info);
- xfree (info);
+ print_key_info_log (ctrl, GPGRT_LOGLVL_ERROR, 2,
+ keyblock->pkt->pkt.public_key, 1);
release_kbnode (keyblock);
rc = keydb_get_keyblock (kdbhd, &keyblock);
while (! rc)
{
- info = format_seckey_info (ctrl, keyblock->pkt->pkt.public_key);
- log_info (" %s\n", info);
- xfree (info);
+ print_key_info_log (ctrl, GPGRT_LOGLVL_INFO, 2,
+ keyblock->pkt->pkt.public_key, 1);
release_kbnode (keyblock);
keyblock = NULL;
rc = keydb_search (kdbhd, &desc, 1, NULL);
if (! rc)
rc = keydb_get_keyblock (kdbhd, &keyblock);
}
rc = GPG_ERR_AMBIGUOUS_NAME;
goto leave;
}
else
{
log_error (_("error searching the keyring: %s\n"), gpg_strerror (rc));
goto leave;
}
/* Get the keyid from the keyblock. */
node = find_kbnode (keyblock, PKT_PUBLIC_KEY);
if (!node)
BUG ();
psk = node->pkt->pkt.public_key;
rc = agent_probe_secret_key (NULL, psk);
if (rc)
{
log_error (_("secret key \"%s\" not found: %s\n"),
uname, gpg_strerror (rc));
goto leave;
}
keyid_from_pk (psk, keyid );
- print_seckey_info (ctrl, psk);
+ print_key_info (ctrl, NULL, 0, psk, 1);
tty_printf("\n");
if (!cpr_get_answer_is_yes ("gen_revoke.okay",
_("Create a revocation certificate for this key? (y/N) ")))
{
rc = 0;
goto leave;
}
/* Get the reason for the revocation. */
reason = ask_revocation_reason (1, 0, 1);
if (!reason)
{
/* User decided to cancel. */
rc = 0;
goto leave;
}
if (!opt.armor)
tty_printf (_("ASCII armored output forced.\n"));
rc = create_revocation (ctrl, NULL, reason, psk, keyblock, NULL, 0, NULL);
if (rc)
goto leave;
/* and issue a usage notice */
tty_printf (_(
"Revocation certificate created.\n\n"
"Please move it to a medium which you can hide away; if Mallory gets\n"
"access to this certificate he can use it to make your key unusable.\n"
"It is smart to print this certificate and store it away, just in case\n"
"your media become unreadable. But have some caution: The print system of\n"
"your machine might store the data and make it available to others!\n"));
leave:
release_kbnode (keyblock);
keydb_release (kdbhd);
release_revocation_reason_info( reason );
return rc;
}
struct revocation_reason_info *
ask_revocation_reason( int key_rev, int cert_rev, int hint )
{
int code=-1;
char *description = NULL;
struct revocation_reason_info *reason;
const char *text_0 = _("No reason specified");
const char *text_1 = _("Key has been compromised");
const char *text_2 = _("Key is superseded");
const char *text_3 = _("Key is no longer used");
const char *text_4 = _("User ID is no longer valid");
const char *code_text = NULL;
do {
code=-1;
xfree(description);
description = NULL;
tty_printf(_("Please select the reason for the revocation:\n"));
tty_printf( " 0 = %s\n", text_0 );
if( key_rev )
tty_printf(" 1 = %s\n", text_1 );
if( key_rev )
tty_printf(" 2 = %s\n", text_2 );
if( key_rev )
tty_printf(" 3 = %s\n", text_3 );
if( cert_rev )
tty_printf(" 4 = %s\n", text_4 );
tty_printf( " Q = %s\n", _("Cancel") );
if( hint )
tty_printf(_("(Probably you want to select %d here)\n"), hint );
while(code==-1) {
int n;
char *answer = cpr_get("ask_revocation_reason.code",
_("Your decision? "));
trim_spaces( answer );
cpr_kill_prompt();
if( *answer == 'q' || *answer == 'Q')
return NULL; /* cancel */
if( hint && !*answer )
n = hint;
else if(!digitp( answer ) )
n = -1;
else
n = atoi(answer);
xfree(answer);
if( n == 0 ) {
code = 0x00; /* no particular reason */
code_text = text_0;
}
else if( key_rev && n == 1 ) {
code = 0x02; /* key has been compromised */
code_text = text_1;
}
else if( key_rev && n == 2 ) {
code = 0x01; /* key is superseded */
code_text = text_2;
}
else if( key_rev && n == 3 ) {
code = 0x03; /* key is no longer used */
code_text = text_3;
}
else if( cert_rev && n == 4 ) {
code = 0x20; /* uid is no longer valid */
code_text = text_4;
}
else
tty_printf(_("Invalid selection.\n"));
}
tty_printf(_("Enter an optional description; "
"end it with an empty line:\n") );
for(;;) {
char *answer = cpr_get("ask_revocation_reason.text", "> " );
trim_trailing_ws( answer, strlen(answer) );
cpr_kill_prompt();
if( !*answer ) {
xfree(answer);
break;
}
{
char *p = make_printable_string( answer, strlen(answer), 0 );
xfree(answer);
answer = p;
}
if( !description )
description = xstrdup(answer);
else {
char *p = xmalloc( strlen(description) + strlen(answer) + 2 );
strcpy(stpcpy(stpcpy( p, description),"\n"),answer);
xfree(description);
description = p;
}
xfree(answer);
}
tty_printf(_("Reason for revocation: %s\n"), code_text );
if( !description )
tty_printf(_("(No description given)\n") );
else
tty_printf("%s\n", description );
} while( !cpr_get_answer_is_yes("ask_revocation_reason.okay",
_("Is this okay? (y/N) ")) );
reason = xmalloc( sizeof *reason );
reason->code = code;
reason->desc = description;
return reason;
}
struct revocation_reason_info *
get_default_uid_revocation_reason(void)
{
struct revocation_reason_info *reason;
reason = xmalloc( sizeof *reason );
reason->code = 0x20; /* uid is no longer valid */
reason->desc = strdup(""); /* no text */
return reason;
}
void
release_revocation_reason_info( struct revocation_reason_info *reason )
{
if( reason ) {
xfree( reason->desc );
xfree( reason );
}
}
diff --git a/g10/sig-check.c b/g10/sig-check.c
index e7f97de65..4c172d692 100644
--- a/g10/sig-check.c
+++ b/g10/sig-check.c
@@ -1,1239 +1,1239 @@
/* sig-check.c - Check a signature
* Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003,
* 2004, 2006 Free Software Foundation, Inc.
* Copyright (C) 2015, 2016 g10 Code GmbH
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "gpg.h"
#include "../common/util.h"
#include "packet.h"
#include "keydb.h"
#include "main.h"
#include "../common/status.h"
#include "../common/i18n.h"
#include "options.h"
#include "pkglue.h"
#include "../common/compliance.h"
static int check_signature_end (PKT_public_key *pk, PKT_signature *sig,
gcry_md_hd_t digest,
const void *extrahash, size_t extrahashlen,
int *r_expired, int *r_revoked,
PKT_public_key *ret_pk);
static int check_signature_end_simple (PKT_public_key *pk, PKT_signature *sig,
gcry_md_hd_t digest,
const void *extrahash,
size_t extrahashlen);
/* Statistics for signature verification. */
struct
{
unsigned int total; /* Total number of verifications. */
unsigned int cached; /* Number of seen cache entries. */
unsigned int goodsig;/* Number of good verifications from the cache. */
unsigned int badsig; /* Number of bad verifications from the cache. */
} cache_stats;
/* Dump verification stats. */
void
sig_check_dump_stats (void)
{
log_info ("sig_cache: total=%u cached=%u good=%u bad=%u\n",
cache_stats.total, cache_stats.cached,
cache_stats.goodsig, cache_stats.badsig);
}
/* Check a signature. This is shorthand for check_signature2 with
the unnamed arguments passed as NULL. */
int
check_signature (ctrl_t ctrl, PKT_signature *sig, gcry_md_hd_t digest)
{
return check_signature2 (ctrl, sig, digest, NULL, 0, NULL, NULL, NULL, NULL);
}
/* Check a signature.
*
* Looks up the public key that created the signature (SIG->KEYID)
* from the key db. Makes sure that the signature is valid (it was
* not created prior to the key, the public key was created in the
* past, and the signature does not include any unsupported critical
* features), finishes computing the hash of the signature data, and
* checks that the signature verifies the digest. If the key that
* generated the signature is a subkey, this function also verifies
* that there is a valid backsig from the subkey to the primary key.
* Finally, if status fd is enabled and the signature class is 0x00 or
* 0x01, then a STATUS_SIG_ID is emitted on the status fd.
*
* SIG is the signature to check.
*
* DIGEST contains a valid hash context that already includes the
* signed data. This function adds the relevant meta-data from the
* signature packet to compute the final hash. (See Section 5.2 of
* RFC 4880: "The concatenation of the data being signed and the
* signature data from the version number through the hashed subpacket
* data (inclusive) is hashed.")
*
* EXTRAHASH and EXTRAHASHLEN is additional data which is hashed with
* v5 signatures. They may be NULL to use the default.
*
* If R_EXPIREDATE is not NULL, R_EXPIREDATE is set to the key's
* expiry.
*
* If R_EXPIRED is not NULL, *R_EXPIRED is set to 1 if PK has expired
* (0 otherwise). Note: PK being expired does not cause this function
* to fail.
*
* If R_REVOKED is not NULL, *R_REVOKED is set to 1 if PK has been
* revoked (0 otherwise). Note: PK being revoked does not cause this
* function to fail.
*
* If R_PK is not NULL, the public key is stored at that address if it
* was found; other wise NULL is stored.
*
* Returns 0 on success. An error code otherwise. */
gpg_error_t
check_signature2 (ctrl_t ctrl,
PKT_signature *sig, gcry_md_hd_t digest,
const void *extrahash, size_t extrahashlen,
u32 *r_expiredate,
int *r_expired, int *r_revoked, PKT_public_key **r_pk)
{
int rc=0;
PKT_public_key *pk;
if (r_expiredate)
*r_expiredate = 0;
if (r_expired)
*r_expired = 0;
if (r_revoked)
*r_revoked = 0;
if (r_pk)
*r_pk = NULL;
pk = xtrycalloc (1, sizeof *pk);
if (!pk)
return gpg_error_from_syserror ();
if ((rc=openpgp_md_test_algo(sig->digest_algo)))
{
/* We don't have this digest. */
}
else if (!gnupg_digest_is_allowed (opt.compliance, 0, sig->digest_algo))
{
/* Compliance failure. */
log_info (_("digest algorithm '%s' may not be used in %s mode\n"),
gcry_md_algo_name (sig->digest_algo),
gnupg_compliance_option_string (opt.compliance));
rc = gpg_error (GPG_ERR_DIGEST_ALGO);
}
else if ((rc=openpgp_pk_test_algo(sig->pubkey_algo)))
{
/* We don't have this pubkey algo. */
}
else if (!gcry_md_is_enabled (digest,sig->digest_algo))
{
/* Sanity check that the md has a context for the hash that the
* sig is expecting. This can happen if a onepass sig header
* does not match the actual sig, and also if the clearsign
* "Hash:" header is missing or does not match the actual sig. */
log_info(_("WARNING: signature digest conflict in message\n"));
rc = gpg_error (GPG_ERR_GENERAL);
}
else if (get_pubkey_for_sig (ctrl, pk, sig))
rc = gpg_error (GPG_ERR_NO_PUBKEY);
else if (!gnupg_pk_is_allowed (opt.compliance, PK_USE_VERIFICATION,
pk->pubkey_algo, pk->pkey,
nbits_from_pk (pk),
NULL))
{
/* Compliance failure. */
log_error (_("key %s may not be used for signing in %s mode\n"),
keystr_from_pk (pk),
gnupg_compliance_option_string (opt.compliance));
rc = gpg_error (GPG_ERR_PUBKEY_ALGO);
}
else if (!pk->flags.valid)
{
/* You cannot have a good sig from an invalid key. */
rc = gpg_error (GPG_ERR_BAD_PUBKEY);
}
else
{
if (r_expiredate)
*r_expiredate = pk->expiredate;
rc = check_signature_end (pk, sig, digest, extrahash, extrahashlen,
r_expired, r_revoked, NULL);
/* Check the backsig. This is a back signature (0x19) from
* the subkey on the primary key. The idea here is that it
* should not be possible for someone to "steal" subkeys and
* claim them as their own. The attacker couldn't actually
* use the subkey, but they could try and claim ownership of
* any signatures issued by it. */
if (!rc && !pk->flags.primary && pk->flags.backsig < 2)
{
if (!pk->flags.backsig)
{
log_info (_("WARNING: signing subkey %s is not"
" cross-certified\n"),keystr_from_pk(pk));
log_info (_("please see %s for more information\n"),
"https://gnupg.org/faq/subkey-cross-certify.html");
/* The default option --require-cross-certification
* makes this warning an error. */
if (opt.flags.require_cross_cert)
rc = gpg_error (GPG_ERR_GENERAL);
}
else if(pk->flags.backsig == 1)
{
log_info (_("WARNING: signing subkey %s has an invalid"
" cross-certification\n"), keystr_from_pk(pk));
rc = gpg_error (GPG_ERR_GENERAL);
}
}
}
if( !rc && sig->sig_class < 2 && is_status_enabled() ) {
/* This signature id works best with DLP algorithms because
* they use a random parameter for every signature. Instead of
* this sig-id we could have also used the hash of the document
* and the timestamp, but the drawback of this is, that it is
* not possible to sign more than one identical document within
* one second. Some remote batch processing applications might
* like this feature here.
*
* Note that before 2.0.10, we used RIPE-MD160 for the hash
* and accidentally didn't include the timestamp and algorithm
* information in the hash. Given that this feature is not
* commonly used and that a replay attacks detection should
* not solely be based on this feature (because it does not
* work with RSA), we take the freedom and switch to SHA-1
* with 2.0.10 to take advantage of hardware supported SHA-1
* implementations. We also include the missing information
* in the hash. Note also the SIG_ID as computed by gpg 1.x
* and gpg 2.x didn't matched either because 2.x used to print
* MPIs not in PGP format. */
u32 a = sig->timestamp;
int nsig = pubkey_get_nsig( sig->pubkey_algo );
unsigned char *p, *buffer;
size_t n, nbytes;
int i;
char hashbuf[20]; /* We use SHA-1 here. */
nbytes = 6;
for (i=0; i < nsig; i++ )
{
if (gcry_mpi_print (GCRYMPI_FMT_USG, NULL, 0, &n, sig->data[i]))
BUG();
nbytes += n;
}
/* Make buffer large enough to be later used as output buffer. */
if (nbytes < 100)
nbytes = 100;
nbytes += 10; /* Safety margin. */
/* Fill and hash buffer. */
buffer = p = xmalloc (nbytes);
*p++ = sig->pubkey_algo;
*p++ = sig->digest_algo;
*p++ = (a >> 24) & 0xff;
*p++ = (a >> 16) & 0xff;
*p++ = (a >> 8) & 0xff;
*p++ = a & 0xff;
nbytes -= 6;
for (i=0; i < nsig; i++ )
{
if (gcry_mpi_print (GCRYMPI_FMT_PGP, p, nbytes, &n, sig->data[i]))
BUG();
p += n;
nbytes -= n;
}
gcry_md_hash_buffer (GCRY_MD_SHA1, hashbuf, buffer, p-buffer);
p = make_radix64_string (hashbuf, 20);
sprintf (buffer, "%s %s %lu",
p, strtimestamp (sig->timestamp), (ulong)sig->timestamp);
xfree (p);
write_status_text (STATUS_SIG_ID, buffer);
xfree (buffer);
}
if (r_pk)
*r_pk = pk;
else
{
release_public_key_parts (pk);
xfree (pk);
}
return rc;
}
/* The signature SIG was generated with the public key PK. Check
* whether the signature is valid in the following sense:
*
* - Make sure the public key was created before the signature was
* generated.
*
* - Make sure the public key was created in the past
*
* - Check whether PK has expired (set *R_EXPIRED to 1 if so and 0
* otherwise)
*
* - Check whether PK has been revoked (set *R_REVOKED to 1 if so
* and 0 otherwise).
*
* If either of the first two tests fail, returns an error code.
* Otherwise returns 0. (Thus, this function doesn't fail if the
* public key is expired or revoked.) */
static int
check_signature_metadata_validity (PKT_public_key *pk, PKT_signature *sig,
int *r_expired, int *r_revoked)
{
u32 cur_time;
if (r_expired)
*r_expired = 0;
if (r_revoked)
*r_revoked = 0;
if (pk->timestamp > sig->timestamp )
{
ulong d = pk->timestamp - sig->timestamp;
if ( d < 86400 )
{
log_info (ngettext
("public key %s is %lu second newer than the signature\n",
"public key %s is %lu seconds newer than the signature\n",
d), keystr_from_pk (pk), d);
}
else
{
d /= 86400;
log_info (ngettext
("public key %s is %lu day newer than the signature\n",
"public key %s is %lu days newer than the signature\n",
d), keystr_from_pk (pk), d);
}
if (!opt.ignore_time_conflict)
return GPG_ERR_TIME_CONFLICT; /* pubkey newer than signature. */
}
cur_time = make_timestamp ();
if (pk->timestamp > cur_time)
{
ulong d = pk->timestamp - cur_time;
if (d < 86400)
{
log_info (ngettext("key %s was created %lu second"
" in the future (time warp or clock problem)\n",
"key %s was created %lu seconds"
" in the future (time warp or clock problem)\n",
d), keystr_from_pk (pk), d);
}
else
{
d /= 86400;
log_info (ngettext("key %s was created %lu day"
" in the future (time warp or clock problem)\n",
"key %s was created %lu days"
" in the future (time warp or clock problem)\n",
d), keystr_from_pk (pk), d);
}
if (!opt.ignore_time_conflict)
return GPG_ERR_TIME_CONFLICT;
}
/* Check whether the key has expired. We check the has_expired
* flag which is set after a full evaluation of the key (getkey.c)
* as well as a simple compare to the current time in case the
* merge has for whatever reasons not been done. */
if (pk->has_expired || (pk->expiredate && pk->expiredate < cur_time))
{
char buf[11];
if (opt.verbose)
log_info (_("Note: signature key %s expired %s\n"),
keystr_from_pk(pk), asctimestamp( pk->expiredate ) );
snprintf (buf, sizeof buf, "%lu",(ulong)pk->expiredate);
write_status_text (STATUS_KEYEXPIRED, buf);
if (r_expired)
*r_expired = 1;
}
if (pk->flags.revoked)
{
if (opt.verbose)
log_info (_("Note: signature key %s has been revoked\n"),
keystr_from_pk(pk));
if (r_revoked)
*r_revoked=1;
}
return 0;
}
/* Finish generating a signature and check it. Concretely: make sure
* that the signature is valid (it was not created prior to the key,
* the public key was created in the past, and the signature does not
* include any unsupported critical features), finish computing the
* digest by adding the relevant data from the signature packet, and
* check that the signature verifies the digest.
*
* DIGEST contains a hash context, which has already hashed the signed
* data. This function adds the relevant meta-data from the signature
* packet to compute the final hash. (See Section 5.2 of RFC 4880:
* "The concatenation of the data being signed and the signature data
* from the version number through the hashed subpacket data
* (inclusive) is hashed.")
*
* SIG is the signature to check.
*
* PK is the public key used to generate the signature.
*
* If R_EXPIRED is not NULL, *R_EXPIRED is set to 1 if PK has expired
* (0 otherwise). Note: PK being expired does not cause this function
* to fail.
*
* If R_REVOKED is not NULL, *R_REVOKED is set to 1 if PK has been
* revoked (0 otherwise). Note: PK being revoked does not cause this
* function to fail.
*
* If RET_PK is not NULL, PK is copied into RET_PK on success.
*
* Returns 0 on success. An error code other. */
static int
check_signature_end (PKT_public_key *pk, PKT_signature *sig,
gcry_md_hd_t digest,
const void *extrahash, size_t extrahashlen,
int *r_expired, int *r_revoked, PKT_public_key *ret_pk)
{
int rc = 0;
if ((rc = check_signature_metadata_validity (pk, sig,
r_expired, r_revoked)))
return rc;
if ((rc = check_signature_end_simple (pk, sig, digest,
extrahash, extrahashlen)))
return rc;
if (!rc && ret_pk)
copy_public_key(ret_pk,pk);
return rc;
}
/* This function is similar to check_signature_end, but it only checks
* whether the signature was generated by PK. It does not check
* expiration, revocation, etc. */
static int
check_signature_end_simple (PKT_public_key *pk, PKT_signature *sig,
gcry_md_hd_t digest,
const void *extrahash, size_t extrahashlen)
{
gcry_mpi_t result = NULL;
int rc = 0;
const struct weakhash *weak;
if (!opt.flags.allow_weak_digest_algos)
{
for (weak = opt.weak_digests; weak; weak = weak->next)
if (sig->digest_algo == weak->algo)
{
print_digest_rejected_note(sig->digest_algo);
return GPG_ERR_DIGEST_ALGO;
}
}
/* For key signatures check that the key has a cert usage. We may
* do this only for subkeys because the primary may always issue key
* signature. The latter may not be reflected in the pubkey_usage
* field because we need to check the key signatures to extract the
* key usage. */
if (!pk->flags.primary
&& IS_CERT (sig) && !(pk->pubkey_usage & PUBKEY_USAGE_CERT))
{
rc = gpg_error (GPG_ERR_WRONG_KEY_USAGE);
if (!opt.quiet)
log_info (_("bad key signature from key %s: %s (0x%02x, 0x%x)\n"),
keystr_from_pk (pk), gpg_strerror (rc),
sig->sig_class, pk->pubkey_usage);
return rc;
}
/* For data signatures check that the key has sign usage. */
if (!IS_BACK_SIG (sig) && IS_SIG (sig)
&& !(pk->pubkey_usage & PUBKEY_USAGE_SIG))
{
rc = gpg_error (GPG_ERR_WRONG_KEY_USAGE);
if (!opt.quiet)
log_info (_("bad data signature from key %s: %s (0x%02x, 0x%x)\n"),
keystr_from_pk (pk), gpg_strerror (rc),
sig->sig_class, pk->pubkey_usage);
return rc;
}
/* Make sure the digest algo is enabled (in case of a detached
* signature). */
gcry_md_enable (digest, sig->digest_algo);
/* Complete the digest. */
if (sig->version >= 4)
gcry_md_putc (digest, sig->version);
gcry_md_putc( digest, sig->sig_class );
if (sig->version < 4)
{
u32 a = sig->timestamp;
gcry_md_putc (digest, ((a >> 24) & 0xff));
gcry_md_putc (digest, ((a >> 16) & 0xff));
gcry_md_putc (digest, ((a >> 8) & 0xff));
gcry_md_putc (digest, ( a & 0xff));
}
else
{
byte buf[10];
int i;
size_t n;
gcry_md_putc (digest, sig->pubkey_algo);
gcry_md_putc (digest, sig->digest_algo);
if (sig->hashed)
{
n = sig->hashed->len;
gcry_md_putc (digest, (n >> 8) );
gcry_md_putc (digest, n );
gcry_md_write (digest, sig->hashed->data, n);
n += 6;
}
else
{
/* Two octets for the (empty) length of the hashed
* section. */
gcry_md_putc (digest, 0);
gcry_md_putc (digest, 0);
n = 6;
}
/* Hash data from the literal data packet. */
if (sig->version >= 5
&& (sig->sig_class == 0x00 || sig->sig_class == 0x01))
{
/* - One octet content format
* - File name (one octet length followed by the name)
* - Four octet timestamp */
if (extrahash && extrahashlen)
gcry_md_write (digest, extrahash, extrahashlen);
else /* Detached signature. */
{
memset (buf, 0, 6);
gcry_md_write (digest, buf, 6);
}
}
/* Add some magic per Section 5.2.4 of RFC 4880. */
i = 0;
buf[i++] = sig->version;
buf[i++] = 0xff;
if (sig->version >= 5)
{
#if SIZEOF_SIZE_T > 4
buf[i++] = n >> 56;
buf[i++] = n >> 48;
buf[i++] = n >> 40;
buf[i++] = n >> 32;
#else
buf[i++] = 0;
buf[i++] = 0;
buf[i++] = 0;
buf[i++] = 0;
#endif
}
buf[i++] = n >> 24;
buf[i++] = n >> 16;
buf[i++] = n >> 8;
buf[i++] = n;
gcry_md_write (digest, buf, i);
}
gcry_md_final( digest );
/* Convert the digest to an MPI. */
result = encode_md_value (pk, digest, sig->digest_algo );
if (!result)
return GPG_ERR_GENERAL;
/* Verify the signature. */
if (DBG_CLOCK && sig->sig_class <= 0x01)
log_clock ("enter pk_verify");
rc = pk_verify( pk->pubkey_algo, result, sig->data, pk->pkey );
if (DBG_CLOCK && sig->sig_class <= 0x01)
log_clock ("leave pk_verify");
gcry_mpi_release (result);
if (!rc && sig->flags.unknown_critical)
{
log_info(_("assuming bad signature from key %s"
" due to an unknown critical bit\n"),keystr_from_pk(pk));
rc = GPG_ERR_BAD_SIGNATURE;
}
return rc;
}
/* Add a uid node to a hash context. See section 5.2.4, paragraph 4
* of RFC 4880. */
static void
hash_uid_packet (PKT_user_id *uid, gcry_md_hd_t md, PKT_signature *sig )
{
if (uid->attrib_data)
{
if (sig->version >= 4)
{
byte buf[5];
buf[0] = 0xd1; /* packet of type 17 */
buf[1] = uid->attrib_len >> 24; /* always use 4 length bytes */
buf[2] = uid->attrib_len >> 16;
buf[3] = uid->attrib_len >> 8;
buf[4] = uid->attrib_len;
gcry_md_write( md, buf, 5 );
}
gcry_md_write( md, uid->attrib_data, uid->attrib_len );
}
else
{
if (sig->version >= 4)
{
byte buf[5];
buf[0] = 0xb4; /* indicates a userid packet */
buf[1] = uid->len >> 24; /* always use 4 length bytes */
buf[2] = uid->len >> 16;
buf[3] = uid->len >> 8;
buf[4] = uid->len;
gcry_md_write( md, buf, 5 );
}
gcry_md_write( md, uid->name, uid->len );
}
}
static void
cache_sig_result ( PKT_signature *sig, int result )
{
if (!result)
{
sig->flags.checked = 1;
sig->flags.valid = 1;
}
else if (gpg_err_code (result) == GPG_ERR_BAD_SIGNATURE)
{
sig->flags.checked = 1;
sig->flags.valid = 0;
}
else
{
sig->flags.checked = 0;
sig->flags.valid = 0;
}
}
/* SIG is a key revocation signature. Check if this signature was
* generated by any of the public key PK's designated revokers.
*
* PK is the public key that SIG allegedly revokes.
*
* SIG is the revocation signature to check.
*
* This function avoids infinite recursion, which can happen if two
* keys are designed revokers for each other and they revoke each
* other. This is done by observing that if a key A is revoked by key
* B we still consider the revocation to be valid even if B is
* revoked. Thus, we don't need to determine whether B is revoked to
* determine whether A has been revoked by B, we just need to check
* the signature.
*
* Returns 0 if sig is valid (i.e. pk is revoked), non-0 if not
* revoked. We are careful to make sure that GPG_ERR_NO_PUBKEY is
* only returned when a revocation signature is from a valid
* revocation key designated in a revkey subpacket, but the revocation
* key itself isn't present.
*
* XXX: This code will need to be modified if gpg ever becomes
* multi-threaded. Note that this guarantees that a designated
* revocation sig will never be considered valid unless it is actually
* valid, as well as being issued by a revocation key in a valid
* direct signature. Note also that this is written so that a revoked
* revoker can still issue revocations: i.e. If A revokes B, but A is
* revoked, B is still revoked. I'm not completely convinced this is
* the proper behavior, but it matches how PGP does it. -dms */
int
check_revocation_keys (ctrl_t ctrl, PKT_public_key *pk, PKT_signature *sig)
{
static int busy=0;
int i;
int rc = GPG_ERR_GENERAL;
log_assert (IS_KEY_REV(sig));
log_assert ((sig->keyid[0]!=pk->keyid[0]) || (sig->keyid[0]!=pk->keyid[1]));
/* Avoid infinite recursion. Consider the following:
*
* - We want to check if A is revoked.
*
* - C is a designated revoker for B and has revoked B.
*
* - B is a designated revoker for A and has revoked A.
*
* When checking if A is revoked (in merge_selfsigs_main), we
* observe that A has a designed revoker. As such, we call this
* function. This function sees that there is a valid revocation
* signature, which is signed by B. It then calls check_signature()
* to verify that the signature is good. To check the sig, we need
* to lookup B. Looking up B means calling merge_selfsigs_main,
* which checks whether B is revoked, which calls this function to
* see if B was revoked by some key.
*
* In this case, the added level of indirection doesn't hurt. It
* just means a bit more work. However, if C == A, then we'd end up
* in a loop. But, it doesn't make sense to look up C anyways: even
* if B is revoked, we conservatively consider a valid revocation
* signed by B to revoke A. Since this is the only place where this
* type of recursion can occur, we simply cause this function to
* fail if it is entered recursively. */
if (busy)
{
/* Return an error (i.e. not revoked), but mark the pk as
uncacheable as we don't really know its revocation status
until it is checked directly. */
pk->flags.dont_cache = 1;
return rc;
}
busy=1;
/* es_printf("looking at %08lX with a sig from %08lX\n",(ulong)pk->keyid[1],
(ulong)sig->keyid[1]); */
/* is the issuer of the sig one of our revokers? */
if( !pk->revkey && pk->numrevkeys )
BUG();
else
for(i=0;i<pk->numrevkeys;i++)
{
/* The revoker's keyid. */
u32 keyid[2];
keyid_from_fingerprint (ctrl, pk->revkey[i].fpr, pk->revkey[i].fprlen,
keyid);
if(keyid[0]==sig->keyid[0] && keyid[1]==sig->keyid[1])
/* The signature was generated by a designated revoker.
Verify the signature. */
{
gcry_md_hd_t md;
if (gcry_md_open (&md, sig->digest_algo, 0))
BUG ();
hash_public_key(md,pk);
/* Note: check_signature only checks that the signature
is good. It does not fail if the key is revoked. */
rc = check_signature (ctrl, sig, md);
cache_sig_result(sig,rc);
gcry_md_close (md);
break;
}
}
busy=0;
return rc;
}
/* Check that the backsig BACKSIG from the subkey SUB_PK to its
* primary key MAIN_PK is valid.
*
* Backsigs (0x19) have the same format as binding sigs (0x18), but
* this function is simpler than check_key_signature in a few ways.
* For example, there is no support for expiring backsigs since it is
* questionable what such a thing actually means. Note also that the
* sig cache check here, unlike other sig caches in GnuPG, is not
* persistent. */
int
check_backsig (PKT_public_key *main_pk,PKT_public_key *sub_pk,
PKT_signature *backsig)
{
gcry_md_hd_t md;
int rc;
/* Always check whether the algorithm is available. Although
gcry_md_open would throw an error, some libgcrypt versions will
print a debug message in that case too. */
if ((rc=openpgp_md_test_algo (backsig->digest_algo)))
return rc;
if(!opt.no_sig_cache && backsig->flags.checked)
return backsig->flags.valid? 0 : gpg_error (GPG_ERR_BAD_SIGNATURE);
rc = gcry_md_open (&md, backsig->digest_algo,0);
if (!rc)
{
hash_public_key(md,main_pk);
hash_public_key(md,sub_pk);
rc = check_signature_end (sub_pk, backsig, md, NULL, 0, NULL, NULL, NULL);
cache_sig_result(backsig,rc);
gcry_md_close(md);
}
return rc;
}
/* Check that a signature over a key is valid. This is a
* specialization of check_key_signature2 with the unnamed parameters
* passed as NULL. See the documentation for that function for more
* details. */
int
check_key_signature (ctrl_t ctrl, kbnode_t root, kbnode_t node,
int *is_selfsig)
{
return check_key_signature2 (ctrl, root, node, NULL, NULL,
is_selfsig, NULL, NULL);
}
/* Returns whether SIGNER generated the signature SIG over the packet
* PACKET, which is a key, subkey or uid, and comes from the key block
* KB. (KB is PACKET's corresponding keyblock; we don't assume that
* SIG has been added to the keyblock.)
*
* If SIGNER is set, then checks whether SIGNER generated the
* signature. Otherwise, uses SIG->KEYID to find the alleged signer.
* This parameter can be used to effectively override the alleged
* signer that is stored in SIG.
*
* KB may be NULL if SIGNER is set.
*
* Unlike check_key_signature, this function ignores any cached
* results! That is, it does not consider SIG->FLAGS.CHECKED and
* SIG->FLAGS.VALID nor does it set them.
*
* This doesn't check the signature's semantic mean. Concretely, it
* doesn't check whether a non-self signed revocation signature was
* created by a designated revoker. In fact, it doesn't return an
* error for a binding generated by a completely different key!
*
* Returns 0 if the signature is valid. Returns GPG_ERR_SIG_CLASS if
* this signature can't be over PACKET. Returns GPG_ERR_NOT_FOUND if
* the key that generated the signature (according to SIG) could not
* be found. Returns GPG_ERR_BAD_SIGNATURE if the signature is bad.
* Other errors codes may be returned if something else goes wrong.
*
* IF IS_SELFSIG is not NULL, sets *IS_SELFSIG to 1 if this is a
* self-signature (by the key's primary key) or 0 if not.
*
* If RET_PK is not NULL, returns a copy of the public key that
* generated the signature (i.e., the signer) on success. This must
* be released by the caller using release_public_key_parts (). */
gpg_error_t
check_signature_over_key_or_uid (ctrl_t ctrl, PKT_public_key *signer,
PKT_signature *sig, KBNODE kb, PACKET *packet,
int *is_selfsig, PKT_public_key *ret_pk)
{
int rc;
PKT_public_key *pripk = kb->pkt->pkt.public_key;
gcry_md_hd_t md;
int signer_alloced = 0;
rc = openpgp_pk_test_algo (sig->pubkey_algo);
if (rc)
return rc;
rc = openpgp_md_test_algo (sig->digest_algo);
if (rc)
return rc;
/* A signature's class indicates the type of packet that it
signs. */
if (IS_BACK_SIG (sig) || IS_KEY_SIG (sig) || IS_KEY_REV (sig))
{
/* Key revocations can only be over primary keys. */
if (packet->pkttype != PKT_PUBLIC_KEY)
return gpg_error (GPG_ERR_SIG_CLASS);
}
else if (IS_SUBKEY_SIG (sig) || IS_SUBKEY_REV (sig))
{
if (packet->pkttype != PKT_PUBLIC_SUBKEY)
return gpg_error (GPG_ERR_SIG_CLASS);
}
else if (IS_UID_SIG (sig) || IS_UID_REV (sig))
{
if (packet->pkttype != PKT_USER_ID)
return gpg_error (GPG_ERR_SIG_CLASS);
}
else
return gpg_error (GPG_ERR_SIG_CLASS);
/* PACKET is the right type for SIG. */
if (signer)
{
if (is_selfsig)
{
if (signer->keyid[0] == pripk->keyid[0]
&& signer->keyid[1] == pripk->keyid[1])
*is_selfsig = 1;
else
*is_selfsig = 0;
}
}
else
{
/* Get the signer. If possible, avoid a look up. */
if (sig->keyid[0] == pripk->keyid[0]
&& sig->keyid[1] == pripk->keyid[1])
{
/* Issued by the primary key. */
signer = pripk;
if (is_selfsig)
*is_selfsig = 1;
}
else
{
/* See if one of the subkeys was the signer (although this
* is extremely unlikely). */
kbnode_t ctx = NULL;
kbnode_t n;
while ((n = walk_kbnode (kb, &ctx, 0)))
{
PKT_public_key *subk;
if (n->pkt->pkttype != PKT_PUBLIC_SUBKEY)
continue;
subk = n->pkt->pkt.public_key;
if (sig->keyid[0] == subk->keyid[0]
&& sig->keyid[1] == subk->keyid[1])
{
/* Issued by a subkey. */
signer = subk;
break;
}
}
if (! signer)
{
/* Signer by some other key. */
if (is_selfsig)
*is_selfsig = 0;
if (ret_pk)
{
signer = ret_pk;
/* FIXME: Using memset here is probematic because it
* assumes that there are no allocated fields in
* SIGNER. */
memset (signer, 0, sizeof (*signer));
signer_alloced = 1;
}
else
{
signer = xmalloc_clear (sizeof (*signer));
signer_alloced = 2;
}
if (IS_CERT (sig))
signer->req_usage = PUBKEY_USAGE_CERT;
rc = get_pubkey_for_sig (ctrl, signer, sig);
if (rc)
{
xfree (signer);
signer = NULL;
signer_alloced = 0;
goto leave;
}
}
}
}
/* We checked above that we supported this algo, so an error here is
* a bug. */
if (gcry_md_open (&md, sig->digest_algo, 0))
BUG ();
/* Hash the relevant data. */
if (IS_KEY_SIG (sig) || IS_KEY_REV (sig))
{
log_assert (packet->pkttype == PKT_PUBLIC_KEY);
hash_public_key (md, packet->pkt.public_key);
rc = check_signature_end_simple (signer, sig, md, NULL, 0);
}
else if (IS_BACK_SIG (sig))
{
log_assert (packet->pkttype == PKT_PUBLIC_KEY);
hash_public_key (md, packet->pkt.public_key);
hash_public_key (md, signer);
rc = check_signature_end_simple (signer, sig, md, NULL, 0);
}
else if (IS_SUBKEY_SIG (sig) || IS_SUBKEY_REV (sig))
{
log_assert (packet->pkttype == PKT_PUBLIC_SUBKEY);
hash_public_key (md, pripk);
hash_public_key (md, packet->pkt.public_key);
rc = check_signature_end_simple (signer, sig, md, NULL, 0);
}
else if (IS_UID_SIG (sig) || IS_UID_REV (sig))
{
log_assert (packet->pkttype == PKT_USER_ID);
hash_public_key (md, pripk);
hash_uid_packet (packet->pkt.user_id, md, sig);
rc = check_signature_end_simple (signer, sig, md, NULL, 0);
}
else
{
/* We should never get here. (The first if above should have
* already caught this error.) */
BUG ();
}
gcry_md_close (md);
leave:
if (! rc && ret_pk && ret_pk != signer)
copy_public_key (ret_pk, signer);
if (signer_alloced)
{
/* We looked up SIGNER; it is not a pointer into KB. */
release_public_key_parts (signer);
/* Free if we also allocated the memory. */
if (signer_alloced == 2)
xfree (signer);
}
return rc;
}
/* Check that a signature over a key (e.g., a key revocation, key
* binding, user id certification, etc.) is valid. If the function
* detects a self-signature, it uses the public key from the specified
* key block and does not bother looking up the key specified in the
* signature packet.
*
* ROOT is a keyblock.
*
* NODE references a signature packet that appears in the keyblock
* that should be verified.
*
* If CHECK_PK is set, the specified key is sometimes preferred for
* verifying signatures. See the implementation for details.
*
* If RET_PK is not NULL, the public key that successfully verified
* the signature is copied into *RET_PK.
*
* If IS_SELFSIG is not NULL, *IS_SELFSIG is set to 1 if NODE is a
* self-signature.
*
* If R_EXPIREDATE is not NULL, *R_EXPIREDATE is set to the expiry
* date.
*
* If R_EXPIRED is not NULL, *R_EXPIRED is set to 1 if PK has been
* expired (0 otherwise). Note: PK being revoked does not cause this
* function to fail.
*
*
* If OPT.NO_SIG_CACHE is not set, this function will first check if
* the result of a previous verification is already cached in the
* signature packet's data structure.
*
* TODO: add r_revoked here as well. It has the same problems as
- * r_expiredate and r_expired and the cache. */
+ * r_expiredate and r_expired and the cache [nw]. Which problems [wk]? */
int
check_key_signature2 (ctrl_t ctrl,
kbnode_t root, kbnode_t node, PKT_public_key *check_pk,
PKT_public_key *ret_pk, int *is_selfsig,
u32 *r_expiredate, int *r_expired )
{
PKT_public_key *pk;
PKT_signature *sig;
int algo;
int rc;
if (is_selfsig)
*is_selfsig = 0;
if (r_expiredate)
*r_expiredate = 0;
if (r_expired)
*r_expired = 0;
log_assert (node->pkt->pkttype == PKT_SIGNATURE);
log_assert (root->pkt->pkttype == PKT_PUBLIC_KEY);
pk = root->pkt->pkt.public_key;
sig = node->pkt->pkt.signature;
algo = sig->digest_algo;
/* Check whether we have cached the result of a previous signature
* check. Note that we may no longer have the pubkey or hash
* needed to verify a sig, but can still use the cached value. A
* cache refresh detects and clears these cases. */
if ( !opt.no_sig_cache )
{
cache_stats.total++;
if (sig->flags.checked) /* Cached status available. */
{
cache_stats.cached++;
if (is_selfsig)
{
u32 keyid[2];
keyid_from_pk (pk, keyid);
if (keyid[0] == sig->keyid[0] && keyid[1] == sig->keyid[1])
*is_selfsig = 1;
}
/* BUG: This is wrong for non-self-sigs... needs to be the
* actual pk. */
rc = check_signature_metadata_validity (pk, sig, r_expired, NULL);
if (rc)
return rc;
if (sig->flags.valid)
{
cache_stats.goodsig++;
return 0;
}
cache_stats.badsig++;
return gpg_error (GPG_ERR_BAD_SIGNATURE);
}
}
rc = openpgp_pk_test_algo(sig->pubkey_algo);
if (rc)
return rc;
rc = openpgp_md_test_algo(algo);
if (rc)
return rc;
if (IS_KEY_REV (sig))
{
u32 keyid[2];
keyid_from_pk( pk, keyid );
/* Is it a designated revoker? */
if (keyid[0] != sig->keyid[0] || keyid[1] != sig->keyid[1])
rc = check_revocation_keys (ctrl, pk, sig);
else
{
rc = check_signature_metadata_validity (pk, sig,
r_expired, NULL);
if (! rc)
rc = check_signature_over_key_or_uid (ctrl, pk, sig,
root, root->pkt,
is_selfsig, ret_pk);
}
}
else if (IS_SUBKEY_REV (sig) || IS_SUBKEY_SIG (sig))
{
kbnode_t snode = find_prev_kbnode (root, node, PKT_PUBLIC_SUBKEY);
if (snode)
{
rc = check_signature_metadata_validity (pk, sig,
r_expired, NULL);
if (! rc)
{
/* A subkey revocation (0x28) must be a self-sig, but a
* subkey signature (0x18) needn't be. */
rc = check_signature_over_key_or_uid (ctrl,
IS_SUBKEY_SIG (sig)
? NULL : pk,
sig, root, snode->pkt,
is_selfsig, ret_pk);
}
}
else
{
if (opt.verbose)
{
if (IS_SUBKEY_REV (sig))
log_info (_("key %s: no subkey for subkey"
" revocation signature\n"), keystr_from_pk(pk));
else if (sig->sig_class == 0x18)
log_info(_("key %s: no subkey for subkey"
" binding signature\n"), keystr_from_pk(pk));
}
rc = GPG_ERR_SIG_CLASS;
}
}
else if (IS_KEY_SIG (sig)) /* direct key signature */
{
rc = check_signature_metadata_validity (pk, sig,
r_expired, NULL);
if (! rc)
rc = check_signature_over_key_or_uid (ctrl, pk, sig, root, root->pkt,
is_selfsig, ret_pk);
}
else if (IS_UID_SIG (sig) || IS_UID_REV (sig))
{
kbnode_t unode = find_prev_kbnode (root, node, PKT_USER_ID);
if (unode)
{
rc = check_signature_metadata_validity (pk, sig, r_expired, NULL);
if (! rc)
{
/* If this is a self-sig, ignore check_pk. */
rc = check_signature_over_key_or_uid
(ctrl,
keyid_cmp (pk_keyid (pk), sig->keyid) == 0 ? pk : check_pk,
sig, root, unode->pkt, NULL, ret_pk);
}
}
else
{
if (!opt.quiet)
log_info ("key %s: no user ID for key signature packet"
" of class %02x\n",keystr_from_pk(pk),sig->sig_class);
rc = GPG_ERR_SIG_CLASS;
}
}
else
{
log_info ("sig issued by %s with class %d (digest: %02x %02x)"
" is not valid over a user id or a key id, ignoring.\n",
keystr (sig->keyid), sig->sig_class,
sig->digest_start[0], sig->digest_start[1]);
rc = gpg_error (GPG_ERR_BAD_SIGNATURE);
}
cache_sig_result (sig, rc);
return rc;
}
diff --git a/g10/sign.c b/g10/sign.c
index 176940bff..d71580639 100644
--- a/g10/sign.c
+++ b/g10/sign.c
@@ -1,1793 +1,1796 @@
/* sign.c - sign data
* Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006,
* 2007, 2010, 2012 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include "gpg.h"
#include "options.h"
#include "packet.h"
#include "../common/status.h"
#include "../common/iobuf.h"
#include "keydb.h"
#include "../common/util.h"
#include "main.h"
#include "filter.h"
#include "../common/ttyio.h"
#include "trustdb.h"
#include "../common/status.h"
#include "../common/i18n.h"
#include "pkglue.h"
#include "../common/sysutils.h"
#include "call-agent.h"
#include "../common/mbox-util.h"
#include "../common/compliance.h"
#ifdef HAVE_DOSISH_SYSTEM
#define LF "\r\n"
#else
#define LF "\n"
#endif
/* Hack */
static int recipient_digest_algo;
/* A type for the extra data we hash into v5 signature packets. */
struct pt_extra_hash_data_s
{
unsigned char mode;
u32 timestamp;
unsigned char namelen;
char name[1];
};
typedef struct pt_extra_hash_data_s *pt_extra_hash_data_t;
/*
* Create notations and other stuff. It is assumed that the strings in
* STRLIST are already checked to contain only printable data and have
* a valid NAME=VALUE format.
*/
static void
mk_notation_policy_etc (PKT_signature *sig,
PKT_public_key *pk, PKT_public_key *pksk)
{
const char *string;
char *p = NULL;
strlist_t pu = NULL;
struct notation *nd = NULL;
struct expando_args args;
log_assert (sig->version >= 4);
memset (&args, 0, sizeof(args));
args.pk = pk;
args.pksk = pksk;
/* Notation data. */
if (IS_SIG(sig) && opt.sig_notations)
nd = opt.sig_notations;
else if (IS_CERT(sig) && opt.cert_notations)
nd = opt.cert_notations;
if (nd)
{
struct notation *item;
for (item = nd; item; item = item->next)
{
item->altvalue = pct_expando (item->value,&args);
if (!item->altvalue)
log_error (_("WARNING: unable to %%-expand notation "
"(too large). Using unexpanded.\n"));
}
keygen_add_notations (sig, nd);
for (item = nd; item; item = item->next)
{
xfree (item->altvalue);
item->altvalue = NULL;
}
}
/* Set policy URL. */
if (IS_SIG(sig) && opt.sig_policy_url)
pu = opt.sig_policy_url;
else if (IS_CERT(sig) && opt.cert_policy_url)
pu = opt.cert_policy_url;
for (; pu; pu = pu->next)
{
string = pu->d;
p = pct_expando (string, &args);
if (!p)
{
log_error(_("WARNING: unable to %%-expand policy URL "
"(too large). Using unexpanded.\n"));
p = xstrdup(string);
}
build_sig_subpkt (sig, (SIGSUBPKT_POLICY
| ((pu->flags & 1)?SIGSUBPKT_FLAG_CRITICAL:0)),
p, strlen (p));
xfree (p);
}
/* Preferred keyserver URL. */
if (IS_SIG(sig) && opt.sig_keyserver_url)
pu = opt.sig_keyserver_url;
for (; pu; pu = pu->next)
{
string = pu->d;
p = pct_expando (string, &args);
if (!p)
{
log_error (_("WARNING: unable to %%-expand preferred keyserver URL"
" (too large). Using unexpanded.\n"));
p = xstrdup (string);
}
build_sig_subpkt (sig, (SIGSUBPKT_PREF_KS
| ((pu->flags & 1)?SIGSUBPKT_FLAG_CRITICAL:0)),
p, strlen (p));
xfree (p);
}
/* Set signer's user id. */
if (IS_SIG (sig) && !opt.flags.disable_signer_uid)
{
char *mbox;
/* For now we use the uid which was used to locate the key. */
if (pksk->user_id
&& (mbox = mailbox_from_userid (pksk->user_id->name, 0)))
{
if (DBG_LOOKUP)
log_debug ("setting Signer's UID to '%s'\n", mbox);
build_sig_subpkt (sig, SIGSUBPKT_SIGNERS_UID, mbox, strlen (mbox));
xfree (mbox);
}
else if (opt.sender_list)
{
/* If a list of --sender was given we scan that list and use
* the first one matching a user id of the current key. */
/* FIXME: We need to get the list of user ids for the PKSK
* packet. That requires either a function to look it up
* again or we need to extend the key packet struct to link
* to the primary key which in turn could link to the user
* ids. Too much of a change right now. Let's take just
* one from the supplied list and hope that the caller
* passed a matching one. */
build_sig_subpkt (sig, SIGSUBPKT_SIGNERS_UID,
opt.sender_list->d, strlen (opt.sender_list->d));
}
}
}
/*
* Helper to hash a user ID packet.
*/
static void
hash_uid (gcry_md_hd_t md, int sigversion, const PKT_user_id *uid)
{
byte buf[5];
(void)sigversion;
if (uid->attrib_data)
{
buf[0] = 0xd1; /* Indicates an attribute packet. */
buf[1] = uid->attrib_len >> 24; /* Always use 4 length bytes. */
buf[2] = uid->attrib_len >> 16;
buf[3] = uid->attrib_len >> 8;
buf[4] = uid->attrib_len;
}
else
{
buf[0] = 0xb4; /* Indicates a userid packet. */
buf[1] = uid->len >> 24; /* Always use 4 length bytes. */
buf[2] = uid->len >> 16;
buf[3] = uid->len >> 8;
buf[4] = uid->len;
}
gcry_md_write( md, buf, 5 );
if (uid->attrib_data)
gcry_md_write (md, uid->attrib_data, uid->attrib_len );
else
gcry_md_write (md, uid->name, uid->len );
}
/*
* Helper to hash some parts from the signature. EXTRAHASH gives the
* extra data to be hashed into v5 signatures; it may by NULL for
* detached signatures.
*/
static void
hash_sigversion_to_magic (gcry_md_hd_t md, const PKT_signature *sig,
pt_extra_hash_data_t extrahash)
{
byte buf[10];
int i;
size_t n;
gcry_md_putc (md, sig->version);
gcry_md_putc (md, sig->sig_class);
gcry_md_putc (md, sig->pubkey_algo);
gcry_md_putc (md, sig->digest_algo);
if (sig->hashed)
{
n = sig->hashed->len;
gcry_md_putc (md, (n >> 8) );
gcry_md_putc (md, n );
gcry_md_write (md, sig->hashed->data, n );
n += 6;
}
else
{
gcry_md_putc (md, 0); /* Always hash the length of the subpacket. */
gcry_md_putc (md, 0);
n = 6;
}
/* Hash data from the literal data packet. */
if (sig->version >= 5 && (sig->sig_class == 0x00 || sig->sig_class == 0x01))
{
/* - One octet content format
* - File name (one octet length followed by the name)
* - Four octet timestamp */
if (extrahash)
{
buf[0] = extrahash->mode;
buf[1] = extrahash->namelen;
gcry_md_write (md, buf, 2);
if (extrahash->namelen)
gcry_md_write (md, extrahash->name, extrahash->namelen);
buf[0] = extrahash->timestamp >> 24;
buf[1] = extrahash->timestamp >> 16;
buf[2] = extrahash->timestamp >> 8;
buf[3] = extrahash->timestamp;
gcry_md_write (md, buf, 4);
}
else /* Detached signatures */
{
memset (buf, 0, 6);
gcry_md_write (md, buf, 6);
}
}
/* Add some magic. */
i = 0;
buf[i++] = sig->version;
buf[i++] = 0xff;
if (sig->version >= 5)
{
/* Note: We don't hashed any data larger than 2^32 and thus we
* can always use 0 here. See also note below. */
buf[i++] = 0;
buf[i++] = 0;
buf[i++] = 0;
buf[i++] = 0;
}
buf[i++] = n >> 24; /* (n is only 16 bit, so this is always 0) */
buf[i++] = n >> 16;
buf[i++] = n >> 8;
buf[i++] = n;
gcry_md_write (md, buf, i);
}
/* Perform the sign operation. If CACHE_NONCE is given the agent is
advised to use that cached passphrase for the key. */
static int
do_sign (ctrl_t ctrl, PKT_public_key *pksk, PKT_signature *sig,
gcry_md_hd_t md, int mdalgo, const char *cache_nonce)
{
gpg_error_t err;
byte *dp;
char *hexgrip;
if (pksk->timestamp > sig->timestamp )
{
ulong d = pksk->timestamp - sig->timestamp;
log_info (ngettext("key %s was created %lu second"
" in the future (time warp or clock problem)\n",
"key %s was created %lu seconds"
" in the future (time warp or clock problem)\n",
d), keystr_from_pk (pksk), d);
if (!opt.ignore_time_conflict)
return gpg_error (GPG_ERR_TIME_CONFLICT);
}
print_pubkey_algo_note (pksk->pubkey_algo);
if (!mdalgo)
mdalgo = gcry_md_get_algo (md);
/* Check compliance. */
if (! gnupg_digest_is_allowed (opt.compliance, 1, mdalgo))
{
log_error (_("digest algorithm '%s' may not be used in %s mode\n"),
gcry_md_algo_name (mdalgo),
gnupg_compliance_option_string (opt.compliance));
err = gpg_error (GPG_ERR_DIGEST_ALGO);
goto leave;
}
if (! gnupg_pk_is_allowed (opt.compliance, PK_USE_SIGNING, pksk->pubkey_algo,
pksk->pkey, nbits_from_pk (pksk), NULL))
{
log_error (_("key %s may not be used for signing in %s mode\n"),
keystr_from_pk (pksk),
gnupg_compliance_option_string (opt.compliance));
err = gpg_error (GPG_ERR_PUBKEY_ALGO);
goto leave;
}
if (!gnupg_rng_is_compliant (opt.compliance))
{
err = gpg_error (GPG_ERR_FORBIDDEN);
log_error (_("%s is not compliant with %s mode\n"),
"RNG",
gnupg_compliance_option_string (opt.compliance));
write_status_error ("random-compliance", err);
goto leave;
}
print_digest_algo_note (mdalgo);
dp = gcry_md_read (md, mdalgo);
sig->digest_algo = mdalgo;
sig->digest_start[0] = dp[0];
sig->digest_start[1] = dp[1];
mpi_release (sig->data[0]);
sig->data[0] = NULL;
mpi_release (sig->data[1]);
sig->data[1] = NULL;
err = hexkeygrip_from_pk (pksk, &hexgrip);
if (!err)
{
char *desc;
gcry_sexp_t s_sigval;
desc = gpg_format_keydesc (ctrl, pksk, FORMAT_KEYDESC_NORMAL, 1);
err = agent_pksign (NULL/*ctrl*/, cache_nonce, hexgrip, desc,
pksk->keyid, pksk->main_keyid, pksk->pubkey_algo,
dp, gcry_md_get_algo_dlen (mdalgo), mdalgo,
&s_sigval);
xfree (desc);
if (err)
;
else if (pksk->pubkey_algo == GCRY_PK_RSA
|| pksk->pubkey_algo == GCRY_PK_RSA_S)
sig->data[0] = get_mpi_from_sexp (s_sigval, "s", GCRYMPI_FMT_USG);
else if (openpgp_oid_is_ed25519 (pksk->pkey[0]))
{
sig->data[0] = get_mpi_from_sexp (s_sigval, "r", GCRYMPI_FMT_OPAQUE);
sig->data[1] = get_mpi_from_sexp (s_sigval, "s", GCRYMPI_FMT_OPAQUE);
}
else
{
sig->data[0] = get_mpi_from_sexp (s_sigval, "r", GCRYMPI_FMT_USG);
sig->data[1] = get_mpi_from_sexp (s_sigval, "s", GCRYMPI_FMT_USG);
}
gcry_sexp_release (s_sigval);
}
xfree (hexgrip);
leave:
if (err)
log_error (_("signing failed: %s\n"), gpg_strerror (err));
else
{
if (opt.verbose)
{
char *ustr = get_user_id_string_native (ctrl, sig->keyid);
log_info (_("%s/%s signature from: \"%s\"\n"),
openpgp_pk_algo_name (pksk->pubkey_algo),
openpgp_md_algo_name (sig->digest_algo),
ustr);
xfree (ustr);
}
}
return err;
}
static int
complete_sig (ctrl_t ctrl,
PKT_signature *sig, PKT_public_key *pksk, gcry_md_hd_t md,
const char *cache_nonce)
{
int rc;
/* if (!(rc = check_secret_key (pksk, 0))) */
rc = do_sign (ctrl, pksk, sig, md, 0, cache_nonce);
return rc;
}
/* Return true if the key seems to be on a version 1 OpenPGP card.
This works by asking the agent and may fail if the card has not yet
been used with the agent. */
static int
openpgp_card_v1_p (PKT_public_key *pk)
{
gpg_error_t err;
int result;
/* Shortcut if we are not using RSA: The v1 cards only support RSA
thus there is no point in looking any further. */
if (!is_RSA (pk->pubkey_algo))
return 0;
if (!pk->flags.serialno_valid)
{
char *hexgrip;
err = hexkeygrip_from_pk (pk, &hexgrip);
if (err)
{
log_error ("error computing a keygrip: %s\n", gpg_strerror (err));
return 0; /* Ooops. */
}
xfree (pk->serialno);
agent_get_keyinfo (NULL, hexgrip, &pk->serialno, NULL);
xfree (hexgrip);
pk->flags.serialno_valid = 1;
}
if (!pk->serialno)
result = 0; /* Error from a past agent_get_keyinfo or no card. */
else
{
/* The version number of the card is included in the serialno. */
result = !strncmp (pk->serialno, "D2760001240101", 14);
}
return result;
}
static int
match_dsa_hash (unsigned int qbytes)
{
if (qbytes <= 20)
return DIGEST_ALGO_SHA1;
if (qbytes <= 28)
return DIGEST_ALGO_SHA224;
if (qbytes <= 32)
return DIGEST_ALGO_SHA256;
if (qbytes <= 48)
return DIGEST_ALGO_SHA384;
if (qbytes <= 66 ) /* 66 corresponds to 521 (64 to 512) */
return DIGEST_ALGO_SHA512;
return DEFAULT_DIGEST_ALGO;
/* DEFAULT_DIGEST_ALGO will certainly fail, but it's the best wrong
answer we have if a digest larger than 512 bits is requested. */
}
/*
First try --digest-algo. If that isn't set, see if the recipient
has a preferred algorithm (which is also filtered through
--personal-digest-prefs). If we're making a signature without a
particular recipient (i.e. signing, rather than signing+encrypting)
then take the first algorithm in --personal-digest-prefs that is
usable for the pubkey algorithm. If --personal-digest-prefs isn't
set, then take the OpenPGP default (i.e. SHA-1).
Note that Ed25519+EdDSA takes an input of arbitrary length and thus
we don't enforce any particular algorithm like we do for standard
ECDSA. However, we use SHA256 as the default algorithm.
Possible improvement: Use the highest-ranked usable algorithm from
the signing key prefs either before or after using the personal
list?
*/
static int
hash_for (PKT_public_key *pk)
{
if (opt.def_digest_algo)
{
return opt.def_digest_algo;
}
else if (recipient_digest_algo)
{
return recipient_digest_algo;
}
else if (pk->pubkey_algo == PUBKEY_ALGO_EDDSA
&& openpgp_oid_is_ed25519 (pk->pkey[0]))
{
if (opt.personal_digest_prefs)
return opt.personal_digest_prefs[0].value;
else
return DIGEST_ALGO_SHA256;
}
else if (pk->pubkey_algo == PUBKEY_ALGO_DSA
|| pk->pubkey_algo == PUBKEY_ALGO_ECDSA)
{
unsigned int qbytes = gcry_mpi_get_nbits (pk->pkey[1]);
if (pk->pubkey_algo == PUBKEY_ALGO_ECDSA)
qbytes = ecdsa_qbits_from_Q (qbytes);
qbytes = qbytes/8;
/* It's a DSA key, so find a hash that is the same size as q or
larger. If q is 160, assume it is an old DSA key and use a
160-bit hash unless --enable-dsa2 is set, in which case act
like a new DSA key that just happens to have a 160-bit q
(i.e. allow truncation). If q is not 160, by definition it
must be a new DSA key. */
if (opt.personal_digest_prefs)
{
prefitem_t *prefs;
if (qbytes != 20 || opt.flags.dsa2)
{
for (prefs=opt.personal_digest_prefs; prefs->type; prefs++)
if (gcry_md_get_algo_dlen (prefs->value) >= qbytes)
return prefs->value;
}
else
{
for (prefs=opt.personal_digest_prefs; prefs->type; prefs++)
if (gcry_md_get_algo_dlen (prefs->value) == qbytes)
return prefs->value;
}
}
return match_dsa_hash(qbytes);
}
else if (openpgp_card_v1_p (pk))
{
/* The sk lives on a smartcard, and old smartcards only handle
SHA-1 and RIPEMD/160. Newer smartcards (v2.0) don't have
this restriction anymore. Fortunately the serial number
encodes the version of the card and thus we know that this
key is on a v1 card. */
if(opt.personal_digest_prefs)
{
prefitem_t *prefs;
for (prefs=opt.personal_digest_prefs;prefs->type;prefs++)
if (prefs->value==DIGEST_ALGO_SHA1
|| prefs->value==DIGEST_ALGO_RMD160)
return prefs->value;
}
return DIGEST_ALGO_SHA1;
}
else if (opt.personal_digest_prefs)
{
/* It's not DSA, so we can use whatever the first hash algorithm
is in the pref list */
return opt.personal_digest_prefs[0].value;
}
else
return DEFAULT_DIGEST_ALGO;
}
static void
print_status_sig_created (PKT_public_key *pk, PKT_signature *sig, int what)
{
byte array[MAX_FINGERPRINT_LEN];
char buf[100+MAX_FINGERPRINT_LEN*2];
size_t n;
snprintf (buf, sizeof buf - 2*MAX_FINGERPRINT_LEN, "%c %d %d %02x %lu ",
what, sig->pubkey_algo, sig->digest_algo, sig->sig_class,
(ulong)sig->timestamp );
fingerprint_from_pk (pk, array, &n);
bin2hex (array, n, buf + strlen (buf));
write_status_text( STATUS_SIG_CREATED, buf );
}
/*
* Loop over the secret certificates in SK_LIST and build the one pass
* signature packets. OpenPGP says that the data should be bracket by
* the onepass-sig and signature-packet; so we build these onepass
* packet here in reverse order.
*/
static int
write_onepass_sig_packets (SK_LIST sk_list, IOBUF out, int sigclass )
{
int skcount;
SK_LIST sk_rover;
for (skcount=0, sk_rover=sk_list; sk_rover; sk_rover = sk_rover->next)
skcount++;
for (; skcount; skcount--)
{
PKT_public_key *pk;
PKT_onepass_sig *ops;
PACKET pkt;
int i, rc;
for (i=0, sk_rover = sk_list; sk_rover; sk_rover = sk_rover->next)
if (++i == skcount)
break;
pk = sk_rover->pk;
ops = xmalloc_clear (sizeof *ops);
ops->sig_class = sigclass;
ops->digest_algo = hash_for (pk);
ops->pubkey_algo = pk->pubkey_algo;
keyid_from_pk (pk, ops->keyid);
ops->last = (skcount == 1);
init_packet (&pkt);
pkt.pkttype = PKT_ONEPASS_SIG;
pkt.pkt.onepass_sig = ops;
rc = build_packet (out, &pkt);
free_packet (&pkt, NULL);
if (rc)
{
log_error ("build onepass_sig packet failed: %s\n",
gpg_strerror (rc));
return rc;
}
}
return 0;
}
/*
* Helper to write the plaintext (literal data) packet. At
* R_EXTRAHASH a malloced object with the with the extra data hashed
* into v5 signatures is stored.
*/
static int
write_plaintext_packet (iobuf_t out, iobuf_t inp,
const char *fname, int ptmode,
pt_extra_hash_data_t *r_extrahash)
{
PKT_plaintext *pt = NULL;
u32 filesize;
int rc = 0;
if (!opt.no_literal)
pt = setup_plaintext_name (fname, inp);
/* Try to calculate the length of the data. */
if ( !iobuf_is_pipe_filename (fname) && *fname)
{
off_t tmpsize;
int overflow;
if (!(tmpsize = iobuf_get_filelength (inp, &overflow))
&& !overflow && opt.verbose)
log_info (_("WARNING: '%s' is an empty file\n"), fname);
/* We can't encode the length of very large files because
* OpenPGP uses only 32 bit for file sizes. So if the size of a
* file is larger than 2^32 minus some bytes for packet headers,
* we switch to partial length encoding. */
if (tmpsize < (IOBUF_FILELENGTH_LIMIT - 65536))
filesize = tmpsize;
else
filesize = 0;
/* Because the text_filter modifies the length of the
* data, it is not possible to know the used length
* without a double read of the file - to avoid that
* we simple use partial length packets. */
if (ptmode == 't' || ptmode == 'u' || ptmode == 'm')
filesize = 0;
}
else
filesize = opt.set_filesize? opt.set_filesize : 0; /* stdin */
if (!opt.no_literal)
{
PACKET pkt;
/* Note that PT has been initialized above in no_literal mode. */
pt->timestamp = make_timestamp ();
pt->mode = ptmode;
pt->len = filesize;
pt->new_ctb = !pt->len;
pt->buf = inp;
init_packet (&pkt);
pkt.pkttype = PKT_PLAINTEXT;
pkt.pkt.plaintext = pt;
/*cfx.datalen = filesize? calc_packet_length( &pkt ) : 0;*/
if ((rc = build_packet (out, &pkt)))
log_error ("build_packet(PLAINTEXT) failed: %s\n",
gpg_strerror (rc) );
*r_extrahash = xtrymalloc (sizeof **r_extrahash + pt->namelen);
if (!*r_extrahash)
rc = gpg_error_from_syserror ();
else
{
(*r_extrahash)->mode = pt->mode;
(*r_extrahash)->timestamp = pt->timestamp;
(*r_extrahash)->namelen = pt->namelen;
/* Note that the last byte of NAME won't be initialized
* because we don't need it. */
memcpy ((*r_extrahash)->name, pt->name, pt->namelen);
}
pt->buf = NULL;
free_packet (&pkt, NULL);
}
else
{
byte copy_buffer[4096];
int bytes_copied;
*r_extrahash = xtrymalloc (sizeof **r_extrahash);
if (!*r_extrahash)
{
rc = gpg_error_from_syserror ();
goto leave;
}
/* FIXME: We need to parse INP to get the to be hashed data from
* it. */
(*r_extrahash)->mode = 0;
(*r_extrahash)->timestamp = 0;
(*r_extrahash)->namelen = 0;
while ((bytes_copied = iobuf_read (inp, copy_buffer, 4096)) != -1)
if ((rc = iobuf_write (out, copy_buffer, bytes_copied)))
{
log_error ("copying input to output failed: %s\n",
gpg_strerror (rc));
break;
}
wipememory (copy_buffer, 4096); /* burn buffer */
}
leave:
return rc;
}
/*
* Write the signatures from the SK_LIST to OUT. HASH must be a
* non-finalized hash which will not be changes here. EXTRAHASH is
* either NULL or the extra data tro be hashed into v5 signatures.
*/
static int
write_signature_packets (ctrl_t ctrl,
SK_LIST sk_list, IOBUF out, gcry_md_hd_t hash,
pt_extra_hash_data_t extrahash,
int sigclass, u32 timestamp, u32 duration,
int status_letter, const char *cache_nonce)
{
SK_LIST sk_rover;
/* Loop over the certificates with secret keys. */
for (sk_rover = sk_list; sk_rover; sk_rover = sk_rover->next)
{
PKT_public_key *pk;
PKT_signature *sig;
gcry_md_hd_t md;
int rc;
pk = sk_rover->pk;
/* Build the signature packet. */
sig = xtrycalloc (1, sizeof *sig);
if (!sig)
return gpg_error_from_syserror ();
if (pk->version >= 5)
sig->version = 5; /* Required for v5 keys. */
else
sig->version = 4; /* Required. */
keyid_from_pk (pk, sig->keyid);
sig->digest_algo = hash_for (pk);
sig->pubkey_algo = pk->pubkey_algo;
if (timestamp)
sig->timestamp = timestamp;
else
sig->timestamp = make_timestamp();
if (duration)
sig->expiredate = sig->timestamp + duration;
sig->sig_class = sigclass;
if (gcry_md_copy (&md, hash))
BUG ();
build_sig_subpkt_from_sig (sig, pk);
mk_notation_policy_etc (sig, NULL, pk);
hash_sigversion_to_magic (md, sig, extrahash);
gcry_md_final (md);
rc = do_sign (ctrl, pk, sig, md, hash_for (pk), cache_nonce);
gcry_md_close (md);
if (!rc)
{
/* Write the packet. */
PACKET pkt;
init_packet (&pkt);
pkt.pkttype = PKT_SIGNATURE;
pkt.pkt.signature = sig;
rc = build_packet (out, &pkt);
if (!rc && is_status_enabled())
print_status_sig_created (pk, sig, status_letter);
free_packet (&pkt, NULL);
if (rc)
log_error ("build signature packet failed: %s\n",
gpg_strerror (rc));
}
else
free_seckey_enc (sig);
if (rc)
return rc;
}
return 0;
}
/*
* Sign the files whose names are in FILENAME.
* If DETACHED has the value true,
* make a detached signature. If FILENAMES->d is NULL read from stdin
* and ignore the detached mode. Sign the file with all secret keys
* which can be taken from LOCUSR, if this is NULL, use the default one
* If ENCRYPTFLAG is true, use REMUSER (or ask if it is NULL) to encrypt the
* signed data for these users.
* If OUTFILE is not NULL; this file is used for output and the function
* does not ask for overwrite permission; output is then always
* uncompressed, non-armored and in binary mode.
*/
int
sign_file (ctrl_t ctrl, strlist_t filenames, int detached, strlist_t locusr,
int encryptflag, strlist_t remusr, const char *outfile )
{
const char *fname;
armor_filter_context_t *afx;
compress_filter_context_t zfx;
md_filter_context_t mfx;
text_filter_context_t tfx;
progress_filter_context_t *pfx;
encrypt_filter_context_t efx;
iobuf_t inp = NULL;
iobuf_t out = NULL;
PACKET pkt;
int rc = 0;
PK_LIST pk_list = NULL;
SK_LIST sk_list = NULL;
SK_LIST sk_rover = NULL;
int multifile = 0;
u32 duration=0;
pt_extra_hash_data_t extrahash = NULL;
pfx = new_progress_context ();
afx = new_armor_context ();
memset (&zfx, 0, sizeof zfx);
memset (&mfx, 0, sizeof mfx);
memset (&efx, 0, sizeof efx);
efx.ctrl = ctrl;
init_packet (&pkt);
if (filenames)
{
fname = filenames->d;
multifile = !!filenames->next;
}
else
fname = NULL;
if (fname && filenames->next && (!detached || encryptflag))
log_bug ("multiple files can only be detached signed");
if (encryptflag == 2
&& (rc = setup_symkey (&efx.symkey_s2k, &efx.symkey_dek)))
goto leave;
if (opt.ask_sig_expire && !opt.batch)
duration = ask_expire_interval(1,opt.def_sig_expire);
else
duration = parse_expire_string(opt.def_sig_expire);
/* Note: In the old non-agent version the following call used to
* unprotect the secret key. This is now done on demand by the agent. */
if ((rc = build_sk_list (ctrl, locusr, &sk_list, PUBKEY_USAGE_SIG )))
goto leave;
if (encryptflag
&& (rc = build_pk_list (ctrl, remusr, &pk_list)))
goto leave;
/* Prepare iobufs. */
if (multifile) /* have list of filenames */
inp = NULL; /* we do it later */
else
{
inp = iobuf_open(fname);
if (inp && is_secured_file (iobuf_get_fd (inp)))
{
iobuf_close (inp);
inp = NULL;
gpg_err_set_errno (EPERM);
}
if (!inp)
{
rc = gpg_error_from_syserror ();
log_error (_("can't open '%s': %s\n"), fname? fname: "[stdin]",
strerror (errno));
goto leave;
}
handle_progress (pfx, inp, fname);
}
if (outfile)
{
if (is_secured_filename (outfile))
{
out = NULL;
gpg_err_set_errno (EPERM);
}
else
out = iobuf_create (outfile, 0);
if (!out)
{
rc = gpg_error_from_syserror ();
log_error (_("can't create '%s': %s\n"), outfile, gpg_strerror (rc));
goto leave;
}
else if (opt.verbose)
log_info (_("writing to '%s'\n"), outfile);
}
else if ((rc = open_outfile (-1, fname,
opt.armor? 1 : detached? 2 : 0, 0, &out)))
{
goto leave;
}
/* Prepare to calculate the MD over the input. */
if (opt.textmode && !outfile && !multifile)
{
memset (&tfx, 0, sizeof tfx);
iobuf_push_filter (inp, text_filter, &tfx);
}
if (gcry_md_open (&mfx.md, 0, 0))
BUG ();
if (DBG_HASHING)
gcry_md_debug (mfx.md, "sign");
/* If we're encrypting and signing, it is reasonable to pick the
* hash algorithm to use out of the recipient key prefs. This is
* best effort only, as in a DSA2 and smartcard world there are
* cases where we cannot please everyone with a single hash (DSA2
* wants >160 and smartcards want =160). In the future this could
* be more complex with different hashes for each sk, but the
* current design requires a single hash for all SKs. */
if (pk_list)
{
if (opt.def_digest_algo)
{
if (!opt.expert
&& select_algo_from_prefs (pk_list,PREFTYPE_HASH,
opt.def_digest_algo,
NULL) != opt.def_digest_algo)
{
log_info (_("WARNING: forcing digest algorithm %s (%d)"
" violates recipient preferences\n"),
gcry_md_algo_name (opt.def_digest_algo),
opt.def_digest_algo);
}
}
else
{
int algo;
int smartcard=0;
union pref_hint hint;
hint.digest_length = 0;
/* Of course, if the recipient asks for something
* unreasonable (like the wrong hash for a DSA key) then
* don't do it. Check all sk's - if any are DSA or live
* on a smartcard, then the hash has restrictions and we
* may not be able to give the recipient what they want.
* For DSA, pass a hint for the largest q we have. Note
* that this means that a q>160 key will override a q=160
* key and force the use of truncation for the q=160 key.
* The alternative would be to ignore the recipient prefs
* completely and get a different hash for each DSA key in
* hash_for(). The override behavior here is more or less
* reasonable as it is under the control of the user which
* keys they sign with for a given message and the fact
* that the message with multiple signatures won't be
* usable on an implementation that doesn't understand
* DSA2 anyway. */
for (sk_rover = sk_list; sk_rover; sk_rover = sk_rover->next )
{
if (sk_rover->pk->pubkey_algo == PUBKEY_ALGO_DSA
|| sk_rover->pk->pubkey_algo == PUBKEY_ALGO_ECDSA)
{
int temp_hashlen = gcry_mpi_get_nbits (sk_rover->pk->pkey[1]);
if (sk_rover->pk->pubkey_algo == PUBKEY_ALGO_ECDSA)
temp_hashlen = ecdsa_qbits_from_Q (temp_hashlen);
temp_hashlen = (temp_hashlen+7)/8;
/* Pick a hash that is large enough for our largest Q */
if (hint.digest_length < temp_hashlen)
hint.digest_length = temp_hashlen;
}
/* FIXME: need to check gpg-agent for this. */
/* else if (sk_rover->pk->is_protected */
/* && sk_rover->pk->protect.s2k.mode == 1002) */
/* smartcard = 1; */
}
/* Current smartcards only do 160-bit hashes. If we have
* to have a >160-bit hash, then we can't use the
* recipient prefs as we'd need both =160 and >160 at the
* same time and recipient prefs currently require a
* single hash for all signatures. All this may well have
* to change as the cards add algorithms. */
if ((!smartcard || (smartcard && hint.digest_length==20))
&& ((algo = select_algo_from_prefs (pk_list, PREFTYPE_HASH,
-1, &hint)) > 0))
{
recipient_digest_algo = algo;
}
}
}
for (sk_rover = sk_list; sk_rover; sk_rover = sk_rover->next)
gcry_md_enable (mfx.md, hash_for (sk_rover->pk));
if (!multifile)
iobuf_push_filter (inp, md_filter, &mfx);
if (detached && !encryptflag)
afx->what = 2;
if (opt.armor && !outfile)
push_armor_filter (afx, out);
if (encryptflag)
{
efx.pk_list = pk_list;
/* fixme: set efx.cfx.datalen if known */
iobuf_push_filter (out, encrypt_filter, &efx);
}
if (opt.compress_algo && !outfile && !detached)
{
int compr_algo = opt.compress_algo;
/* If not forced by user */
if (compr_algo==-1)
{
/* If we're not encrypting, then select_algo_from_prefs
* will fail and we'll end up with the default. If we are
* encrypting, select_algo_from_prefs cannot fail since
* there is an assumed preference for uncompressed data.
* Still, if it did fail, we'll also end up with the
* default. */
if ((compr_algo = select_algo_from_prefs (pk_list, PREFTYPE_ZIP,
-1, NULL)) == -1)
{
compr_algo = default_compress_algo();
}
}
else if (!opt.expert && pk_list
&& select_algo_from_prefs (pk_list, PREFTYPE_ZIP,
compr_algo, NULL) != compr_algo)
{
log_info (_("WARNING: forcing compression algorithm %s (%d)"
" violates recipient preferences\n"),
compress_algo_to_string (compr_algo), compr_algo);
}
/* Algo 0 means no compression. */
if (compr_algo)
push_compress_filter (out, &zfx, compr_algo);
}
/* Write the one-pass signature packets if needed */
if (!detached)
{
rc = write_onepass_sig_packets (sk_list, out,
opt.textmode && !outfile ? 0x01:0x00);
if (rc)
goto leave;
}
write_status_begin_signing (mfx.md);
/* Setup the inner packet. */
if (detached)
{
if (multifile)
{
strlist_t sl;
if (opt.verbose)
log_info (_("signing:") );
/* Must walk reverse trough this list. */
for (sl = strlist_last(filenames);
sl;
sl = strlist_prev( filenames, sl))
{
inp = iobuf_open (sl->d);
if (inp && is_secured_file (iobuf_get_fd (inp)))
{
iobuf_close (inp);
inp = NULL;
gpg_err_set_errno (EPERM);
}
if (!inp)
{
rc = gpg_error_from_syserror ();
log_error (_("can't open '%s': %s\n"),
sl->d, gpg_strerror (rc));
goto leave;
}
handle_progress (pfx, inp, sl->d);
if (opt.verbose)
log_printf (" '%s'", sl->d );
if (opt.textmode)
{
memset (&tfx, 0, sizeof tfx);
iobuf_push_filter (inp, text_filter, &tfx);
}
iobuf_push_filter (inp, md_filter, &mfx);
while (iobuf_get (inp) != -1)
;
iobuf_close (inp);
inp = NULL;
}
if (opt.verbose)
log_printf ("\n");
}
else
{
/* Read, so that the filter can calculate the digest. */
while (iobuf_get(inp) != -1)
;
}
}
else
{
rc = write_plaintext_packet (out, inp, fname,
(opt.textmode && !outfile) ?
(opt.mimemode? 'm' : 't') : 'b',
&extrahash);
}
/* Catch errors from above. */
if (rc)
goto leave;
/* Write the signatures. */
rc = write_signature_packets (ctrl, sk_list, out, mfx.md, extrahash,
opt.textmode && !outfile? 0x01 : 0x00,
0, duration, detached ? 'D':'S', NULL);
if (rc)
goto leave;
leave:
if (rc)
iobuf_cancel (out);
else
{
iobuf_close (out);
if (encryptflag)
write_status (STATUS_END_ENCRYPTION);
}
iobuf_close (inp);
gcry_md_close (mfx.md);
release_sk_list (sk_list);
release_pk_list (pk_list);
recipient_digest_algo = 0;
release_progress_context (pfx);
release_armor_context (afx);
xfree (extrahash);
return rc;
}
/*
* Make a clear signature. Note that opt.armor is not needed.
*/
int
clearsign_file (ctrl_t ctrl,
const char *fname, strlist_t locusr, const char *outfile)
{
armor_filter_context_t *afx;
progress_filter_context_t *pfx;
gcry_md_hd_t textmd = NULL;
iobuf_t inp = NULL;
iobuf_t out = NULL;
PACKET pkt;
int rc = 0;
SK_LIST sk_list = NULL;
SK_LIST sk_rover = NULL;
u32 duration = 0;
pfx = new_progress_context ();
afx = new_armor_context ();
init_packet( &pkt );
if (opt.ask_sig_expire && !opt.batch)
duration = ask_expire_interval (1, opt.def_sig_expire);
else
duration = parse_expire_string (opt.def_sig_expire);
/* Note: In the old non-agent version the following call used to
* unprotect the secret key. This is now done on demand by the agent. */
if ((rc=build_sk_list (ctrl, locusr, &sk_list, PUBKEY_USAGE_SIG)))
goto leave;
/* Prepare iobufs. */
inp = iobuf_open (fname);
if (inp && is_secured_file (iobuf_get_fd (inp)))
{
iobuf_close (inp);
inp = NULL;
gpg_err_set_errno (EPERM);
}
if (!inp)
{
rc = gpg_error_from_syserror ();
log_error (_("can't open '%s': %s\n"),
fname? fname: "[stdin]", gpg_strerror (rc));
goto leave;
}
handle_progress (pfx, inp, fname);
if (outfile)
{
if (is_secured_filename (outfile))
{
outfile = NULL;
gpg_err_set_errno (EPERM);
}
else
out = iobuf_create (outfile, 0);
if (!out)
{
rc = gpg_error_from_syserror ();
log_error (_("can't create '%s': %s\n"), outfile, gpg_strerror (rc));
goto leave;
}
else if (opt.verbose)
log_info (_("writing to '%s'\n"), outfile);
}
else if ((rc = open_outfile (-1, fname, 1, 0, &out)))
{
goto leave;
}
iobuf_writestr (out, "-----BEGIN PGP SIGNED MESSAGE-----" LF);
{
const char *s;
int any = 0;
byte hashs_seen[256];
memset (hashs_seen, 0, sizeof hashs_seen);
iobuf_writestr (out, "Hash: " );
for (sk_rover = sk_list; sk_rover; sk_rover = sk_rover->next)
{
int i = hash_for (sk_rover->pk);
if (!hashs_seen[ i & 0xff ])
{
s = gcry_md_algo_name (i);
if (s)
{
hashs_seen[ i & 0xff ] = 1;
if (any)
iobuf_put (out, ',');
iobuf_writestr (out, s);
any = 1;
}
}
}
log_assert (any);
iobuf_writestr (out, LF);
}
if (opt.not_dash_escaped)
iobuf_writestr (out,
"NotDashEscaped: You need "GPG_NAME
" to verify this message" LF);
iobuf_writestr (out, LF );
if (gcry_md_open (&textmd, 0, 0))
BUG ();
for (sk_rover = sk_list; sk_rover; sk_rover = sk_rover->next)
gcry_md_enable (textmd, hash_for(sk_rover->pk));
if (DBG_HASHING)
gcry_md_debug (textmd, "clearsign");
copy_clearsig_text (out, inp, textmd, !opt.not_dash_escaped, opt.escape_from);
/* fixme: check for read errors */
/* Now write the armor. */
afx->what = 2;
push_armor_filter (afx, out);
/* Write the signatures. */
rc = write_signature_packets (ctrl, sk_list, out, textmd, NULL, 0x01, 0,
duration, 'C', NULL);
if (rc)
goto leave;
leave:
if (rc)
iobuf_cancel (out);
else
iobuf_close (out);
iobuf_close (inp);
gcry_md_close (textmd);
release_sk_list (sk_list);
release_progress_context (pfx);
release_armor_context (afx);
return rc;
}
/*
* Sign and conventionally encrypt the given file.
* FIXME: Far too much code is duplicated - revamp the whole file.
*/
int
sign_symencrypt_file (ctrl_t ctrl, const char *fname, strlist_t locusr)
{
armor_filter_context_t *afx;
progress_filter_context_t *pfx;
compress_filter_context_t zfx;
md_filter_context_t mfx;
text_filter_context_t tfx;
cipher_filter_context_t cfx;
iobuf_t inp = NULL;
iobuf_t out = NULL;
PACKET pkt;
STRING2KEY *s2k = NULL;
int rc = 0;
SK_LIST sk_list = NULL;
SK_LIST sk_rover = NULL;
int algo;
u32 duration = 0;
int canceled;
pt_extra_hash_data_t extrahash = NULL;
pfx = new_progress_context ();
afx = new_armor_context ();
memset (&zfx, 0, sizeof zfx);
memset (&mfx, 0, sizeof mfx);
memset (&tfx, 0, sizeof tfx);
memset (&cfx, 0, sizeof cfx);
init_packet (&pkt);
if (opt.ask_sig_expire && !opt.batch)
duration = ask_expire_interval (1, opt.def_sig_expire);
else
duration = parse_expire_string (opt.def_sig_expire);
/* Note: In the old non-agent version the following call used to
* unprotect the secret key. This is now done on demand by the agent. */
rc = build_sk_list (ctrl, locusr, &sk_list, PUBKEY_USAGE_SIG);
if (rc)
goto leave;
/* Prepare iobufs. */
inp = iobuf_open (fname);
if (inp && is_secured_file (iobuf_get_fd (inp)))
{
iobuf_close (inp);
inp = NULL;
gpg_err_set_errno (EPERM);
}
if (!inp)
{
rc = gpg_error_from_syserror ();
log_error (_("can't open '%s': %s\n"),
fname? fname: "[stdin]", gpg_strerror (rc));
goto leave;
}
handle_progress (pfx, inp, fname);
/* Prepare key. */
s2k = xmalloc_clear (sizeof *s2k);
s2k->mode = opt.s2k_mode;
s2k->hash_algo = S2K_DIGEST_ALGO;
algo = default_cipher_algo ();
cfx.dek = passphrase_to_dek (algo, s2k, 1, 1, NULL, &canceled);
if (!cfx.dek || !cfx.dek->keylen)
{
rc = gpg_error (canceled?GPG_ERR_CANCELED:GPG_ERR_BAD_PASSPHRASE);
log_error (_("error creating passphrase: %s\n"), gpg_strerror (rc));
goto leave;
}
cfx.dek->use_aead = use_aead (NULL, cfx.dek->algo);
if (!cfx.dek->use_aead)
cfx.dek->use_mdc = !!use_mdc (NULL, cfx.dek->algo);
if (!opt.quiet || !opt.batch)
log_info (_("%s.%s encryption will be used\n"),
openpgp_cipher_algo_name (algo),
cfx.dek->use_aead? openpgp_aead_algo_name (cfx.dek->use_aead)
/**/ : "CFB");
/* Now create the outfile. */
rc = open_outfile (-1, fname, opt.armor? 1:0, 0, &out);
if (rc)
goto leave;
/* Prepare to calculate the MD over the input. */
if (opt.textmode)
iobuf_push_filter (inp, text_filter, &tfx);
if (gcry_md_open (&mfx.md, 0, 0))
BUG ();
if (DBG_HASHING)
gcry_md_debug (mfx.md, "symc-sign");
for (sk_rover = sk_list; sk_rover; sk_rover = sk_rover->next)
gcry_md_enable (mfx.md, hash_for (sk_rover->pk));
iobuf_push_filter (inp, md_filter, &mfx);
/* Push armor output filter */
if (opt.armor)
push_armor_filter (afx, out);
/* Write the symmetric key packet */
/* (current filters: armor)*/
{
PKT_symkey_enc *enc = xmalloc_clear( sizeof *enc );
enc->version = 4;
enc->cipher_algo = cfx.dek->algo;
enc->s2k = *s2k;
pkt.pkttype = PKT_SYMKEY_ENC;
pkt.pkt.symkey_enc = enc;
if ((rc = build_packet (out, &pkt)))
log_error ("build symkey packet failed: %s\n", gpg_strerror (rc));
xfree (enc);
}
/* Push the encryption filter */
iobuf_push_filter (out,
cfx.dek->use_aead? cipher_filter_aead
/**/ : cipher_filter_cfb,
&cfx);
/* Push the compress filter */
if (default_compress_algo())
{
if (cfx.dek && (cfx.dek->use_mdc || cfx.dek->use_aead))
zfx.new_ctb = 1;
push_compress_filter (out, &zfx,default_compress_algo() );
}
/* Write the one-pass signature packets */
/* (current filters: zip - encrypt - armor) */
rc = write_onepass_sig_packets (sk_list, out, opt.textmode? 0x01:0x00);
if (rc)
goto leave;
write_status_begin_signing (mfx.md);
/* Pipe data through all filters; i.e. write the signed stuff. */
/* (current filters: zip - encrypt - armor) */
rc = write_plaintext_packet (out, inp, fname,
opt.textmode ? (opt.mimemode?'m':'t'):'b',
&extrahash);
if (rc)
goto leave;
/* Write the signatures. */
/* (current filters: zip - encrypt - armor) */
rc = write_signature_packets (ctrl, sk_list, out, mfx.md, extrahash,
opt.textmode? 0x01 : 0x00,
0, duration, 'S', NULL);
if (rc)
goto leave;
leave:
if (rc)
iobuf_cancel (out);
else
{
iobuf_close (out);
write_status (STATUS_END_ENCRYPTION);
}
iobuf_close (inp);
release_sk_list (sk_list);
gcry_md_close (mfx.md);
xfree (cfx.dek);
xfree (s2k);
release_progress_context (pfx);
release_armor_context (afx);
xfree (extrahash);
return rc;
}
/*
* Create a v4 signature in *RET_SIG.
*
* PK is the primary key to sign (required for all sigs)
* UID is the user id to sign (required for 0x10..0x13, 0x30)
* SUBPK is subkey to sign (required for 0x18, 0x19, 0x28)
*
* PKSK is the signing key
*
* SIGCLASS is the type of signature to create.
*
* DIGEST_ALGO is the digest algorithm. If it is 0 the function
* selects an appropriate one.
*
* TIMESTAMP is the timestamp to use for the signature. 0 means "now"
*
* DURATION is the amount of time (in seconds) until the signature
* expires.
*
* This function creates the following subpackets: issuer, created,
* and expire (if duration is not 0). Additional subpackets can be
* added using MKSUBPKT, which is called after these subpackets are
* added and before the signature is generated. OPAQUE is passed to
* MKSUBPKT.
*/
int
make_keysig_packet (ctrl_t ctrl,
PKT_signature **ret_sig, PKT_public_key *pk,
PKT_user_id *uid, PKT_public_key *subpk,
PKT_public_key *pksk,
- int sigclass, int digest_algo,
+ int sigclass,
u32 timestamp, u32 duration,
int (*mksubpkt)(PKT_signature *, void *), void *opaque,
const char *cache_nonce)
{
PKT_signature *sig;
int rc = 0;
int sigversion;
+ int digest_algo;
gcry_md_hd_t md;
log_assert ((sigclass >= 0x10 && sigclass <= 0x13) || sigclass == 0x1F
|| sigclass == 0x20 || sigclass == 0x18 || sigclass == 0x19
|| sigclass == 0x30 || sigclass == 0x28 );
if (pksk->version >= 5)
sigversion = 5;
else
sigversion = 4;
- if (!digest_algo)
+ /* Select the digest algo to use. */
+ if (opt.cert_digest_algo) /* Forceful override by the user. */
+ digest_algo = opt.cert_digest_algo;
+ else if (pksk->pubkey_algo == PUBKEY_ALGO_DSA) /* Meet DSA requirements. */
+ digest_algo = match_dsa_hash (gcry_mpi_get_nbits (pksk->pkey[1])/8);
+ else if (pksk->pubkey_algo == PUBKEY_ALGO_ECDSA /* Meet ECDSA requirements. */
+ || pksk->pubkey_algo == PUBKEY_ALGO_EDDSA)
{
- /* Basically, this means use SHA1 always unless the user
- * specified something (use whatever they said), or it's DSA
- * (use the best match). They still can't pick an inappropriate
- * hash for DSA or the signature will fail. Note that this
- * still allows the caller of make_keysig_packet to override the
- * user setting if it must. */
-
- if (opt.cert_digest_algo)
- digest_algo = opt.cert_digest_algo;
- else if (pksk->pubkey_algo == PUBKEY_ALGO_DSA)
- digest_algo = match_dsa_hash (gcry_mpi_get_nbits (pksk->pkey[1])/8);
- else if (pksk->pubkey_algo == PUBKEY_ALGO_ECDSA
- || pksk->pubkey_algo == PUBKEY_ALGO_EDDSA)
- {
- if (openpgp_oid_is_ed25519 (pksk->pkey[0]))
- digest_algo = DIGEST_ALGO_SHA256;
- else
- digest_algo = match_dsa_hash
- (ecdsa_qbits_from_Q (gcry_mpi_get_nbits (pksk->pkey[1]))/8);
- }
+ if (openpgp_oid_is_ed25519 (pksk->pkey[0]))
+ digest_algo = DIGEST_ALGO_SHA256;
else
- digest_algo = DEFAULT_DIGEST_ALGO;
+ digest_algo = match_dsa_hash
+ (ecdsa_qbits_from_Q (gcry_mpi_get_nbits (pksk->pkey[1]))/8);
}
+ else /* Use the default. */
+ digest_algo = DEFAULT_DIGEST_ALGO;
if (gcry_md_open (&md, digest_algo, 0))
BUG ();
/* Hash the public key certificate. */
hash_public_key (md, pk);
if (sigclass == 0x18 || sigclass == 0x19 || sigclass == 0x28)
{
/* Hash the subkey binding/backsig/revocation. */
hash_public_key (md, subpk);
}
else if (sigclass != 0x1F && sigclass != 0x20)
{
/* Hash the user id. */
hash_uid (md, sigversion, uid);
}
/* Make the signature packet. */
sig = xmalloc_clear (sizeof *sig);
sig->version = sigversion;
sig->flags.exportable = 1;
sig->flags.revocable = 1;
keyid_from_pk (pksk, sig->keyid);
sig->pubkey_algo = pksk->pubkey_algo;
sig->digest_algo = digest_algo;
sig->timestamp = timestamp? timestamp : make_timestamp ();
if (duration)
sig->expiredate = sig->timestamp + duration;
sig->sig_class = sigclass;
build_sig_subpkt_from_sig (sig, pksk);
mk_notation_policy_etc (sig, pk, pksk);
/* Crucial that the call to mksubpkt comes LAST before the calls
* to finalize the sig as that makes it possible for the mksubpkt
* function to get a reliable pointer to the subpacket area. */
if (mksubpkt)
rc = (*mksubpkt)(sig, opaque);
if (!rc)
{
hash_sigversion_to_magic (md, sig, NULL);
gcry_md_final (md);
rc = complete_sig (ctrl, sig, pksk, md, cache_nonce);
}
gcry_md_close (md);
if (rc)
free_seckey_enc (sig);
else
*ret_sig = sig;
return rc;
}
/*
* Create a new signature packet based on an existing one.
* Only user ID signatures are supported for now.
* PK is the public key to work on.
* PKSK is the key used to make the signature.
*
* TODO: Merge this with make_keysig_packet.
*/
gpg_error_t
update_keysig_packet (ctrl_t ctrl,
PKT_signature **ret_sig,
PKT_signature *orig_sig,
PKT_public_key *pk,
PKT_user_id *uid,
PKT_public_key *subpk,
PKT_public_key *pksk,
int (*mksubpkt)(PKT_signature *, void *),
void *opaque)
{
PKT_signature *sig;
gpg_error_t rc = 0;
int digest_algo;
gcry_md_hd_t md;
if ((!orig_sig || !pk || !pksk)
|| (orig_sig->sig_class >= 0x10 && orig_sig->sig_class <= 0x13 && !uid)
|| (orig_sig->sig_class == 0x18 && !subpk))
return GPG_ERR_GENERAL;
+ /* Either use the override digest algo or in the normal case the
+ * original digest algorithm. However, iff the original digest
+ * algorithms is SHA-1 and we are in gnupg or de-vs compliance mode
+ * we switch to SHA-256 (done by the macro). */
if (opt.cert_digest_algo)
digest_algo = opt.cert_digest_algo;
+ else if (pksk->pubkey_algo == PUBKEY_ALGO_DSA
+ || pksk->pubkey_algo == PUBKEY_ALGO_ECDSA
+ || pksk->pubkey_algo == PUBKEY_ALGO_EDDSA)
+ digest_algo = orig_sig->digest_algo;
+ else if (orig_sig->digest_algo == DIGEST_ALGO_SHA1
+ || orig_sig->digest_algo == DIGEST_ALGO_RMD160)
+ digest_algo = DEFAULT_DIGEST_ALGO;
else
digest_algo = orig_sig->digest_algo;
if (gcry_md_open (&md, digest_algo, 0))
BUG ();
/* Hash the public key certificate and the user id. */
hash_public_key (md, pk);
if (orig_sig->sig_class == 0x18)
hash_public_key (md, subpk);
else
hash_uid (md, orig_sig->version, uid);
/* Create a new signature packet. */
sig = copy_signature (NULL, orig_sig);
sig->digest_algo = digest_algo;
/* We need to create a new timestamp so that new sig expiration
* calculations are done correctly... */
sig->timestamp = make_timestamp();
/* ... but we won't make a timestamp earlier than the existing
* one. */
{
int tmout = 0;
while (sig->timestamp <= orig_sig->timestamp)
{
if (++tmout > 5 && !opt.ignore_time_conflict)
{
rc = gpg_error (GPG_ERR_TIME_CONFLICT);
goto leave;
}
gnupg_sleep (1);
sig->timestamp = make_timestamp();
}
}
/* Note that already expired sigs will remain expired (with a
* duration of 1) since build-packet.c:build_sig_subpkt_from_sig
* detects this case. */
/* Put the updated timestamp into the sig. Note that this will
* automagically lower any sig expiration dates to correctly
* correspond to the differences in the timestamps (i.e. the
* duration will shrink). */
build_sig_subpkt_from_sig (sig, pksk);
if (mksubpkt)
rc = (*mksubpkt)(sig, opaque);
if (!rc)
{
hash_sigversion_to_magic (md, sig, NULL);
gcry_md_final (md);
rc = complete_sig (ctrl, sig, pksk, md, NULL);
}
leave:
gcry_md_close (md);
if (rc)
free_seckey_enc (sig);
else
*ret_sig = sig;
return rc;
}
diff --git a/g10/skclist.c b/g10/skclist.c
index c9c41d0d9..c13566e2b 100644
--- a/g10/skclist.c
+++ b/g10/skclist.c
@@ -1,551 +1,590 @@
/* skclist.c - Build a list of secret keys
* Copyright (C) 1998, 1999, 2000, 2001, 2006,
* 2010 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include "gpg.h"
#include "options.h"
#include "packet.h"
#include "../common/status.h"
#include "keydb.h"
#include "../common/util.h"
#include "../common/i18n.h"
#include "call-agent.h"
/* Return true if Libgcrypt's RNG is in faked mode. */
int
random_is_faked (void)
{
return !!gcry_control (GCRYCTL_FAKED_RANDOM_P, 0);
}
void
release_sk_list (SK_LIST sk_list)
{
SK_LIST sk_rover;
for (; sk_list; sk_list = sk_rover)
{
sk_rover = sk_list->next;
free_public_key (sk_list->pk);
xfree (sk_list);
}
}
/* Check that we are only using keys which don't have
* the string "(insecure!)" or "not secure" or "do not use"
* in one of the user ids. */
static int
is_insecure (ctrl_t ctrl, PKT_public_key *pk)
{
u32 keyid[2];
KBNODE node = NULL, u;
int insecure = 0;
keyid_from_pk (pk, keyid);
node = get_pubkeyblock (ctrl, keyid);
for (u = node; u; u = u->next)
{
if (u->pkt->pkttype == PKT_USER_ID)
{
PKT_user_id *id = u->pkt->pkt.user_id;
if (id->attrib_data)
continue; /* skip attribute packets */
if (strstr (id->name, "(insecure!)")
|| strstr (id->name, "not secure")
|| strstr (id->name, "do not use")
|| strstr (id->name, "(INSECURE!)"))
{
insecure = 1;
break;
}
}
}
release_kbnode (node);
return insecure;
}
static int
key_present_in_sk_list (SK_LIST sk_list, PKT_public_key *pk)
{
for (; sk_list; sk_list = sk_list->next)
{
if (!cmp_public_keys (sk_list->pk, pk))
return 0;
}
return -1;
}
static int
is_duplicated_entry (strlist_t list, strlist_t item)
{
for (; list && list != item; list = list->next)
{
if (!strcmp (list->d, item->d))
return 1;
}
return 0;
}
gpg_error_t
build_sk_list (ctrl_t ctrl,
strlist_t locusr, SK_LIST *ret_sk_list, unsigned int use)
{
gpg_error_t err;
SK_LIST sk_list = NULL;
/* XXX: Change this function to use get_pubkeys instead of
getkey_byname to detect ambiguous key specifications and warn
about duplicate keyblocks. For ambiguous key specifications on
the command line or provided interactively, prompt the user to
select the best key. If a key specification is ambiguous and we
are in batch mode, die. */
if (!locusr) /* No user ids given - use the card key or the default key. */
{
struct agent_card_info_s info;
PKT_public_key *pk;
char *serialno;
memset (&info, 0, sizeof(info));
pk = xmalloc_clear (sizeof *pk);
pk->req_usage = use;
/* Check if a card is available. If any, use the key as a hint. */
err = agent_scd_serialno (&serialno, NULL);
if (!err)
{
xfree (serialno);
err = agent_scd_getattr ("KEY-FPR", &info);
if (err)
log_error ("error retrieving key fingerprint from card: %s\n",
gpg_strerror (err));
}
err = get_seckey_default_or_card (ctrl, pk,
info.fpr1len? info.fpr1 : NULL,
info.fpr1len);
if (err)
{
free_public_key (pk);
pk = NULL;
log_error ("no default secret key: %s\n", gpg_strerror (err));
write_status_text (STATUS_INV_SGNR, get_inv_recpsgnr_code (err));
}
else if ((err = openpgp_pk_test_algo2 (pk->pubkey_algo, use)))
{
free_public_key (pk);
pk = NULL;
log_error ("invalid default secret key: %s\n", gpg_strerror (err));
write_status_text (STATUS_INV_SGNR, get_inv_recpsgnr_code (err));
}
else
{
SK_LIST r;
if (random_is_faked () && !is_insecure (ctrl, pk))
{
log_info (_("key is not flagged as insecure - "
"can't use it with the faked RNG!\n"));
free_public_key (pk);
pk = NULL;
write_status_text (STATUS_INV_SGNR,
get_inv_recpsgnr_code (GPG_ERR_NOT_TRUSTED));
}
else
{
r = xmalloc (sizeof *r);
r->pk = pk;
pk = NULL;
r->next = sk_list;
r->mark = 0;
sk_list = r;
}
}
}
else /* Check the given user ids. */
{
strlist_t locusr_orig = locusr;
for (; locusr; locusr = locusr->next)
{
PKT_public_key *pk;
err = 0;
/* Do an early check against duplicated entries. However
* this won't catch all duplicates because the user IDs may
* be specified in different ways. */
if (is_duplicated_entry (locusr_orig, locusr))
{
log_info (_("skipped \"%s\": duplicated\n"), locusr->d);
continue;
}
pk = xmalloc_clear (sizeof *pk);
pk->req_usage = use;
if ((err = getkey_byname (ctrl, NULL, pk, locusr->d, 1, NULL)))
{
free_public_key (pk);
pk = NULL;
log_error (_("skipped \"%s\": %s\n"),
locusr->d, gpg_strerror (err));
write_status_text_and_buffer
(STATUS_INV_SGNR, get_inv_recpsgnr_code (err),
locusr->d, strlen (locusr->d), -1);
}
else if (!key_present_in_sk_list (sk_list, pk))
{
free_public_key (pk);
pk = NULL;
log_info (_("skipped: secret key already present\n"));
}
else if ((err = openpgp_pk_test_algo2 (pk->pubkey_algo, use)))
{
free_public_key (pk);
pk = NULL;
log_error ("skipped \"%s\": %s\n", locusr->d, gpg_strerror (err));
write_status_text_and_buffer
(STATUS_INV_SGNR, get_inv_recpsgnr_code (err),
locusr->d, strlen (locusr->d), -1);
}
else
{
SK_LIST r;
if (pk->version == 4 && (use & PUBKEY_USAGE_SIG)
&& pk->pubkey_algo == PUBKEY_ALGO_ELGAMAL_E)
{
log_info (_("skipped \"%s\": %s\n"), locusr->d,
_("this is a PGP generated Elgamal key which"
" is not secure for signatures!"));
free_public_key (pk);
pk = NULL;
write_status_text_and_buffer
(STATUS_INV_SGNR,
get_inv_recpsgnr_code (GPG_ERR_WRONG_KEY_USAGE),
locusr->d, strlen (locusr->d), -1);
}
else if (random_is_faked () && !is_insecure (ctrl, pk))
{
log_info (_("key is not flagged as insecure - "
"can't use it with the faked RNG!\n"));
free_public_key (pk);
pk = NULL;
write_status_text_and_buffer
(STATUS_INV_SGNR,
get_inv_recpsgnr_code (GPG_ERR_NOT_TRUSTED),
locusr->d, strlen (locusr->d), -1);
}
else
{
r = xmalloc (sizeof *r);
r->pk = pk;
pk = NULL;
r->next = sk_list;
r->mark = 0;
sk_list = r;
}
}
}
}
if (!err && !sk_list)
{
log_error ("no valid signators\n");
write_status_text (STATUS_NO_SGNR, "0");
err = gpg_error (GPG_ERR_NO_USER_ID);
}
if (err)
release_sk_list (sk_list);
else
*ret_sk_list = sk_list;
return err;
}
/* Enumerate some secret keys (specifically, those specified with
* --default-key and --try-secret-key). Use the following procedure:
*
* 1) Initialize a void pointer to NULL
* 2) Pass a reference to this pointer to this function (content)
* and provide space for the secret key (sk)
* 3) Call this function as long as it does not return an error (or
* until you are done). The error code GPG_ERR_EOF indicates the
* end of the listing.
* 4) Call this function a last time with SK set to NULL,
* so that can free it's context.
*
* In pseudo-code:
*
* void *ctx = NULL;
* PKT_public_key *sk = xmalloc_clear (sizeof (*sk));
*
* while ((err = enum_secret_keys (&ctx, sk)))
* { // Process SK.
* if (done)
* break;
* sk = xmalloc_clear (sizeof (*sk));
* }
*
* // Release any resources used by CTX.
* enum_secret_keys (&ctx, NULL);
*
* if (gpg_err_code (err) != GPG_ERR_EOF)
* ; // An error occurred.
*/
gpg_error_t
enum_secret_keys (ctrl_t ctrl, void **context, PKT_public_key *sk)
{
gpg_error_t err = 0;
const char *name;
kbnode_t keyblock;
struct
{
int eof;
int state;
strlist_t sl;
strlist_t card_list;
char *serialno;
char fpr2[2 * MAX_FINGERPRINT_LEN + 3 ];
struct agent_card_info_s info;
kbnode_t keyblock;
kbnode_t node;
getkey_ctx_t ctx;
SK_LIST results;
} *c = *context;
+#if MAX_FINGERPRINT_LEN < KEYGRIP_LEN
+# error buffer too short for this configuration
+#endif
+
if (!c)
{
/* Make a new context. */
c = xtrycalloc (1, sizeof *c);
if (!c)
{
err = gpg_error_from_syserror ();
free_public_key (sk);
return err;
}
*context = c;
}
if (!sk)
{
/* Free the context. */
xfree (c->serialno);
free_strlist (c->card_list);
release_sk_list (c->results);
release_kbnode (c->keyblock);
getkey_end (ctrl, c->ctx);
xfree (c);
*context = NULL;
return 0;
}
if (c->eof)
{
free_public_key (sk);
return gpg_error (GPG_ERR_EOF);
}
for (;;)
{
/* Loop until we have a keyblock. */
while (!c->keyblock)
{
/* Loop over the list of secret keys. */
do
{
char *serialno;
name = NULL;
keyblock = NULL;
switch (c->state)
{
case 0: /* First try to use the --default-key. */
name = parse_def_secret_key (ctrl);
c->state = 1;
break;
case 1: /* Init list of keys to try. */
c->sl = opt.secret_keys_to_try;
c->state++;
break;
case 2: /* Get next item from list. */
if (c->sl)
{
name = c->sl->d;
c->sl = c->sl->next;
}
else
c->state++;
break;
case 3: /* Init list of card keys to try. */
err = agent_scd_cardlist (&c->card_list);
if (!err)
agent_scd_serialno (&c->serialno, NULL);
c->sl = c->card_list;
c->state++;
break;
case 4: /* Get next item from card list. */
if (c->sl)
{
err = agent_scd_serialno (&serialno, c->sl->d);
if (err)
{
if (opt.verbose)
log_info (_("error getting serial number of card: %s\n"),
gpg_strerror (err));
+ c->sl = c->sl->next;
continue;
}
xfree (serialno);
c->info.fpr2len = 0;
err = agent_scd_getattr ("KEY-FPR", &c->info);
+ if (!err)
+ {
+ if (c->info.fpr2len)
+ {
+ c->fpr2[0] = '0';
+ c->fpr2[1] = 'x';
+ bin2hex (c->info.fpr2, sizeof c->info.fpr2,
+ c->fpr2 + 2);
+ name = c->fpr2;
+ }
+ }
+ else if (gpg_err_code (err) == GPG_ERR_INV_NAME)
+ {
+ /* KEY-FPR not supported by the card - get
+ * the key using the keygrip. */
+ char *keyref;
+ strlist_t kplist;
+ const char *s;
+ int i;
+
+ err = agent_scd_getattr_one ("$ENCRKEYID", &keyref);
+ if (!err)
+ {
+ err = agent_scd_keypairinfo (ctrl, keyref,
+ &kplist);
+ if (!err)
+ {
+ c->fpr2[0] = '&';
+ for (i=1, s=kplist->d;
+ (*s && *s != ' '
+ && i < sizeof c->fpr2 - 3);
+ s++, i++)
+ c->fpr2[i] = *s;
+ c->fpr2[i] = 0;
+ name = c->fpr2;
+ free_strlist (kplist);
+ }
+ xfree (keyref);
+ }
+ }
+
if (err)
- log_error ("error retrieving key fingerprint from card: %s\n",
+ log_error ("error retrieving key from card: %s\n",
gpg_strerror (err));
- if (c->info.fpr2len)
- {
- c->fpr2[0] = '0';
- c->fpr2[1] = 'x';
- bin2hex (c->info.fpr2, sizeof c->info.fpr2,c->fpr2+2);
- name = c->fpr2;
- }
c->sl = c->sl->next;
}
else
{
serialno = c->serialno;
if (serialno)
{
/* Select the original card again. */
agent_scd_serialno (&c->serialno, serialno);
xfree (serialno);
}
c->state++;
}
break;
case 5: /* Init search context to enum all secret keys. */
err = getkey_bynames (ctrl, &c->ctx, NULL, NULL, 1,
&keyblock);
if (err)
{
release_kbnode (keyblock);
keyblock = NULL;
getkey_end (ctrl, c->ctx);
c->ctx = NULL;
}
c->state++;
break;
case 6: /* Get next item from the context. */
if (c->ctx)
{
err = getkey_next (ctrl, c->ctx, NULL, &keyblock);
if (err)
{
release_kbnode (keyblock);
keyblock = NULL;
getkey_end (ctrl, c->ctx);
c->ctx = NULL;
}
}
else
c->state++;
break;
default: /* No more names to check - stop. */
c->eof = 1;
free_public_key (sk);
return gpg_error (GPG_ERR_EOF);
}
}
while ((!name || !*name) && !keyblock);
if (keyblock)
c->node = c->keyblock = keyblock;
else
{
err = getkey_byname (ctrl, NULL, NULL, name, 1, &c->keyblock);
if (err)
{
/* getkey_byname might return a keyblock even in the
error case - I have not checked. Thus better release
it. */
release_kbnode (c->keyblock);
c->keyblock = NULL;
}
else
c->node = c->keyblock;
}
}
/* Get the next key from the current keyblock. */
for (; c->node; c->node = c->node->next)
{
if (c->node->pkt->pkttype == PKT_PUBLIC_KEY
|| c->node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
SK_LIST r;
/* Skip this candidate if it's already enumerated. */
for (r = c->results; r; r = r->next)
if (!cmp_public_keys (r->pk, c->node->pkt->pkt.public_key))
break;
if (r)
continue;
copy_public_key (sk, c->node->pkt->pkt.public_key);
c->node = c->node->next;
r = xtrycalloc (1, sizeof (*r));
if (!r)
{
err = gpg_error_from_syserror ();
free_public_key (sk);
return err;
}
r->pk = sk;
r->next = c->results;
c->results = r;
return 0; /* Found. */
}
}
/* Dispose the keyblock and continue. */
release_kbnode (c->keyblock);
c->keyblock = NULL;
}
}
diff --git a/g10/tofu.c b/g10/tofu.c
index 44f354512..e78da15c1 100644
--- a/g10/tofu.c
+++ b/g10/tofu.c
@@ -1,4045 +1,4037 @@
/* tofu.c - TOFU trust model.
* Copyright (C) 2015, 2016 g10 Code GmbH
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
/* TODO:
- Format the fingerprints nicely when printing (similar to gpg
--list-keys)
*/
#include <config.h>
#include <stdio.h>
#include <sys/stat.h>
#include <stdarg.h>
#include <sqlite3.h>
#include <time.h>
#include "gpg.h"
#include "../common/types.h"
#include "../common/logging.h"
#include "../common/stringhelp.h"
#include "options.h"
#include "../common/mbox-util.h"
#include "../common/i18n.h"
#include "../common/ttyio.h"
#include "trustdb.h"
#include "../common/mkdir_p.h"
#include "gpgsql.h"
#include "../common/status.h"
#include "tofu.h"
#define CONTROL_L ('L' - 'A' + 1)
/* Number of days with signed / ecnrypted messages required to
* indicate that enough history is available for basic trust. */
#define BASIC_TRUST_THRESHOLD 4
/* Number of days with signed / encrypted messages required to
* indicate that a lot of history is available. */
#define FULL_TRUST_THRESHOLD 21
/* A struct with data pertaining to the tofu DB. There is one such
struct per session and it is cached in session's ctrl structure.
To initialize this or get the current singleton, call opendbs().
There is no need to explicitly release it; cleanup is done when the
CTRL object is released. */
struct tofu_dbs_s
{
sqlite3 *db;
char *want_lock_file;
time_t want_lock_file_ctime;
struct
{
sqlite3_stmt *savepoint_batch;
sqlite3_stmt *savepoint_batch_commit;
sqlite3_stmt *record_binding_get_old_policy;
sqlite3_stmt *record_binding_update;
sqlite3_stmt *get_policy_select_policy_and_conflict;
sqlite3_stmt *get_trust_bindings_with_this_email;
sqlite3_stmt *get_trust_gather_other_user_ids;
sqlite3_stmt *get_trust_gather_signature_stats;
sqlite3_stmt *get_trust_gather_encryption_stats;
sqlite3_stmt *register_already_seen;
sqlite3_stmt *register_signature;
sqlite3_stmt *register_encryption;
} s;
int in_batch_transaction;
int in_transaction;
time_t batch_update_started;
};
#define STRINGIFY(s) STRINGIFY2(s)
#define STRINGIFY2(s) #s
/* The grouping parameters when collecting signature statistics. */
/* If a message is signed a couple of hours in the future, just assume
some clock skew. */
#define TIME_AGO_FUTURE_IGNORE (2 * 60 * 60)
/* Days. */
#define TIME_AGO_UNIT_SMALL (24 * 60 * 60)
#define TIME_AGO_SMALL_THRESHOLD (7 * TIME_AGO_UNIT_SMALL)
/* Months. */
#define TIME_AGO_UNIT_MEDIUM (30 * 24 * 60 * 60)
#define TIME_AGO_MEDIUM_THRESHOLD (2 * TIME_AGO_UNIT_MEDIUM)
/* Years. */
#define TIME_AGO_UNIT_LARGE (365 * 24 * 60 * 60)
#define TIME_AGO_LARGE_THRESHOLD (2 * TIME_AGO_UNIT_LARGE)
/* Local prototypes. */
static gpg_error_t end_transaction (ctrl_t ctrl, int only_batch);
static char *email_from_user_id (const char *user_id);
static int show_statistics (tofu_dbs_t dbs,
const char *fingerprint, const char *email,
enum tofu_policy policy,
estream_t outfp, int only_status_fd, time_t now);
const char *
tofu_policy_str (enum tofu_policy policy)
{
switch (policy)
{
case TOFU_POLICY_NONE: return "none";
case TOFU_POLICY_AUTO: return "auto";
case TOFU_POLICY_GOOD: return "good";
case TOFU_POLICY_UNKNOWN: return "unknown";
case TOFU_POLICY_BAD: return "bad";
case TOFU_POLICY_ASK: return "ask";
default: return "???";
}
}
/* Convert a binding policy (e.g., TOFU_POLICY_BAD) to a trust level
(e.g., TRUST_BAD) in light of the current configuration. */
int
tofu_policy_to_trust_level (enum tofu_policy policy)
{
if (policy == TOFU_POLICY_AUTO)
/* If POLICY is AUTO, fallback to OPT.TOFU_DEFAULT_POLICY. */
policy = opt.tofu_default_policy;
switch (policy)
{
case TOFU_POLICY_AUTO:
/* If POLICY and OPT.TOFU_DEFAULT_POLICY are both AUTO, default
to marginal trust. */
return TRUST_MARGINAL;
case TOFU_POLICY_GOOD:
return TRUST_FULLY;
case TOFU_POLICY_UNKNOWN:
return TRUST_UNKNOWN;
case TOFU_POLICY_BAD:
return TRUST_NEVER;
case TOFU_POLICY_ASK:
return TRUST_UNKNOWN;
default:
log_bug ("Bad value for trust policy: %d\n",
opt.tofu_default_policy);
return 0;
}
}
/* Start a transaction on DB. If ONLY_BATCH is set, then this will
start a batch transaction if we haven't started a batch transaction
and one has been requested. */
static gpg_error_t
begin_transaction (ctrl_t ctrl, int only_batch)
{
tofu_dbs_t dbs = ctrl->tofu.dbs;
int rc;
char *err = NULL;
log_assert (dbs);
/* If we've been in batch update mode for a while (on average, more
* than 500 ms), to prevent starving other gpg processes, we drop
* and retake the batch lock.
*
* Note: gnupg_get_time has a one second resolution, if we wanted a
* higher resolution, we could use npth_clock_gettime. */
if (/* No real transactions. */
dbs->in_transaction == 0
/* There is an open batch transaction. */
&& dbs->in_batch_transaction
/* And some time has gone by since it was started. */
&& dbs->batch_update_started != gnupg_get_time ())
{
struct stat statbuf;
/* If we are in a batch update, then batch updates better have
been enabled. */
log_assert (ctrl->tofu.batch_updated_wanted);
/* Check if another process wants to run. (We just ignore any
* stat failure. A waiter might have to wait a bit longer, but
* otherwise there should be no impact.) */
if (stat (dbs->want_lock_file, &statbuf) == 0
&& statbuf.st_ctime != dbs->want_lock_file_ctime)
{
end_transaction (ctrl, 2);
/* Yield to allow another process a chance to run. Note:
* testing suggests that anything less than a 100ms tends to
* not result in the other process getting the lock. */
gnupg_usleep (100000);
}
else
dbs->batch_update_started = gnupg_get_time ();
}
if (/* We don't have an open batch transaction. */
!dbs->in_batch_transaction
&& (/* Batch mode is enabled or we are starting a new transaction. */
ctrl->tofu.batch_updated_wanted || dbs->in_transaction == 0))
{
struct stat statbuf;
/* We are in batch mode, but we don't have an open batch
* transaction. Since the batch save point must be the outer
* save point, it must be taken before the inner save point. */
log_assert (dbs->in_transaction == 0);
rc = gpgsql_stepx (dbs->db, &dbs->s.savepoint_batch,
NULL, NULL, &err,
"begin immediate transaction;", GPGSQL_ARG_END);
if (rc)
{
log_error (_("error beginning transaction on TOFU database: %s\n"),
err);
sqlite3_free (err);
return gpg_error (GPG_ERR_GENERAL);
}
dbs->in_batch_transaction = 1;
dbs->batch_update_started = gnupg_get_time ();
if (stat (dbs->want_lock_file, &statbuf) == 0)
dbs->want_lock_file_ctime = statbuf.st_ctime;
}
if (only_batch)
return 0;
log_assert (dbs->in_transaction >= 0);
dbs->in_transaction ++;
rc = gpgsql_exec_printf (dbs->db, NULL, NULL, &err,
"savepoint inner%d;",
dbs->in_transaction);
if (rc)
{
log_error (_("error beginning transaction on TOFU database: %s\n"),
err);
sqlite3_free (err);
return gpg_error (GPG_ERR_GENERAL);
}
return 0;
}
/* Commit a transaction. If ONLY_BATCH is 1, then this only ends the
* batch transaction if we have left batch mode. If ONLY_BATCH is 2,
* this commits any open batch transaction even if we are still in
* batch mode. */
static gpg_error_t
end_transaction (ctrl_t ctrl, int only_batch)
{
tofu_dbs_t dbs = ctrl->tofu.dbs;
int rc;
char *err = NULL;
if (only_batch || (! only_batch && dbs->in_transaction == 1))
{
if (!dbs)
return 0; /* Shortcut to allow for easier cleanup code. */
/* If we are releasing the batch transaction, then we better not
be in a normal transaction. */
if (only_batch)
log_assert (dbs->in_transaction == 0);
if (/* Batch mode disabled? */
(!ctrl->tofu.batch_updated_wanted || only_batch == 2)
/* But, we still have an open batch transaction? */
&& dbs->in_batch_transaction)
{
/* The batch transaction is still in open, but we've left
* batch mode. */
dbs->in_batch_transaction = 0;
dbs->in_transaction = 0;
rc = gpgsql_stepx (dbs->db, &dbs->s.savepoint_batch_commit,
NULL, NULL, &err,
"commit transaction;", GPGSQL_ARG_END);
if (rc)
{
log_error (_("error committing transaction on TOFU database: %s\n"),
err);
sqlite3_free (err);
return gpg_error (GPG_ERR_GENERAL);
}
return 0;
}
if (only_batch)
return 0;
}
log_assert (dbs);
log_assert (dbs->in_transaction > 0);
rc = gpgsql_exec_printf (dbs->db, NULL, NULL, &err,
"release inner%d;", dbs->in_transaction);
dbs->in_transaction --;
if (rc)
{
log_error (_("error committing transaction on TOFU database: %s\n"),
err);
sqlite3_free (err);
return gpg_error (GPG_ERR_GENERAL);
}
return 0;
}
static gpg_error_t
rollback_transaction (ctrl_t ctrl)
{
tofu_dbs_t dbs = ctrl->tofu.dbs;
int rc;
char *err = NULL;
log_assert (dbs);
log_assert (dbs->in_transaction > 0);
/* Be careful to not undo any progress made by closed transactions in
batch mode. */
rc = gpgsql_exec_printf (dbs->db, NULL, NULL, &err,
"rollback to inner%d;",
dbs->in_transaction);
dbs->in_transaction --;
if (rc)
{
log_error (_("error rolling back transaction on TOFU database: %s\n"),
err);
sqlite3_free (err);
return gpg_error (GPG_ERR_GENERAL);
}
return 0;
}
void
tofu_begin_batch_update (ctrl_t ctrl)
{
ctrl->tofu.batch_updated_wanted ++;
}
void
tofu_end_batch_update (ctrl_t ctrl)
{
log_assert (ctrl->tofu.batch_updated_wanted > 0);
ctrl->tofu.batch_updated_wanted --;
end_transaction (ctrl, 1);
}
/* Suspend any extant batch transaction (it is safe to call this even
no batch transaction has been started). Note: you cannot suspend a
batch transaction if you are in a normal transaction. The batch
transaction can be resumed explicitly by calling
tofu_resume_batch_transaction or implicitly by starting a normal
transaction. */
static void
tofu_suspend_batch_transaction (ctrl_t ctrl)
{
end_transaction (ctrl, 2);
}
/* Resume a batch transaction if there is no extant batch transaction
and one has been requested using tofu_begin_batch_transaction. */
static void
tofu_resume_batch_transaction (ctrl_t ctrl)
{
begin_transaction (ctrl, 1);
}
/* Wrapper around strtol which prints a warning in case of a
* conversion error. On success the converted value is stored at
* R_VALUE and 0 is returned; on error FALLBACK is stored at R_VALUE
* and an error code is returned. */
static gpg_error_t
string_to_long (long *r_value, const char *string, long fallback, int line)
{
gpg_error_t err;
char *tail = NULL;
gpg_err_set_errno (0);
*r_value = strtol (string, &tail, 0);
if (errno || !(!strcmp (tail, ".0") || !*tail))
{
err = errno? gpg_error_from_errno (errno) : gpg_error (GPG_ERR_BAD_DATA);
log_debug ("%s:%d: strtol failed for TOFU DB data; returned string"
" (string='%.10s%s'; tail='%.10s%s'): %s\n",
__FILE__, line,
string, string && strlen(string) > 10 ? "..." : "",
tail, tail && strlen(tail) > 10 ? "..." : "",
gpg_strerror (err));
*r_value = fallback;
}
else
err = 0;
return err;
}
/* Wrapper around strtoul which prints a warning in case of a
* conversion error. On success the converted value is stored at
* R_VALUE and 0 is returned; on error FALLBACK is stored at R_VALUE
* and an error code is returned. */
static gpg_error_t
string_to_ulong (unsigned long *r_value, const char *string,
unsigned long fallback, int line)
{
gpg_error_t err;
char *tail = NULL;
gpg_err_set_errno (0);
*r_value = strtoul (string, &tail, 0);
if (errno || !(!strcmp (tail, ".0") || !*tail))
{
err = errno? gpg_error_from_errno (errno) : gpg_error (GPG_ERR_BAD_DATA);
log_debug ("%s:%d: strtoul failed for TOFU DB data; returned string"
" (string='%.10s%s'; tail='%.10s%s'): %s\n",
__FILE__, line,
string, string && strlen(string) > 10 ? "..." : "",
tail, tail && strlen(tail) > 10 ? "..." : "",
gpg_strerror (err));
*r_value = fallback;
}
else
err = 0;
return err;
}
/* Collect results of a select count (*) ...; style query. Aborts if
the argument is not a valid integer (or real of the form X.0). */
static int
get_single_unsigned_long_cb (void *cookie, int argc, char **argv,
char **azColName)
{
unsigned long int *count = cookie;
(void) azColName;
log_assert (argc == 1);
if (string_to_ulong (count, argv[0], 0, __LINE__))
return 1; /* Abort. */
return 0;
}
static int
get_single_unsigned_long_cb2 (void *cookie, int argc, char **argv,
char **azColName, sqlite3_stmt *stmt)
{
(void) stmt;
return get_single_unsigned_long_cb (cookie, argc, argv, azColName);
}
/* We expect a single integer column whose name is "version". COOKIE
must point to an int. This function always aborts. On error or a
if the version is bad, sets *VERSION to -1. */
static int
version_check_cb (void *cookie, int argc, char **argv, char **azColName)
{
int *version = cookie;
if (argc != 1 || strcmp (azColName[0], "version") != 0)
{
*version = -1;
return 1;
}
if (strcmp (argv[0], "1") == 0)
*version = 1;
else
{
log_error (_("unsupported TOFU database version: %s\n"), argv[0]);
*version = -1;
}
/* Don't run again. */
return 1;
}
static int
check_utks (sqlite3 *db)
{
int rc;
char *err = NULL;
struct key_item *utks;
struct key_item *ki;
int utk_count;
char *utks_string = NULL;
char keyid_str[16+1];
long utks_unchanged = 0;
/* An early version of the v1 format did not include the list of
* known ultimately trusted keys.
*
* This list is used to detect when the set of ultimately trusted
* keys changes. We need to detect this to invalidate the effective
* policy, which can change if an ultimately trusted key is added or
* removed. */
rc = sqlite3_exec (db,
"create table if not exists ultimately_trusted_keys"
" (keyid);\n",
NULL, NULL, &err);
if (rc)
{
- log_error (_("error creating 'ultimately_trusted_keys' TOFU table: %s\n"),
+ log_error ("error creating 'ultimately_trusted_keys' TOFU table: %s\n",
err);
sqlite3_free (err);
goto out;
}
utks = tdb_utks ();
for (ki = utks, utk_count = 0; ki; ki = ki->next, utk_count ++)
;
if (utk_count)
{
/* Build a list of keyids of the form "XXX","YYY","ZZZ". */
int len = (1 + 16 + 1 + 1) * utk_count;
int o = 0;
utks_string = xmalloc (len);
*utks_string = 0;
for (ki = utks, utk_count = 0; ki; ki = ki->next, utk_count ++)
{
utks_string[o ++] = '\'';
format_keyid (ki->kid, KF_LONG,
keyid_str, sizeof (keyid_str));
memcpy (&utks_string[o], keyid_str, 16);
o += 16;
utks_string[o ++] = '\'';
utks_string[o ++] = ',';
}
utks_string[o - 1] = 0;
log_assert (o == len);
}
rc = gpgsql_exec_printf
(db, get_single_unsigned_long_cb, &utks_unchanged, &err,
"select"
/* Removed UTKs? (Known UTKs in current UTKs.) */
" ((select count(*) from ultimately_trusted_keys"
" where (keyid in (%s))) == %d)"
" and"
/* New UTKs? */
" ((select count(*) from ultimately_trusted_keys"
" where keyid not in (%s)) == 0);",
utks_string ? utks_string : "",
utk_count,
utks_string ? utks_string : "");
xfree (utks_string);
if (rc)
{
log_error (_("TOFU DB error"));
print_further_info ("checking if ultimately trusted keys changed: %s",
err);
sqlite3_free (err);
goto out;
}
if (utks_unchanged)
goto out;
if (DBG_TRUST)
log_debug ("TOFU: ultimately trusted keys changed.\n");
/* Given that the set of ultimately trusted keys
* changed, clear any cached policies. */
rc = gpgsql_exec_printf
(db, NULL, NULL, &err,
"update bindings set effective_policy = %d;",
TOFU_POLICY_NONE);
if (rc)
{
log_error (_("TOFU DB error"));
print_further_info ("clearing cached policies: %s", err);
sqlite3_free (err);
goto out;
}
/* Now, update the UTK table. */
rc = sqlite3_exec (db,
"drop table ultimately_trusted_keys;",
NULL, NULL, &err);
if (rc)
{
log_error (_("TOFU DB error"));
print_further_info ("dropping ultimately_trusted_keys: %s", err);
sqlite3_free (err);
goto out;
}
rc = sqlite3_exec (db,
"create table if not exists"
" ultimately_trusted_keys (keyid);\n",
NULL, NULL, &err);
if (rc)
{
log_error (_("TOFU DB error"));
print_further_info ("creating ultimately_trusted_keys: %s", err);
sqlite3_free (err);
goto out;
}
for (ki = utks; ki; ki = ki->next)
{
format_keyid (ki->kid, KF_LONG,
keyid_str, sizeof (keyid_str));
rc = gpgsql_exec_printf
(db, NULL, NULL, &err,
"insert into ultimately_trusted_keys values ('%s');",
keyid_str);
if (rc)
{
log_error (_("TOFU DB error"));
print_further_info ("updating ultimately_trusted_keys: %s",
err);
sqlite3_free (err);
goto out;
}
}
out:
return rc;
}
/* If the DB is new, initialize it. Otherwise, check the DB's
version.
Return 0 if the database is okay and 1 otherwise. */
static int
initdb (sqlite3 *db)
{
char *err = NULL;
int rc;
unsigned long int count;
int version = -1;
rc = sqlite3_exec (db, "begin transaction;", NULL, NULL, &err);
if (rc)
{
log_error (_("error beginning transaction on TOFU database: %s\n"),
err);
sqlite3_free (err);
return 1;
}
/* If the DB has no tables, then assume this is a new DB that needs
to be initialized. */
rc = sqlite3_exec (db,
"select count(*) from sqlite_master where type='table';",
get_single_unsigned_long_cb, &count, &err);
if (rc)
{
log_error (_("error reading TOFU database: %s\n"), err);
print_further_info ("query available tables");
sqlite3_free (err);
goto out;
}
else if (count != 0)
/* Assume that the DB is already initialized. Make sure the
version is okay. */
{
rc = sqlite3_exec (db, "select version from version;", version_check_cb,
&version, &err);
if (rc == SQLITE_ABORT && version == 1)
/* Happy, happy, joy, joy. */
{
sqlite3_free (err);
rc = 0;
goto out;
}
else if (rc == SQLITE_ABORT && version == -1)
/* Unsupported version. */
{
/* An error message was already displayed. */
sqlite3_free (err);
goto out;
}
else if (rc)
/* Some error. */
{
log_error (_("error determining TOFU database's version: %s\n"), err);
sqlite3_free (err);
goto out;
}
else
{
/* Unexpected success. This can only happen if there are no
rows. (select returned 0, but expected ABORT.) */
log_error (_("error determining TOFU database's version: %s\n"),
gpg_strerror (GPG_ERR_NO_DATA));
rc = 1;
goto out;
}
}
/* Create the version table. */
rc = sqlite3_exec (db,
"create table version (version INTEGER);",
NULL, NULL, &err);
if (rc)
{
log_error (_("error initializing TOFU database: %s\n"), err);
print_further_info ("create version");
sqlite3_free (err);
goto out;
}
/* Initialize the version table, which contains a single integer
value. */
rc = sqlite3_exec (db,
"insert into version values (1);",
NULL, NULL, &err);
if (rc)
{
log_error (_("error initializing TOFU database: %s\n"), err);
print_further_info ("insert version");
sqlite3_free (err);
goto out;
}
/* The list of <fingerprint, email> bindings and auxiliary data.
*
* OID is a unique ID identifying this binding (and used by the
* signatures table, see below). Note: OIDs will never be
* reused.
*
* FINGERPRINT: The key's fingerprint.
*
* EMAIL: The normalized email address.
*
* USER_ID: The unmodified user id from which EMAIL was extracted.
*
* TIME: The time this binding was first observed.
*
* POLICY: The trust policy (TOFU_POLICY_BAD, etc. as an integer).
*
* CONFLICT is either NULL or a fingerprint. Assume that we have
* a binding <0xdeadbeef, foo@example.com> and then we observe
* <0xbaddecaf, foo@example.com>. There two bindings conflict
* (they have the same email address). When we observe the
* latter binding, we warn the user about the conflict and ask
* for a policy decision about the new binding. We also change
* the old binding's policy to ask if it was auto. So that we
* know why this occurred, we also set conflict to 0xbaddecaf.
*/
rc = gpgsql_exec_printf
(db, NULL, NULL, &err,
"create table bindings\n"
" (oid INTEGER PRIMARY KEY AUTOINCREMENT,\n"
" fingerprint TEXT, email TEXT, user_id TEXT, time INTEGER,\n"
" policy INTEGER CHECK (policy in (%d, %d, %d, %d, %d)),\n"
" conflict STRING,\n"
" unique (fingerprint, email));\n"
"create index bindings_fingerprint_email\n"
" on bindings (fingerprint, email);\n"
"create index bindings_email on bindings (email);\n",
TOFU_POLICY_AUTO, TOFU_POLICY_GOOD, TOFU_POLICY_UNKNOWN,
TOFU_POLICY_BAD, TOFU_POLICY_ASK);
if (rc)
{
log_error (_("error initializing TOFU database: %s\n"), err);
print_further_info ("create bindings");
sqlite3_free (err);
goto out;
}
/* The signatures that we have observed.
*
* BINDING refers to a record in the bindings table, which
* describes the binding (i.e., this is a foreign key that
* references bindings.oid).
*
* SIG_DIGEST is the digest stored in the signature.
*
* SIG_TIME is the timestamp stored in the signature.
*
* ORIGIN is a free-form string that describes who fed this
* signature to GnuPG (e.g., email:claws).
*
* TIME is the time this signature was registered. */
rc = sqlite3_exec (db,
"create table signatures "
" (binding INTEGER NOT NULL, sig_digest TEXT,"
" origin TEXT, sig_time INTEGER, time INTEGER,"
" primary key (binding, sig_digest, origin));",
NULL, NULL, &err);
if (rc)
{
log_error (_("error initializing TOFU database: %s\n"), err);
print_further_info ("create signatures");
sqlite3_free (err);
goto out;
}
out:
if (! rc)
{
/* Early version of the v1 format did not include the encryption
table. Add it. */
rc = sqlite3_exec (db,
"create table if not exists encryptions"
" (binding INTEGER NOT NULL,"
" time INTEGER);"
"create index if not exists encryptions_binding"
" on encryptions (binding);\n",
NULL, NULL, &err);
if (rc)
{
- log_error (_("error creating 'encryptions' TOFU table: %s\n"),
+ log_error ("error creating 'encryptions' TOFU table: %s\n",
err);
sqlite3_free (err);
}
}
if (! rc)
{
/* The effective policy for a binding. If a key is ultimately
* trusted, then the effective policy of all of its bindings is
* good. Likewise if a key is signed by an ultimately trusted
* key, etc. If the effective policy is NONE, then we need to
* recompute the effective policy. Otherwise, the effective
* policy is considered to be up to date, i.e., effective_policy
* is a cache of the computed policy. */
rc = gpgsql_exec_printf
(db, NULL, NULL, &err,
"alter table bindings"
" add column effective_policy INTEGER"
" DEFAULT %d"
" CHECK (effective_policy in (%d, %d, %d, %d, %d, %d));",
TOFU_POLICY_NONE,
TOFU_POLICY_NONE, TOFU_POLICY_AUTO, TOFU_POLICY_GOOD,
TOFU_POLICY_UNKNOWN, TOFU_POLICY_BAD, TOFU_POLICY_ASK);
if (rc)
{
if (rc == SQLITE_ERROR)
/* Almost certainly "duplicate column name", which we can
* safely ignore. */
rc = 0;
else
- log_error (_("adding column effective_policy to bindings DB: %s\n"),
+ log_error ("adding column effective_policy to bindings DB: %s\n",
err);
sqlite3_free (err);
}
}
if (! rc)
rc = check_utks (db);
if (rc)
{
rc = sqlite3_exec (db, "rollback;", NULL, NULL, &err);
if (rc)
{
log_error (_("error rolling back transaction on TOFU database: %s\n"),
err);
sqlite3_free (err);
}
return 1;
}
else
{
rc = sqlite3_exec (db, "end transaction;", NULL, NULL, &err);
if (rc)
{
log_error (_("error committing transaction on TOFU database: %s\n"),
err);
sqlite3_free (err);
return 1;
}
return 0;
}
}
static int
busy_handler (void *cookie, int call_count)
{
ctrl_t ctrl = cookie;
tofu_dbs_t dbs = ctrl->tofu.dbs;
(void) call_count;
/* Update the want-lock-file time stamp (specifically, the ctime) so
* that the current owner knows that we (well, someone) want the
* lock. */
if (dbs)
{
/* Note: we don't fail if we can't create the lock file: this
* process will have to wait a bit longer, but otherwise nothing
* horrible should happen. */
estream_t fp;
fp = es_fopen (dbs->want_lock_file, "w");
if (! fp)
log_debug ("TOFU: Error opening '%s': %s\n",
dbs->want_lock_file, strerror (errno));
else
es_fclose (fp);
}
/* Call again. */
return 1;
}
/* Create a new DB handle. Returns NULL on error. */
/* FIXME: Change to return an error code for better reporting by the
caller. */
static tofu_dbs_t
opendbs (ctrl_t ctrl)
{
char *filename;
sqlite3 *db;
int rc;
if (!ctrl->tofu.dbs)
{
filename = make_filename (gnupg_homedir (), "tofu.db", NULL);
rc = sqlite3_open (filename, &db);
if (rc)
{
log_error (_("error opening TOFU database '%s': %s\n"),
filename, sqlite3_errmsg (db));
/* Even if an error occurs, DB is guaranteed to be valid. */
sqlite3_close (db);
db = NULL;
}
/* If a DB is locked wait up to 5 seconds for the lock to be cleared
before failing. */
if (db)
{
sqlite3_busy_timeout (db, 5 * 1000);
sqlite3_busy_handler (db, busy_handler, ctrl);
}
if (db && initdb (db))
{
sqlite3_close (db);
db = NULL;
}
if (db)
{
ctrl->tofu.dbs = xmalloc_clear (sizeof *ctrl->tofu.dbs);
ctrl->tofu.dbs->db = db;
ctrl->tofu.dbs->want_lock_file = xasprintf ("%s-want-lock", filename);
}
xfree (filename);
}
else
log_assert (ctrl->tofu.dbs->db);
return ctrl->tofu.dbs;
}
/* Release all of the resources associated with the DB handle. */
void
tofu_closedbs (ctrl_t ctrl)
{
tofu_dbs_t dbs;
sqlite3_stmt **statements;
dbs = ctrl->tofu.dbs;
if (!dbs)
return; /* Not initialized. */
log_assert (dbs->in_transaction == 0);
end_transaction (ctrl, 2);
/* Arghh, that is a surprising use of the struct. */
for (statements = (void *) &dbs->s;
(void *) statements < (void *) &(&dbs->s)[1];
statements ++)
sqlite3_finalize (*statements);
sqlite3_close (dbs->db);
xfree (dbs->want_lock_file);
xfree (dbs);
ctrl->tofu.dbs = NULL;
}
/* Collect results of a select min (foo) ...; style query. Aborts if
the argument is not a valid integer (or real of the form X.0). */
static int
get_single_long_cb (void *cookie, int argc, char **argv, char **azColName)
{
long *count = cookie;
(void) azColName;
log_assert (argc == 1);
if (string_to_long (count, argv[0], 0, __LINE__))
return 1; /* Abort. */
return 0;
}
static int
get_single_long_cb2 (void *cookie, int argc, char **argv, char **azColName,
sqlite3_stmt *stmt)
{
(void) stmt;
return get_single_long_cb (cookie, argc, argv, azColName);
}
/* Record (or update) a trust policy about a (possibly new)
binding.
If SHOW_OLD is set, the binding's old policy is displayed. */
static gpg_error_t
record_binding (tofu_dbs_t dbs, const char *fingerprint, const char *email,
const char *user_id,
enum tofu_policy policy, enum tofu_policy effective_policy,
const char *conflict, int set_conflict,
int show_old, time_t now)
{
char *fingerprint_pp = format_hexfingerprint (fingerprint, NULL, 0);
gpg_error_t rc;
char *err = NULL;
if (! (policy == TOFU_POLICY_AUTO
|| policy == TOFU_POLICY_GOOD
|| policy == TOFU_POLICY_UNKNOWN
|| policy == TOFU_POLICY_BAD
|| policy == TOFU_POLICY_ASK))
log_bug ("%s: Bad value for policy (%d)!\n", __func__, policy);
if (DBG_TRUST || show_old)
{
/* Get the old policy. Since this is just for informational
* purposes, there is no need to start a transaction or to die
* if there is a failure. */
/* policy_old needs to be a long and not an enum tofu_policy,
because we pass it by reference to get_single_long_cb2, which
expects a long. */
long policy_old = TOFU_POLICY_NONE;
rc = gpgsql_stepx
(dbs->db, &dbs->s.record_binding_get_old_policy,
get_single_long_cb2, &policy_old, &err,
"select policy from bindings where fingerprint = ? and email = ?",
GPGSQL_ARG_STRING, fingerprint, GPGSQL_ARG_STRING, email,
GPGSQL_ARG_END);
if (rc)
{
log_debug ("TOFU: Error reading from binding database"
" (reading policy for <key: %s, user id: %s>): %s\n",
fingerprint, email, err);
sqlite3_free (err);
}
if (policy_old != TOFU_POLICY_NONE)
(show_old ? log_info : log_debug)
("Changing TOFU trust policy for binding"
" <key: %s, user id: %s> from %s to %s.\n",
fingerprint, show_old ? user_id : email,
tofu_policy_str (policy_old),
tofu_policy_str (policy));
else
(show_old ? log_info : log_debug)
("Setting TOFU trust policy for new binding"
" <key: %s, user id: %s> to %s.\n",
fingerprint, show_old ? user_id : email,
tofu_policy_str (policy));
}
if (opt.dry_run)
{
log_info ("TOFU database update skipped due to --dry-run\n");
rc = 0;
goto leave;
}
rc = gpgsql_stepx
(dbs->db, &dbs->s.record_binding_update, NULL, NULL, &err,
"insert or replace into bindings\n"
" (oid, fingerprint, email, user_id, time,"
" policy, conflict, effective_policy)\n"
" values (\n"
/* If we don't explicitly reuse the OID, then SQLite will
* reallocate a new one. We just need to search for the OID
* based on the fingerprint and email since they are unique. */
" (select oid from bindings where fingerprint = ? and email = ?),\n"
" ?, ?, ?, ?, ?,"
/* If SET_CONFLICT is 0, then preserve conflict's current value. */
" case ?"
" when 0 then"
" (select conflict from bindings where fingerprint = ? and email = ?)"
" else ?"
" end,"
" ?);",
/* oid subquery. */
GPGSQL_ARG_STRING, fingerprint, GPGSQL_ARG_STRING, email,
/* values 2 through 6. */
GPGSQL_ARG_STRING, fingerprint, GPGSQL_ARG_STRING, email,
GPGSQL_ARG_STRING, user_id,
GPGSQL_ARG_LONG_LONG, (long long) now,
GPGSQL_ARG_INT, (int) policy,
/* conflict subquery. */
GPGSQL_ARG_INT, set_conflict ? 1 : 0,
GPGSQL_ARG_STRING, fingerprint, GPGSQL_ARG_STRING, email,
GPGSQL_ARG_STRING, conflict ? conflict : "",
GPGSQL_ARG_INT, (int) effective_policy,
GPGSQL_ARG_END);
if (rc)
{
log_error (_("error updating TOFU database: %s\n"), err);
print_further_info (" insert bindings <key: %s, user id: %s> = %s",
fingerprint, email, tofu_policy_str (policy));
sqlite3_free (err);
goto leave;
}
leave:
xfree (fingerprint_pp);
return rc;
}
/* Collect the strings returned by a query in a simple string list.
Any NULL values are converted to the empty string.
If a result has 3 rows and each row contains two columns, then the
results are added to the list as follows (the value is parentheses
is the 1-based index in the final list):
row 1, col 2 (6)
row 1, col 1 (5)
row 2, col 2 (4)
row 2, col 1 (3)
row 3, col 2 (2)
row 3, col 1 (1)
This is because add_to_strlist pushes the results onto the front of
the list. The end result is that the rows are backwards, but the
columns are in the expected order. */
static int
strings_collect_cb (void *cookie, int argc, char **argv, char **azColName)
{
int i;
strlist_t *strlist = cookie;
(void) azColName;
for (i = argc - 1; i >= 0; i --)
add_to_strlist (strlist, argv[i] ? argv[i] : "");
return 0;
}
static int
strings_collect_cb2 (void *cookie, int argc, char **argv, char **azColName,
sqlite3_stmt *stmt)
{
(void) stmt;
return strings_collect_cb (cookie, argc, argv, azColName);
}
/* Auxiliary data structure to collect statistics about
signatures. */
struct signature_stats
{
struct signature_stats *next;
/* The user-assigned policy for this binding. */
enum tofu_policy policy;
/* How long ago the signature was created (rounded to a multiple of
TIME_AGO_UNIT_SMALL, etc.). */
long time_ago;
/* Number of signatures during this time. */
unsigned long count;
/* If the corresponding key/user id has been expired / revoked. */
int is_expired;
int is_revoked;
/* The key that generated this signature. */
char fingerprint[1];
};
static void
signature_stats_free (struct signature_stats *stats)
{
while (stats)
{
struct signature_stats *next = stats->next;
xfree (stats);
stats = next;
}
}
static void
signature_stats_prepend (struct signature_stats **statsp,
const char *fingerprint,
enum tofu_policy policy,
long time_ago,
unsigned long count)
{
struct signature_stats *stats =
xmalloc_clear (sizeof (*stats) + strlen (fingerprint));
stats->next = *statsp;
*statsp = stats;
strcpy (stats->fingerprint, fingerprint);
stats->policy = policy;
stats->time_ago = time_ago;
stats->count = count;
}
/* Process rows that contain the four columns:
<fingerprint, policy, time ago, count>. */
static int
signature_stats_collect_cb (void *cookie, int argc, char **argv,
char **azColName, sqlite3_stmt *stmt)
{
struct signature_stats **statsp = cookie;
int i = 0;
enum tofu_policy policy;
long time_ago;
unsigned long count;
long along;
(void) azColName;
(void) stmt;
i ++;
if (string_to_long (&along, argv[i], 0, __LINE__))
return 1; /* Abort */
policy = along;
i ++;
if (! argv[i])
time_ago = 0;
else
{
if (string_to_long (&time_ago, argv[i], 0, __LINE__))
return 1; /* Abort. */
}
i ++;
/* If time_ago is NULL, then we had no messages, but we still have a
single row, which count(*) turns into 1. */
if (! argv[i - 1])
count = 0;
else
{
if (string_to_ulong (&count, argv[i], 0, __LINE__))
return 1; /* Abort */
}
i ++;
log_assert (argc == i);
signature_stats_prepend (statsp, argv[0], policy, time_ago, count);
return 0;
}
/* Format the first part of a conflict message and return that as a
* malloced string. Returns NULL on error. */
static char *
format_conflict_msg_part1 (int policy, strlist_t conflict_set,
const char *email)
{
estream_t fp;
char *fingerprint;
char *tmpstr, *text;
log_assert (conflict_set);
fingerprint = conflict_set->d;
fp = es_fopenmem (0, "rw,samethread");
if (!fp)
log_fatal ("error creating memory stream: %s\n",
gpg_strerror (gpg_error_from_syserror()));
if (policy == TOFU_POLICY_NONE)
{
es_fprintf (fp,
_("This is the first time the email address \"%s\" is "
"being used with key %s."),
email, fingerprint);
es_fputs (" ", fp);
}
else if (policy == TOFU_POLICY_ASK && conflict_set->next)
{
int conflicts = strlist_length (conflict_set);
es_fprintf
(fp, ngettext("The email address \"%s\" is associated with %d key!",
"The email address \"%s\" is associated with %d keys!",
conflicts),
email, conflicts);
if (opt.verbose)
es_fprintf (fp,
_(" Since this binding's policy was 'auto', it has been "
"changed to 'ask'."));
es_fputs (" ", fp);
}
es_fprintf (fp,
_("Please indicate whether this email address should"
" be associated with key %s or whether you think someone"
" is impersonating \"%s\"."),
fingerprint, email);
es_fputc ('\n', fp);
es_fputc (0, fp);
if (es_fclose_snatch (fp, (void **)&tmpstr, NULL))
log_fatal ("error snatching memory stream\n");
text = format_text (tmpstr, 72, 80);
es_free (tmpstr);
return text;
}
/* Return 1 if A signed B and B signed A. */
static int
cross_sigs (const char *email, kbnode_t a, kbnode_t b)
{
int i;
PKT_public_key *a_pk = a->pkt->pkt.public_key;
PKT_public_key *b_pk = b->pkt->pkt.public_key;
char a_keyid[33];
char b_keyid[33];
if (DBG_TRUST)
{
format_keyid (pk_main_keyid (a_pk),
KF_LONG, a_keyid, sizeof (a_keyid));
format_keyid (pk_main_keyid (b_pk),
KF_LONG, b_keyid, sizeof (b_keyid));
}
for (i = 0; i < 2; i ++)
{
/* See if SIGNER signed SIGNEE. */
kbnode_t signer = i == 0 ? a : b;
kbnode_t signee = i == 0 ? b : a;
PKT_public_key *signer_pk = signer->pkt->pkt.public_key;
u32 *signer_kid = pk_main_keyid (signer_pk);
kbnode_t n;
int saw_email = 0;
/* Iterate over SIGNEE's keyblock and see if there is a valid
signature from SIGNER. */
for (n = signee; n; n = n->next)
{
PKT_signature *sig;
if (n->pkt->pkttype == PKT_USER_ID)
{
if (saw_email)
/* We're done: we've processed all signatures on the
user id. */
break;
else
{
/* See if this is the matching user id. */
PKT_user_id *user_id = n->pkt->pkt.user_id;
char *email2 = email_from_user_id (user_id->name);
if (strcmp (email, email2) == 0)
saw_email = 1;
xfree (email2);
}
}
if (! saw_email)
continue;
if (n->pkt->pkttype != PKT_SIGNATURE)
continue;
sig = n->pkt->pkt.signature;
if (! (sig->sig_class == 0x10
|| sig->sig_class == 0x11
|| sig->sig_class == 0x12
|| sig->sig_class == 0x13))
/* Not a signature over a user id. */
continue;
/* SIG is on SIGNEE's keyblock. If SIG was generated by the
signer, then it's a match. */
if (keyid_cmp (sig->keyid, signer_kid) == 0)
/* Match! */
break;
}
if (! n)
/* We didn't find a signature from signer over signee. */
{
if (DBG_TRUST)
log_debug ("No cross sig between %s and %s\n",
a_keyid, b_keyid);
return 0;
}
}
/* A signed B and B signed A. */
if (DBG_TRUST)
log_debug ("Cross sig between %s and %s\n",
a_keyid, b_keyid);
return 1;
}
/* Return whether the key was signed by an ultimately trusted key. */
static int
signed_by_utk (const char *email, kbnode_t a)
{
kbnode_t n;
int saw_email = 0;
for (n = a; n; n = n->next)
{
PKT_signature *sig;
if (n->pkt->pkttype == PKT_USER_ID)
{
if (saw_email)
/* We're done: we've processed all signatures on the
user id. */
break;
else
{
/* See if this is the matching user id. */
PKT_user_id *user_id = n->pkt->pkt.user_id;
char *email2 = email_from_user_id (user_id->name);
if (strcmp (email, email2) == 0)
saw_email = 1;
xfree (email2);
}
}
if (! saw_email)
continue;
if (n->pkt->pkttype != PKT_SIGNATURE)
continue;
sig = n->pkt->pkt.signature;
if (! (sig->sig_class == 0x10
|| sig->sig_class == 0x11
|| sig->sig_class == 0x12
|| sig->sig_class == 0x13))
/* Not a signature over a user id. */
continue;
/* SIG is on SIGNEE's keyblock. If SIG was generated by the
signer, then it's a match. */
if (tdb_keyid_is_utk (sig->keyid))
{
/* Match! */
if (DBG_TRUST)
log_debug ("TOFU: %s is signed by an ultimately trusted key.\n",
pk_keyid_str (a->pkt->pkt.public_key));
return 1;
}
}
if (DBG_TRUST)
log_debug ("TOFU: %s is NOT signed by an ultimately trusted key.\n",
pk_keyid_str (a->pkt->pkt.public_key));
return 0;
}
enum
{
BINDING_NEW = 1 << 0,
BINDING_CONFLICT = 1 << 1,
BINDING_EXPIRED = 1 << 2,
BINDING_REVOKED = 1 << 3
};
/* Ask the user about the binding. There are three ways we could end
* up here:
*
* - This is a new binding and there is a conflict
* (policy == TOFU_POLICY_NONE && conflict_set_count > 1),
*
* - This is a new binding and opt.tofu_default_policy is set to
* ask. (policy == TOFU_POLICY_NONE && opt.tofu_default_policy ==
* TOFU_POLICY_ASK), or,
*
* - The policy is ask (the user deferred last time) (policy ==
* TOFU_POLICY_ASK).
*
* Note: this function must not be called while in a transaction!
*
* CONFLICT_SET includes all of the conflicting bindings
* with FINGERPRINT first. FLAGS is a bit-wise or of
* BINDING_NEW, etc.
*/
static void
ask_about_binding (ctrl_t ctrl,
enum tofu_policy *policy,
int *trust_level,
strlist_t conflict_set,
const char *fingerprint,
const char *email,
const char *user_id,
time_t now)
{
tofu_dbs_t dbs;
strlist_t iter;
int conflict_set_count = strlist_length (conflict_set);
char *sqerr = NULL;
int rc;
estream_t fp;
strlist_t other_user_ids = NULL;
struct signature_stats *stats = NULL;
struct signature_stats *stats_iter = NULL;
char *prompt = NULL;
const char *choices;
dbs = ctrl->tofu.dbs;
log_assert (dbs);
log_assert (dbs->in_transaction == 0);
fp = es_fopenmem (0, "rw,samethread");
if (!fp)
log_fatal ("error creating memory stream: %s\n",
gpg_strerror (gpg_error_from_syserror()));
{
char *text = format_conflict_msg_part1 (*policy, conflict_set, email);
if (!text) /* FIXME: Return the error all the way up. */
log_fatal ("format failed: %s\n",
gpg_strerror (gpg_error_from_syserror()));
es_fputs (text, fp);
es_fputc ('\n', fp);
xfree (text);
}
begin_transaction (ctrl, 0);
/* Find other user ids associated with this key and whether the
* bindings are marked as good or bad. */
rc = gpgsql_stepx
(dbs->db, &dbs->s.get_trust_gather_other_user_ids,
strings_collect_cb2, &other_user_ids, &sqerr,
"select user_id, policy from bindings where fingerprint = ?;",
GPGSQL_ARG_STRING, fingerprint, GPGSQL_ARG_END);
if (rc)
{
log_error (_("error gathering other user IDs: %s\n"), sqerr);
sqlite3_free (sqerr);
sqerr = NULL;
rc = gpg_error (GPG_ERR_GENERAL);
}
if (other_user_ids)
{
strlist_t strlist_iter;
es_fprintf (fp, _("This key's user IDs:\n"));
for (strlist_iter = other_user_ids;
strlist_iter;
strlist_iter = strlist_iter->next)
{
char *other_user_id = strlist_iter->d;
char *other_thing;
enum tofu_policy other_policy;
log_assert (strlist_iter->next);
strlist_iter = strlist_iter->next;
other_thing = strlist_iter->d;
other_policy = atoi (other_thing);
es_fprintf (fp, " %s (", other_user_id);
es_fprintf (fp, _("policy: %s"), tofu_policy_str (other_policy));
es_fprintf (fp, ")\n");
}
es_fprintf (fp, "\n");
free_strlist (other_user_ids);
}
/* Get the stats for all the keys in CONFLICT_SET. */
strlist_rev (&conflict_set);
for (iter = conflict_set; iter && ! rc; iter = iter->next)
{
#define STATS_SQL(table, time, sign) \
"select fingerprint, policy, time_ago, count(*)\n" \
" from\n" \
" (select bindings.*,\n" \
" "sign" case\n" \
" when delta ISNULL then 1\n" \
/* From the future (but if its just a couple of hours in the \
* future don't turn it into a warning)? Or should we use \
* small, medium or large units? (Note: whatever we do, we \
* keep the value in seconds. Then when we group, everything \
* that rounds to the same number of seconds is grouped.) */ \
" when delta < -("STRINGIFY (TIME_AGO_FUTURE_IGNORE)") then 2\n" \
" when delta < ("STRINGIFY (TIME_AGO_SMALL_THRESHOLD)")\n" \
" then 3\n" \
" when delta < ("STRINGIFY (TIME_AGO_MEDIUM_THRESHOLD)")\n" \
" then 4\n" \
" when delta < ("STRINGIFY (TIME_AGO_LARGE_THRESHOLD)")\n" \
" then 5\n" \
" else 6\n" \
" end time_ago,\n" \
" delta time_ago_raw\n" \
" from bindings\n" \
" left join\n" \
" (select *,\n" \
" cast(? - " time " as real) delta\n" \
" from " table ") ss\n" \
" on ss.binding = bindings.oid)\n" \
" where email = ? and fingerprint = ?\n" \
" group by time_ago\n" \
/* Make sure the current key is first. */ \
" order by time_ago desc;\n"
/* Use the time when we saw the signature, not when the
signature was created as that can be forged. */
rc = gpgsql_stepx
(dbs->db, &dbs->s.get_trust_gather_signature_stats,
signature_stats_collect_cb, &stats, &sqerr,
STATS_SQL ("signatures", "time", ""),
GPGSQL_ARG_LONG_LONG, (long long) now,
GPGSQL_ARG_STRING, email,
GPGSQL_ARG_STRING, iter->d,
GPGSQL_ARG_END);
if (rc)
{
rc = gpg_error (GPG_ERR_GENERAL);
break;
}
if (!stats || strcmp (iter->d, stats->fingerprint) != 0)
/* No stats for this binding. Add a dummy entry. */
signature_stats_prepend (&stats, iter->d, TOFU_POLICY_AUTO, 1, 1);
rc = gpgsql_stepx
(dbs->db, &dbs->s.get_trust_gather_encryption_stats,
signature_stats_collect_cb, &stats, &sqerr,
STATS_SQL ("encryptions", "time", "-"),
GPGSQL_ARG_LONG_LONG, (long long) now,
GPGSQL_ARG_STRING, email,
GPGSQL_ARG_STRING, iter->d,
GPGSQL_ARG_END);
if (rc)
{
rc = gpg_error (GPG_ERR_GENERAL);
break;
}
#undef STATS_SQL
if (!stats || strcmp (iter->d, stats->fingerprint) != 0
|| stats->time_ago > 0)
/* No stats for this binding. Add a dummy entry. */
signature_stats_prepend (&stats, iter->d, TOFU_POLICY_AUTO, -1, 1);
}
end_transaction (ctrl, 0);
strlist_rev (&conflict_set);
if (rc)
{
strlist_t strlist_iter;
log_error (_("error gathering signature stats: %s\n"), sqerr);
sqlite3_free (sqerr);
sqerr = NULL;
es_fprintf (fp, ngettext("The email address \"%s\" is"
" associated with %d key:\n",
"The email address \"%s\" is"
" associated with %d keys:\n",
conflict_set_count),
email, conflict_set_count);
for (strlist_iter = conflict_set;
strlist_iter;
strlist_iter = strlist_iter->next)
es_fprintf (fp, " %s\n", strlist_iter->d);
}
else
{
char *key = NULL;
strlist_t binding;
int seen_in_past = 0;
int encrypted = 1;
es_fprintf (fp, _("Statistics for keys"
" with the email address \"%s\":\n"),
email);
for (stats_iter = stats; stats_iter; stats_iter = stats_iter->next)
{
#if 0
log_debug ("%s: time_ago: %ld; count: %ld\n",
stats_iter->fingerprint,
stats_iter->time_ago,
stats_iter->count);
#endif
if (stats_iter->time_ago > 0 && encrypted)
{
/* We've change from the encrypted stats to the verified
* stats. Reset SEEN_IN_PAST. */
encrypted = 0;
seen_in_past = 0;
}
if (! key || strcmp (key, stats_iter->fingerprint))
{
int this_key;
char *key_pp;
key = stats_iter->fingerprint;
this_key = strcmp (key, fingerprint) == 0;
key_pp = format_hexfingerprint (key, NULL, 0);
es_fprintf (fp, " %s (", key_pp);
/* Find the associated binding. */
for (binding = conflict_set;
binding;
binding = binding->next)
if (strcmp (key, binding->d) == 0)
break;
log_assert (binding);
if ((binding->flags & BINDING_REVOKED))
{
es_fprintf (fp, _("revoked"));
es_fprintf (fp, ", ");
}
else if ((binding->flags & BINDING_EXPIRED))
{
es_fprintf (fp, _("expired"));
es_fprintf (fp, ", ");
}
if (this_key)
es_fprintf (fp, _("this key"));
else
es_fprintf (fp, _("policy: %s"),
tofu_policy_str (stats_iter->policy));
es_fputs ("):\n", fp);
xfree (key_pp);
seen_in_past = 0;
show_statistics (dbs, stats_iter->fingerprint, email,
TOFU_POLICY_ASK, NULL, 1, now);
}
if (labs(stats_iter->time_ago) == 1)
{
/* The 1 in this case is the NULL entry. */
log_assert (stats_iter->count == 1);
stats_iter->count = 0;
}
seen_in_past += stats_iter->count;
es_fputs (" ", fp);
if (!stats_iter->count)
{
if (stats_iter->time_ago > 0)
es_fprintf (fp, ngettext("Verified %d message.",
"Verified %d messages.",
seen_in_past), seen_in_past);
else
es_fprintf (fp, ngettext("Encrypted %d message.",
"Encrypted %d messages.",
seen_in_past), seen_in_past);
}
else if (labs(stats_iter->time_ago) == 2)
{
if (stats_iter->time_ago > 0)
es_fprintf (fp, ngettext("Verified %d message in the future.",
"Verified %d messages in the future.",
seen_in_past), seen_in_past);
else
es_fprintf (fp, ngettext("Encrypted %d message in the future.",
"Encrypted %d messages in the future.",
seen_in_past), seen_in_past);
/* Reset it. */
seen_in_past = 0;
}
else
{
if (labs(stats_iter->time_ago) == 3)
{
int days = 1 + stats_iter->time_ago / TIME_AGO_UNIT_SMALL;
if (stats_iter->time_ago > 0)
es_fprintf
(fp,
ngettext("Messages verified over the past %d day: %d.",
"Messages verified over the past %d days: %d.",
days), days, seen_in_past);
else
es_fprintf
(fp,
ngettext("Messages encrypted over the past %d day: %d.",
"Messages encrypted over the past %d days: %d.",
days), days, seen_in_past);
}
else if (labs(stats_iter->time_ago) == 4)
{
int months = 1 + stats_iter->time_ago / TIME_AGO_UNIT_MEDIUM;
if (stats_iter->time_ago > 0)
es_fprintf
(fp,
ngettext("Messages verified over the past %d month: %d.",
"Messages verified over the past %d months: %d.",
months), months, seen_in_past);
else
es_fprintf
(fp,
ngettext("Messages encrypted over the past %d month: %d.",
"Messages encrypted over the past %d months: %d.",
months), months, seen_in_past);
}
else if (labs(stats_iter->time_ago) == 5)
{
int years = 1 + stats_iter->time_ago / TIME_AGO_UNIT_LARGE;
if (stats_iter->time_ago > 0)
es_fprintf
(fp,
ngettext("Messages verified over the past %d year: %d.",
"Messages verified over the past %d years: %d.",
years), years, seen_in_past);
else
es_fprintf
(fp,
ngettext("Messages encrypted over the past %d year: %d.",
"Messages encrypted over the past %d years: %d.",
years), years, seen_in_past);
}
else if (labs(stats_iter->time_ago) == 6)
{
if (stats_iter->time_ago > 0)
es_fprintf
(fp, _("Messages verified in the past: %d."),
seen_in_past);
else
es_fprintf
(fp, _("Messages encrypted in the past: %d."),
seen_in_past);
}
else
log_assert (! "Broken SQL.\n");
}
es_fputs ("\n", fp);
}
}
if (conflict_set_count > 1 || (conflict_set->flags & BINDING_CONFLICT))
{
/* This is a conflict. */
/* TRANSLATORS: Please translate the text found in the source
* file below. We don't directly internationalize that text so
* that we can tweak it without breaking translations. */
const char *text = _("TOFU detected a binding conflict");
char *textbuf;
if (!strcmp (text, "TOFU detected a binding conflict"))
{
/* No translation. Use the English text. */
text =
"Normally, an email address is associated with a single key. "
"However, people sometimes generate a new key if "
"their key is too old or they think it might be compromised. "
"Alternatively, a new key may indicate a man-in-the-middle "
"attack! Before accepting this association, you should talk to or "
"call the person to make sure this new key is legitimate.";
}
textbuf = format_text (text, 72, 80);
es_fprintf (fp, "\n%s\n", textbuf? textbuf : "[OUT OF CORE!]");
xfree (textbuf);
}
es_fputc ('\n', fp);
/* Add a NUL terminator. */
es_fputc (0, fp);
if (es_fclose_snatch (fp, (void **) &prompt, NULL))
log_fatal ("error snatching memory stream\n");
/* I think showing the large message once is sufficient. If we
* would move it right before the cpr_get many lines will scroll
* away and the user might not realize that he merely entered a
* wrong choice (because he does not see that either). As a small
* benefit we allow C-L to redisplay everything. */
tty_printf ("%s", prompt);
/* Suspend any transaction: it could take a while until the user
responds. */
tofu_suspend_batch_transaction (ctrl);
while (1)
{
char *response;
/* TRANSLATORS: Two letters (normally the lower and upper case
* version of the hotkey) for each of the five choices. If
* there is only one choice in your language, repeat it. */
choices = _("gG" "aA" "uU" "rR" "bB");
if (strlen (choices) != 10)
log_bug ("Bad TOFU conflict translation! Please report.");
response = cpr_get
("tofu.conflict",
_("(G)ood, (A)ccept once, (U)nknown, (R)eject once, (B)ad? "));
trim_spaces (response);
cpr_kill_prompt ();
if (*response == CONTROL_L)
tty_printf ("%s", prompt);
else if (!response[0])
/* Default to unknown. Don't save it. */
{
tty_printf (_("Defaulting to unknown.\n"));
*policy = TOFU_POLICY_UNKNOWN;
break;
}
else if (!response[1])
{
char *choice = strchr (choices, *response);
if (choice)
{
int c = ((size_t) choice - (size_t) choices) / 2;
switch (c)
{
case 0: /* Good. */
*policy = TOFU_POLICY_GOOD;
*trust_level = tofu_policy_to_trust_level (*policy);
break;
case 1: /* Accept once. */
*policy = TOFU_POLICY_ASK;
*trust_level = tofu_policy_to_trust_level (TOFU_POLICY_GOOD);
break;
case 2: /* Unknown. */
*policy = TOFU_POLICY_UNKNOWN;
*trust_level = tofu_policy_to_trust_level (*policy);
break;
case 3: /* Reject once. */
*policy = TOFU_POLICY_ASK;
*trust_level = tofu_policy_to_trust_level (TOFU_POLICY_BAD);
break;
case 4: /* Bad. */
*policy = TOFU_POLICY_BAD;
*trust_level = tofu_policy_to_trust_level (*policy);
break;
default:
log_bug ("c should be between 0 and 4 but it is %d!", c);
}
if (record_binding (dbs, fingerprint, email, user_id,
*policy, TOFU_POLICY_NONE, NULL, 0, 0, now))
{
/* If there's an error registering the
* binding, don't save the signature. */
*trust_level = _tofu_GET_TRUST_ERROR;
}
break;
}
}
xfree (response);
}
tofu_resume_batch_transaction (ctrl);
xfree (prompt);
signature_stats_free (stats);
}
/* Return the set of keys that conflict with the binding <fingerprint,
email> (including the binding itself, which will be first in the
list). For each returned key also sets BINDING_NEW, etc. */
static strlist_t
build_conflict_set (ctrl_t ctrl, tofu_dbs_t dbs,
PKT_public_key *pk, const char *fingerprint,
const char *email)
{
gpg_error_t rc;
char *sqerr;
strlist_t conflict_set = NULL;
int conflict_set_count;
strlist_t iter;
kbnode_t *kb_all;
KEYDB_HANDLE hd;
int i;
/* Get the fingerprints of any bindings that share the email address
* and whether the bindings have a known conflict.
*
* Note: if the binding in question is in the DB, it will also be
* returned. Thus, if the result set is empty, then <email,
* fingerprint> is a new binding. */
rc = gpgsql_stepx
(dbs->db, &dbs->s.get_trust_bindings_with_this_email,
strings_collect_cb2, &conflict_set, &sqerr,
"select"
/* A binding should only appear once, but try not to break in the
* case of corruption. */
" fingerprint || case sum(conflict NOTNULL) when 0 then '' else '!' end"
" from bindings where email = ?"
" group by fingerprint"
/* Make sure the current key comes first in the result list (if
it is present). */
" order by fingerprint = ? asc, fingerprint desc;",
GPGSQL_ARG_STRING, email,
GPGSQL_ARG_STRING, fingerprint,
GPGSQL_ARG_END);
if (rc)
{
log_error (_("error reading TOFU database: %s\n"), sqerr);
print_further_info ("listing fingerprints");
sqlite3_free (sqerr);
rc = gpg_error (GPG_ERR_GENERAL);
return NULL;
}
/* Set BINDING_CONFLICT if the binding has a known conflict. This
* allows us to distinguish between bindings where the user
* explicitly set the policy to ask and bindings where we set the
* policy to ask due to a conflict. */
for (iter = conflict_set; iter; iter = iter->next)
{
/* Fixme: Why the check against N+1? */
int l = strlen (iter->d);
if (!(l == 2 * 20
|| l == 2 * 20 + 1
|| l == 2 * 32
|| l == 2 * 32 + 1))
{
log_error (_("TOFU db corruption detected.\n"));
print_further_info ("fingerprint '%s' is %d characters long",
iter->d, l);
}
if (l >= 1 && iter->d[l - 1] == '!')
{
iter->flags |= BINDING_CONFLICT;
/* Remove the !. */
iter->d[l - 1] = 0;
}
}
/* If the current binding has not yet been recorded, add it to the
* list. (The order by above ensures that if it is present, it will
* be first.) */
if (! (conflict_set && strcmp (conflict_set->d, fingerprint) == 0))
{
add_to_strlist (&conflict_set, fingerprint);
conflict_set->flags |= BINDING_NEW;
}
conflict_set_count = strlist_length (conflict_set);
/* Eliminate false conflicts. */
if (conflict_set_count == 1)
/* We only have a single key. There are no false conflicts to
eliminate. But, we do need to set the flags. */
{
if (pk->has_expired)
conflict_set->flags |= BINDING_EXPIRED;
if (pk->flags.revoked)
conflict_set->flags |= BINDING_REVOKED;
return conflict_set;
}
/* If two keys have cross signatures, then they are controlled by
* the same person and thus are not in conflict. */
kb_all = xcalloc (sizeof (kb_all[0]), conflict_set_count);
hd = keydb_new ();
for (i = 0, iter = conflict_set;
i < conflict_set_count;
i ++, iter = iter->next)
{
char *fp = iter->d;
KEYDB_SEARCH_DESC desc;
kbnode_t kb;
PKT_public_key *binding_pk;
kbnode_t n;
int found_user_id;
rc = keydb_search_reset (hd);
if (rc)
{
- log_error (_("resetting keydb: %s\n"),
- gpg_strerror (rc));
+ log_error ("resetting keydb failed: %s\n", gpg_strerror (rc));
continue;
}
rc = classify_user_id (fp, &desc, 0);
if (rc)
{
log_error (_("error parsing key specification '%s': %s\n"),
fp, gpg_strerror (rc));
continue;
}
rc = keydb_search (hd, &desc, 1, NULL);
if (rc)
{
/* Note: it is entirely possible that we don't have the key
corresponding to an entry in the TOFU DB. This can
happen if we merge two TOFU DBs, but not the key
rings. */
log_info (_("key \"%s\" not found: %s\n"),
fp, gpg_strerror (rc));
continue;
}
rc = keydb_get_keyblock (hd, &kb);
if (rc)
{
log_error (_("error reading keyblock: %s\n"),
gpg_strerror (rc));
print_further_info ("fingerprint: %s", fp);
continue;
}
merge_keys_and_selfsig (ctrl, kb);
log_assert (kb->pkt->pkttype == PKT_PUBLIC_KEY);
kb_all[i] = kb;
/* Since we have the key block, use this opportunity to figure
* out if the binding is expired or revoked. */
binding_pk = kb->pkt->pkt.public_key;
/* The binding is always expired/revoked if the key is
* expired/revoked. */
if (binding_pk->has_expired)
iter->flags |= BINDING_EXPIRED;
if (binding_pk->flags.revoked)
iter->flags |= BINDING_REVOKED;
/* The binding is also expired/revoked if the user id is
* expired/revoked. */
n = kb;
found_user_id = 0;
while ((n = find_next_kbnode (n, PKT_USER_ID)) && ! found_user_id)
{
PKT_user_id *user_id2 = n->pkt->pkt.user_id;
char *email2;
if (user_id2->attrib_data)
continue;
email2 = email_from_user_id (user_id2->name);
if (strcmp (email, email2) == 0)
{
found_user_id = 1;
if (user_id2->flags.revoked)
iter->flags |= BINDING_REVOKED;
if (user_id2->flags.expired)
iter->flags |= BINDING_EXPIRED;
}
xfree (email2);
}
if (! found_user_id)
{
log_info (_("TOFU db corruption detected.\n"));
print_further_info ("user id '%s' not on key block '%s'",
email, fingerprint);
}
}
keydb_release (hd);
/* Now that we have the key blocks, check for cross sigs. */
{
int j;
strlist_t *prevp;
strlist_t iter_next;
int *die;
log_assert (conflict_set_count > 0);
die = xtrycalloc (conflict_set_count, sizeof *die);
if (!die)
{
/*err = gpg_error_from_syserror ();*/
xoutofcore (); /* Fixme: Let the function return an error. */
}
for (i = 0; i < conflict_set_count; i ++)
{
/* Look for cross sigs between this key (i == 0) or a key
* that has cross sigs with i == 0 (i.e., transitively) */
if (! (i == 0 || die[i]))
continue;
for (j = i + 1; j < conflict_set_count; j ++)
/* Be careful: we might not have a key block for a key. */
if (kb_all[i] && kb_all[j] && cross_sigs (email, kb_all[i], kb_all[j]))
die[j] = 1;
}
/* Free unconflicting bindings (and all of the key blocks). */
for (iter = conflict_set, prevp = &conflict_set, i = 0;
iter;
iter = iter_next, i ++)
{
iter_next = iter->next;
release_kbnode (kb_all[i]);
if (die[i])
{
*prevp = iter_next;
iter->next = NULL;
free_strlist (iter);
conflict_set_count --;
}
else
{
prevp = &iter->next;
}
}
/* We shouldn't have removed the head. */
log_assert (conflict_set);
log_assert (conflict_set_count >= 1);
xfree (die);
}
xfree (kb_all);
if (DBG_TRUST)
{
log_debug ("binding <key: %s, email: %s> conflicts:\n",
fingerprint, email);
for (iter = conflict_set; iter; iter = iter->next)
{
log_debug (" %s:%s%s%s%s\n",
iter->d,
(iter->flags & BINDING_NEW) ? " new" : "",
(iter->flags & BINDING_CONFLICT) ? " known_conflict" : "",
(iter->flags & BINDING_EXPIRED) ? " expired" : "",
(iter->flags & BINDING_REVOKED) ? " revoked" : "");
}
}
return conflict_set;
}
/* Return the effective policy for the binding <FINGERPRINT, EMAIL>
* (email has already been normalized). Returns
* _tofu_GET_POLICY_ERROR if an error occurs. Returns any conflict
* information in *CONFLICT_SETP if CONFLICT_SETP is not NULL and the
* returned policy is TOFU_POLICY_ASK (consequently, if there is a
* conflict, but the user set the policy to good *CONFLICT_SETP will
* empty). Note: as per build_conflict_set, which is used to build
* the conflict information, the conflict information includes the
* current user id as the first element of the linked list.
*
* This function registers the binding in the bindings table if it has
* not yet been registered.
*/
static enum tofu_policy
get_policy (ctrl_t ctrl, tofu_dbs_t dbs, PKT_public_key *pk,
const char *fingerprint, const char *user_id, const char *email,
strlist_t *conflict_setp, time_t now)
{
int rc;
char *err = NULL;
strlist_t results = NULL;
enum tofu_policy policy = _tofu_GET_POLICY_ERROR;
enum tofu_policy effective_policy_orig = TOFU_POLICY_NONE;
enum tofu_policy effective_policy = _tofu_GET_POLICY_ERROR;
long along;
char *conflict_orig = NULL;
char *conflict = NULL;
strlist_t conflict_set = NULL;
int conflict_set_count;
/* Check if the <FINGERPRINT, EMAIL> binding is known
(TOFU_POLICY_NONE cannot appear in the DB. Thus, if POLICY is
still TOFU_POLICY_NONE after executing the query, then the
result set was empty.) */
rc = gpgsql_stepx (dbs->db, &dbs->s.get_policy_select_policy_and_conflict,
strings_collect_cb2, &results, &err,
"select policy, conflict, effective_policy from bindings\n"
" where fingerprint = ? and email = ?",
GPGSQL_ARG_STRING, fingerprint,
GPGSQL_ARG_STRING, email,
GPGSQL_ARG_END);
if (rc)
{
log_error (_("error reading TOFU database: %s\n"), err);
print_further_info ("reading the policy");
sqlite3_free (err);
rc = gpg_error (GPG_ERR_GENERAL);
goto out;
}
if (strlist_length (results) == 0)
{
/* No results. Use the defaults. */
policy = TOFU_POLICY_NONE;
effective_policy = TOFU_POLICY_NONE;
}
else if (strlist_length (results) == 3)
{
/* Parse and sanity check the results. */
if (string_to_long (&along, results->d, 0, __LINE__))
{
log_error (_("error reading TOFU database: %s\n"),
gpg_strerror (GPG_ERR_BAD_DATA));
print_further_info ("bad value for policy: %s", results->d);
goto out;
}
policy = along;
if (! (policy == TOFU_POLICY_AUTO
|| policy == TOFU_POLICY_GOOD
|| policy == TOFU_POLICY_UNKNOWN
|| policy == TOFU_POLICY_BAD
|| policy == TOFU_POLICY_ASK))
{
log_error (_("error reading TOFU database: %s\n"),
gpg_strerror (GPG_ERR_DB_CORRUPTED));
print_further_info ("invalid value for policy (%d)", policy);
effective_policy = _tofu_GET_POLICY_ERROR;
goto out;
}
if (*results->next->d)
conflict = xstrdup (results->next->d);
if (string_to_long (&along, results->next->next->d, 0, __LINE__))
{
log_error (_("error reading TOFU database: %s\n"),
gpg_strerror (GPG_ERR_BAD_DATA));
print_further_info ("bad value for effective policy: %s",
results->next->next->d);
goto out;
}
effective_policy = along;
if (! (effective_policy == TOFU_POLICY_NONE
|| effective_policy == TOFU_POLICY_AUTO
|| effective_policy == TOFU_POLICY_GOOD
|| effective_policy == TOFU_POLICY_UNKNOWN
|| effective_policy == TOFU_POLICY_BAD
|| effective_policy == TOFU_POLICY_ASK))
{
log_error (_("error reading TOFU database: %s\n"),
gpg_strerror (GPG_ERR_DB_CORRUPTED));
print_further_info ("invalid value for effective_policy (%d)",
effective_policy);
effective_policy = _tofu_GET_POLICY_ERROR;
goto out;
}
}
else
{
/* The result has the wrong form. */
log_error (_("error reading TOFU database: %s\n"),
gpg_strerror (GPG_ERR_BAD_DATA));
print_further_info ("reading policy: expected 3 columns, got %d\n",
strlist_length (results));
goto out;
}
/* Save the effective policy and conflict so we know if we changed
* them. */
effective_policy_orig = effective_policy;
conflict_orig = conflict;
/* Unless there is a conflict, if the effective policy is cached,
* just return it. The reason we don't do this when there is a
* conflict is because of the following scenario: assume A and B
* conflict and B has signed A's key. Now, later we import A's
* signature on B. We need to recheck A, but the signature was on
* B, i.e., when B changes, we invalidate B's effective policy, but
* we also need to invalidate A's effective policy. Instead, we
* assume that conflicts are rare and don't optimize for them, which
* would complicate the code. */
if (effective_policy != TOFU_POLICY_NONE && !conflict)
goto out;
/* If the user explicitly set the policy, then respect that. */
if (policy != TOFU_POLICY_AUTO && policy != TOFU_POLICY_NONE)
{
effective_policy = policy;
goto out;
}
/* Unless proven wrong, assume the effective policy is 'auto'. */
effective_policy = TOFU_POLICY_AUTO;
/* See if the key is ultimately trusted. */
{
u32 kid[2];
keyid_from_pk (pk, kid);
if (tdb_keyid_is_utk (kid))
{
effective_policy = TOFU_POLICY_GOOD;
goto out;
}
}
/* See if the key is signed by an ultimately trusted key. */
{
int fingerprint_raw_len = strlen (fingerprint) / 2;
char fingerprint_raw[MAX_FINGERPRINT_LEN];
int len = 0;
/* FIXME(fingerprint) */
if (fingerprint_raw_len != 20 /*sizeof fingerprint_raw */
|| ((len = hex2bin (fingerprint,
fingerprint_raw, fingerprint_raw_len))
!= strlen (fingerprint)))
{
if (DBG_TRUST)
log_debug ("TOFU: Bad fingerprint: %s (len: %zu, parsed: %d)\n",
fingerprint, strlen (fingerprint), len);
}
else
{
int lookup_err;
kbnode_t kb;
lookup_err = get_pubkey_byfprint (ctrl, NULL, &kb,
fingerprint_raw,
fingerprint_raw_len);
if (lookup_err)
{
if (DBG_TRUST)
log_debug ("TOFU: Looking up %s: %s\n",
fingerprint, gpg_strerror (lookup_err));
}
else
{
int is_signed_by_utk = signed_by_utk (email, kb);
release_kbnode (kb);
if (is_signed_by_utk)
{
effective_policy = TOFU_POLICY_GOOD;
goto out;
}
}
}
}
/* Check for any conflicts / see if a previously discovered conflict
* disappeared. The latter can happen if the conflicting bindings
* are now cross signed, for instance. */
conflict_set = build_conflict_set (ctrl, dbs, pk, fingerprint, email);
conflict_set_count = strlist_length (conflict_set);
if (conflict_set_count == 0)
{
/* build_conflict_set should always at least return the current
binding. Something went wrong. */
effective_policy = _tofu_GET_POLICY_ERROR;
goto out;
}
if (conflict_set_count == 1
&& (conflict_set->flags & BINDING_NEW))
{
/* We've never observed a binding with this email address and we
* have a default policy, which is not to ask the user. */
/* If we've seen this binding, then we've seen this email and
* policy couldn't possibly be TOFU_POLICY_NONE. */
log_assert (policy == TOFU_POLICY_NONE);
if (DBG_TRUST)
log_debug ("TOFU: New binding <key: %s, user id: %s>, no conflict.\n",
fingerprint, email);
effective_policy = TOFU_POLICY_AUTO;
goto out;
}
if (conflict_set_count == 1
&& (conflict_set->flags & BINDING_CONFLICT))
{
/* No known conflicts now, but there was a conflict. This means
* at some point, there was a conflict and we changed this
* binding's policy to ask and set the conflicting key. The
* conflict can go away if there is not a cross sig between the
* two keys. In this case, just silently clear the conflict and
* reset the policy to auto. */
if (DBG_TRUST)
log_debug ("TOFU: binding <key: %s, user id: %s> had a conflict, but it's been resolved (probably via cross sig).\n",
fingerprint, email);
effective_policy = TOFU_POLICY_AUTO;
conflict = NULL;
goto out;
}
if (conflict_set_count == 1)
{
/* No conflicts and never marked as conflicting. */
log_assert (!conflict);
effective_policy = TOFU_POLICY_AUTO;
goto out;
}
/* There is a conflicting key. */
log_assert (conflict_set_count > 1);
effective_policy = TOFU_POLICY_ASK;
conflict = xstrdup (conflict_set->next->d);
out:
log_assert (policy == _tofu_GET_POLICY_ERROR
|| policy == TOFU_POLICY_NONE
|| policy == TOFU_POLICY_AUTO
|| policy == TOFU_POLICY_GOOD
|| policy == TOFU_POLICY_UNKNOWN
|| policy == TOFU_POLICY_BAD
|| policy == TOFU_POLICY_ASK);
/* Everything but NONE. */
log_assert (effective_policy == _tofu_GET_POLICY_ERROR
|| effective_policy == TOFU_POLICY_AUTO
|| effective_policy == TOFU_POLICY_GOOD
|| effective_policy == TOFU_POLICY_UNKNOWN
|| effective_policy == TOFU_POLICY_BAD
|| effective_policy == TOFU_POLICY_ASK);
if (effective_policy != TOFU_POLICY_ASK && conflict)
conflict = NULL;
/* If we don't have a record of this binding, its effective policy
* changed, or conflict changed, update the DB. */
if (effective_policy != _tofu_GET_POLICY_ERROR
&& (/* New binding. */
policy == TOFU_POLICY_NONE
/* effective_policy changed. */
|| effective_policy != effective_policy_orig
/* conflict changed. */
|| (conflict != conflict_orig
&& (!conflict || !conflict_orig
|| strcmp (conflict, conflict_orig) != 0))))
{
if (record_binding (dbs, fingerprint, email, user_id,
policy == TOFU_POLICY_NONE ? TOFU_POLICY_AUTO : policy,
effective_policy, conflict, 1, 0, now) != 0)
- log_error (_("error setting TOFU binding's policy"
- " to %s\n"), tofu_policy_str (policy));
+ log_error ("error setting TOFU binding's policy"
+ " to %s\n", tofu_policy_str (policy));
}
/* If the caller wants the set of conflicts, return it. */
if (effective_policy == TOFU_POLICY_ASK && conflict_setp)
{
if (! conflict_set)
conflict_set = build_conflict_set (ctrl, dbs, pk, fingerprint, email);
*conflict_setp = conflict_set;
}
else
{
free_strlist (conflict_set);
if (conflict_setp)
*conflict_setp = NULL;
}
xfree (conflict_orig);
if (conflict != conflict_orig)
xfree (conflict);
free_strlist (results);
return effective_policy;
}
/* Return the trust level (TRUST_NEVER, etc.) for the binding
* <FINGERPRINT, EMAIL> (email is already normalized). If no policy
* is registered, returns TOFU_POLICY_NONE. If an error occurs,
* returns _tofu_GET_TRUST_ERROR.
*
* PK is the public key object for FINGERPRINT.
*
* USER_ID is the unadulterated user id.
*
* If MAY_ASK is set, then we may interact with the user. This is
* necessary if there is a conflict or the binding's policy is
* TOFU_POLICY_ASK. In the case of a conflict, we set the new
* conflicting binding's policy to TOFU_POLICY_ASK. In either case,
* we return TRUST_UNDEFINED. Note: if MAY_ASK is set, then this
* function must not be called while in a transaction! */
static enum tofu_policy
get_trust (ctrl_t ctrl, PKT_public_key *pk,
const char *fingerprint, const char *email,
const char *user_id, int may_ask,
enum tofu_policy *policyp, strlist_t *conflict_setp,
time_t now)
{
tofu_dbs_t dbs = ctrl->tofu.dbs;
int in_transaction = 0;
enum tofu_policy policy;
int rc;
char *sqerr = NULL;
strlist_t conflict_set = NULL;
int trust_level = TRUST_UNKNOWN;
strlist_t iter;
log_assert (dbs);
if (may_ask)
log_assert (dbs->in_transaction == 0);
if (opt.batch)
may_ask = 0;
log_assert (pk_is_primary (pk));
/* Make sure _tofu_GET_TRUST_ERROR isn't equal to any of the trust
levels. */
log_assert (_tofu_GET_TRUST_ERROR != TRUST_UNKNOWN
&& _tofu_GET_TRUST_ERROR != TRUST_EXPIRED
&& _tofu_GET_TRUST_ERROR != TRUST_UNDEFINED
&& _tofu_GET_TRUST_ERROR != TRUST_NEVER
&& _tofu_GET_TRUST_ERROR != TRUST_MARGINAL
&& _tofu_GET_TRUST_ERROR != TRUST_FULLY
&& _tofu_GET_TRUST_ERROR != TRUST_ULTIMATE);
begin_transaction (ctrl, 0);
in_transaction = 1;
/* We need to call get_policy even if the key is ultimately trusted
* to make sure the binding has been registered. */
policy = get_policy (ctrl, dbs, pk, fingerprint, user_id, email,
&conflict_set, now);
if (policy == TOFU_POLICY_ASK)
/* The conflict set should always contain at least one element:
* the current key. */
log_assert (conflict_set);
else
/* If the policy is not TOFU_POLICY_ASK, then conflict_set will be
* NULL. */
log_assert (! conflict_set);
/* If the key is ultimately trusted, there is nothing to do. */
{
u32 kid[2];
keyid_from_pk (pk, kid);
if (tdb_keyid_is_utk (kid))
{
trust_level = TRUST_ULTIMATE;
policy = TOFU_POLICY_GOOD;
goto out;
}
}
if (policy == TOFU_POLICY_AUTO)
{
policy = opt.tofu_default_policy;
if (DBG_TRUST)
log_debug ("TOFU: binding <key: %s, user id: %s>'s policy is"
" auto (default: %s).\n",
fingerprint, email,
tofu_policy_str (opt.tofu_default_policy));
if (policy == TOFU_POLICY_ASK)
/* The default policy is ASK, but there is no conflict (policy
* was 'auto'). In this case, we need to make sure the
* conflict set includes at least the current user id. */
{
add_to_strlist (&conflict_set, fingerprint);
}
}
switch (policy)
{
case TOFU_POLICY_AUTO:
case TOFU_POLICY_GOOD:
case TOFU_POLICY_UNKNOWN:
case TOFU_POLICY_BAD:
/* The saved judgement is auto -> auto, good, unknown or bad.
* We don't need to ask the user anything. */
if (DBG_TRUST)
log_debug ("TOFU: Known binding <key: %s, user id: %s>'s policy: %s\n",
fingerprint, email, tofu_policy_str (policy));
trust_level = tofu_policy_to_trust_level (policy);
goto out;
case TOFU_POLICY_ASK:
/* We need to ask the user what to do. */
break;
case _tofu_GET_POLICY_ERROR:
trust_level = _tofu_GET_TRUST_ERROR;
goto out;
default:
log_bug ("%s: Impossible value for policy (%d)\n", __func__, policy);
}
/* We get here if:
*
* 1. The saved policy is auto and the default policy is ask
* (get_policy() == TOFU_POLICY_AUTO
* && opt.tofu_default_policy == TOFU_POLICY_ASK)
*
* 2. The saved policy is ask (either last time the user selected
* accept once or reject once or there was a conflict and this
* binding's policy was changed from auto to ask)
* (policy == TOFU_POLICY_ASK).
*/
log_assert (policy == TOFU_POLICY_ASK);
if (may_ask)
{
/* We can't be in a normal transaction in ask_about_binding. */
end_transaction (ctrl, 0);
in_transaction = 0;
/* If we get here, we need to ask the user about the binding. */
ask_about_binding (ctrl,
&policy,
&trust_level,
conflict_set,
fingerprint,
email,
user_id,
now);
}
else
{
trust_level = TRUST_UNDEFINED;
}
/* Mark any conflicting bindings that have an automatic policy as
* now requiring confirmation. Note: we do this after we ask for
* confirmation so that when the current policy is printed, it is
* correct. */
if (! in_transaction)
{
begin_transaction (ctrl, 0);
in_transaction = 1;
}
/* The conflict set should always contain at least one element:
* the current key. */
log_assert (conflict_set);
for (iter = conflict_set->next; iter; iter = iter->next)
{
/* We don't immediately set the effective policy to 'ask,
because */
rc = gpgsql_exec_printf
(dbs->db, NULL, NULL, &sqerr,
"update bindings set effective_policy = %d, conflict = %Q"
" where email = %Q and fingerprint = %Q and effective_policy != %d;",
TOFU_POLICY_NONE, fingerprint,
email, iter->d, TOFU_POLICY_ASK);
if (rc)
{
log_error (_("error changing TOFU policy: %s\n"), sqerr);
print_further_info ("binding: <key: %s, user id: %s>",
fingerprint, user_id);
sqlite3_free (sqerr);
sqerr = NULL;
rc = gpg_error (GPG_ERR_GENERAL);
}
else if (DBG_TRUST)
log_debug ("Set %s to conflict with %s\n",
iter->d, fingerprint);
}
out:
if (in_transaction)
end_transaction (ctrl, 0);
if (policyp)
*policyp = policy;
if (conflict_setp)
*conflict_setp = conflict_set;
else
free_strlist (conflict_set);
return trust_level;
}
/* Return a malloced string of the form
* "7~months"
* The caller should replace all '~' in the returned string by a space
* and also free the returned string.
*
* This is actually a bad hack which may not work correctly with all
* languages.
*/
static char *
time_ago_str (long long int t)
{
/* It would be nice to use a macro to do this, but gettext
works on the unpreprocessed code. */
#define MIN_SECS (60)
#define HOUR_SECS (60 * MIN_SECS)
#define DAY_SECS (24 * HOUR_SECS)
#define WEEK_SECS (7 * DAY_SECS)
#define MONTH_SECS (30 * DAY_SECS)
#define YEAR_SECS (365 * DAY_SECS)
if (t > 2 * YEAR_SECS)
{
long long int c = t / YEAR_SECS;
return xtryasprintf (ngettext("%lld~year", "%lld~years", c), c);
}
if (t > 2 * MONTH_SECS)
{
long long int c = t / MONTH_SECS;
return xtryasprintf (ngettext("%lld~month", "%lld~months", c), c);
}
if (t > 2 * WEEK_SECS)
{
long long int c = t / WEEK_SECS;
return xtryasprintf (ngettext("%lld~week", "%lld~weeks", c), c);
}
if (t > 2 * DAY_SECS)
{
long long int c = t / DAY_SECS;
return xtryasprintf (ngettext("%lld~day", "%lld~days", c), c);
}
if (t > 2 * HOUR_SECS)
{
long long int c = t / HOUR_SECS;
return xtryasprintf (ngettext("%lld~hour", "%lld~hours", c), c);
}
if (t > 2 * MIN_SECS)
{
long long int c = t / MIN_SECS;
return xtryasprintf (ngettext("%lld~minute", "%lld~minutes", c), c);
}
return xtryasprintf (ngettext("%lld~second", "%lld~seconds", t), t);
}
/* If FP is NULL, write TOFU_STATS status line. If FP is not NULL
* write a "tfs" record to that stream. */
static void
write_stats_status (estream_t fp,
enum tofu_policy policy,
unsigned long signature_count,
unsigned long signature_first_seen,
unsigned long signature_most_recent,
unsigned long signature_days,
unsigned long encryption_count,
unsigned long encryption_first_done,
unsigned long encryption_most_recent,
unsigned long encryption_days)
{
int summary;
int validity;
unsigned long days_sq;
/* Use the euclidean distance (m = sqrt(a^2 + b^2)) rather then the
sum of the magnitudes (m = a + b) to ensure a balance between
verified signatures and encrypted messages. */
days_sq = signature_days * signature_days + encryption_days * encryption_days;
if (days_sq < 1)
validity = 1; /* Key without history. */
else if (days_sq < (2 * BASIC_TRUST_THRESHOLD) * (2 * BASIC_TRUST_THRESHOLD))
validity = 2; /* Key with too little history. */
else if (days_sq < (2 * FULL_TRUST_THRESHOLD) * (2 * FULL_TRUST_THRESHOLD))
validity = 3; /* Key with enough history for basic trust. */
else
validity = 4; /* Key with a lot of history. */
if (policy == TOFU_POLICY_ASK)
summary = 0; /* Key requires attention. */
else
summary = validity;
if (fp)
{
es_fprintf (fp, "tfs:1:%d:%lu:%lu:%s:%lu:%lu:%lu:%lu:%d:%lu:%lu:\n",
summary, signature_count, encryption_count,
tofu_policy_str (policy),
signature_first_seen, signature_most_recent,
encryption_first_done, encryption_most_recent,
validity, signature_days, encryption_days);
}
else
{
write_status_printf (STATUS_TOFU_STATS,
"%d %lu %lu %s %lu %lu %lu %lu %d %lu %lu",
summary,
signature_count,
encryption_count,
tofu_policy_str (policy),
signature_first_seen,
signature_most_recent,
encryption_first_done,
encryption_most_recent,
validity,
signature_days, encryption_days);
}
}
/* Note: If OUTFP is not NULL, this function merely prints a "tfs" record
* to OUTFP.
*
* POLICY is the key's policy (as returned by get_policy).
*
* Returns 0 if ONLY_STATUS_FD is set. Otherwise, returns whether
* the caller should call show_warning after iterating over all user
* ids.
*/
static int
show_statistics (tofu_dbs_t dbs,
const char *fingerprint, const char *email,
enum tofu_policy policy,
estream_t outfp, int only_status_fd, time_t now)
{
char *fingerprint_pp;
int rc;
strlist_t strlist = NULL;
char *err = NULL;
unsigned long signature_first_seen = 0;
unsigned long signature_most_recent = 0;
unsigned long signature_count = 0;
unsigned long signature_days = 0;
unsigned long encryption_first_done = 0;
unsigned long encryption_most_recent = 0;
unsigned long encryption_count = 0;
unsigned long encryption_days = 0;
int show_warning = 0;
if (only_status_fd && ! is_status_enabled ())
return 0;
fingerprint_pp = format_hexfingerprint (fingerprint, NULL, 0);
/* Get the signature stats. */
rc = gpgsql_exec_printf
(dbs->db, strings_collect_cb, &strlist, &err,
"select count (*), coalesce (min (signatures.time), 0),\n"
" coalesce (max (signatures.time), 0)\n"
" from signatures\n"
" left join bindings on signatures.binding = bindings.oid\n"
" where fingerprint = %Q and email = %Q;",
fingerprint, email);
if (rc)
{
log_error (_("error reading TOFU database: %s\n"), err);
print_further_info ("getting signature statistics");
sqlite3_free (err);
rc = gpg_error (GPG_ERR_GENERAL);
goto out;
}
rc = gpgsql_exec_printf
(dbs->db, strings_collect_cb, &strlist, &err,
"select count (*) from\n"
" (select round(signatures.time / (24 * 60 * 60)) day\n"
" from signatures\n"
" left join bindings on signatures.binding = bindings.oid\n"
" where fingerprint = %Q and email = %Q\n"
" group by day);",
fingerprint, email);
if (rc)
{
log_error (_("error reading TOFU database: %s\n"), err);
print_further_info ("getting signature statistics (by day)");
sqlite3_free (err);
rc = gpg_error (GPG_ERR_GENERAL);
goto out;
}
if (strlist)
{
/* We expect exactly 4 elements. */
log_assert (strlist->next);
log_assert (strlist->next->next);
log_assert (strlist->next->next->next);
log_assert (! strlist->next->next->next->next);
string_to_ulong (&signature_days, strlist->d, -1, __LINE__);
string_to_ulong (&signature_count, strlist->next->d, -1, __LINE__);
string_to_ulong (&signature_first_seen,
strlist->next->next->d, -1, __LINE__);
string_to_ulong (&signature_most_recent,
strlist->next->next->next->d, -1, __LINE__);
free_strlist (strlist);
strlist = NULL;
}
/* Get the encryption stats. */
rc = gpgsql_exec_printf
(dbs->db, strings_collect_cb, &strlist, &err,
"select count (*), coalesce (min (encryptions.time), 0),\n"
" coalesce (max (encryptions.time), 0)\n"
" from encryptions\n"
" left join bindings on encryptions.binding = bindings.oid\n"
" where fingerprint = %Q and email = %Q;",
fingerprint, email);
if (rc)
{
log_error (_("error reading TOFU database: %s\n"), err);
print_further_info ("getting encryption statistics");
sqlite3_free (err);
rc = gpg_error (GPG_ERR_GENERAL);
goto out;
}
rc = gpgsql_exec_printf
(dbs->db, strings_collect_cb, &strlist, &err,
"select count (*) from\n"
" (select round(encryptions.time / (24 * 60 * 60)) day\n"
" from encryptions\n"
" left join bindings on encryptions.binding = bindings.oid\n"
" where fingerprint = %Q and email = %Q\n"
" group by day);",
fingerprint, email);
if (rc)
{
log_error (_("error reading TOFU database: %s\n"), err);
print_further_info ("getting encryption statistics (by day)");
sqlite3_free (err);
rc = gpg_error (GPG_ERR_GENERAL);
goto out;
}
if (strlist)
{
/* We expect exactly 4 elements. */
log_assert (strlist->next);
log_assert (strlist->next->next);
log_assert (strlist->next->next->next);
log_assert (! strlist->next->next->next->next);
string_to_ulong (&encryption_days, strlist->d, -1, __LINE__);
string_to_ulong (&encryption_count, strlist->next->d, -1, __LINE__);
string_to_ulong (&encryption_first_done,
strlist->next->next->d, -1, __LINE__);
string_to_ulong (&encryption_most_recent,
strlist->next->next->next->d, -1, __LINE__);
free_strlist (strlist);
strlist = NULL;
}
if (!outfp)
write_status_text_and_buffer (STATUS_TOFU_USER, fingerprint,
email, strlen (email), 0);
write_stats_status (outfp, policy,
signature_count,
signature_first_seen,
signature_most_recent,
signature_days,
encryption_count,
encryption_first_done,
encryption_most_recent,
encryption_days);
if (!outfp && !only_status_fd)
{
estream_t fp;
char *msg;
fp = es_fopenmem (0, "rw,samethread");
if (! fp)
log_fatal ("error creating memory stream: %s\n",
gpg_strerror (gpg_error_from_syserror()));
if (signature_count == 0 && encryption_count == 0)
{
es_fprintf (fp,
_("%s: Verified 0~signatures and encrypted 0~messages."),
email);
}
else
{
if (signature_count == 0)
es_fprintf (fp, _("%s: Verified 0 signatures."), email);
else
{
- /* TRANSLATORS: The final %s is replaced by a string like
- "7~months". */
+ /* Note: Translation not possible with that wording. */
char *ago_str = time_ago_str (now - signature_first_seen);
es_fprintf
- (fp,
- ngettext("%s: Verified %ld~signature in the past %s.",
- "%s: Verified %ld~signatures in the past %s.",
- signature_count),
+ (fp, "%s: Verified %ld~signatures in the past %s.",
email, signature_count, ago_str);
xfree (ago_str);
}
es_fputs (" ", fp);
if (encryption_count == 0)
es_fprintf (fp, _("Encrypted 0 messages."));
else
{
char *ago_str = time_ago_str (now - encryption_first_done);
- /* TRANSLATORS: The final %s is replaced by a string like
- "7~months". */
- es_fprintf (fp,
- ngettext("Encrypted %ld~message in the past %s.",
- "Encrypted %ld~messages in the past %s.",
- encryption_count),
+ /* Note: Translation not possible with this kind of
+ * composition. */
+ es_fprintf (fp, "Encrypted %ld~messages in the past %s.",
encryption_count, ago_str);
xfree (ago_str);
}
}
if (opt.verbose)
{
es_fputs (" ", fp);
es_fprintf (fp, _("(policy: %s)"), tofu_policy_str (policy));
}
es_fputs ("\n", fp);
{
char *tmpmsg, *p;
es_fputc (0, fp);
if (es_fclose_snatch (fp, (void **) &tmpmsg, NULL))
log_fatal ("error snatching memory stream\n");
msg = format_text (tmpmsg, 72, 80);
if (!msg) /* FIXME: Return the error all the way up. */
log_fatal ("format failed: %s\n",
gpg_strerror (gpg_error_from_syserror()));
es_free (tmpmsg);
/* Print a status line but suppress the trailing LF.
* Spaces are not percent escaped. */
if (*msg)
write_status_buffer (STATUS_TOFU_STATS_LONG,
msg, strlen (msg)-1, -1);
/* Remove the non-breaking space markers. */
for (p=msg; *p; p++)
if (*p == '~')
*p = ' ';
}
log_string (GPGRT_LOGLVL_INFO, msg);
xfree (msg);
if (policy == TOFU_POLICY_AUTO)
{
if (signature_count == 0)
log_info (_("Warning: we have yet to see"
" a message signed using this key and user id!\n"));
else if (signature_count == 1)
log_info (_("Warning: we've only seen one message"
" signed using this key and user id!\n"));
if (encryption_count == 0)
log_info (_("Warning: you have yet to encrypt"
" a message to this key!\n"));
else if (encryption_count == 1)
log_info (_("Warning: you have only encrypted"
" one message to this key!\n"));
/* Cf. write_stats_status */
if ((encryption_count * encryption_count
+ signature_count * signature_count)
< ((2 * BASIC_TRUST_THRESHOLD) * (2 * BASIC_TRUST_THRESHOLD)))
show_warning = 1;
}
}
out:
xfree (fingerprint_pp);
return show_warning;
}
static void
show_warning (const char *fingerprint, strlist_t user_id_list)
{
char *set_policy_command;
char *text;
char *tmpmsg;
set_policy_command =
xasprintf ("gpg --tofu-policy bad %s", fingerprint);
tmpmsg = xasprintf
(ngettext
("Warning: if you think you've seen more signatures "
"by this key and user id, then this key might be a "
"forgery! Carefully examine the email address for small "
"variations. If the key is suspect, then use\n"
" %s\n"
"to mark it as being bad.\n",
"Warning: if you think you've seen more signatures "
"by this key and these user ids, then this key might be a "
"forgery! Carefully examine the email addresses for small "
"variations. If the key is suspect, then use\n"
" %s\n"
"to mark it as being bad.\n",
strlist_length (user_id_list)),
set_policy_command);
text = format_text (tmpmsg, 72, 80);
if (!text) /* FIXME: Return the error all the way up. */
log_fatal ("format failed: %s\n",
gpg_strerror (gpg_error_from_syserror()));
xfree (tmpmsg);
log_string (GPGRT_LOGLVL_INFO, text);
xfree (text);
es_free (set_policy_command);
}
/* Extract the email address from a user id and normalize it. If the
user id doesn't contain an email address, then we use the whole
user_id and normalize that. The returned string must be freed. */
static char *
email_from_user_id (const char *user_id)
{
char *email = mailbox_from_userid (user_id, 0);
if (! email)
{
/* Hmm, no email address was provided or we are out of core. Just
take the lower-case version of the whole user id. It could be
a hostname, for instance. */
email = ascii_strlwr (xstrdup (user_id));
}
return email;
}
/* Register the signature with the bindings <fingerprint, USER_ID>,
for each USER_ID in USER_ID_LIST. The fingerprint is taken from
the primary key packet PK.
SIG_DIGEST_BIN is the binary representation of the message's
digest. SIG_DIGEST_BIN_LEN is its length.
SIG_TIME is the time that the signature was generated.
ORIGIN is a free-formed string describing the origin of the
signature. If this was from an email and the Claws MUA was used,
then this should be something like: "email:claws". If this is
NULL, the default is simply "unknown".
If MAY_ASK is 1, then this function may interact with the user.
This is necessary if there is a conflict or the binding's policy is
TOFU_POLICY_ASK.
This function returns 0 on success and an error code if an error
occurred. */
gpg_error_t
tofu_register_signature (ctrl_t ctrl,
PKT_public_key *pk, strlist_t user_id_list,
const byte *sig_digest_bin, int sig_digest_bin_len,
time_t sig_time, const char *origin)
{
time_t now = gnupg_get_time ();
gpg_error_t rc;
tofu_dbs_t dbs;
char *fingerprint = NULL;
strlist_t user_id;
char *email = NULL;
char *sqlerr = NULL;
char *sig_digest = NULL;
unsigned long c;
dbs = opendbs (ctrl);
if (! dbs)
{
rc = gpg_error (GPG_ERR_GENERAL);
log_error (_("error opening TOFU database: %s\n"),
gpg_strerror (rc));
return rc;
}
/* We do a query and then an insert. Make sure they are atomic
by wrapping them in a transaction. */
rc = begin_transaction (ctrl, 0);
if (rc)
return rc;
log_assert (pk_is_primary (pk));
sig_digest = make_radix64_string (sig_digest_bin, sig_digest_bin_len);
if (!sig_digest)
{
rc = gpg_error_from_syserror ();
goto leave;
}
fingerprint = hexfingerprint (pk, NULL, 0);
if (!fingerprint)
{
rc = gpg_error_from_syserror ();
goto leave;
}
if (! origin)
origin = "unknown"; /* The default origin is simply "unknown". */
for (user_id = user_id_list; user_id; user_id = user_id->next)
{
email = email_from_user_id (user_id->d);
if (DBG_TRUST)
log_debug ("TOFU: Registering signature %s with binding"
" <key: %s, user id: %s>\n",
sig_digest, fingerprint, email);
/* Make sure the binding exists and record any TOFU
conflicts. */
if (get_trust (ctrl, pk, fingerprint, email, user_id->d,
0, NULL, NULL, now)
== _tofu_GET_TRUST_ERROR)
{
rc = gpg_error (GPG_ERR_GENERAL);
xfree (email);
break;
}
/* If we've already seen this signature before, then don't add
it again. */
rc = gpgsql_stepx
(dbs->db, &dbs->s.register_already_seen,
get_single_unsigned_long_cb2, &c, &sqlerr,
"select count (*)\n"
" from signatures left join bindings\n"
" on signatures.binding = bindings.oid\n"
" where fingerprint = ? and email = ? and sig_time = ?\n"
" and sig_digest = ?",
GPGSQL_ARG_STRING, fingerprint, GPGSQL_ARG_STRING, email,
GPGSQL_ARG_LONG_LONG, (long long) sig_time,
GPGSQL_ARG_STRING, sig_digest,
GPGSQL_ARG_END);
if (rc)
{
log_error (_("error reading TOFU database: %s\n"), sqlerr);
print_further_info ("checking existence");
sqlite3_free (sqlerr);
rc = gpg_error (GPG_ERR_GENERAL);
}
else if (c > 1)
/* Duplicates! This should not happen. In particular,
because <fingerprint, email, sig_time, sig_digest> is the
primary key! */
log_debug ("SIGNATURES DB contains duplicate records"
" <key: %s, email: %s, time: 0x%lx, sig: %s,"
" origin: %s>."
" Please report.\n",
fingerprint, email, (unsigned long) sig_time,
sig_digest, origin);
else if (c == 1)
{
if (DBG_TRUST)
log_debug ("Already observed the signature and binding"
" <key: %s, email: %s, time: 0x%lx, sig: %s,"
" origin: %s>\n",
fingerprint, email, (unsigned long) sig_time,
sig_digest, origin);
}
else if (opt.dry_run)
{
log_info ("TOFU database update skipped due to --dry-run\n");
}
else
/* This is the first time that we've seen this signature and
binding. Record it. */
{
if (DBG_TRUST)
log_debug ("TOFU: Saving signature"
" <key: %s, user id: %s, sig: %s>\n",
fingerprint, email, sig_digest);
log_assert (c == 0);
rc = gpgsql_stepx
(dbs->db, &dbs->s.register_signature, NULL, NULL, &sqlerr,
"insert into signatures\n"
" (binding, sig_digest, origin, sig_time, time)\n"
" values\n"
" ((select oid from bindings\n"
" where fingerprint = ? and email = ?),\n"
" ?, ?, ?, ?);",
GPGSQL_ARG_STRING, fingerprint, GPGSQL_ARG_STRING, email,
GPGSQL_ARG_STRING, sig_digest, GPGSQL_ARG_STRING, origin,
GPGSQL_ARG_LONG_LONG, (long long) sig_time,
GPGSQL_ARG_LONG_LONG, (long long) now,
GPGSQL_ARG_END);
if (rc)
{
log_error (_("error updating TOFU database: %s\n"), sqlerr);
print_further_info ("insert signatures");
sqlite3_free (sqlerr);
rc = gpg_error (GPG_ERR_GENERAL);
}
}
xfree (email);
if (rc)
break;
}
leave:
if (rc)
rollback_transaction (ctrl);
else
rc = end_transaction (ctrl, 0);
xfree (fingerprint);
xfree (sig_digest);
return rc;
}
gpg_error_t
tofu_register_encryption (ctrl_t ctrl,
PKT_public_key *pk, strlist_t user_id_list,
int may_ask)
{
time_t now = gnupg_get_time ();
gpg_error_t rc = 0;
tofu_dbs_t dbs;
kbnode_t kb = NULL;
int free_user_id_list = 0;
char *fingerprint = NULL;
strlist_t user_id;
char *sqlerr = NULL;
int in_batch = 0;
dbs = opendbs (ctrl);
if (! dbs)
{
rc = gpg_error (GPG_ERR_GENERAL);
log_error (_("error opening TOFU database: %s\n"),
gpg_strerror (rc));
return rc;
}
if (/* We need the key block to find the primary key. */
! pk_is_primary (pk)
/* We need the key block to find all user ids. */
|| ! user_id_list)
kb = get_pubkeyblock (ctrl, pk->keyid);
/* Make sure PK is a primary key. */
if (! pk_is_primary (pk))
pk = kb->pkt->pkt.public_key;
if (! user_id_list)
{
/* Use all non-revoked user ids. Do use expired user ids. */
kbnode_t n = kb;
while ((n = find_next_kbnode (n, PKT_USER_ID)))
{
PKT_user_id *uid = n->pkt->pkt.user_id;
if (uid->flags.revoked)
continue;
add_to_strlist (&user_id_list, uid->name);
}
free_user_id_list = 1;
if (! user_id_list)
log_info (_("WARNING: Encrypting to %s, which has no "
"non-revoked user ids\n"),
keystr (pk->keyid));
}
fingerprint = hexfingerprint (pk, NULL, 0);
if (!fingerprint)
{
rc = gpg_error_from_syserror ();
goto leave;
}
tofu_begin_batch_update (ctrl);
in_batch = 1;
tofu_resume_batch_transaction (ctrl);
for (user_id = user_id_list; user_id; user_id = user_id->next)
{
char *email = email_from_user_id (user_id->d);
strlist_t conflict_set = NULL;
enum tofu_policy policy;
/* Make sure the binding exists and that we recognize any
conflicts. */
int tl = get_trust (ctrl, pk, fingerprint, email, user_id->d,
may_ask, &policy, &conflict_set, now);
if (tl == _tofu_GET_TRUST_ERROR)
{
/* An error. */
rc = gpg_error (GPG_ERR_GENERAL);
xfree (email);
goto leave;
}
/* If there is a conflict and MAY_ASK is true, we need to show
* the TOFU statistics for the current binding and the
* conflicting bindings. But, if we are not in batch mode, then
* they have already been printed (this is required to make sure
* the information is available to the caller before cpr_get is
* called). */
if (policy == TOFU_POLICY_ASK && may_ask && opt.batch)
{
strlist_t iter;
/* The conflict set should contain at least the current
* key. */
log_assert (conflict_set);
for (iter = conflict_set; iter; iter = iter->next)
show_statistics (dbs, iter->d, email,
TOFU_POLICY_ASK, NULL, 1, now);
}
free_strlist (conflict_set);
rc = gpgsql_stepx
(dbs->db, &dbs->s.register_encryption, NULL, NULL, &sqlerr,
"insert into encryptions\n"
" (binding, time)\n"
" values\n"
" ((select oid from bindings\n"
" where fingerprint = ? and email = ?),\n"
" ?);",
GPGSQL_ARG_STRING, fingerprint, GPGSQL_ARG_STRING, email,
GPGSQL_ARG_LONG_LONG, (long long) now,
GPGSQL_ARG_END);
if (rc)
{
log_error (_("error updating TOFU database: %s\n"), sqlerr);
print_further_info ("insert encryption");
sqlite3_free (sqlerr);
rc = gpg_error (GPG_ERR_GENERAL);
}
xfree (email);
}
leave:
if (in_batch)
tofu_end_batch_update (ctrl);
release_kbnode (kb);
if (free_user_id_list)
free_strlist (user_id_list);
xfree (fingerprint);
return rc;
}
/* Combine a trust level returned from the TOFU trust model with a
trust level returned by the PGP trust model. This is primarily of
interest when the trust model is tofu+pgp (TM_TOFU_PGP).
This function ors together the upper bits (the values not covered
by TRUST_MASK, i.e., TRUST_FLAG_REVOKED, etc.). */
int
tofu_wot_trust_combine (int tofu_base, int wot_base)
{
int tofu = tofu_base & TRUST_MASK;
int wot = wot_base & TRUST_MASK;
int upper = (tofu_base & ~TRUST_MASK) | (wot_base & ~TRUST_MASK);
log_assert (tofu == TRUST_UNKNOWN
|| tofu == TRUST_EXPIRED
|| tofu == TRUST_UNDEFINED
|| tofu == TRUST_NEVER
|| tofu == TRUST_MARGINAL
|| tofu == TRUST_FULLY
|| tofu == TRUST_ULTIMATE);
log_assert (wot == TRUST_UNKNOWN
|| wot == TRUST_EXPIRED
|| wot == TRUST_UNDEFINED
|| wot == TRUST_NEVER
|| wot == TRUST_MARGINAL
|| wot == TRUST_FULLY
|| wot == TRUST_ULTIMATE);
/* We first consider negative trust policys. These trump positive
trust policies. */
if (tofu == TRUST_NEVER || wot == TRUST_NEVER)
/* TRUST_NEVER trumps everything else. */
return upper | TRUST_NEVER;
if (tofu == TRUST_EXPIRED || wot == TRUST_EXPIRED)
/* TRUST_EXPIRED trumps everything but TRUST_NEVER. */
return upper | TRUST_EXPIRED;
/* Now we only have positive or neutral trust policies. We take
the max. */
if (tofu == TRUST_ULTIMATE)
return upper | TRUST_ULTIMATE | TRUST_FLAG_TOFU_BASED;
if (wot == TRUST_ULTIMATE)
return upper | TRUST_ULTIMATE;
if (tofu == TRUST_FULLY)
return upper | TRUST_FULLY | TRUST_FLAG_TOFU_BASED;
if (wot == TRUST_FULLY)
return upper | TRUST_FULLY;
if (tofu == TRUST_MARGINAL)
return upper | TRUST_MARGINAL | TRUST_FLAG_TOFU_BASED;
if (wot == TRUST_MARGINAL)
return upper | TRUST_MARGINAL;
if (tofu == TRUST_UNDEFINED)
return upper | TRUST_UNDEFINED | TRUST_FLAG_TOFU_BASED;
if (wot == TRUST_UNDEFINED)
return upper | TRUST_UNDEFINED;
return upper | TRUST_UNKNOWN;
}
/* Write a "tfs" record for a --with-colons listing. */
gpg_error_t
tofu_write_tfs_record (ctrl_t ctrl, estream_t fp,
PKT_public_key *pk, const char *user_id)
{
time_t now = gnupg_get_time ();
gpg_error_t err = 0;
tofu_dbs_t dbs;
char *fingerprint;
char *email = NULL;
enum tofu_policy policy;
if (!*user_id)
return 0; /* No TOFU stats possible for an empty ID. */
dbs = opendbs (ctrl);
if (!dbs)
{
err = gpg_error (GPG_ERR_GENERAL);
log_error (_("error opening TOFU database: %s\n"), gpg_strerror (err));
return err;
}
fingerprint = hexfingerprint (pk, NULL, 0);
if (!fingerprint)
{
err = gpg_error_from_syserror ();
goto leave;
}
email = email_from_user_id (user_id);
policy = get_policy (ctrl, dbs, pk, fingerprint, user_id, email, NULL, now);
show_statistics (dbs, fingerprint, email, policy, fp, 0, now);
leave:
xfree (email);
xfree (fingerprint);
return err;
}
/* Return the validity (TRUST_NEVER, etc.) of the bindings
<FINGERPRINT, USER_ID>, for each USER_ID in USER_ID_LIST. If
USER_ID_LIST->FLAG is set, then the id is considered to be expired.
PK is the primary key packet.
If MAY_ASK is 1 and the policy is TOFU_POLICY_ASK, then the user
will be prompted to choose a policy. If MAY_ASK is 0 and the
policy is TOFU_POLICY_ASK, then TRUST_UNKNOWN is returned.
Returns TRUST_UNDEFINED if an error occurs.
Fixme: eturn an error code
*/
int
tofu_get_validity (ctrl_t ctrl, PKT_public_key *pk, strlist_t user_id_list,
int may_ask)
{
time_t now = gnupg_get_time ();
tofu_dbs_t dbs;
char *fingerprint = NULL;
strlist_t user_id;
int trust_level = TRUST_UNKNOWN;
int bindings = 0;
int bindings_valid = 0;
int need_warning = 0;
int had_conflict = 0;
dbs = opendbs (ctrl);
if (! dbs)
{
log_error (_("error opening TOFU database: %s\n"),
gpg_strerror (GPG_ERR_GENERAL));
return TRUST_UNDEFINED;
}
fingerprint = hexfingerprint (pk, NULL, 0);
if (!fingerprint)
log_fatal ("%s: malloc failed\n", __func__);
tofu_begin_batch_update (ctrl);
/* Start the batch transaction now. */
tofu_resume_batch_transaction (ctrl);
for (user_id = user_id_list; user_id; user_id = user_id->next, bindings ++)
{
char *email = email_from_user_id (user_id->d);
strlist_t conflict_set = NULL;
enum tofu_policy policy;
/* Always call get_trust to make sure the binding is
registered. */
int tl = get_trust (ctrl, pk, fingerprint, email, user_id->d,
may_ask, &policy, &conflict_set, now);
if (tl == _tofu_GET_TRUST_ERROR)
{
/* An error. */
trust_level = TRUST_UNDEFINED;
xfree (email);
goto die;
}
if (DBG_TRUST)
log_debug ("TOFU: validity for <key: %s, user id: %s>: %s%s.\n",
fingerprint, email,
trust_value_to_string (tl),
user_id->flags ? " (but expired)" : "");
if (user_id->flags)
tl = TRUST_EXPIRED;
if (tl != TRUST_EXPIRED)
bindings_valid ++;
if (may_ask && tl != TRUST_ULTIMATE && tl != TRUST_EXPIRED)
{
/* If policy is ask, then we already printed out the
* conflict information in ask_about_binding or will do so
* in a moment. */
if (policy != TOFU_POLICY_ASK)
need_warning |=
show_statistics (dbs, fingerprint, email, policy, NULL, 0, now);
/* If there is a conflict and MAY_ASK is true, we need to
* show the TOFU statistics for the current binding and the
* conflicting bindings. But, if we are not in batch mode,
* then they have already been printed (this is required to
* make sure the information is available to the caller
* before cpr_get is called). */
if (policy == TOFU_POLICY_ASK && opt.batch)
{
strlist_t iter;
/* The conflict set should contain at least the current
* key. */
log_assert (conflict_set);
had_conflict = 1;
for (iter = conflict_set; iter; iter = iter->next)
show_statistics (dbs, iter->d, email,
TOFU_POLICY_ASK, NULL, 1, now);
}
}
free_strlist (conflict_set);
if (tl == TRUST_NEVER)
trust_level = TRUST_NEVER;
else if (tl == TRUST_EXPIRED)
/* Ignore expired bindings in the trust calculation. */
;
else if (tl > trust_level)
{
/* The expected values: */
log_assert (tl == TRUST_UNKNOWN || tl == TRUST_UNDEFINED
|| tl == TRUST_MARGINAL || tl == TRUST_FULLY
|| tl == TRUST_ULTIMATE);
/* We assume the following ordering: */
log_assert (TRUST_UNKNOWN < TRUST_UNDEFINED);
log_assert (TRUST_UNDEFINED < TRUST_MARGINAL);
log_assert (TRUST_MARGINAL < TRUST_FULLY);
log_assert (TRUST_FULLY < TRUST_ULTIMATE);
trust_level = tl;
}
xfree (email);
}
if (need_warning && ! had_conflict)
show_warning (fingerprint, user_id_list);
die:
tofu_end_batch_update (ctrl);
xfree (fingerprint);
if (bindings_valid == 0)
{
if (DBG_TRUST)
log_debug ("no (of %d) valid bindings."
" Can't get TOFU validity for this set of user ids.\n",
bindings);
return TRUST_NEVER;
}
return trust_level;
}
/* Set the policy for all non-revoked user ids in the keyblock KB to
POLICY.
If no key is available with the specified key id, then this
function returns GPG_ERR_NO_PUBKEY.
Returns 0 on success and an error code otherwise. */
gpg_error_t
tofu_set_policy (ctrl_t ctrl, kbnode_t kb, enum tofu_policy policy)
{
gpg_error_t err = 0;
time_t now = gnupg_get_time ();
tofu_dbs_t dbs;
PKT_public_key *pk;
char *fingerprint = NULL;
log_assert (kb->pkt->pkttype == PKT_PUBLIC_KEY);
pk = kb->pkt->pkt.public_key;
dbs = opendbs (ctrl);
if (! dbs)
{
log_error (_("error opening TOFU database: %s\n"),
gpg_strerror (GPG_ERR_GENERAL));
return gpg_error (GPG_ERR_GENERAL);
}
if (DBG_TRUST)
log_debug ("Setting TOFU policy for %s to %s\n",
keystr (pk->keyid), tofu_policy_str (policy));
if (! pk_is_primary (pk))
log_bug ("%s: Passed a subkey, but expecting a primary key.\n", __func__);
fingerprint = hexfingerprint (pk, NULL, 0);
if (!fingerprint)
return gpg_error_from_syserror ();
begin_transaction (ctrl, 0);
for (; kb; kb = kb->next)
{
PKT_user_id *user_id;
char *email;
if (kb->pkt->pkttype != PKT_USER_ID)
continue;
user_id = kb->pkt->pkt.user_id;
if (user_id->flags.revoked)
/* Skip revoked user ids. (Don't skip expired user ids, the
expiry can be changed.) */
continue;
email = email_from_user_id (user_id->name);
err = record_binding (dbs, fingerprint, email, user_id->name,
policy, TOFU_POLICY_NONE, NULL, 0, 1, now);
if (err)
{
- log_error (_("error setting policy for key %s, user id \"%s\": %s"),
+ log_error ("error setting policy for key %s, user id \"%s\": %s",
fingerprint, email, gpg_strerror (err));
xfree (email);
break;
}
xfree (email);
}
if (err)
rollback_transaction (ctrl);
else
end_transaction (ctrl, 0);
xfree (fingerprint);
return err;
}
/* Return the TOFU policy for the specified binding in *POLICY. If no
policy has been set for the binding, sets *POLICY to
TOFU_POLICY_NONE.
PK is a primary public key and USER_ID is a user id.
Returns 0 on success and an error code otherwise. */
gpg_error_t
tofu_get_policy (ctrl_t ctrl, PKT_public_key *pk, PKT_user_id *user_id,
enum tofu_policy *policy)
{
time_t now = gnupg_get_time ();
tofu_dbs_t dbs;
char *fingerprint;
char *email;
/* Make sure PK is a primary key. */
log_assert (pk_is_primary (pk));
dbs = opendbs (ctrl);
if (! dbs)
{
log_error (_("error opening TOFU database: %s\n"),
gpg_strerror (GPG_ERR_GENERAL));
return gpg_error (GPG_ERR_GENERAL);
}
fingerprint = hexfingerprint (pk, NULL, 0);
if (!fingerprint)
return gpg_error_from_syserror ();
email = email_from_user_id (user_id->name);
*policy = get_policy (ctrl, dbs, pk, fingerprint,
user_id->name, email, NULL, now);
xfree (email);
xfree (fingerprint);
if (*policy == _tofu_GET_POLICY_ERROR)
return gpg_error (GPG_ERR_GENERAL);
return 0;
}
gpg_error_t
tofu_notice_key_changed (ctrl_t ctrl, kbnode_t kb)
{
tofu_dbs_t dbs;
PKT_public_key *pk;
char *fingerprint;
char *sqlerr = NULL;
int rc;
/* Make sure PK is a primary key. */
setup_main_keyids (kb);
pk = kb->pkt->pkt.public_key;
log_assert (pk_is_primary (pk));
dbs = opendbs (ctrl);
if (! dbs)
{
log_error (_("error opening TOFU database: %s\n"),
gpg_strerror (GPG_ERR_GENERAL));
return gpg_error (GPG_ERR_GENERAL);
}
fingerprint = hexfingerprint (pk, NULL, 0);
if (!fingerprint)
return gpg_error_from_syserror ();
rc = gpgsql_stepx (dbs->db, NULL, NULL, NULL, &sqlerr,
"update bindings set effective_policy = ?"
" where fingerprint = ?;",
GPGSQL_ARG_INT, (int) TOFU_POLICY_NONE,
GPGSQL_ARG_STRING, fingerprint,
GPGSQL_ARG_END);
xfree (fingerprint);
if (rc == _tofu_GET_POLICY_ERROR)
return gpg_error (GPG_ERR_GENERAL);
return 0;
}
diff --git a/g13/server.c b/g13/server.c
index defde6c02..780295214 100644
--- a/g13/server.c
+++ b/g13/server.c
@@ -1,783 +1,783 @@
/* server.c - The G13 Assuan server
* Copyright (C) 2009 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <errno.h>
#include <assert.h>
#include "g13.h"
#include <assuan.h>
#include "../common/i18n.h"
#include "keyblob.h"
#include "server.h"
#include "create.h"
#include "mount.h"
#include "suspend.h"
#include "../common/server-help.h"
#include "../common/asshelp.h"
#include "../common/call-gpg.h"
/* The filepointer for status message used in non-server mode */
static FILE *statusfp;
/* Local data for this server module. A pointer to this is stored in
the CTRL object of each connection. */
struct server_local_s
{
/* The Assuan context we are working on. */
assuan_context_t assuan_ctx;
char *containername; /* Malloced active containername. */
};
/* Local prototypes. */
static int command_has_option (const char *cmd, const char *cmdopt);
/*
Helper functions.
*/
/* Set an error and a description. */
#define set_error(e,t) assuan_set_error (ctx, gpg_error (e), (t))
/* Helper to print a message while leaving a command. */
static gpg_error_t
leave_cmd (assuan_context_t ctx, gpg_error_t err)
{
if (err)
{
const char *name = assuan_get_command_name (ctx);
if (!name)
name = "?";
if (gpg_err_source (err) == GPG_ERR_SOURCE_DEFAULT)
log_error ("command '%s' failed: %s\n", name,
gpg_strerror (err));
else
log_error ("command '%s' failed: %s <%s>\n", name,
gpg_strerror (err), gpg_strsource (err));
}
return err;
}
/* The handler for Assuan OPTION commands. */
static gpg_error_t
option_handler (assuan_context_t ctx, const char *key, const char *value)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err = 0;
(void)ctrl;
if (!strcmp (key, "putenv"))
{
/* Change the session's environment to be used for the
Pinentry. Valid values are:
<NAME> Delete envvar NAME
<KEY>= Set envvar NAME to the empty string
<KEY>=<VALUE> Set envvar NAME to VALUE
*/
err = session_env_putenv (opt.session_env, value);
}
else if (!strcmp (key, "display"))
{
err = session_env_setenv (opt.session_env, "DISPLAY", value);
}
else if (!strcmp (key, "ttyname"))
{
err = session_env_setenv (opt.session_env, "GPG_TTY", value);
}
else if (!strcmp (key, "ttytype"))
{
err = session_env_setenv (opt.session_env, "TERM", value);
}
else if (!strcmp (key, "lc-ctype"))
{
xfree (opt.lc_ctype);
opt.lc_ctype = xtrystrdup (value);
if (!opt.lc_ctype)
err = gpg_error_from_syserror ();
}
else if (!strcmp (key, "lc-messages"))
{
xfree (opt.lc_messages);
opt.lc_messages = xtrystrdup (value);
if (!opt.lc_messages)
err = gpg_error_from_syserror ();
}
else if (!strcmp (key, "xauthority"))
{
err = session_env_setenv (opt.session_env, "XAUTHORITY", value);
}
else if (!strcmp (key, "pinentry-user-data"))
{
err = session_env_setenv (opt.session_env, "PINENTRY_USER_DATA", value);
}
else if (!strcmp (key, "allow-pinentry-notify"))
{
; /* We always allow it. */
}
else
err = gpg_error (GPG_ERR_UNKNOWN_OPTION);
return err;
}
/* The handler for an Assuan RESET command. */
static gpg_error_t
reset_notify (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
(void)line;
xfree (ctrl->server_local->containername);
ctrl->server_local->containername = NULL;
FREE_STRLIST (ctrl->recipients);
assuan_close_input_fd (ctx);
assuan_close_output_fd (ctx);
return 0;
}
static const char hlp_open[] =
"OPEN [<options>] <filename>\n"
"\n"
"Open the container FILENAME. FILENAME must be percent-plus\n"
"escaped. A quick check to see whether this is a suitable G13\n"
"container file is done. However no cryptographic check or any\n"
"other check is done. This command is used to define the target for\n"
"further commands. The filename is reset with the RESET command,\n"
"another OPEN or the CREATE command.";
static gpg_error_t
cmd_open (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err = 0;
char *p, *pend;
size_t len;
/* In any case reset the active container. */
xfree (ctrl->server_local->containername);
ctrl->server_local->containername = NULL;
/* Parse the line. */
line = skip_options (line);
for (p=line; *p && !spacep (p); p++)
;
pend = p;
while (spacep(p))
p++;
if (*p || pend == line)
{
err = gpg_error (GPG_ERR_ASS_SYNTAX);
goto leave;
}
*pend = 0;
/* Unescape the line and check for embedded Nul bytes. */
len = percent_plus_unescape_inplace (line, 0);
line[len] = 0;
if (!len || memchr (line, 0, len))
{
err = gpg_error (GPG_ERR_INV_NAME);
goto leave;
}
/* Do a basic check. */
err = g13_is_container (ctrl, line);
if (err)
goto leave;
/* Store the filename. */
ctrl->server_local->containername = xtrystrdup (line);
if (!ctrl->server_local->containername)
err = gpg_error_from_syserror ();
leave:
return leave_cmd (ctx, err);
}
static const char hlp_mount[] =
"MOUNT [options] [<mountpoint>]\n"
"\n"
"Mount the currently open file onto MOUNTPOINT. If MOUNTPOINT is not\n"
"given the system picks an unused mountpoint. MOUNTPOINT must\n"
"be percent-plus escaped to allow for arbitrary names.";
static gpg_error_t
cmd_mount (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err = 0;
char *p, *pend;
size_t len;
line = skip_options (line);
for (p=line; *p && !spacep (p); p++)
;
pend = p;
while (spacep(p))
p++;
if (*p)
{
err = gpg_error (GPG_ERR_ASS_SYNTAX);
goto leave;
}
*pend = 0;
/* Unescape the line and check for embedded Nul bytes. */
len = percent_plus_unescape_inplace (line, 0);
line[len] = 0;
if (memchr (line, 0, len))
{
err = gpg_error (GPG_ERR_INV_NAME);
goto leave;
}
if (!ctrl->server_local->containername)
{
err = gpg_error (GPG_ERR_MISSING_ACTION);
goto leave;
}
/* Perform the mount. */
err = g13_mount_container (ctrl, ctrl->server_local->containername,
*line? line : NULL);
leave:
return leave_cmd (ctx, err);
}
static const char hlp_umount[] =
"UMOUNT [options] [<mountpoint>]\n"
"\n"
"Unmount the currently open file or the one opened at MOUNTPOINT.\n"
"MOUNTPOINT must be percent-plus escaped. On success the mountpoint\n"
"is returned via a \"MOUNTPOINT\" status line.";
static gpg_error_t
cmd_umount (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err = 0;
char *p, *pend;
size_t len;
line = skip_options (line);
for (p=line; *p && !spacep (p); p++)
;
pend = p;
while (spacep(p))
p++;
if (*p)
{
err = gpg_error (GPG_ERR_ASS_SYNTAX);
goto leave;
}
*pend = 0;
/* Unescape the line and check for embedded Nul bytes. */
len = percent_plus_unescape_inplace (line, 0);
line[len] = 0;
if (memchr (line, 0, len))
{
err = gpg_error (GPG_ERR_INV_NAME);
goto leave;
}
/* Perform the unmount. */
err = g13_umount_container (ctrl, ctrl->server_local->containername,
*line? line : NULL);
leave:
return leave_cmd (ctx, err);
}
static const char hlp_suspend[] =
"SUSPEND\n"
"\n"
"Suspend the currently set device.";
static gpg_error_t
cmd_suspend (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err;
line = skip_options (line);
if (*line)
{
err = gpg_error (GPG_ERR_ASS_SYNTAX);
goto leave;
}
/* Perform the suspend operation. */
err = g13_suspend_container (ctrl, ctrl->server_local->containername);
leave:
return leave_cmd (ctx, err);
}
static const char hlp_resume[] =
"RESUME\n"
"\n"
"Resume the currently set device.";
static gpg_error_t
cmd_resume (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err;
line = skip_options (line);
if (*line)
{
err = gpg_error (GPG_ERR_ASS_SYNTAX);
goto leave;
}
/* Perform the suspend operation. */
err = g13_resume_container (ctrl, ctrl->server_local->containername);
leave:
return leave_cmd (ctx, err);
}
static const char hlp_recipient[] =
"RECIPIENT <userID>\n"
"\n"
"Add USERID to the list of recipients to be used for the next CREATE\n"
"command. All recipient commands are cumulative until a RESET or an\n"
"successful create command.";
static gpg_error_t
cmd_recipient (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err = 0;
line = skip_options (line);
if (!add_to_strlist_try (&ctrl->recipients, line))
err = gpg_error_from_syserror ();
return leave_cmd (ctx, err);
}
static const char hlp_signer[] =
"SIGNER <userID>\n"
"\n"
"Not yet implemented.";
static gpg_error_t
cmd_signer (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err;
(void)ctrl;
(void)line;
err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
return leave_cmd (ctx, err);
}
static const char hlp_create[] =
"CREATE [options] <filename>\n"
"\n"
"Create a new container. On success the OPEN command is \n"
"implictly done for the new container.";
static gpg_error_t
cmd_create (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err;
char *p, *pend;
size_t len;
/* First we close the active container. */
xfree (ctrl->server_local->containername);
ctrl->server_local->containername = NULL;
/* Parse the line. */
line = skip_options (line);
for (p=line; *p && !spacep (p); p++)
;
pend = p;
while (spacep(p))
p++;
if (*p || pend == line)
{
err = gpg_error (GPG_ERR_ASS_SYNTAX);
goto leave;
}
*pend = 0;
/* Unescape the line and check for embedded Nul bytes. */
len = percent_plus_unescape_inplace (line, 0);
line[len] = 0;
if (!len || memchr (line, 0, len))
{
err = gpg_error (GPG_ERR_INV_NAME);
goto leave;
}
/* Create container. */
err = g13_create_container (ctrl, line);
if (!err)
{
FREE_STRLIST (ctrl->recipients);
/* Store the filename. */
ctrl->server_local->containername = xtrystrdup (line);
if (!ctrl->server_local->containername)
err = gpg_error_from_syserror ();
}
leave:
return leave_cmd (ctx, err);
}
static const char hlp_getinfo[] =
"GETINFO <what>\n"
"\n"
"Multipurpose function to return a variety of information.\n"
"Supported values for WHAT are:\n"
"\n"
" version - Return the version of the program.\n"
" pid - Return the process id of the server.\n"
" cmd_has_option CMD OPT\n"
" - Return OK if the command CMD implements the option OPT.";
static gpg_error_t
cmd_getinfo (assuan_context_t ctx, char *line)
{
gpg_error_t err = 0;
if (!strcmp (line, "version"))
{
const char *s = PACKAGE_VERSION;
err = assuan_send_data (ctx, s, strlen (s));
}
else if (!strcmp (line, "pid"))
{
char numbuf[50];
snprintf (numbuf, sizeof numbuf, "%lu", (unsigned long)getpid ());
err = assuan_send_data (ctx, numbuf, strlen (numbuf));
}
else if (!strncmp (line, "cmd_has_option", 14)
&& (line[14] == ' ' || line[14] == '\t' || !line[14]))
{
char *cmd, *cmdopt;
line += 14;
while (*line == ' ' || *line == '\t')
line++;
if (!*line)
err = gpg_error (GPG_ERR_MISSING_VALUE);
else
{
cmd = line;
while (*line && (*line != ' ' && *line != '\t'))
line++;
if (!*line)
err = gpg_error (GPG_ERR_MISSING_VALUE);
else
{
*line++ = 0;
while (*line == ' ' || *line == '\t')
line++;
if (!*line)
err = gpg_error (GPG_ERR_MISSING_VALUE);
else
{
cmdopt = line;
if (!command_has_option (cmd, cmdopt))
- err = gpg_error (GPG_ERR_GENERAL);
+ err = gpg_error (GPG_ERR_FALSE);
}
}
}
}
else
err = set_error (GPG_ERR_ASS_PARAMETER, "unknown value for WHAT");
return leave_cmd (ctx, err);
}
/* Return true if the command CMD implements the option CMDOPT. */
static int
command_has_option (const char *cmd, const char *cmdopt)
{
(void)cmd;
(void)cmdopt;
return 0;
}
/* Tell the Assuan library about our commands. */
static int
register_commands (assuan_context_t ctx)
{
static struct {
const char *name;
assuan_handler_t handler;
const char * const help;
} table[] = {
{ "OPEN", cmd_open, hlp_open },
{ "MOUNT", cmd_mount, hlp_mount},
{ "UMOUNT", cmd_umount, hlp_umount },
{ "SUSPEND", cmd_suspend, hlp_suspend },
{ "RESUME", cmd_resume, hlp_resume },
{ "RECIPIENT", cmd_recipient, hlp_recipient },
{ "SIGNER", cmd_signer, hlp_signer },
{ "CREATE", cmd_create, hlp_create },
{ "INPUT", NULL },
{ "OUTPUT", NULL },
{ "GETINFO", cmd_getinfo,hlp_getinfo },
{ NULL }
};
gpg_error_t err;
int i;
for (i=0; table[i].name; i++)
{
err = assuan_register_command (ctx, table[i].name, table[i].handler,
table[i].help);
if (err)
return err;
}
return 0;
}
/* Startup the server. DEFAULT_RECPLIST is the list of recipients as
set from the command line or config file. We only require those
marked as encrypt-to. */
gpg_error_t
g13_server (ctrl_t ctrl)
{
gpg_error_t err;
assuan_fd_t filedes[2];
assuan_context_t ctx = NULL;
static const char hello[] = ("GNU Privacy Guard's G13 server "
PACKAGE_VERSION " ready");
/* We use a pipe based server so that we can work from scripts.
assuan_init_pipe_server will automagically detect when we are
called with a socketpair and ignore FIELDES in this case. */
filedes[0] = assuan_fdopen (0);
filedes[1] = assuan_fdopen (1);
err = assuan_new (&ctx);
if (err)
{
log_error ("failed to allocate an Assuan context: %s\n",
gpg_strerror (err));
goto leave;
}
err = assuan_init_pipe_server (ctx, filedes);
if (err)
{
log_error ("failed to initialize the server: %s\n", gpg_strerror (err));
goto leave;
}
err = register_commands (ctx);
if (err)
{
log_error ("failed to the register commands with Assuan: %s\n",
gpg_strerror (err));
goto leave;
}
assuan_set_pointer (ctx, ctrl);
if (opt.verbose || opt.debug)
{
char *tmp;
tmp = xtryasprintf ("Home: %s\n"
"Config: %s\n"
"%s",
gnupg_homedir (),
opt.config_filename,
hello);
if (tmp)
{
assuan_set_hello_line (ctx, tmp);
xfree (tmp);
}
}
else
assuan_set_hello_line (ctx, hello);
assuan_register_reset_notify (ctx, reset_notify);
assuan_register_option_handler (ctx, option_handler);
ctrl->server_local = xtrycalloc (1, sizeof *ctrl->server_local);
if (!ctrl->server_local)
{
err = gpg_error_from_syserror ();
goto leave;
}
ctrl->server_local->assuan_ctx = ctx;
while ( !(err = assuan_accept (ctx)) )
{
err = assuan_process (ctx);
if (err)
log_info ("Assuan processing failed: %s\n", gpg_strerror (err));
}
if (err == -1)
err = 0;
else
log_info ("Assuan accept problem: %s\n", gpg_strerror (err));
leave:
reset_notify (ctx, NULL); /* Release all items hold by SERVER_LOCAL. */
if (ctrl->server_local)
{
xfree (ctrl->server_local);
ctrl->server_local = NULL;
}
assuan_release (ctx);
return err;
}
/* Send a status line with status ID NO. The arguments are a list of
strings terminated by a NULL argument. */
gpg_error_t
g13_status (ctrl_t ctrl, int no, ...)
{
gpg_error_t err = 0;
va_list arg_ptr;
const char *text;
va_start (arg_ptr, no);
if (ctrl->no_server && ctrl->status_fd == -1)
; /* No status wanted. */
else if (ctrl->no_server)
{
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
{
err = vprint_assuan_status_strings (ctrl->server_local->assuan_ctx,
get_status_string (no), arg_ptr);
}
va_end (arg_ptr);
return err;
}
/* Helper to notify the client about Pinentry events. Returns an gpg
error code. */
gpg_error_t
g13_proxy_pinentry_notify (ctrl_t ctrl, const unsigned char *line)
{
if (!ctrl || !ctrl->server_local)
return 0;
return assuan_inquire (ctrl->server_local->assuan_ctx, line, NULL, NULL, 0);
}
/*
* Decrypt the keyblob (ENCKEYBLOB,ENCKEYBLOBLEN) and store the result
* at (R_KEYBLOB, R_KEYBLOBLEN). Returns 0 on success or an error
* code. On error R_KEYBLOB is set to NULL.
*
* This actually does not belong here but for that simple wrapper it
* does not make sense to add another source file. Note that we do
* not want to have this in keyblob.c, because that code is also used
* by the syshelp.
*/
gpg_error_t
g13_keyblob_decrypt (ctrl_t ctrl, const void *enckeyblob, size_t enckeybloblen,
void **r_keyblob, size_t *r_keybloblen)
{
gpg_error_t err;
/* FIXME: For now we only implement OpenPGP. */
err = gpg_decrypt_blob (ctrl, opt.gpg_program, opt.gpg_arguments,
enckeyblob, enckeybloblen,
r_keyblob, r_keybloblen);
return err;
}
diff --git a/kbx/keybox-blob.c b/kbx/keybox-blob.c
index 0bcd4a323..8ac67afbe 100644
--- a/kbx/keybox-blob.c
+++ b/kbx/keybox-blob.c
@@ -1,1114 +1,1114 @@
/* keybox-blob.c - KBX Blob handling
* Copyright (C) 2000, 2001, 2002, 2003, 2008 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
/*
* The keybox data format
The KeyBox uses an augmented OpenPGP/X.509 key format. This makes
random access to a keyblock/certificate easier and also gives the
opportunity to store additional information (e.g. the fingerprint)
along with the key. All integers are stored in network byte order,
offsets are counted from the beginning of the Blob.
** Overview of blob types
| Byte 4 | Blob type |
|--------+--------------|
| 0 | Empty blob |
| 1 | First blob |
| 2 | OpenPGP blob |
| 3 | X.509 blob |
** The First blob
The first blob of a plain KBX file has a special format:
- u32 Length of this blob
- byte Blob type (1)
- byte Version number (1)
- u16 Header flags
bit 0 - RFU
bit 1 - Is being or has been used for OpenPGP blobs
- b4 Magic 'KBXf'
- u32 RFU
- u32 file_created_at
- u32 last_maintenance_run
- u32 RFU
- u32 RFU
** The OpenPGP and X.509 blobs
The OpenPGP and X.509 blobs are very similar, things which are
X.509 specific are noted like [X.509: xxx]
- u32 Length of this blob (including these 4 bytes)
- byte Blob type
2 = OpenPGP
3 = X509
- byte Version number of this blob type
1 = Blob with 20 byte fingerprints
2 = Blob with 32 byte fingerprints and no keyids.
- u16 Blob flags
bit 0 = contains secret key material (not used)
bit 1 = ephemeral blob (e.g. used while querying external resources)
- u32 Offset to the OpenPGP keyblock or the X.509 DER encoded
certificate
- u32 The length of the keyblock or certificate
- u16 [NKEYS] Number of keys (at least 1!) [X509: always 1]
- u16 Size of the key information structure (at least 28 or 56).
- NKEYS times:
Version 1 blob:
- b20 The fingerprint of the key.
Fingerprints are always 20 bytes, MD5 left padded with zeroes.
- u32 Offset to the n-th key's keyID (a keyID is always 8 byte)
or 0 if not known which is the case only for X.509.
Note that this separate keyid is not anymore used by
gnupg since the support for v3 keys has been removed.
We create this field anyway for backward compatibility with
old EOL-ed versions. Eventually we will completely move
to the version 2 blob format.
- u16 Key flags
bit 0 = qualified signature (not yet implemented}
- u16 RFU
- bN Optional filler up to the specified length of this
structure.
Version 2 blob:
- b32 The fingerprint of the key. This fingerprint is
either 20 or 32 bytes. A 20 byte fingerprint is
right filled with zeroes.
- u16 Key flags
bit 0 = qualified signature (not yet implemented}
bit 7 = 32 byte fingerprint in use.
- u16 RFU
- b20 keygrip
- bN Optional filler up to the specified length of this
structure.
- u16 Size of the serial number (may be zero)
- bN The serial number. N as given above.
- u16 Number of user IDs
- u16 [NUIDS] Size of user ID information structure
- NUIDS times:
For X509, the first user ID is the Issuer, the second the
Subject and the others are subjectAltNames. For OpenPGP we only
store the information from UserID packets here.
- u32 Blob offset to the n-th user ID
- u32 Length of this user ID.
- u16 User ID flags.
(not yet used)
- byte Validity
- byte RFU
- u16 [NSIGS] Number of signatures
- u16 Size of signature information (4)
- NSIGS times:
- u32 Expiration time of signature with some special values.
Since version 2.1.20 these special valuesare not anymore
used for OpenPGP:
- 0x00000000 = not checked
- 0x00000001 = missing key
- 0x00000002 = bad signature
- 0x10000000 = valid and expires at some date in 1978.
- 0xffffffff = valid and does not expire
- u8 Assigned ownertrust [X509: not used]
- u8 All_Validity
OpenPGP: See ../g10/trustdb/TRUST_* [not yet used]
X509: Bit 4 set := key has been revoked.
Note that this value matches TRUST_FLAG_REVOKED
- u16 RFU
- u32 Recheck_after
- - u32 Latest timestamp in the keyblock (useful for KS syncronsiation?)
+ - u32 Latest timestamp in the keyblock (useful for KS synchronization?)
- u32 Blob created at
- u32 [NRES] Size of reserved space (not including this field)
- bN Reserved space of size NRES for future use.
- bN Arbitrary space for example used to store data which is not
part of the keyblock or certificate. For example the v3 key
IDs go here.
- bN Space for the keyblock or certificate.
- bN RFU. This is the remaining space after keyblock and before
the checksum. It is not covered by the checksum.
- - b20 SHA-1 checksum (useful for KS syncronisation?)
+ - b20 SHA-1 checksum (useful for KS synchronization?)
Note, that KBX versions before GnuPG 2.1 used an MD5
checksum. However it was only created but never checked.
Thus we do not expect problems if we switch to SHA-1. If
the checksum fails and the first 4 bytes are zero, we can
try again with MD5. SHA-1 has the advantage that it is
faster on CPUs with dedicated SHA-1 support.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <assert.h>
#include <time.h>
#include "keybox-defs.h"
#include <gcrypt.h>
#ifdef KEYBOX_WITH_X509
#include <ksba.h>
#endif
#include "../common/gettime.h"
/* special values of the signature status */
#define SF_NONE(a) ( !(a) )
#define SF_NOKEY(a) ((a) & (1<<0))
#define SF_BAD(a) ((a) & (1<<1))
#define SF_VALID(a) ((a) & (1<<29))
struct membuf {
size_t len;
size_t size;
char *buf;
int out_of_core;
};
struct keyboxblob_key {
char fpr[32];
u32 off_kid;
ulong off_kid_addr;
u16 flags;
u16 fprlen; /* Either 20 or 32 */
};
struct keyboxblob_uid {
u32 off;
ulong off_addr;
char *name; /* used only with x509 */
u32 len;
u16 flags;
byte validity;
};
struct keyid_list {
struct keyid_list *next;
int seqno;
byte kid[8];
};
struct fixup_list {
struct fixup_list *next;
u32 off;
u32 val;
};
struct keyboxblob {
byte *blob;
size_t bloblen;
off_t fileoffset;
/* stuff used only by keybox_create_blob */
unsigned char *serialbuf;
const unsigned char *serial;
size_t seriallen;
int nkeys;
struct keyboxblob_key *keys;
int nuids;
struct keyboxblob_uid *uids;
int nsigs;
u32 *sigs;
struct fixup_list *fixups;
int fixup_out_of_core;
struct keyid_list *temp_kids;
struct membuf bufbuf; /* temporary store for the blob */
struct membuf *buf;
};
/* 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;
}
if (buf)
memcpy (mb->buf + mb->len, buf, len);
else
memset (mb->buf + mb->len, 0, 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;
}
static void
put8 (struct membuf *mb, byte a )
{
put_membuf (mb, &a, 1);
}
static void
put16 (struct membuf *mb, u16 a )
{
unsigned char tmp[2];
tmp[0] = a>>8;
tmp[1] = a;
put_membuf (mb, tmp, 2);
}
static void
put32 (struct membuf *mb, u32 a )
{
unsigned char tmp[4];
tmp[0] = a>>24;
tmp[1] = a>>16;
tmp[2] = a>>8;
tmp[3] = a;
put_membuf (mb, tmp, 4);
}
/* Store a value in the fixup list */
static void
add_fixup (KEYBOXBLOB blob, u32 off, u32 val)
{
struct fixup_list *fl;
if (blob->fixup_out_of_core)
return;
fl = xtrycalloc(1, sizeof *fl);
if (!fl)
blob->fixup_out_of_core = 1;
else
{
fl->off = off;
fl->val = val;
fl->next = blob->fixups;
blob->fixups = fl;
}
}
/*
OpenPGP specific stuff
*/
/* We must store the keyid at some place because we can't calculate
the offset yet. This is only used for v3 keyIDs. Function returns
an index value for later fixup or -1 for out of core. The value
must be a non-zero value. */
static int
pgp_temp_store_kid (KEYBOXBLOB blob, struct _keybox_openpgp_key_info *kinfo)
{
struct keyid_list *k, *r;
k = xtrymalloc (sizeof *k);
if (!k)
return -1;
memcpy (k->kid, kinfo->keyid, 8);
k->seqno = 0;
k->next = blob->temp_kids;
blob->temp_kids = k;
for (r=k; r; r = r->next)
k->seqno++;
return k->seqno;
}
/* Helper for pgp_create_key_part. */
static gpg_error_t
pgp_create_key_part_single (KEYBOXBLOB blob, int n,
struct _keybox_openpgp_key_info *kinfo)
{
size_t fprlen;
int off;
fprlen = kinfo->fprlen;
memcpy (blob->keys[n].fpr, kinfo->fpr, fprlen);
blob->keys[n].fprlen = fprlen;
if (fprlen < 20) /* v3 fpr - shift right and fill with zeroes. */
{
memmove (blob->keys[n].fpr + 20 - fprlen, blob->keys[n].fpr, fprlen);
memset (blob->keys[n].fpr, 0, 20 - fprlen);
off = pgp_temp_store_kid (blob, kinfo);
if (off == -1)
return gpg_error_from_syserror ();
blob->keys[n].off_kid = off;
}
else
blob->keys[n].off_kid = 0; /* Will be fixed up later */
blob->keys[n].flags = 0;
return 0;
}
static gpg_error_t
pgp_create_key_part (KEYBOXBLOB blob, keybox_openpgp_info_t info)
{
gpg_error_t err;
int n = 0;
struct _keybox_openpgp_key_info *kinfo;
err = pgp_create_key_part_single (blob, n++, &info->primary);
if (err)
return err;
if (info->nsubkeys)
for (kinfo = &info->subkeys; kinfo; kinfo = kinfo->next)
if ((err=pgp_create_key_part_single (blob, n++, kinfo)))
return err;
assert (n == blob->nkeys);
return 0;
}
static void
pgp_create_uid_part (KEYBOXBLOB blob, keybox_openpgp_info_t info)
{
int n = 0;
struct _keybox_openpgp_uid_info *u;
if (info->nuids)
{
for (u = &info->uids; u; u = u->next)
{
blob->uids[n].off = u->off;
blob->uids[n].len = u->len;
blob->uids[n].flags = 0;
blob->uids[n].validity = 0;
n++;
}
}
assert (n == blob->nuids);
}
static void
pgp_create_sig_part (KEYBOXBLOB blob, u32 *sigstatus)
{
int n;
for (n=0; n < blob->nsigs; n++)
{
blob->sigs[n] = sigstatus? sigstatus[n+1] : 0;
}
}
static int
pgp_create_blob_keyblock (KEYBOXBLOB blob,
const unsigned char *image, size_t imagelen)
{
struct membuf *a = blob->buf;
int n;
u32 kbstart = a->len;
add_fixup (blob, 8, kbstart);
for (n = 0; n < blob->nuids; n++)
add_fixup (blob, blob->uids[n].off_addr, kbstart + blob->uids[n].off);
put_membuf (a, image, imagelen);
add_fixup (blob, 12, a->len - kbstart);
return 0;
}
#ifdef KEYBOX_WITH_X509
/*
X.509 specific stuff
*/
/* Write the raw certificate out */
static int
x509_create_blob_cert (KEYBOXBLOB blob, ksba_cert_t cert)
{
struct membuf *a = blob->buf;
const unsigned char *image;
size_t length;
u32 kbstart = a->len;
/* Store our offset for later fixup */
add_fixup (blob, 8, kbstart);
image = ksba_cert_get_image (cert, &length);
if (!image)
return gpg_error (GPG_ERR_GENERAL);
put_membuf (a, image, length);
add_fixup (blob, 12, a->len - kbstart);
return 0;
}
#endif /*KEYBOX_WITH_X509*/
/* Write a stored keyID out to the buffer */
static void
write_stored_kid (KEYBOXBLOB blob, int seqno)
{
struct keyid_list *r;
for ( r = blob->temp_kids; r; r = r->next )
{
if (r->seqno == seqno )
{
put_membuf (blob->buf, r->kid, 8);
return;
}
}
never_reached ();
}
/* Release a list of key IDs */
static void
release_kid_list (struct keyid_list *kl)
{
struct keyid_list *r, *r2;
for ( r = kl; r; r = r2 )
{
r2 = r->next;
xfree (r);
}
}
/* Create a new blob header. If WANT_FPR32 is set a version 2 blob is
* created. */
static int
create_blob_header (KEYBOXBLOB blob, int blobtype, int as_ephemeral,
int want_fpr32)
{
struct membuf *a = blob->buf;
int i;
put32 ( a, 0 ); /* blob length, needs fixup */
put8 ( a, blobtype);
put8 ( a, want_fpr32? 2:1 ); /* blob type version */
put16 ( a, as_ephemeral? 2:0 ); /* blob flags */
put32 ( a, 0 ); /* offset to the raw data, needs fixup */
put32 ( a, 0 ); /* length of the raw data, needs fixup */
put16 ( a, blob->nkeys );
if (want_fpr32)
put16 ( a, 32 + 2 + 2 + 20); /* size of key info */
else
put16 ( a, 20 + 4 + 2 + 2 ); /* size of key info */
for ( i=0; i < blob->nkeys; i++ )
{
if (want_fpr32)
{
put_membuf (a, blob->keys[i].fpr, blob->keys[i].fprlen);
blob->keys[i].off_kid_addr = a->len;
if (blob->keys[i].fprlen == 32)
put16 ( a, (blob->keys[i].flags | 0x80));
else
put16 ( a, blob->keys[i].flags);
put16 ( a, 0 ); /* reserved */
/* FIXME: Put the real grip here instead of the filler. */
put_membuf (a, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", 20);
}
else
{
log_assert (blob->keys[i].fprlen <= 20);
put_membuf (a, blob->keys[i].fpr, 20);
blob->keys[i].off_kid_addr = a->len;
put32 ( a, 0 ); /* offset to keyid, fixed up later */
put16 ( a, blob->keys[i].flags );
put16 ( a, 0 ); /* reserved */
}
}
put16 (a, blob->seriallen); /*fixme: check that it fits into 16 bits*/
if (blob->serial)
put_membuf (a, blob->serial, blob->seriallen);
put16 ( a, blob->nuids );
put16 ( a, 4 + 4 + 2 + 1 + 1 ); /* size of uid info */
for (i=0; i < blob->nuids; i++)
{
blob->uids[i].off_addr = a->len;
put32 ( a, 0 ); /* offset to userid, fixed up later */
put32 ( a, blob->uids[i].len );
put16 ( a, blob->uids[i].flags );
put8 ( a, 0 ); /* validity */
put8 ( a, 0 ); /* reserved */
}
put16 ( a, blob->nsigs );
put16 ( a, 4 ); /* size of sig info */
for (i=0; i < blob->nsigs; i++)
{
put32 ( a, blob->sigs[i]);
}
put8 ( a, 0 ); /* assigned ownertrust */
put8 ( a, 0 ); /* validity of all user IDs */
put16 ( a, 0 ); /* reserved */
put32 ( a, 0 ); /* time of next recheck */
put32 ( a, 0 ); /* newest timestamp (none) */
put32 ( a, make_timestamp() ); /* creation time */
put32 ( a, 0 ); /* size of reserved space */
/* reserved space (which is currently of size 0) */
/* space where we write keyIDs and other stuff so that the
pointers can actually point to somewhere */
if (blobtype == KEYBOX_BLOBTYPE_PGP && !want_fpr32)
{
/* For version 1 blobs, we need to store the keyids for all v3
* keys because those key IDs are not part of the fingerprint.
* While we are doing that, we fixup all the keyID offsets. For
* version 2 blobs (which can't carry v3 keys) we compute the
* keyids in the fly because they are just stripped down
* fingerprints. */
for (i=0; i < blob->nkeys; i++ )
{
if (blob->keys[i].off_kid)
{ /* this is a v3 one */
add_fixup (blob, blob->keys[i].off_kid_addr, a->len);
write_stored_kid (blob, blob->keys[i].off_kid);
}
else
{ /* the better v4 key IDs - just store an offset 8 bytes back */
add_fixup (blob, blob->keys[i].off_kid_addr,
blob->keys[i].off_kid_addr - 8);
}
}
}
if (blobtype == KEYBOX_BLOBTYPE_X509)
{
/* We don't want to point to ASN.1 encoded UserIDs (DNs) but to
the utf-8 string representation of them */
for (i=0; i < blob->nuids; i++ )
{
if (blob->uids[i].name)
{ /* this is a v3 one */
add_fixup (blob, blob->uids[i].off_addr, a->len);
put_membuf (blob->buf, blob->uids[i].name, blob->uids[i].len);
}
}
}
return 0;
}
static int
create_blob_trailer (KEYBOXBLOB blob)
{
(void)blob;
return 0;
}
static int
create_blob_finish (KEYBOXBLOB blob)
{
struct membuf *a = blob->buf;
unsigned char *p;
unsigned char *pp;
size_t n;
/* Write a placeholder for the checksum */
put_membuf (a, NULL, 20);
/* get the memory area */
n = 0; /* (Just to avoid compiler warning.) */
p = get_membuf (a, &n);
if (!p)
return gpg_error (GPG_ERR_ENOMEM);
assert (n >= 20);
/* fixup the length */
add_fixup (blob, 0, n);
/* do the fixups */
if (blob->fixup_out_of_core)
{
xfree (p);
return gpg_error (GPG_ERR_ENOMEM);
}
{
struct fixup_list *fl, *next;
for (fl = blob->fixups; fl; fl = next)
{
assert (fl->off+4 <= n);
p[fl->off+0] = fl->val >> 24;
p[fl->off+1] = fl->val >> 16;
p[fl->off+2] = fl->val >> 8;
p[fl->off+3] = fl->val;
next = fl->next;
xfree (fl);
}
blob->fixups = NULL;
}
/* Compute and store the SHA-1 checksum. */
gcry_md_hash_buffer (GCRY_MD_SHA1, p + n - 20, p, n - 20);
pp = xtrymalloc (n);
if ( !pp )
{
xfree (p);
return gpg_error_from_syserror ();
}
memcpy (pp , p, n);
xfree (p);
blob->blob = pp;
blob->bloblen = n;
return 0;
}
gpg_error_t
_keybox_create_openpgp_blob (KEYBOXBLOB *r_blob,
keybox_openpgp_info_t info,
const unsigned char *image,
size_t imagelen,
int as_ephemeral)
{
gpg_error_t err;
KEYBOXBLOB blob;
int need_fpr32 = 0;
*r_blob = NULL;
/* Check whether we need a blob with 32 bit fingerprints. We could
* use this always but for backward compatiblity we do this only for
* v5 keys. */
if (info->primary.version == 5)
need_fpr32 = 1;
else
{
struct _keybox_openpgp_key_info *kinfo;
for (kinfo = &info->subkeys; kinfo; kinfo = kinfo->next)
if (kinfo->version == 5)
{
need_fpr32 = 1;
break;
}
}
blob = xtrycalloc (1, sizeof *blob);
if (!blob)
return gpg_error_from_syserror ();
blob->nkeys = 1 + info->nsubkeys;
blob->keys = xtrycalloc (blob->nkeys, sizeof *blob->keys );
if (!blob->keys)
{
err = gpg_error_from_syserror ();
goto leave;
}
blob->nuids = info->nuids;
if (blob->nuids)
{
blob->uids = xtrycalloc (blob->nuids, sizeof *blob->uids );
if (!blob->uids)
{
err = gpg_error_from_syserror ();
goto leave;
}
}
blob->nsigs = info->nsigs;
if (blob->nsigs)
{
blob->sigs = xtrycalloc (blob->nsigs, sizeof *blob->sigs );
if (!blob->sigs)
{
err = gpg_error_from_syserror ();
goto leave;
}
}
err = pgp_create_key_part (blob, info);
if (err)
goto leave;
pgp_create_uid_part (blob, info);
pgp_create_sig_part (blob, NULL);
init_membuf (&blob->bufbuf, 1024);
blob->buf = &blob->bufbuf;
err = create_blob_header (blob, KEYBOX_BLOBTYPE_PGP,
as_ephemeral, need_fpr32);
if (err)
goto leave;
err = pgp_create_blob_keyblock (blob, image, imagelen);
if (err)
goto leave;
err = create_blob_trailer (blob);
if (err)
goto leave;
err = create_blob_finish (blob);
if (err)
goto leave;
leave:
release_kid_list (blob->temp_kids);
blob->temp_kids = NULL;
if (err)
_keybox_release_blob (blob);
else
*r_blob = blob;
return err;
}
#ifdef KEYBOX_WITH_X509
/* Return an allocated string with the email address extracted from a
DN. Note hat we use this code also in ../sm/keylist.c. */
static char *
x509_email_kludge (const char *name)
{
const char *p, *string;
unsigned char *buf;
int n;
string = name;
for (;;)
{
p = strstr (string, "1.2.840.113549.1.9.1=#");
if (!p)
return NULL;
if (p == name || (p > string+1 && p[-1] == ',' && p[-2] != '\\'))
{
name = p + 22;
break;
}
string = p + 22;
}
/* This looks pretty much like an email address in the subject's DN
we use this to add an additional user ID entry. This way,
OpenSSL generated keys get a nicer and usable listing. */
for (n=0, p=name; hexdigitp (p) && hexdigitp (p+1); p +=2, n++)
;
if (!n)
return NULL;
buf = xtrymalloc (n+3);
if (!buf)
return NULL; /* oops, out of core */
*buf = '<';
for (n=1, p=name; hexdigitp (p); p +=2, n++)
buf[n] = xtoi_2 (p);
buf[n++] = '>';
buf[n] = 0;
return (char*)buf;
}
/* Note: We should move calculation of the digest into libksba and
remove that parameter */
int
_keybox_create_x509_blob (KEYBOXBLOB *r_blob, ksba_cert_t cert,
unsigned char *sha1_digest, int as_ephemeral)
{
int i, rc = 0;
KEYBOXBLOB blob;
unsigned char *sn;
char *p;
char **names = NULL;
size_t max_names;
*r_blob = NULL;
blob = xtrycalloc (1, sizeof *blob);
if( !blob )
return gpg_error_from_syserror ();
sn = ksba_cert_get_serial (cert);
if (sn)
{
size_t n, len;
n = gcry_sexp_canon_len (sn, 0, NULL, NULL);
if (n < 2)
{
xfree (sn);
return gpg_error (GPG_ERR_GENERAL);
}
blob->serialbuf = sn;
sn++; n--; /* skip '(' */
for (len=0; n && *sn && *sn != ':' && digitp (sn); n--, sn++)
len = len*10 + atoi_1 (sn);
if (*sn != ':')
{
xfree (blob->serialbuf);
blob->serialbuf = NULL;
return gpg_error (GPG_ERR_GENERAL);
}
sn++;
blob->serial = sn;
blob->seriallen = len;
}
blob->nkeys = 1;
/* create list of names */
blob->nuids = 0;
max_names = 100;
names = xtrymalloc (max_names * sizeof *names);
if (!names)
{
rc = gpg_error_from_syserror ();
goto leave;
}
p = ksba_cert_get_issuer (cert, 0);
if (!p)
{
rc = gpg_error (GPG_ERR_MISSING_VALUE);
goto leave;
}
names[blob->nuids++] = p;
for (i=0; (p = ksba_cert_get_subject (cert, i)); i++)
{
if (blob->nuids >= max_names)
{
char **tmp;
max_names += 100;
tmp = xtryrealloc (names, max_names * sizeof *names);
if (!tmp)
{
rc = gpg_error_from_syserror ();
goto leave;
}
names = tmp;
}
names[blob->nuids++] = p;
if (!i && (p=x509_email_kludge (p)))
names[blob->nuids++] = p; /* due to !i we don't need to check bounds*/
}
/* space for signature information */
blob->nsigs = 1;
blob->keys = xtrycalloc (blob->nkeys, sizeof *blob->keys );
blob->uids = xtrycalloc (blob->nuids, sizeof *blob->uids );
blob->sigs = xtrycalloc (blob->nsigs, sizeof *blob->sigs );
if (!blob->keys || !blob->uids || !blob->sigs)
{
rc = gpg_error (GPG_ERR_ENOMEM);
goto leave;
}
memcpy (blob->keys[0].fpr, sha1_digest, 20);
blob->keys[0].off_kid = 0; /* We don't have keyids */
blob->keys[0].flags = 0;
/* issuer and subject names */
for (i=0; i < blob->nuids; i++)
{
blob->uids[i].name = names[i];
blob->uids[i].len = strlen(names[i]);
names[i] = NULL;
blob->uids[i].flags = 0;
blob->uids[i].validity = 0;
}
xfree (names);
names = NULL;
/* signatures */
blob->sigs[0] = 0; /* not yet checked */
/* Create a temporary buffer for further processing */
init_membuf (&blob->bufbuf, 1024);
blob->buf = &blob->bufbuf;
/* write out what we already have */
rc = create_blob_header (blob, KEYBOX_BLOBTYPE_X509, as_ephemeral, 0);
if (rc)
goto leave;
rc = x509_create_blob_cert (blob, cert);
if (rc)
goto leave;
rc = create_blob_trailer (blob);
if (rc)
goto leave;
rc = create_blob_finish ( blob );
if (rc)
goto leave;
leave:
release_kid_list (blob->temp_kids);
blob->temp_kids = NULL;
if (names)
{
for (i=0; i < blob->nuids; i++)
xfree (names[i]);
xfree (names);
}
if (rc)
{
_keybox_release_blob (blob);
*r_blob = NULL;
}
else
{
*r_blob = blob;
}
return rc;
}
#endif /*KEYBOX_WITH_X509*/
int
_keybox_new_blob (KEYBOXBLOB *r_blob,
unsigned char *image, size_t imagelen, off_t off)
{
KEYBOXBLOB blob;
*r_blob = NULL;
blob = xtrycalloc (1, sizeof *blob);
if (!blob)
return gpg_error_from_syserror ();
blob->blob = image;
blob->bloblen = imagelen;
blob->fileoffset = off;
*r_blob = blob;
return 0;
}
void
_keybox_release_blob (KEYBOXBLOB blob)
{
int i;
if (!blob)
return;
if (blob->buf)
{
size_t len;
xfree (get_membuf (blob->buf, &len));
}
xfree (blob->keys );
xfree (blob->serialbuf);
for (i=0; i < blob->nuids; i++)
xfree (blob->uids[i].name);
xfree (blob->uids );
xfree (blob->sigs );
xfree (blob->blob );
xfree (blob );
}
const unsigned char *
_keybox_get_blob_image ( KEYBOXBLOB blob, size_t *n )
{
*n = blob->bloblen;
return blob->blob;
}
off_t
_keybox_get_blob_fileoffset (KEYBOXBLOB blob)
{
return blob->fileoffset;
}
void
_keybox_update_header_blob (KEYBOXBLOB blob, int for_openpgp)
{
if (blob->bloblen >= 32 && blob->blob[4] == KEYBOX_BLOBTYPE_HEADER)
{
u32 val = make_timestamp ();
/* Update the last maintenance run times tamp. */
blob->blob[20] = (val >> 24);
blob->blob[20+1] = (val >> 16);
blob->blob[20+2] = (val >> 8);
blob->blob[20+3] = (val );
if (for_openpgp)
blob->blob[7] |= 0x02; /* OpenPGP data may be available. */
}
}
diff --git a/kbx/keybox-init.c b/kbx/keybox-init.c
index 6a83f7162..2223f4d15 100644
--- a/kbx/keybox-init.c
+++ b/kbx/keybox-init.c
@@ -1,329 +1,330 @@
/* keybox-init.c - Initialization of the library
* 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 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <assert.h>
#include "keybox-defs.h"
#include "../common/mischelp.h"
static KB_NAME kb_names;
/* Register a filename for plain keybox files. Returns 0 on success,
* GPG_ERR_EEXIST if it has already been registered, or another error
* code. On success or with error code GPG_ERR_EEXIST a token usable
* to access the keybox handle is stored at R_TOKEN, NULL is stored
* for all other errors. */
gpg_error_t
keybox_register_file (const char *fname, int secret, void **r_token)
{
KB_NAME kr;
*r_token = NULL;
for (kr=kb_names; kr; kr = kr->next)
{
if (same_file_p (kr->fname, fname) )
{
*r_token = kr;
return gpg_error (GPG_ERR_EEXIST); /* Already registered. */
}
}
kr = xtrymalloc (sizeof *kr + strlen (fname));
if (!kr)
return gpg_error_from_syserror ();
strcpy (kr->fname, fname);
kr->secret = !!secret;
kr->handle_table = NULL;
kr->handle_table_size = 0;
kr->lockhd = NULL;
kr->is_locked = 0;
kr->did_full_scan = 0;
/* keep a list of all issued pointers */
kr->next = kb_names;
kb_names = kr;
/* create the offset table the first time a function here is used */
/* if (!kb_offtbl) */
/* kb_offtbl = new_offset_hash_table (); */
*r_token = kr;
return 0;
}
int
keybox_is_writable (void *token)
{
KB_NAME r = token;
return r? !access (r->fname, W_OK) : 0;
}
static KEYBOX_HANDLE
do_keybox_new (KB_NAME resource, int secret, int for_openpgp)
{
KEYBOX_HANDLE hd;
int idx;
assert (resource && !resource->secret == !secret);
hd = xtrycalloc (1, sizeof *hd);
if (hd)
{
hd->kb = resource;
hd->secret = !!secret;
hd->for_openpgp = for_openpgp;
if (!resource->handle_table)
{
resource->handle_table_size = 3;
resource->handle_table = xtrycalloc (resource->handle_table_size,
sizeof *resource->handle_table);
if (!resource->handle_table)
{
resource->handle_table_size = 0;
xfree (hd);
return NULL;
}
}
for (idx=0; idx < resource->handle_table_size; idx++)
if (!resource->handle_table[idx])
{
resource->handle_table[idx] = hd;
break;
}
if (!(idx < resource->handle_table_size))
{
KEYBOX_HANDLE *tmptbl;
size_t newsize;
newsize = resource->handle_table_size + 5;
tmptbl = xtryrealloc (resource->handle_table,
newsize * sizeof (*tmptbl));
if (!tmptbl)
{
xfree (hd);
return NULL;
}
resource->handle_table = tmptbl;
resource->handle_table_size = newsize;
resource->handle_table[idx] = hd;
for (idx++; idx < resource->handle_table_size; idx++)
resource->handle_table[idx] = NULL;
}
}
return hd;
}
/* Create a new handle for the resource associated with TOKEN. SECRET
is just a cross-check. This is the OpenPGP version. The returned
handle must be released using keybox_release. */
KEYBOX_HANDLE
keybox_new_openpgp (void *token, int secret)
{
KB_NAME resource = token;
return do_keybox_new (resource, secret, 1);
}
/* Create a new handle for the resource associated with TOKEN. SECRET
is just a cross-check. This is the X.509 version. The returned
handle must be released using keybox_release. */
KEYBOX_HANDLE
keybox_new_x509 (void *token, int secret)
{
KB_NAME resource = token;
return do_keybox_new (resource, secret, 0);
}
void
keybox_release (KEYBOX_HANDLE hd)
{
if (!hd)
return;
if (hd->kb->handle_table)
{
int idx;
for (idx=0; idx < hd->kb->handle_table_size; idx++)
if (hd->kb->handle_table[idx] == hd)
hd->kb->handle_table[idx] = NULL;
}
_keybox_release_blob (hd->found.blob);
_keybox_release_blob (hd->saved_found.blob);
if (hd->fp)
{
fclose (hd->fp);
hd->fp = NULL;
}
xfree (hd->word_match.name);
xfree (hd->word_match.pattern);
xfree (hd);
}
/* Save the current found state in HD for later retrieval by
keybox_restore_found_state. Only one state may be saved. */
void
keybox_push_found_state (KEYBOX_HANDLE hd)
{
if (hd->saved_found.blob)
{
_keybox_release_blob (hd->saved_found.blob);
hd->saved_found.blob = NULL;
}
hd->saved_found = hd->found;
hd->found.blob = NULL;
}
/* Restore the saved found state in HD. */
void
keybox_pop_found_state (KEYBOX_HANDLE hd)
{
if (hd->found.blob)
{
_keybox_release_blob (hd->found.blob);
hd->found.blob = NULL;
}
hd->found = hd->saved_found;
hd->saved_found.blob = NULL;
}
const char *
keybox_get_resource_name (KEYBOX_HANDLE hd)
{
if (!hd || !hd->kb)
return NULL;
return hd->kb->fname;
}
int
keybox_set_ephemeral (KEYBOX_HANDLE hd, int yes)
{
if (!hd)
return gpg_error (GPG_ERR_INV_HANDLE);
hd->ephemeral = yes;
return 0;
}
/* Close the file of the resource identified by HD. For consistent
results this function closes the files of all handles pointing to
the resource identified by HD. */
void
_keybox_close_file (KEYBOX_HANDLE hd)
{
int idx;
KEYBOX_HANDLE roverhd;
if (!hd || !hd->kb || !hd->kb->handle_table)
return;
for (idx=0; idx < hd->kb->handle_table_size; idx++)
if ((roverhd = hd->kb->handle_table[idx]))
{
if (roverhd->fp)
{
fclose (roverhd->fp);
roverhd->fp = NULL;
}
}
assert (!hd->fp);
}
/*
- * Lock the keybox at handle HD, or unlock if YES is false.
+ * Lock the keybox at handle HD, or unlock if YES is false. TIMEOUT
+ * is the value used for dotlock_take. In general -1 should be used
+ * when taking a lock; use 0 when releasing a lock.
*/
gpg_error_t
-keybox_lock (KEYBOX_HANDLE hd, int yes)
+keybox_lock (KEYBOX_HANDLE hd, int yes, long timeout)
{
gpg_error_t err = 0;
KB_NAME kb = hd->kb;
if (!keybox_is_writable (kb))
return 0;
/* Make sure the lock handle has been created. */
if (!kb->lockhd)
{
kb->lockhd = dotlock_create (kb->fname, 0);
if (!kb->lockhd)
{
err = gpg_error_from_syserror ();
log_info ("can't allocate lock for '%s'\n", kb->fname );
return err;
}
}
if (yes) /* Take the lock. */
{
if (!kb->is_locked)
{
#ifdef HAVE_W32_SYSTEM
- /* Under Windows we need to close the file before we try
- * to lock it. This is because another process might have
- * taken the lock and is using keybox_file_rename to
- * rename the base file. How if our dotlock_take below is
- * waiting for the lock but we have the base file still
- * open, keybox_file_rename will never succeed as we are
- * in a deadlock. */
- if (hd->fp)
- {
- fclose (hd->fp);
- hd->fp = NULL;
- }
+ /* Under Windows we need to close the file before we try
+ * to lock it. This is because another process might have
+ * taken the lock and is using keybox_file_rename to
+ * rename the base file. Now if our dotlock_take below is
+ * waiting for the lock but we have the base file still
+ * open, keybox_file_rename will never succeed as we are
+ * in a deadlock. */
+ _keybox_close_file (hd);
#endif /*HAVE_W32_SYSTEM*/
- if (dotlock_take (kb->lockhd, -1))
+ if (dotlock_take (kb->lockhd, timeout))
{
err = gpg_error_from_syserror ();
- log_info ("can't lock '%s'\n", kb->fname );
+ if (!timeout && gpg_err_code (err) == GPG_ERR_EACCES)
+ ; /* No diagnostic if we only tried to lock. */
+ else
+ log_info ("can't lock '%s'\n", kb->fname );
}
else
kb->is_locked = 1;
}
}
else /* Release the lock. */
{
if (kb->is_locked)
{
if (dotlock_release (kb->lockhd))
{
err = gpg_error_from_syserror ();
log_info ("can't unlock '%s'\n", kb->fname );
}
else
kb->is_locked = 0;
}
}
return err;
}
diff --git a/kbx/keybox-search.c b/kbx/keybox-search.c
index 101e1b5ea..77469a24c 100644
--- a/kbx/keybox-search.c
+++ b/kbx/keybox-search.c
@@ -1,1315 +1,1347 @@
/* keybox-search.c - Search operations
* Copyright (C) 2001, 2002, 2003, 2004, 2012,
* 2013 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <errno.h>
#include "keybox-defs.h"
#include <gcrypt.h>
#include "../common/host2net.h"
#include "../common/mbox-util.h"
#define xtoi_1(p) (*(p) <= '9'? (*(p)- '0'): \
*(p) <= 'F'? (*(p)-'A'+10):(*(p)-'a'+10))
#define xtoi_2(p) ((xtoi_1(p) * 16) + xtoi_1((p)+1))
struct sn_array_s {
int snlen;
unsigned char *sn;
};
#define get32(a) buf32_to_ulong ((a))
#define get16(a) buf16_to_ulong ((a))
static inline unsigned int
blob_get_blob_flags (KEYBOXBLOB blob)
{
const unsigned char *buffer;
size_t length;
buffer = _keybox_get_blob_image (blob, &length);
if (length < 8)
return 0; /* oops */
return get16 (buffer + 6);
}
/* Return the first keyid from the blob. Returns true if
available. */
static int
blob_get_first_keyid (KEYBOXBLOB blob, u32 *kid)
{
const unsigned char *buffer;
size_t length, nkeys, keyinfolen;
int fpr32;
buffer = _keybox_get_blob_image (blob, &length);
if (length < 48)
return 0; /* blob too short */
fpr32 = buffer[5] == 2;
if (fpr32 && length < 56)
return 0; /* blob to short */
nkeys = get16 (buffer + 16);
keyinfolen = get16 (buffer + 18);
if (!nkeys || keyinfolen < (fpr32?56:28))
return 0; /* invalid blob */
if (fpr32 && (get16 (buffer + 20 + 32) & 0x80))
{
/* 32 byte fingerprint. */
kid[0] = get32 (buffer + 20);
kid[1] = get32 (buffer + 20 + 4);
}
else /* 20 byte fingerprint. */
{
kid[0] = get32 (buffer + 20 + 12);
kid[1] = get32 (buffer + 20 + 16);
}
return 1;
}
/* Return information on the flag WHAT within the blob BUFFER,LENGTH.
Return the offset and the length (in bytes) of the flag in
FLAGOFF,FLAG_SIZE. */
gpg_err_code_t
_keybox_get_flag_location (const unsigned char *buffer, size_t length,
int what, size_t *flag_off, size_t *flag_size)
{
size_t pos;
size_t nkeys, keyinfolen;
size_t nuids, uidinfolen;
size_t nserial;
size_t nsigs, siginfolen, siginfooff;
switch (what)
{
case KEYBOX_FLAG_BLOB:
if (length < 8)
return GPG_ERR_INV_OBJ;
*flag_off = 6;
*flag_size = 2;
break;
case KEYBOX_FLAG_OWNERTRUST:
case KEYBOX_FLAG_VALIDITY:
case KEYBOX_FLAG_CREATED_AT:
case KEYBOX_FLAG_SIG_INFO:
if (length < 20)
return GPG_ERR_INV_OBJ;
/* Key info. */
nkeys = get16 (buffer + 16);
keyinfolen = get16 (buffer + 18 );
if (keyinfolen < 28)
return GPG_ERR_INV_OBJ;
pos = 20 + keyinfolen*nkeys;
if (pos+2 > length)
return GPG_ERR_INV_OBJ; /* Out of bounds. */
/* Serial number. */
nserial = get16 (buffer+pos);
pos += 2 + nserial;
if (pos+4 > length)
return GPG_ERR_INV_OBJ; /* Out of bounds. */
/* User IDs. */
nuids = get16 (buffer + pos); pos += 2;
uidinfolen = get16 (buffer + pos); pos += 2;
if (uidinfolen < 12 )
return GPG_ERR_INV_OBJ;
pos += uidinfolen*nuids;
if (pos+4 > length)
return GPG_ERR_INV_OBJ ; /* Out of bounds. */
/* Signature info. */
siginfooff = pos;
nsigs = get16 (buffer + pos); pos += 2;
siginfolen = get16 (buffer + pos); pos += 2;
if (siginfolen < 4 )
return GPG_ERR_INV_OBJ;
pos += siginfolen*nsigs;
if (pos+1+1+2+4+4+4+4 > length)
return GPG_ERR_INV_OBJ ; /* Out of bounds. */
*flag_size = 1;
*flag_off = pos;
switch (what)
{
case KEYBOX_FLAG_VALIDITY:
*flag_off += 1;
break;
case KEYBOX_FLAG_CREATED_AT:
*flag_size = 4;
*flag_off += 1+2+4+4+4;
break;
case KEYBOX_FLAG_SIG_INFO:
*flag_size = siginfolen * nsigs;
*flag_off = siginfooff;
break;
default:
break;
}
break;
default:
return GPG_ERR_INV_FLAG;
}
return 0;
}
/* Return one of the flags WHAT in VALUE from the blob BUFFER of
LENGTH bytes. Return 0 on success or an raw error code. */
static gpg_err_code_t
get_flag_from_image (const unsigned char *buffer, size_t length,
int what, unsigned int *value)
{
gpg_err_code_t ec;
size_t pos, size;
*value = 0;
ec = _keybox_get_flag_location (buffer, length, what, &pos, &size);
if (!ec)
switch (size)
{
case 1: *value = buffer[pos]; break;
case 2: *value = get16 (buffer + pos); break;
case 4: *value = get32 (buffer + pos); break;
default: ec = GPG_ERR_BUG; break;
}
return ec;
}
static int
blob_cmp_sn (KEYBOXBLOB blob, const unsigned char *sn, int snlen)
{
const unsigned char *buffer;
size_t length;
size_t pos, off;
size_t nkeys, keyinfolen;
size_t nserial;
buffer = _keybox_get_blob_image (blob, &length);
if (length < 40)
return 0; /* blob too short */
/*keys*/
nkeys = get16 (buffer + 16);
keyinfolen = get16 (buffer + 18 );
if (keyinfolen < 28)
return 0; /* invalid blob */
pos = 20 + keyinfolen*nkeys;
if (pos+2 > length)
return 0; /* out of bounds */
/*serial*/
nserial = get16 (buffer+pos);
off = pos + 2;
if (off+nserial > length)
return 0; /* out of bounds */
return nserial == snlen && !memcmp (buffer+off, sn, snlen);
}
/* Returns 0 if not found or the number of the key which was found.
For X.509 this is always 1, for OpenPGP this is 1 for the primary
key and 2 and more for the subkeys. */
static int
blob_cmp_fpr (KEYBOXBLOB blob, const unsigned char *fpr, unsigned int fprlen)
{
const unsigned char *buffer;
size_t length;
size_t pos, off;
size_t nkeys, keyinfolen;
int idx, fpr32, storedfprlen;
buffer = _keybox_get_blob_image (blob, &length);
if (length < 40)
return 0; /* blob too short */
fpr32 = buffer[5] == 2;
/*keys*/
nkeys = get16 (buffer + 16);
keyinfolen = get16 (buffer + 18 );
if (keyinfolen < (fpr32?56:28))
return 0; /* invalid blob */
pos = 20;
if (pos + (uint64_t)keyinfolen*nkeys > (uint64_t)length)
return 0; /* out of bounds */
for (idx=0; idx < nkeys; idx++)
{
off = pos + idx*keyinfolen;
if (fpr32)
storedfprlen = (get16 (buffer + off + 32) & 0x80)? 32:20;
else
storedfprlen = 20;
if (storedfprlen == fprlen
&& !memcmp (buffer + off, fpr, storedfprlen))
return idx+1; /* found */
}
return 0; /* not found */
}
/* Helper for has_short_kid and has_long_kid. */
static int
blob_cmp_fpr_part (KEYBOXBLOB blob, const unsigned char *fpr,
int fproff, int fprlen)
{
const unsigned char *buffer;
size_t length;
size_t pos, off;
size_t nkeys, keyinfolen;
int idx, fpr32, storedfprlen;
buffer = _keybox_get_blob_image (blob, &length);
if (length < 40)
return 0; /* blob too short */
fpr32 = buffer[5] == 2;
/*keys*/
nkeys = get16 (buffer + 16);
keyinfolen = get16 (buffer + 18 );
if (keyinfolen < (fpr32?56:28))
return 0; /* invalid blob */
pos = 20;
if (pos + (uint64_t)keyinfolen*nkeys > (uint64_t)length)
return 0; /* out of bounds */
if (fpr32)
fproff = 0; /* keyid are the high-order bits. */
for (idx=0; idx < nkeys; idx++)
{
off = pos + idx*keyinfolen;
if (fpr32)
storedfprlen = (get16 (buffer + off + 32) & 0x80)? 32:20;
else
storedfprlen = 20;
if (storedfprlen == fproff + fprlen
&& !memcmp (buffer + off + fproff, fpr, fprlen))
return idx+1; /* found */
}
return 0; /* not found */
}
static int
blob_cmp_name (KEYBOXBLOB blob, int idx,
const char *name, size_t namelen, int substr, int x509)
{
const unsigned char *buffer;
size_t length;
size_t pos, off, len;
size_t nkeys, keyinfolen;
size_t nuids, uidinfolen;
size_t nserial;
buffer = _keybox_get_blob_image (blob, &length);
if (length < 40)
return 0; /* blob too short */
/*keys*/
nkeys = get16 (buffer + 16);
keyinfolen = get16 (buffer + 18 );
if (keyinfolen < 28)
return 0; /* invalid blob */
pos = 20 + keyinfolen*nkeys;
if ((uint64_t)pos+2 > (uint64_t)length)
return 0; /* out of bounds */
/*serial*/
nserial = get16 (buffer+pos);
pos += 2 + nserial;
if (pos+4 > length)
return 0; /* out of bounds */
/* user ids*/
nuids = get16 (buffer + pos); pos += 2;
uidinfolen = get16 (buffer + pos); pos += 2;
if (uidinfolen < 12 /* should add a: || nuidinfolen > MAX_UIDINFOLEN */)
return 0; /* invalid blob */
if (pos + uidinfolen*nuids > length)
return 0; /* out of bounds */
if (idx < 0)
{ /* Compare all names. Note that for X.509 we start with index 1
so to skip the issuer at index 0. */
for (idx = !!x509; idx < nuids; idx++)
{
size_t mypos = pos;
mypos += idx*uidinfolen;
off = get32 (buffer+mypos);
len = get32 (buffer+mypos+4);
if ((uint64_t)off+(uint64_t)len > (uint64_t)length)
return 0; /* error: better stop here out of bounds */
if (len < 1)
continue; /* empty name */
if (substr)
{
if (ascii_memcasemem (buffer+off, len, name, namelen))
return idx+1; /* found */
}
else
{
if (len == namelen && !memcmp (buffer+off, name, len))
return idx+1; /* found */
}
}
}
else
{
if (idx > nuids)
return 0; /* no user ID with that idx */
pos += idx*uidinfolen;
off = get32 (buffer+pos);
len = get32 (buffer+pos+4);
if (off+len > length)
return 0; /* out of bounds */
if (len < 1)
return 0; /* empty name */
if (substr)
{
if (ascii_memcasemem (buffer+off, len, name, namelen))
return idx+1; /* found */
}
else
{
if (len == namelen && !memcmp (buffer+off, name, len))
return idx+1; /* found */
}
}
return 0; /* not found */
}
/* Compare all email addresses of the subject. With SUBSTR given as
True a substring search is done in the mail address. The X509 flag
indicated whether the search is done on an X.509 blob. */
static int
blob_cmp_mail (KEYBOXBLOB blob, const char *name, size_t namelen, int substr,
int x509)
{
const unsigned char *buffer;
size_t length;
size_t pos, off, len;
size_t nkeys, keyinfolen;
size_t nuids, uidinfolen;
size_t nserial;
int idx;
/* fixme: this code is common to blob_cmp_mail */
buffer = _keybox_get_blob_image (blob, &length);
if (length < 40)
return 0; /* blob too short */
/*keys*/
nkeys = get16 (buffer + 16);
keyinfolen = get16 (buffer + 18 );
if (keyinfolen < 28)
return 0; /* invalid blob */
pos = 20 + keyinfolen*nkeys;
if (pos+2 > length)
return 0; /* out of bounds */
/*serial*/
nserial = get16 (buffer+pos);
pos += 2 + nserial;
if (pos+4 > length)
return 0; /* out of bounds */
/* user ids*/
nuids = get16 (buffer + pos); pos += 2;
uidinfolen = get16 (buffer + pos); pos += 2;
if (uidinfolen < 12 /* should add a: || nuidinfolen > MAX_UIDINFOLEN */)
return 0; /* invalid blob */
if (pos + uidinfolen*nuids > length)
return 0; /* out of bounds */
if (namelen < 1)
return 0;
/* Note that for X.509 we start at index 1 because index 0 is used
for the issuer name. */
for (idx=!!x509 ;idx < nuids; idx++)
{
size_t mypos = pos;
size_t mylen;
mypos += idx*uidinfolen;
off = get32 (buffer+mypos);
len = get32 (buffer+mypos+4);
if ((uint64_t)off+(uint64_t)len > (uint64_t)length)
return 0; /* error: better stop here - out of bounds */
if (x509)
{
if (len < 2 || buffer[off] != '<')
continue; /* empty name or trailing 0 not stored */
len--; /* one back */
if ( len < 3 || buffer[off+len] != '>')
continue; /* not a proper email address */
off++;
len--;
}
else /* OpenPGP. */
{
/* We need to forward to the mailbox part. */
mypos = off;
mylen = len;
for ( ; len && buffer[off] != '<'; len--, off++)
;
if (len < 2 || buffer[off] != '<')
{
/* Mailbox not explicitly given or too short. Restore
OFF and LEN and check whether the entire string
resembles a mailbox without the angle brackets. */
off = mypos;
len = mylen;
if (!is_valid_mailbox_mem (buffer+off, len))
continue; /* Not a mail address. */
}
else /* Seems to be standard user id with mail address. */
{
off++; /* Point to first char of the mail address. */
len--;
/* Search closing '>'. */
for (mypos=off; len && buffer[mypos] != '>'; len--, mypos++)
;
if (!len || buffer[mypos] != '>' || off == mypos)
continue; /* Not a proper mail address. */
len = mypos - off;
}
}
if (substr)
{
if (ascii_memcasemem (buffer+off, len, name, namelen))
return idx+1; /* found */
}
else
{
if (len == namelen && !ascii_memcasecmp (buffer+off, name, len))
return idx+1; /* found */
}
}
return 0; /* not found */
}
/* Return true if the key in BLOB matches the 20 bytes keygrip GRIP.
* We don't have the keygrips as meta data, thus we need to parse the
* certificate. Fixme: We might want to return proper error codes
* instead of failing a search for invalid certificates etc. */
static int
blob_openpgp_has_grip (KEYBOXBLOB blob, const unsigned char *grip)
{
int rc = 0;
const unsigned char *buffer;
size_t length;
size_t cert_off, cert_len;
struct _keybox_openpgp_info info;
struct _keybox_openpgp_key_info *k;
buffer = _keybox_get_blob_image (blob, &length);
if (length < 40)
return 0; /* Too short. */
cert_off = get32 (buffer+8);
cert_len = get32 (buffer+12);
if ((uint64_t)cert_off+(uint64_t)cert_len > (uint64_t)length)
return 0; /* Too short. */
if (_keybox_parse_openpgp (buffer + cert_off, cert_len, NULL, &info))
return 0; /* Parse error. */
if (!memcmp (info.primary.grip, grip, 20))
{
rc = 1;
goto leave;
}
if (info.nsubkeys)
{
k = &info.subkeys;
do
{
if (!memcmp (k->grip, grip, 20))
{
rc = 1;
goto leave;
}
k = k->next;
}
while (k);
}
leave:
_keybox_destroy_openpgp_info (&info);
return rc;
}
#ifdef KEYBOX_WITH_X509
/* Return true if the key in BLOB matches the 20 bytes keygrip GRIP.
We don't have the keygrips as meta data, thus we need to parse the
certificate. Fixme: We might want to return proper error codes
instead of failing a search for invalid certificates etc. */
static int
blob_x509_has_grip (KEYBOXBLOB blob, const unsigned char *grip)
{
int rc;
const unsigned char *buffer;
size_t length;
size_t cert_off, cert_len;
ksba_reader_t reader = NULL;
ksba_cert_t cert = NULL;
ksba_sexp_t p = NULL;
gcry_sexp_t s_pkey;
unsigned char array[20];
unsigned char *rcp;
size_t n;
buffer = _keybox_get_blob_image (blob, &length);
if (length < 40)
return 0; /* Too short. */
cert_off = get32 (buffer+8);
cert_len = get32 (buffer+12);
if ((uint64_t)cert_off+(uint64_t)cert_len > (uint64_t)length)
return 0; /* Too short. */
rc = ksba_reader_new (&reader);
if (rc)
return 0; /* Problem with ksba. */
rc = ksba_reader_set_mem (reader, buffer+cert_off, cert_len);
if (rc)
goto failed;
rc = ksba_cert_new (&cert);
if (rc)
goto failed;
rc = ksba_cert_read_der (cert, reader);
if (rc)
goto failed;
p = ksba_cert_get_public_key (cert);
if (!p)
goto failed;
n = gcry_sexp_canon_len (p, 0, NULL, NULL);
if (!n)
goto failed;
rc = gcry_sexp_sscan (&s_pkey, NULL, (char*)p, n);
if (rc)
{
gcry_sexp_release (s_pkey);
goto failed;
}
rcp = gcry_pk_get_keygrip (s_pkey, array);
gcry_sexp_release (s_pkey);
if (!rcp)
goto failed; /* Can't calculate keygrip. */
xfree (p);
ksba_cert_release (cert);
ksba_reader_release (reader);
return !memcmp (array, grip, 20);
failed:
xfree (p);
ksba_cert_release (cert);
ksba_reader_release (reader);
return 0;
}
#endif /*KEYBOX_WITH_X509*/
/*
The has_foo functions are used as helpers for search
*/
static inline int
has_short_kid (KEYBOXBLOB blob, u32 lkid)
{
unsigned char buf[4];
buf[0] = lkid >> 24;
buf[1] = lkid >> 16;
buf[2] = lkid >> 8;
buf[3] = lkid;
return blob_cmp_fpr_part (blob, buf, 16, 4);
}
static inline int
has_long_kid (KEYBOXBLOB blob, u32 mkid, u32 lkid)
{
unsigned char buf[8];
buf[0] = mkid >> 24;
buf[1] = mkid >> 16;
buf[2] = mkid >> 8;
buf[3] = mkid;
buf[4] = lkid >> 24;
buf[5] = lkid >> 16;
buf[6] = lkid >> 8;
buf[7] = lkid;
return blob_cmp_fpr_part (blob, buf, 12, 8);
}
static inline int
has_fingerprint (KEYBOXBLOB blob, const unsigned char *fpr, unsigned int fprlen)
{
return blob_cmp_fpr (blob, fpr, fprlen);
}
static inline int
has_keygrip (KEYBOXBLOB blob, const unsigned char *grip)
{
if (blob_get_type (blob) == KEYBOX_BLOBTYPE_PGP)
return blob_openpgp_has_grip (blob, grip);
#ifdef KEYBOX_WITH_X509
if (blob_get_type (blob) == KEYBOX_BLOBTYPE_X509)
return blob_x509_has_grip (blob, grip);
#endif
return 0;
}
static inline int
has_issuer (KEYBOXBLOB blob, const char *name)
{
size_t namelen;
return_val_if_fail (name, 0);
if (blob_get_type (blob) != KEYBOX_BLOBTYPE_X509)
return 0;
namelen = strlen (name);
return blob_cmp_name (blob, 0 /* issuer */, name, namelen, 0, 1);
}
static inline int
has_issuer_sn (KEYBOXBLOB blob, const char *name,
const unsigned char *sn, int snlen)
{
size_t namelen;
return_val_if_fail (name, 0);
return_val_if_fail (sn, 0);
if (blob_get_type (blob) != KEYBOX_BLOBTYPE_X509)
return 0;
namelen = strlen (name);
return (blob_cmp_sn (blob, sn, snlen)
&& blob_cmp_name (blob, 0 /* issuer */, name, namelen, 0, 1));
}
static inline int
has_sn (KEYBOXBLOB blob, const unsigned char *sn, int snlen)
{
return_val_if_fail (sn, 0);
if (blob_get_type (blob) != KEYBOX_BLOBTYPE_X509)
return 0;
return blob_cmp_sn (blob, sn, snlen);
}
static inline int
has_subject (KEYBOXBLOB blob, const char *name)
{
size_t namelen;
return_val_if_fail (name, 0);
if (blob_get_type (blob) != KEYBOX_BLOBTYPE_X509)
return 0;
namelen = strlen (name);
return blob_cmp_name (blob, 1 /* subject */, name, namelen, 0, 1);
}
static inline int
has_username (KEYBOXBLOB blob, const char *name, int substr)
{
size_t namelen;
int btype;
return_val_if_fail (name, 0);
btype = blob_get_type (blob);
if (btype != KEYBOX_BLOBTYPE_PGP && btype != KEYBOX_BLOBTYPE_X509)
return 0;
namelen = strlen (name);
return blob_cmp_name (blob, -1 /* all subject/user names */, name,
namelen, substr, (btype == KEYBOX_BLOBTYPE_X509));
}
static inline int
has_mail (KEYBOXBLOB blob, const char *name, int substr)
{
size_t namelen;
int btype;
return_val_if_fail (name, 0);
btype = blob_get_type (blob);
if (btype != KEYBOX_BLOBTYPE_PGP && btype != KEYBOX_BLOBTYPE_X509)
return 0;
if (btype == KEYBOX_BLOBTYPE_PGP && *name == '<')
name++; /* Hack to remove the leading '<' for gpg. */
namelen = strlen (name);
if (namelen && name[namelen-1] == '>')
namelen--;
return blob_cmp_mail (blob, name, namelen, substr,
(btype == KEYBOX_BLOBTYPE_X509));
}
static void
release_sn_array (struct sn_array_s *array, size_t size)
{
size_t n;
for (n=0; n < size; n++)
xfree (array[n].sn);
xfree (array);
}
/* Helper to open the file. */
static gpg_error_t
open_file (KEYBOX_HANDLE hd)
{
hd->fp = fopen (hd->kb->fname, "rb");
if (!hd->fp)
{
hd->error = gpg_error_from_syserror ();
return hd->error;
}
return 0;
}
/*
The search API
*/
gpg_error_t
keybox_search_reset (KEYBOX_HANDLE hd)
{
if (!hd)
return gpg_error (GPG_ERR_INV_VALUE);
if (hd->found.blob)
{
_keybox_release_blob (hd->found.blob);
hd->found.blob = NULL;
}
if (hd->fp)
{
if (fseeko (hd->fp, 0, SEEK_SET))
{
/* Ooops. Seek did not work. Close so that the search will
* open the file again. */
fclose (hd->fp);
hd->fp = NULL;
}
}
hd->error = 0;
hd->eof = 0;
return 0;
}
/* Note: When in ephemeral mode the search function does visit all
blobs but in standard mode, blobs flagged as ephemeral are ignored.
If WANT_BLOBTYPE is not 0 only blobs of this type are considered.
The value at R_SKIPPED is updated by the number of skipped long
records (counts PGP and X.509). */
gpg_error_t
keybox_search (KEYBOX_HANDLE hd, KEYBOX_SEARCH_DESC *desc, size_t ndesc,
keybox_blobtype_t want_blobtype,
size_t *r_descindex, unsigned long *r_skipped)
{
gpg_error_t rc;
size_t n;
int need_words, any_skip;
KEYBOXBLOB blob = NULL;
struct sn_array_s *sn_array = NULL;
int pk_no, uid_no;
+ off_t lastfoundoff;
if (!hd)
return gpg_error (GPG_ERR_INV_VALUE);
- /* clear last found result */
+ /* Clear last found result but reord the offset of the last found
+ * blob which we may need later. */
if (hd->found.blob)
{
+ lastfoundoff = _keybox_get_blob_fileoffset (hd->found.blob);
_keybox_release_blob (hd->found.blob);
hd->found.blob = NULL;
}
+ else
+ lastfoundoff = 0;
if (hd->error)
return hd->error; /* still in error state */
if (hd->eof)
return -1; /* still EOF */
/* figure out what information we need */
need_words = any_skip = 0;
for (n=0; n < ndesc; n++)
{
switch (desc[n].mode)
{
case KEYDB_SEARCH_MODE_WORDS:
need_words = 1;
break;
case KEYDB_SEARCH_MODE_FIRST:
/* always restart the search in this mode */
keybox_search_reset (hd);
+ lastfoundoff = 0;
break;
default:
break;
}
if (desc[n].skipfnc)
any_skip = 1;
if (desc[n].snlen == -1 && !sn_array)
{
sn_array = xtrycalloc (ndesc, sizeof *sn_array);
if (!sn_array)
return (hd->error = gpg_error_from_syserror ());
}
}
(void)need_words; /* Not yet implemented. */
if (!hd->fp)
{
rc = open_file (hd);
if (rc)
{
xfree (sn_array);
return rc;
}
+ /* log_debug ("%s: re-opened file\n", __func__); */
+ if (ndesc && desc[0].mode != KEYDB_SEARCH_MODE_FIRST && lastfoundoff)
+ {
+ /* Search mode is not first and the last search operation
+ * returned a blob which also was not the first one. We now
+ * need to skip over that blob and hope that the file has
+ * not changed. */
+ if (fseeko (hd->fp, lastfoundoff, SEEK_SET))
+ {
+ rc = gpg_error_from_syserror ();
+ log_debug ("%s: seeking to last found offset failed: %s\n",
+ __func__, gpg_strerror (rc));
+ xfree (sn_array);
+ return gpg_error (GPG_ERR_NOTHING_FOUND);
+ }
+ /* log_debug ("%s: re-opened file and sought to last offset\n", */
+ /* __func__); */
+ rc = _keybox_read_blob (NULL, hd->fp, NULL);
+ if (rc)
+ {
+ log_debug ("%s: skipping last found blob failed: %s\n",
+ __func__, gpg_strerror (rc));
+ xfree (sn_array);
+ return gpg_error (GPG_ERR_NOTHING_FOUND);
+ }
+ }
}
/* Kludge: We need to convert an SN given as hexstring to its binary
representation - in some cases we are not able to store it in the
search descriptor, because due to the way we use it, it is not
possible to free allocated memory. */
if (sn_array)
{
const unsigned char *s;
int i, odd;
size_t snlen;
for (n=0; n < ndesc; n++)
{
if (!desc[n].sn)
;
else if (desc[n].snlen == -1)
{
unsigned char *sn;
s = desc[n].sn;
for (i=0; *s && *s != '/'; s++, i++)
;
odd = (i & 1);
snlen = (i+1)/2;
sn_array[n].sn = xtrymalloc (snlen);
if (!sn_array[n].sn)
{
hd->error = gpg_error_from_syserror ();
release_sn_array (sn_array, n);
return hd->error;
}
sn_array[n].snlen = snlen;
sn = sn_array[n].sn;
s = desc[n].sn;
if (odd)
{
*sn++ = xtoi_1 (s);
s++;
}
for (; *s && *s != '/'; s += 2)
*sn++ = xtoi_2 (s);
}
else
{
const unsigned char *sn;
sn = desc[n].sn;
snlen = desc[n].snlen;
sn_array[n].sn = xtrymalloc (snlen);
if (!sn_array[n].sn)
{
hd->error = gpg_error_from_syserror ();
release_sn_array (sn_array, n);
return hd->error;
}
sn_array[n].snlen = snlen;
memcpy (sn_array[n].sn, sn, snlen);
}
}
}
pk_no = uid_no = 0;
for (;;)
{
unsigned int blobflags;
int blobtype;
_keybox_release_blob (blob); blob = NULL;
rc = _keybox_read_blob (&blob, hd->fp, NULL);
if (gpg_err_code (rc) == GPG_ERR_TOO_LARGE
&& gpg_err_source (rc) == GPG_ERR_SOURCE_KEYBOX)
{
++*r_skipped;
continue; /* Skip too large records. */
}
if (rc)
break;
blobtype = blob_get_type (blob);
if (blobtype == KEYBOX_BLOBTYPE_HEADER)
continue;
if (want_blobtype && blobtype != want_blobtype)
continue;
blobflags = blob_get_blob_flags (blob);
if (!hd->ephemeral && (blobflags & 2))
continue; /* Not in ephemeral mode but blob is flagged ephemeral. */
for (n=0; n < ndesc; n++)
{
switch (desc[n].mode)
{
case KEYDB_SEARCH_MODE_NONE:
never_reached ();
break;
case KEYDB_SEARCH_MODE_EXACT:
uid_no = has_username (blob, desc[n].u.name, 0);
if (uid_no)
goto found;
break;
case KEYDB_SEARCH_MODE_MAIL:
uid_no = has_mail (blob, desc[n].u.name, 0);
if (uid_no)
goto found;
break;
case KEYDB_SEARCH_MODE_MAILSUB:
uid_no = has_mail (blob, desc[n].u.name, 1);
if (uid_no)
goto found;
break;
case KEYDB_SEARCH_MODE_SUBSTR:
uid_no = has_username (blob, desc[n].u.name, 1);
if (uid_no)
goto found;
break;
case KEYDB_SEARCH_MODE_MAILEND:
case KEYDB_SEARCH_MODE_WORDS:
/* not yet implemented */
break;
case KEYDB_SEARCH_MODE_ISSUER:
if (has_issuer (blob, desc[n].u.name))
goto found;
break;
case KEYDB_SEARCH_MODE_ISSUER_SN:
if (has_issuer_sn (blob, desc[n].u.name,
sn_array? sn_array[n].sn : desc[n].sn,
sn_array? sn_array[n].snlen : desc[n].snlen))
goto found;
break;
case KEYDB_SEARCH_MODE_SN:
if (has_sn (blob, sn_array? sn_array[n].sn : desc[n].sn,
sn_array? sn_array[n].snlen : desc[n].snlen))
goto found;
break;
case KEYDB_SEARCH_MODE_SUBJECT:
if (has_subject (blob, desc[n].u.name))
goto found;
break;
case KEYDB_SEARCH_MODE_SHORT_KID:
pk_no = has_short_kid (blob, desc[n].u.kid[1]);
if (pk_no)
goto found;
break;
case KEYDB_SEARCH_MODE_LONG_KID:
pk_no = has_long_kid (blob, desc[n].u.kid[0], desc[n].u.kid[1]);
if (pk_no)
goto found;
break;
case KEYDB_SEARCH_MODE_FPR:
pk_no = has_fingerprint (blob, desc[n].u.fpr, desc[n].fprlen);
if (pk_no)
goto found;
break;
case KEYDB_SEARCH_MODE_KEYGRIP:
if (has_keygrip (blob, desc[n].u.grip))
goto found;
break;
case KEYDB_SEARCH_MODE_FIRST:
goto found;
break;
case KEYDB_SEARCH_MODE_NEXT:
goto found;
break;
default:
rc = gpg_error (GPG_ERR_INV_VALUE);
goto found;
}
}
continue;
found:
/* Record which DESC we matched on. Note this value is only
meaningful if this function returns with no errors. */
if(r_descindex)
*r_descindex = n;
for (n=any_skip?0:ndesc; n < ndesc; n++)
{
u32 kid[2];
if (desc[n].skipfnc
&& blob_get_first_keyid (blob, kid)
&& desc[n].skipfnc (desc[n].skipfncvalue, kid, uid_no))
break;
}
if (n == ndesc)
break; /* got it */
}
if (!rc)
{
hd->found.blob = blob;
hd->found.pk_no = pk_no;
hd->found.uid_no = uid_no;
}
else if (rc == -1 || gpg_err_code (rc) == GPG_ERR_EOF)
{
_keybox_release_blob (blob);
hd->eof = 1;
}
else
{
_keybox_release_blob (blob);
hd->error = rc;
}
if (sn_array)
release_sn_array (sn_array, ndesc);
return rc;
}
/*
Functions to return a certificate or a keyblock. To be used after
a successful search operation.
*/
/* Return the last found keyblock. Returns 0 on success and stores a
* new iobuf at R_IOBUF. R_UID_NO and R_PK_NO are used to return the
* number of the key or user id which was matched the search criteria;
* if not known they are set to 0. */
gpg_error_t
keybox_get_keyblock (KEYBOX_HANDLE hd, iobuf_t *r_iobuf,
int *r_pk_no, int *r_uid_no)
{
gpg_error_t err;
const unsigned char *buffer;
size_t length;
size_t image_off, image_len;
size_t siginfo_off, siginfo_len;
*r_iobuf = NULL;
if (!hd)
return gpg_error (GPG_ERR_INV_VALUE);
if (!hd->found.blob)
return gpg_error (GPG_ERR_NOTHING_FOUND);
if (blob_get_type (hd->found.blob) != KEYBOX_BLOBTYPE_PGP)
return gpg_error (GPG_ERR_WRONG_BLOB_TYPE);
buffer = _keybox_get_blob_image (hd->found.blob, &length);
if (length < 40)
return gpg_error (GPG_ERR_TOO_SHORT);
image_off = get32 (buffer+8);
image_len = get32 (buffer+12);
if ((uint64_t)image_off+(uint64_t)image_len > (uint64_t)length)
return gpg_error (GPG_ERR_TOO_SHORT);
err = _keybox_get_flag_location (buffer, length, KEYBOX_FLAG_SIG_INFO,
&siginfo_off, &siginfo_len);
if (err)
return err;
*r_pk_no = hd->found.pk_no;
*r_uid_no = hd->found.uid_no;
*r_iobuf = iobuf_temp_with_content (buffer+image_off, image_len);
return 0;
}
#ifdef KEYBOX_WITH_X509
/*
Return the last found cert. Caller must free it.
*/
int
keybox_get_cert (KEYBOX_HANDLE hd, ksba_cert_t *r_cert)
{
const unsigned char *buffer;
size_t length;
size_t cert_off, cert_len;
ksba_reader_t reader = NULL;
ksba_cert_t cert = NULL;
int rc;
if (!hd)
return gpg_error (GPG_ERR_INV_VALUE);
if (!hd->found.blob)
return gpg_error (GPG_ERR_NOTHING_FOUND);
if (blob_get_type (hd->found.blob) != KEYBOX_BLOBTYPE_X509)
return gpg_error (GPG_ERR_WRONG_BLOB_TYPE);
buffer = _keybox_get_blob_image (hd->found.blob, &length);
if (length < 40)
return gpg_error (GPG_ERR_TOO_SHORT);
cert_off = get32 (buffer+8);
cert_len = get32 (buffer+12);
if ((uint64_t)cert_off+(uint64_t)cert_len > (uint64_t)length)
return gpg_error (GPG_ERR_TOO_SHORT);
rc = ksba_reader_new (&reader);
if (rc)
return rc;
rc = ksba_reader_set_mem (reader, buffer+cert_off, cert_len);
if (rc)
{
ksba_reader_release (reader);
/* fixme: need to map the error codes */
return gpg_error (GPG_ERR_GENERAL);
}
rc = ksba_cert_new (&cert);
if (rc)
{
ksba_reader_release (reader);
return rc;
}
rc = ksba_cert_read_der (cert, reader);
if (rc)
{
ksba_cert_release (cert);
ksba_reader_release (reader);
/* fixme: need to map the error codes */
return gpg_error (GPG_ERR_GENERAL);
}
*r_cert = cert;
ksba_reader_release (reader);
return 0;
}
#endif /*KEYBOX_WITH_X509*/
/* Return the flags named WHAT at the address of VALUE. IDX is used
only for certain flags and should be 0 if not required. */
int
keybox_get_flags (KEYBOX_HANDLE hd, int what, int idx, unsigned int *value)
{
const unsigned char *buffer;
size_t length;
gpg_err_code_t ec;
(void)idx; /* Not yet used. */
if (!hd)
return gpg_error (GPG_ERR_INV_VALUE);
if (!hd->found.blob)
return gpg_error (GPG_ERR_NOTHING_FOUND);
buffer = _keybox_get_blob_image (hd->found.blob, &length);
ec = get_flag_from_image (buffer, length, what, value);
return ec? gpg_error (ec):0;
}
off_t
keybox_offset (KEYBOX_HANDLE hd)
{
if (!hd->fp)
return 0;
return ftello (hd->fp);
}
gpg_error_t
keybox_seek (KEYBOX_HANDLE hd, off_t offset)
{
gpg_error_t err;
if (hd->error)
return hd->error; /* still in error state */
if (! hd->fp)
{
if (!offset)
{
/* No need to open the file. An unopened file is effectively at
offset 0. */
return 0;
}
err = open_file (hd);
if (err)
return err;
}
err = fseeko (hd->fp, offset, SEEK_SET);
hd->error = gpg_error_from_errno (err);
return hd->error;
}
diff --git a/kbx/keybox-update.c b/kbx/keybox-update.c
index 580330f52..e09fefc41 100644
--- a/kbx/keybox-update.c
+++ b/kbx/keybox-update.c
@@ -1,797 +1,797 @@
/* keybox-update.c - keybox update operations
* Copyright (C) 2001, 2003, 2004, 2012 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <unistd.h>
#include <assert.h>
#include "keybox-defs.h"
#include "../common/sysutils.h"
#include "../common/host2net.h"
#include "../common/utilproto.h"
#define EXTSEP_S "."
#define FILECOPY_INSERT 1
#define FILECOPY_DELETE 2
#define FILECOPY_UPDATE 3
#if !defined(HAVE_FSEEKO) && !defined(fseeko)
#ifdef HAVE_LIMITS_H
# include <limits.h>
#endif
#ifndef LONG_MAX
# define LONG_MAX ((long) ((unsigned long) -1 >> 1))
#endif
#ifndef LONG_MIN
# define LONG_MIN (-1 - LONG_MAX)
#endif
/****************
* A substitute for fseeko, for hosts that don't have it.
*/
static int
fseeko (FILE * stream, off_t newpos, int whence)
{
while (newpos != (long) newpos)
{
long pos = newpos < 0 ? LONG_MIN : LONG_MAX;
if (fseek (stream, pos, whence) != 0)
return -1;
newpos -= pos;
whence = SEEK_CUR;
}
return fseek (stream, (long) newpos, whence);
}
#endif /* !defined(HAVE_FSEEKO) && !defined(fseeko) */
static int
create_tmp_file (const char *template,
char **r_bakfname, char **r_tmpfname, FILE **r_fp)
{
gpg_error_t err;
err = keybox_tmp_names (template, 0, r_bakfname, r_tmpfname);
if (!err)
{
*r_fp = fopen (*r_tmpfname, "wb");
if (!*r_fp)
{
err = gpg_error_from_syserror ();
xfree (*r_tmpfname);
*r_tmpfname = NULL;
xfree (*r_bakfname);
*r_bakfname = NULL;
}
}
return err;
}
static int
rename_tmp_file (const char *bakfname, const char *tmpfname,
const char *fname, int secret )
{
int rc=0;
int block = 0;
/* restrict the permissions for secret keyboxs */
#ifndef HAVE_DOSISH_SYSTEM
/* if (secret && !opt.preserve_permissions) */
/* { */
/* if (chmod (tmpfname, S_IRUSR | S_IWUSR) ) */
/* { */
/* log_debug ("chmod of '%s' failed: %s\n", */
/* tmpfname, strerror(errno) ); */
/* return KEYBOX_Write_File; */
/* } */
/* } */
#endif
/* fixme: invalidate close caches (not used with stdio)*/
/* iobuf_ioctl (NULL, IOBUF_IOCTL_INVALIDATE_CACHE, 0, (char*)tmpfname ); */
/* iobuf_ioctl (NULL, IOBUF_IOCTL_INVALIDATE_CACHE, 0, (char*)bakfname ); */
/* iobuf_ioctl (NULL, IOBUF_IOCTL_INVALIDATE_CACHE, 0, (char*)fname ); */
/* First make a backup file except for secret keyboxes. */
if (!secret)
{
block = 1;
rc = gnupg_rename_file (fname, bakfname, &block);
if (rc)
goto leave;
}
/* Then rename the file. */
rc = gnupg_rename_file (tmpfname, fname, NULL);
if (block)
{
gnupg_unblock_all_signals ();
block = 0;
}
/* if (rc) */
/* { */
/* if (secret) */
/* { */
/* log_info ("WARNING: 2 files with confidential" */
/* " information exists.\n"); */
/* log_info ("%s is the unchanged one\n", fname ); */
/* log_info ("%s is the new one\n", tmpfname ); */
/* log_info ("Please fix this possible security flaw\n"); */
/* } */
/* } */
leave:
if (block)
gnupg_unblock_all_signals ();
return rc;
}
/* Perform insert/delete/update operation. MODE is one of
FILECOPY_INSERT, FILECOPY_DELETE, FILECOPY_UPDATE. FOR_OPENPGP
indicates that this is called due to an OpenPGP keyblock change. */
static int
blob_filecopy (int mode, const char *fname, KEYBOXBLOB blob,
int secret, int for_openpgp, off_t start_offset)
{
FILE *fp, *newfp;
int rc=0;
char *bakfname = NULL;
char *tmpfname = NULL;
char buffer[4096]; /* (Must be at least 32 bytes) */
int nread, nbytes;
/* Open the source file. Because we do a rename, we have to check the
permissions of the file */
if (access (fname, W_OK))
return gpg_error_from_syserror ();
fp = fopen (fname, "rb");
if (mode == FILECOPY_INSERT && !fp && errno == ENOENT)
{
/* Insert mode but file does not exist:
Create a new keybox file. */
newfp = fopen (fname, "wb");
if (!newfp )
return gpg_error_from_syserror ();
rc = _keybox_write_header_blob (newfp, for_openpgp);
if (rc)
{
fclose (newfp);
return rc;
}
rc = _keybox_write_blob (blob, newfp);
if (rc)
{
fclose (newfp);
return rc;
}
if ( fclose (newfp) )
return gpg_error_from_syserror ();
/* if (chmod( fname, S_IRUSR | S_IWUSR )) */
/* { */
/* log_debug ("%s: chmod failed: %s\n", fname, strerror(errno) ); */
/* return KEYBOX_File_Error; */
/* } */
return 0; /* Ready. */
}
if (!fp)
{
rc = gpg_error_from_syserror ();
goto leave;
}
/* Create the new file. On success NEWFP is initialized. */
rc = create_tmp_file (fname, &bakfname, &tmpfname, &newfp);
if (rc)
{
fclose (fp);
goto leave;
}
/* prepare for insert */
if (mode == FILECOPY_INSERT)
{
int first_record = 1;
/* Copy everything to the new file. If this is for OpenPGP, we
make sure that the openpgp flag is set in the header. (We
failsafe the blob type.) */
while ( (nread = fread (buffer, 1, DIM(buffer), fp)) > 0 )
{
if (first_record && for_openpgp
&& buffer[4] == KEYBOX_BLOBTYPE_HEADER)
{
first_record = 0;
buffer[7] |= 0x02; /* OpenPGP data may be available. */
}
if (fwrite (buffer, nread, 1, newfp) != 1)
{
rc = gpg_error_from_syserror ();
fclose (fp);
fclose (newfp);
goto leave;
}
}
if (ferror (fp))
{
rc = gpg_error_from_syserror ();
fclose (fp);
fclose (newfp);
goto leave;
}
}
/* Prepare for delete or update. */
if ( mode == FILECOPY_DELETE || mode == FILECOPY_UPDATE )
{
off_t current = 0;
/* Copy first part to the new file. */
while ( current < start_offset )
{
nbytes = DIM(buffer);
if (current + nbytes > start_offset)
nbytes = start_offset - current;
nread = fread (buffer, 1, nbytes, fp);
if (!nread)
break;
current += nread;
if (fwrite (buffer, nread, 1, newfp) != 1)
{
rc = gpg_error_from_syserror ();
fclose (fp);
fclose (newfp);
goto leave;
}
}
if (ferror (fp))
{
rc = gpg_error_from_syserror ();
fclose (fp);
fclose (newfp);
goto leave;
}
/* Skip this blob. */
rc = _keybox_read_blob (NULL, fp, NULL);
if (rc)
{
fclose (fp);
fclose (newfp);
return rc;
}
}
/* Do an insert or update. */
if ( mode == FILECOPY_INSERT || mode == FILECOPY_UPDATE )
{
rc = _keybox_write_blob (blob, newfp);
if (rc)
{
fclose (fp);
fclose (newfp);
return rc;
}
}
/* Copy the rest of the packet for an delete or update. */
if (mode == FILECOPY_DELETE || mode == FILECOPY_UPDATE)
{
while ( (nread = fread (buffer, 1, DIM(buffer), fp)) > 0 )
{
if (fwrite (buffer, nread, 1, newfp) != 1)
{
rc = gpg_error_from_syserror ();
fclose (fp);
fclose (newfp);
goto leave;
}
}
if (ferror (fp))
{
rc = gpg_error_from_syserror ();
fclose (fp);
fclose (newfp);
goto leave;
}
}
/* Close both files. */
if (fclose(fp))
{
rc = gpg_error_from_syserror ();
fclose (newfp);
goto leave;
}
if (fclose(newfp))
{
rc = gpg_error_from_syserror ();
goto leave;
}
rc = rename_tmp_file (bakfname, tmpfname, fname, secret);
leave:
xfree(bakfname);
xfree(tmpfname);
return rc;
}
/* Insert the OpenPGP keyblock {IMAGE,IMAGELEN} into HD. */
gpg_error_t
keybox_insert_keyblock (KEYBOX_HANDLE hd, const void *image, size_t imagelen)
{
gpg_error_t err;
const char *fname;
KEYBOXBLOB blob;
size_t nparsed;
struct _keybox_openpgp_info info;
if (!hd)
return gpg_error (GPG_ERR_INV_HANDLE);
if (!hd->kb)
return gpg_error (GPG_ERR_INV_HANDLE);
fname = hd->kb->fname;
if (!fname)
return gpg_error (GPG_ERR_INV_HANDLE);
/* Close this one otherwise we will mess up the position for a next
search. Fixme: it would be better to adjust the position after
the write operation. */
_keybox_close_file (hd);
err = _keybox_parse_openpgp (image, imagelen, &nparsed, &info);
if (err)
return err;
assert (nparsed <= imagelen);
err = _keybox_create_openpgp_blob (&blob, &info, image, imagelen,
hd->ephemeral);
_keybox_destroy_openpgp_info (&info);
if (!err)
{
err = blob_filecopy (FILECOPY_INSERT, fname, blob, hd->secret, 1, 0);
_keybox_release_blob (blob);
/* if (!rc && !hd->secret && kb_offtbl) */
/* { */
/* update_offset_hash_table_from_kb (kb_offtbl, kb, 0); */
/* } */
}
return err;
}
/* Update the current key at HD with the given OpenPGP keyblock in
{IMAGE,IMAGELEN}. */
gpg_error_t
keybox_update_keyblock (KEYBOX_HANDLE hd, const void *image, size_t imagelen)
{
gpg_error_t err;
const char *fname;
off_t off;
KEYBOXBLOB blob;
size_t nparsed;
struct _keybox_openpgp_info info;
if (!hd || !image || !imagelen)
return gpg_error (GPG_ERR_INV_VALUE);
if (!hd->found.blob)
return gpg_error (GPG_ERR_NOTHING_FOUND);
if (blob_get_type (hd->found.blob) != KEYBOX_BLOBTYPE_PGP)
return gpg_error (GPG_ERR_WRONG_BLOB_TYPE);
fname = hd->kb->fname;
if (!fname)
return gpg_error (GPG_ERR_INV_HANDLE);
off = _keybox_get_blob_fileoffset (hd->found.blob);
if (off == (off_t)-1)
return gpg_error (GPG_ERR_GENERAL);
- /* Close this the file so that we do no mess up the position for a
+ /* Close the file so that we do no mess up the position for a
next search. */
_keybox_close_file (hd);
/* Build a new blob. */
err = _keybox_parse_openpgp (image, imagelen, &nparsed, &info);
if (err)
return err;
assert (nparsed <= imagelen);
err = _keybox_create_openpgp_blob (&blob, &info, image, imagelen,
hd->ephemeral);
_keybox_destroy_openpgp_info (&info);
/* Update the keyblock. */
if (!err)
{
err = blob_filecopy (FILECOPY_UPDATE, fname, blob, hd->secret, 1, off);
_keybox_release_blob (blob);
}
return err;
}
#ifdef KEYBOX_WITH_X509
int
keybox_insert_cert (KEYBOX_HANDLE hd, ksba_cert_t cert,
unsigned char *sha1_digest)
{
int rc;
const char *fname;
KEYBOXBLOB blob;
if (!hd)
return gpg_error (GPG_ERR_INV_HANDLE);
if (!hd->kb)
return gpg_error (GPG_ERR_INV_HANDLE);
fname = hd->kb->fname;
if (!fname)
return gpg_error (GPG_ERR_INV_HANDLE);
/* Close this one otherwise we will mess up the position for a next
search. Fixme: it would be better to adjust the position after
the write operation. */
_keybox_close_file (hd);
rc = _keybox_create_x509_blob (&blob, cert, sha1_digest, hd->ephemeral);
if (!rc)
{
rc = blob_filecopy (FILECOPY_INSERT, fname, blob, hd->secret, 0, 0);
_keybox_release_blob (blob);
/* if (!rc && !hd->secret && kb_offtbl) */
/* { */
/* update_offset_hash_table_from_kb (kb_offtbl, kb, 0); */
/* } */
}
return rc;
}
int
keybox_update_cert (KEYBOX_HANDLE hd, ksba_cert_t cert,
unsigned char *sha1_digest)
{
(void)hd;
(void)cert;
(void)sha1_digest;
return -1;
}
#endif /*KEYBOX_WITH_X509*/
/* Note: We assume that the keybox has been locked before the current
search was executed. This is needed so that we can depend on the
offset information of the flags. */
int
keybox_set_flags (KEYBOX_HANDLE hd, int what, int idx, unsigned int value)
{
off_t off;
const char *fname;
FILE *fp;
gpg_err_code_t ec;
size_t flag_pos, flag_size;
const unsigned char *buffer;
size_t length;
(void)idx; /* Not yet used. */
if (!hd)
return gpg_error (GPG_ERR_INV_VALUE);
if (!hd->found.blob)
return gpg_error (GPG_ERR_NOTHING_FOUND);
if (!hd->kb)
return gpg_error (GPG_ERR_INV_HANDLE);
if (!hd->found.blob)
return gpg_error (GPG_ERR_NOTHING_FOUND);
fname = hd->kb->fname;
if (!fname)
return gpg_error (GPG_ERR_INV_HANDLE);
off = _keybox_get_blob_fileoffset (hd->found.blob);
if (off == (off_t)-1)
return gpg_error (GPG_ERR_GENERAL);
buffer = _keybox_get_blob_image (hd->found.blob, &length);
ec = _keybox_get_flag_location (buffer, length, what, &flag_pos, &flag_size);
if (ec)
return gpg_error (ec);
off += flag_pos;
_keybox_close_file (hd);
fp = fopen (hd->kb->fname, "r+b");
if (!fp)
return gpg_error_from_syserror ();
ec = 0;
if (fseeko (fp, off, SEEK_SET))
ec = gpg_err_code_from_syserror ();
else
{
unsigned char tmp[4];
tmp[0] = value >> 24;
tmp[1] = value >> 16;
tmp[2] = value >> 8;
tmp[3] = value;
switch (flag_size)
{
case 1:
case 2:
case 4:
if (fwrite (tmp+4-flag_size, flag_size, 1, fp) != 1)
ec = gpg_err_code_from_syserror ();
break;
default:
ec = GPG_ERR_BUG;
break;
}
}
if (fclose (fp))
{
if (!ec)
ec = gpg_err_code_from_syserror ();
}
return gpg_error (ec);
}
int
keybox_delete (KEYBOX_HANDLE hd)
{
off_t off;
const char *fname;
FILE *fp;
int rc;
if (!hd)
return gpg_error (GPG_ERR_INV_VALUE);
if (!hd->found.blob)
return gpg_error (GPG_ERR_NOTHING_FOUND);
if (!hd->kb)
return gpg_error (GPG_ERR_INV_HANDLE);
fname = hd->kb->fname;
if (!fname)
return gpg_error (GPG_ERR_INV_HANDLE);
off = _keybox_get_blob_fileoffset (hd->found.blob);
if (off == (off_t)-1)
return gpg_error (GPG_ERR_GENERAL);
off += 4;
_keybox_close_file (hd);
fp = fopen (hd->kb->fname, "r+b");
if (!fp)
return gpg_error_from_syserror ();
if (fseeko (fp, off, SEEK_SET))
rc = gpg_error_from_syserror ();
else if (putc (0, fp) == EOF)
rc = gpg_error_from_syserror ();
else
rc = 0;
if (fclose (fp))
{
if (!rc)
rc = gpg_error_from_syserror ();
}
return rc;
}
/* Compress the keybox file. This should be run with the file
locked. */
int
keybox_compress (KEYBOX_HANDLE hd)
{
int read_rc, rc;
const char *fname;
FILE *fp, *newfp;
char *bakfname = NULL;
char *tmpfname = NULL;
int first_blob;
KEYBOXBLOB blob = NULL;
u32 cut_time;
int any_changes = 0;
int skipped_deleted;
if (!hd)
return gpg_error (GPG_ERR_INV_HANDLE);
if (!hd->kb)
return gpg_error (GPG_ERR_INV_HANDLE);
if (hd->secret)
return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
fname = hd->kb->fname;
if (!fname)
return gpg_error (GPG_ERR_INV_HANDLE);
_keybox_close_file (hd);
/* Open the source file. Because we do a rename, we have to check the
permissions of the file */
if (access (fname, W_OK))
return gpg_error_from_syserror ();
fp = fopen (fname, "rb");
if (!fp && errno == ENOENT)
return 0; /* Ready. File has been deleted right after the access above. */
if (!fp)
{
rc = gpg_error_from_syserror ();
return rc;
}
/* A quick test to see if we need to compress the file at all. We
schedule a compress run after 3 hours. */
if ( !_keybox_read_blob (&blob, fp, NULL) )
{
const unsigned char *buffer;
size_t length;
buffer = _keybox_get_blob_image (blob, &length);
if (length > 4 && buffer[4] == KEYBOX_BLOBTYPE_HEADER)
{
u32 last_maint = buf32_to_u32 (buffer+20);
if ( (last_maint + 3*3600) > time (NULL) )
{
fclose (fp);
_keybox_release_blob (blob);
return 0; /* Compress run not yet needed. */
}
}
_keybox_release_blob (blob);
fseek (fp, 0, SEEK_SET);
clearerr (fp);
}
/* Create the new file. */
rc = create_tmp_file (fname, &bakfname, &tmpfname, &newfp);
if (rc)
{
fclose (fp);
return rc;;
}
/* Processing loop. By reading using _keybox_read_blob we
automagically skip any blobs flagged as deleted. Thus what we
only have to do is to check all ephemeral flagged blocks whether
their time has come and write out all other blobs. */
cut_time = time(NULL) - 86400;
first_blob = 1;
skipped_deleted = 0;
for (rc=0; !(read_rc = _keybox_read_blob (&blob, fp, &skipped_deleted));
_keybox_release_blob (blob), blob = NULL )
{
unsigned int blobflags;
const unsigned char *buffer;
size_t length, pos, size;
u32 created_at;
if (skipped_deleted)
any_changes = 1;
buffer = _keybox_get_blob_image (blob, &length);
if (first_blob)
{
first_blob = 0;
if (length > 4 && buffer[4] == KEYBOX_BLOBTYPE_HEADER)
{
/* Write out the blob with an updated maintenance time
stamp and if needed (ie. used by gpg) set the openpgp
flag. */
_keybox_update_header_blob (blob, hd->for_openpgp);
rc = _keybox_write_blob (blob, newfp);
if (rc)
break;
continue;
}
/* The header blob is missing. Insert it. */
rc = _keybox_write_header_blob (newfp, hd->for_openpgp);
if (rc)
break;
any_changes = 1;
}
else if (length > 4 && buffer[4] == KEYBOX_BLOBTYPE_HEADER)
{
/* Oops: There is another header record - remove it. */
any_changes = 1;
continue;
}
if (_keybox_get_flag_location (buffer, length,
KEYBOX_FLAG_BLOB, &pos, &size)
|| size != 2)
{
rc = gpg_error (GPG_ERR_BUG);
break;
}
blobflags = buf16_to_uint (buffer+pos);
if ((blobflags & KEYBOX_FLAG_BLOB_EPHEMERAL))
{
/* This is an ephemeral blob. */
if (_keybox_get_flag_location (buffer, length,
KEYBOX_FLAG_CREATED_AT, &pos, &size)
|| size != 4)
created_at = 0; /* oops. */
else
created_at = buf32_to_u32 (buffer+pos);
if (created_at && created_at < cut_time)
{
any_changes = 1;
continue; /* Skip this blob. */
}
}
rc = _keybox_write_blob (blob, newfp);
if (rc)
break;
}
if (skipped_deleted)
any_changes = 1;
_keybox_release_blob (blob); blob = NULL;
if (!rc && read_rc == -1)
rc = 0;
else if (!rc)
rc = read_rc;
/* Close both files. */
if (fclose(fp) && !rc)
rc = gpg_error_from_syserror ();
if (fclose(newfp) && !rc)
rc = gpg_error_from_syserror ();
/* Rename or remove the temporary file. */
if (rc || !any_changes)
gnupg_remove (tmpfname);
else
rc = rename_tmp_file (bakfname, tmpfname, fname, hd->secret);
xfree(bakfname);
xfree(tmpfname);
return rc;
}
diff --git a/kbx/keybox.h b/kbx/keybox.h
index 665b05fc0..4d941571e 100644
--- a/kbx/keybox.h
+++ b/kbx/keybox.h
@@ -1,137 +1,137 @@
/* keybox.h - Keybox operations
* Copyright (C) 2001, 2003, 2012 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#ifndef KEYBOX_H
#define KEYBOX_H 1
#ifdef __cplusplus
extern "C" {
#if 0
}
#endif
#endif
#include "../common/iobuf.h"
#include "keybox-search-desc.h"
#ifdef KEYBOX_WITH_X509
# include <ksba.h>
#endif
typedef struct keybox_handle *KEYBOX_HANDLE;
typedef enum
{
KEYBOX_FLAG_BLOB, /* The blob flags. */
KEYBOX_FLAG_VALIDITY, /* The validity of the entire key. */
KEYBOX_FLAG_OWNERTRUST, /* The assigned ownertrust. */
KEYBOX_FLAG_KEY, /* The key flags; requires a key index. */
KEYBOX_FLAG_UID, /* The user ID flags; requires an uid index. */
KEYBOX_FLAG_UID_VALIDITY,/* The validity of a specific uid, requires
an uid index. */
KEYBOX_FLAG_CREATED_AT, /* The date the block was created. */
KEYBOX_FLAG_SIG_INFO, /* The signature info block. */
} keybox_flag_t;
/* Flag values used with KEYBOX_FLAG_BLOB. */
#define KEYBOX_FLAG_BLOB_SECRET 1
#define KEYBOX_FLAG_BLOB_EPHEMERAL 2
/* The keybox blob types. */
typedef enum
{
KEYBOX_BLOBTYPE_EMPTY = 0,
KEYBOX_BLOBTYPE_HEADER = 1,
KEYBOX_BLOBTYPE_PGP = 2,
KEYBOX_BLOBTYPE_X509 = 3
} keybox_blobtype_t;
/*-- keybox-init.c --*/
gpg_error_t keybox_register_file (const char *fname, int secret,
void **r_token);
int keybox_is_writable (void *token);
KEYBOX_HANDLE keybox_new_openpgp (void *token, int secret);
KEYBOX_HANDLE keybox_new_x509 (void *token, int secret);
void keybox_release (KEYBOX_HANDLE hd);
void keybox_push_found_state (KEYBOX_HANDLE hd);
void keybox_pop_found_state (KEYBOX_HANDLE hd);
const char *keybox_get_resource_name (KEYBOX_HANDLE hd);
int keybox_set_ephemeral (KEYBOX_HANDLE hd, int yes);
-gpg_error_t keybox_lock (KEYBOX_HANDLE hd, int yes);
+gpg_error_t keybox_lock (KEYBOX_HANDLE hd, int yes, long timeout);
/*-- keybox-file.c --*/
/* Fixme: This function does not belong here: Provide a better
interface to create a new keybox file. */
int _keybox_write_header_blob (FILE *fp, int openpgp_flag);
/*-- keybox-search.c --*/
gpg_error_t keybox_get_keyblock (KEYBOX_HANDLE hd, iobuf_t *r_iobuf,
int *r_uid_no, int *r_pk_no);
#ifdef KEYBOX_WITH_X509
int keybox_get_cert (KEYBOX_HANDLE hd, ksba_cert_t *ret_cert);
#endif /*KEYBOX_WITH_X509*/
int keybox_get_flags (KEYBOX_HANDLE hd, int what, int idx, unsigned int *value);
gpg_error_t keybox_search_reset (KEYBOX_HANDLE hd);
gpg_error_t keybox_search (KEYBOX_HANDLE hd,
KEYBOX_SEARCH_DESC *desc, size_t ndesc,
keybox_blobtype_t want_blobtype,
size_t *r_descindex, unsigned long *r_skipped);
off_t keybox_offset (KEYBOX_HANDLE hd);
gpg_error_t keybox_seek (KEYBOX_HANDLE hd, off_t offset);
/*-- keybox-update.c --*/
gpg_error_t keybox_insert_keyblock (KEYBOX_HANDLE hd,
const void *image, size_t imagelen);
gpg_error_t keybox_update_keyblock (KEYBOX_HANDLE hd,
const void *image, size_t imagelen);
#ifdef KEYBOX_WITH_X509
int keybox_insert_cert (KEYBOX_HANDLE hd, ksba_cert_t cert,
unsigned char *sha1_digest);
int keybox_update_cert (KEYBOX_HANDLE hd, ksba_cert_t cert,
unsigned char *sha1_digest);
#endif /*KEYBOX_WITH_X509*/
int keybox_set_flags (KEYBOX_HANDLE hd, int what, int idx, unsigned int value);
int keybox_delete (KEYBOX_HANDLE hd);
int keybox_compress (KEYBOX_HANDLE hd);
/*-- --*/
#if 0
int keybox_locate_writable (KEYBOX_HANDLE hd);
int keybox_rebuild_cache (void *);
#endif
/*-- keybox-util.c --*/
gpg_error_t keybox_tmp_names (const char *filename, int for_keyring,
char **r_bakname, char **r_tmpname);
#ifdef __cplusplus
}
#endif
#endif /*KEYBOX_H*/
diff --git a/m4/iconv.m4 b/m4/iconv.m4
index 66bc76f48..a285e9daa 100644
--- a/m4/iconv.m4
+++ b/m4/iconv.m4
@@ -1,180 +1,288 @@
-# iconv.m4 serial AM6 (gettext-0.17)
-dnl Copyright (C) 2000-2002, 2007 Free Software Foundation, Inc.
+# iconv.m4 serial 21
+dnl Copyright (C) 2000-2002, 2007-2014, 2016-2019 Free Software Foundation,
+dnl Inc.
dnl This file is free software; the Free Software Foundation
dnl gives unlimited permission to copy and/or distribute it,
dnl with or without modifications, as long as this notice is preserved.
dnl From Bruno Haible.
AC_DEFUN([AM_ICONV_LINKFLAGS_BODY],
[
dnl Prerequisites of AC_LIB_LINKFLAGS_BODY.
AC_REQUIRE([AC_LIB_PREPARE_PREFIX])
AC_REQUIRE([AC_LIB_RPATH])
dnl Search for libiconv and define LIBICONV, LTLIBICONV and INCICONV
dnl accordingly.
AC_LIB_LINKFLAGS_BODY([iconv])
])
AC_DEFUN([AM_ICONV_LINK],
[
dnl Some systems have iconv in libc, some have it in libiconv (OSF/1 and
dnl those with the standalone portable GNU libiconv installed).
AC_REQUIRE([AC_CANONICAL_HOST]) dnl for cross-compiles
dnl Search for libiconv and define LIBICONV, LTLIBICONV and INCICONV
dnl accordingly.
AC_REQUIRE([AM_ICONV_LINKFLAGS_BODY])
dnl Add $INCICONV to CPPFLAGS before performing the following checks,
dnl because if the user has installed libiconv and not disabled its use
dnl via --without-libiconv-prefix, he wants to use it. The first
- dnl AC_TRY_LINK will then fail, the second AC_TRY_LINK will succeed.
+ dnl AC_LINK_IFELSE will then fail, the second AC_LINK_IFELSE will succeed.
am_save_CPPFLAGS="$CPPFLAGS"
AC_LIB_APPENDTOVAR([CPPFLAGS], [$INCICONV])
- AC_CACHE_CHECK([for iconv], am_cv_func_iconv, [
+ AC_CACHE_CHECK([for iconv], [am_cv_func_iconv], [
am_cv_func_iconv="no, consider installing GNU libiconv"
am_cv_lib_iconv=no
- AC_TRY_LINK([#include <stdlib.h>
-#include <iconv.h>],
- [iconv_t cd = iconv_open("","");
- iconv(cd,NULL,NULL,NULL,NULL);
- iconv_close(cd);],
- am_cv_func_iconv=yes)
+ AC_LINK_IFELSE(
+ [AC_LANG_PROGRAM(
+ [[
+#include <stdlib.h>
+#include <iconv.h>
+ ]],
+ [[iconv_t cd = iconv_open("","");
+ iconv(cd,NULL,NULL,NULL,NULL);
+ iconv_close(cd);]])],
+ [am_cv_func_iconv=yes])
if test "$am_cv_func_iconv" != yes; then
am_save_LIBS="$LIBS"
LIBS="$LIBS $LIBICONV"
- AC_TRY_LINK([#include <stdlib.h>
-#include <iconv.h>],
- [iconv_t cd = iconv_open("","");
- iconv(cd,NULL,NULL,NULL,NULL);
- iconv_close(cd);],
- am_cv_lib_iconv=yes
- am_cv_func_iconv=yes)
+ AC_LINK_IFELSE(
+ [AC_LANG_PROGRAM(
+ [[
+#include <stdlib.h>
+#include <iconv.h>
+ ]],
+ [[iconv_t cd = iconv_open("","");
+ iconv(cd,NULL,NULL,NULL,NULL);
+ iconv_close(cd);]])],
+ [am_cv_lib_iconv=yes]
+ [am_cv_func_iconv=yes])
LIBS="$am_save_LIBS"
fi
])
if test "$am_cv_func_iconv" = yes; then
- AC_CACHE_CHECK([for working iconv], am_cv_func_iconv_works, [
- dnl This tests against bugs in AIX 5.1 and HP-UX 11.11.
+ AC_CACHE_CHECK([for working iconv], [am_cv_func_iconv_works], [
+ dnl This tests against bugs in AIX 5.1, AIX 6.1..7.1, HP-UX 11.11,
+ dnl Solaris 10.
am_save_LIBS="$LIBS"
if test $am_cv_lib_iconv = yes; then
LIBS="$LIBS $LIBICONV"
fi
- AC_TRY_RUN([
+ am_cv_func_iconv_works=no
+ for ac_iconv_const in '' 'const'; do
+ AC_RUN_IFELSE(
+ [AC_LANG_PROGRAM(
+ [[
#include <iconv.h>
#include <string.h>
-int main ()
-{
+
+#ifndef ICONV_CONST
+# define ICONV_CONST $ac_iconv_const
+#endif
+ ]],
+ [[int result = 0;
/* Test against AIX 5.1 bug: Failures are not distinguishable from successful
returns. */
{
iconv_t cd_utf8_to_88591 = iconv_open ("ISO8859-1", "UTF-8");
if (cd_utf8_to_88591 != (iconv_t)(-1))
{
- static const char input[] = "\342\202\254"; /* EURO SIGN */
+ static ICONV_CONST char input[] = "\342\202\254"; /* EURO SIGN */
char buf[10];
- const char *inptr = input;
+ ICONV_CONST char *inptr = input;
size_t inbytesleft = strlen (input);
char *outptr = buf;
size_t outbytesleft = sizeof (buf);
size_t res = iconv (cd_utf8_to_88591,
- (char **) &inptr, &inbytesleft,
+ &inptr, &inbytesleft,
+ &outptr, &outbytesleft);
+ if (res == 0)
+ result |= 1;
+ iconv_close (cd_utf8_to_88591);
+ }
+ }
+ /* Test against Solaris 10 bug: Failures are not distinguishable from
+ successful returns. */
+ {
+ iconv_t cd_ascii_to_88591 = iconv_open ("ISO8859-1", "646");
+ if (cd_ascii_to_88591 != (iconv_t)(-1))
+ {
+ static ICONV_CONST char input[] = "\263";
+ char buf[10];
+ ICONV_CONST char *inptr = input;
+ size_t inbytesleft = strlen (input);
+ char *outptr = buf;
+ size_t outbytesleft = sizeof (buf);
+ size_t res = iconv (cd_ascii_to_88591,
+ &inptr, &inbytesleft,
&outptr, &outbytesleft);
if (res == 0)
- return 1;
+ result |= 2;
+ iconv_close (cd_ascii_to_88591);
+ }
+ }
+ /* Test against AIX 6.1..7.1 bug: Buffer overrun. */
+ {
+ iconv_t cd_88591_to_utf8 = iconv_open ("UTF-8", "ISO-8859-1");
+ if (cd_88591_to_utf8 != (iconv_t)(-1))
+ {
+ static ICONV_CONST char input[] = "\304";
+ static char buf[2] = { (char)0xDE, (char)0xAD };
+ ICONV_CONST char *inptr = input;
+ size_t inbytesleft = 1;
+ char *outptr = buf;
+ size_t outbytesleft = 1;
+ size_t res = iconv (cd_88591_to_utf8,
+ &inptr, &inbytesleft,
+ &outptr, &outbytesleft);
+ if (res != (size_t)(-1) || outptr - buf > 1 || buf[1] != (char)0xAD)
+ result |= 4;
+ iconv_close (cd_88591_to_utf8);
}
}
#if 0 /* This bug could be worked around by the caller. */
/* Test against HP-UX 11.11 bug: Positive return value instead of 0. */
{
iconv_t cd_88591_to_utf8 = iconv_open ("utf8", "iso88591");
if (cd_88591_to_utf8 != (iconv_t)(-1))
{
- static const char input[] = "\304rger mit b\366sen B\374bchen ohne Augenma\337";
+ static ICONV_CONST char input[] = "\304rger mit b\366sen B\374bchen ohne Augenma\337";
char buf[50];
- const char *inptr = input;
+ ICONV_CONST char *inptr = input;
size_t inbytesleft = strlen (input);
char *outptr = buf;
size_t outbytesleft = sizeof (buf);
size_t res = iconv (cd_88591_to_utf8,
- (char **) &inptr, &inbytesleft,
+ &inptr, &inbytesleft,
&outptr, &outbytesleft);
if ((int)res > 0)
- return 1;
+ result |= 8;
+ iconv_close (cd_88591_to_utf8);
}
}
#endif
/* Test against HP-UX 11.11 bug: No converter from EUC-JP to UTF-8 is
provided. */
- if (/* Try standardized names. */
- iconv_open ("UTF-8", "EUC-JP") == (iconv_t)(-1)
- /* Try IRIX, OSF/1 names. */
- && iconv_open ("UTF-8", "eucJP") == (iconv_t)(-1)
- /* Try AIX names. */
- && iconv_open ("UTF-8", "IBM-eucJP") == (iconv_t)(-1)
- /* Try HP-UX names. */
- && iconv_open ("utf8", "eucJP") == (iconv_t)(-1))
- return 1;
- return 0;
-}], [am_cv_func_iconv_works=yes], [am_cv_func_iconv_works=no],
- [case "$host_os" in
- aix* | hpux*) am_cv_func_iconv_works="guessing no" ;;
- *) am_cv_func_iconv_works="guessing yes" ;;
- esac])
+ {
+ /* Try standardized names. */
+ iconv_t cd1 = iconv_open ("UTF-8", "EUC-JP");
+ /* Try IRIX, OSF/1 names. */
+ iconv_t cd2 = iconv_open ("UTF-8", "eucJP");
+ /* Try AIX names. */
+ iconv_t cd3 = iconv_open ("UTF-8", "IBM-eucJP");
+ /* Try HP-UX names. */
+ iconv_t cd4 = iconv_open ("utf8", "eucJP");
+ if (cd1 == (iconv_t)(-1) && cd2 == (iconv_t)(-1)
+ && cd3 == (iconv_t)(-1) && cd4 == (iconv_t)(-1))
+ result |= 16;
+ if (cd1 != (iconv_t)(-1))
+ iconv_close (cd1);
+ if (cd2 != (iconv_t)(-1))
+ iconv_close (cd2);
+ if (cd3 != (iconv_t)(-1))
+ iconv_close (cd3);
+ if (cd4 != (iconv_t)(-1))
+ iconv_close (cd4);
+ }
+ return result;
+]])],
+ [am_cv_func_iconv_works=yes], ,
+ [case "$host_os" in
+ aix* | hpux*) am_cv_func_iconv_works="guessing no" ;;
+ *) am_cv_func_iconv_works="guessing yes" ;;
+ esac])
+ test "$am_cv_func_iconv_works" = no || break
+ done
LIBS="$am_save_LIBS"
])
case "$am_cv_func_iconv_works" in
*no) am_func_iconv=no am_cv_lib_iconv=no ;;
*) am_func_iconv=yes ;;
esac
else
am_func_iconv=no am_cv_lib_iconv=no
fi
if test "$am_func_iconv" = yes; then
- AC_DEFINE(HAVE_ICONV, 1,
+ AC_DEFINE([HAVE_ICONV], [1],
[Define if you have the iconv() function and it works.])
fi
if test "$am_cv_lib_iconv" = yes; then
AC_MSG_CHECKING([how to link with libiconv])
AC_MSG_RESULT([$LIBICONV])
else
dnl If $LIBICONV didn't lead to a usable library, we don't need $INCICONV
dnl either.
CPPFLAGS="$am_save_CPPFLAGS"
LIBICONV=
LTLIBICONV=
fi
- AC_SUBST(LIBICONV)
- AC_SUBST(LTLIBICONV)
+ AC_SUBST([LIBICONV])
+ AC_SUBST([LTLIBICONV])
])
-AC_DEFUN([AM_ICONV],
+dnl Define AM_ICONV using AC_DEFUN_ONCE for Autoconf >= 2.64, in order to
+dnl avoid warnings like
+dnl "warning: AC_REQUIRE: `AM_ICONV' was expanded before it was required".
+dnl This is tricky because of the way 'aclocal' is implemented:
+dnl - It requires defining an auxiliary macro whose name ends in AC_DEFUN.
+dnl Otherwise aclocal's initial scan pass would miss the macro definition.
+dnl - It requires a line break inside the AC_DEFUN_ONCE and AC_DEFUN expansions.
+dnl Otherwise aclocal would emit many "Use of uninitialized value $1"
+dnl warnings.
+m4_define([gl_iconv_AC_DEFUN],
+ m4_version_prereq([2.64],
+ [[AC_DEFUN_ONCE(
+ [$1], [$2])]],
+ [m4_ifdef([gl_00GNULIB],
+ [[AC_DEFUN_ONCE(
+ [$1], [$2])]],
+ [[AC_DEFUN(
+ [$1], [$2])]])]))
+gl_iconv_AC_DEFUN([AM_ICONV],
[
AM_ICONV_LINK
if test "$am_cv_func_iconv" = yes; then
AC_MSG_CHECKING([for iconv declaration])
- AC_CACHE_VAL(am_cv_proto_iconv, [
- AC_TRY_COMPILE([
+ AC_CACHE_VAL([am_cv_proto_iconv], [
+ AC_COMPILE_IFELSE(
+ [AC_LANG_PROGRAM(
+ [[
#include <stdlib.h>
#include <iconv.h>
extern
#ifdef __cplusplus
"C"
#endif
-#if defined(__STDC__) || defined(__cplusplus)
+#if defined(__STDC__) || defined(_MSC_VER) || defined(__cplusplus)
size_t iconv (iconv_t cd, char * *inbuf, size_t *inbytesleft, char * *outbuf, size_t *outbytesleft);
#else
size_t iconv();
#endif
-], [], am_cv_proto_iconv_arg1="", am_cv_proto_iconv_arg1="const")
+ ]],
+ [[]])],
+ [am_cv_proto_iconv_arg1=""],
+ [am_cv_proto_iconv_arg1="const"])
am_cv_proto_iconv="extern size_t iconv (iconv_t cd, $am_cv_proto_iconv_arg1 char * *inbuf, size_t *inbytesleft, char * *outbuf, size_t *outbytesleft);"])
am_cv_proto_iconv=`echo "[$]am_cv_proto_iconv" | tr -s ' ' | sed -e 's/( /(/'`
- AC_MSG_RESULT([$]{ac_t:-
- }[$]am_cv_proto_iconv)
- AC_DEFINE_UNQUOTED(ICONV_CONST, $am_cv_proto_iconv_arg1,
- [Define as const if the declaration of iconv() needs const.])
+ AC_MSG_RESULT([
+ $am_cv_proto_iconv])
+ else
+ dnl When compiling GNU libiconv on a system that does not have iconv yet,
+ dnl pick the POSIX compliant declaration without 'const'.
+ am_cv_proto_iconv_arg1=""
fi
+ AC_DEFINE_UNQUOTED([ICONV_CONST], [$am_cv_proto_iconv_arg1],
+ [Define as const if the declaration of iconv() needs const.])
+ dnl Also substitute ICONV_CONST in the gnulib generated <iconv.h>.
+ m4_ifdef([gl_ICONV_H_DEFAULTS],
+ [AC_REQUIRE([gl_ICONV_H_DEFAULTS])
+ if test -n "$am_cv_proto_iconv_arg1"; then
+ ICONV_CONST="const"
+ fi
+ ])
])
diff --git a/po/ja.po b/po/ja.po
index d2b4c7e8c..ccca194bb 100644
--- a/po/ja.po
+++ b/po/ja.po
@@ -1,8727 +1,8862 @@
# Japanese messages for GnuPG
# Copyright (C) 1999, 2000, 2002, 2003, 2004, 2013 Free Software Foundation, Inc.
# This file is distributed under the same license as the GnuPG package.
# IIDA Yosiaki <iida@gnu.org>, 1999, 2000, 2002, 2003, 2004.
# Yoshihiro Kajiki <kajiki@ylug.org>, 1999.
# Takashi P.KATOH, 2002.
-# NIIBE Yutaka <gniibe@fsij.org>, 2013, 2014, 2015, 2016, 2017, 2018.
+# NIIBE Yutaka <gniibe@fsij.org>, 2013, 2014, 2015, 2016, 2017, 2018, 2019.
#
msgid ""
msgstr ""
-"Project-Id-Version: gnupg 2.2.6\n"
+"Project-Id-Version: gnupg 2.2.16\n"
"Report-Msgid-Bugs-To: translations@gnupg.org\n"
-"PO-Revision-Date: 2018-04-12 10:51+0900\n"
+"PO-Revision-Date: 2019-06-27 08:29+0900\n"
"Last-Translator: NIIBE Yutaka <gniibe@fsij.org>\n"
"Language-Team: none\n"
"Language: ja\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
#, c-format
msgid "failed to acquire the pinentry lock: %s\n"
msgstr "pinentryのロックの獲得に失敗しました: %s\n"
-#. TRANSLATORS: These are labels for buttons etc used in
-#. Pinentries. An underscore indicates that the next letter
-#. should be used as an accelerator. Double the underscore for
-#. a literal one. The actual to be translated text starts after
-#. the second vertical bar. Note that gpg-agent has been set to
-#. utf-8 so that the strings are in the expected encoding.
+#. TRANSLATORS: These are labels for buttons etc as used in
+#. * Pinentries. In your translation copy the text before the
+#. * second vertical bar verbatim; translate only the following
+#. * text. An underscore indicates that the next letter should be
+#. * used as an accelerator. Double the underscore to have
+#. * pinentry display a literal underscore.
msgid "|pinentry-label|_OK"
msgstr "|pinentry-label|_OK"
msgid "|pinentry-label|_Cancel"
msgstr "|pinentry-label|キャンセル(_C)"
msgid "|pinentry-label|_Yes"
msgstr "|pinentry-label|_Yes"
msgid "|pinentry-label|_No"
msgstr "|pinentry-label|_No"
msgid "|pinentry-label|PIN:"
msgstr "|pinentry-label|PIN:"
msgid "|pinentry-label|_Save in password manager"
msgstr "|pinentry-label|パスワードマネージャに保管(_S)"
msgid "Do you really want to make your passphrase visible on the screen?"
msgstr "本当に画面にパスフレーズを見えるようにしますか?"
msgid "|pinentry-tt|Make passphrase visible"
msgstr "|pinentry-tt|パスフレーズを見えるようにする"
msgid "|pinentry-tt|Hide passphrase"
msgstr "|pinentry-tt|パスフレーズを隠す"
#. TRANSLATORS: This string is displayed by Pinentry as the label
#. for the quality bar.
msgid "Quality:"
msgstr "品質:"
#. TRANSLATORS: This string is a tooltip, shown by pinentry when
#. hovering over the quality bar. Please use an appropriate
#. string to describe what this is about. The length of the
#. tooltip is limited to about 900 characters. If you do not
#. translate this entry, a default english text (see source)
#. will be used.
msgid "pinentry.qualitybar.tooltip"
msgstr "pinentry.qualitybar.tooltip"
msgid ""
"Please enter your PIN, so that the secret key can be unlocked for this "
"session"
msgstr ""
"あなたのPINを入力してください(このセッションで秘密鍵のロックを解除するために"
"使われます)"
msgid ""
"Please enter your passphrase, so that the secret key can be unlocked for "
"this session"
msgstr ""
"あなたのパスフレーズを入力してください(このセッションで秘密鍵のロックを解除す"
"るために使われます)"
msgid "PIN:"
msgstr "PIN:"
msgid "Passphrase:"
msgstr "パスフレーズ:"
msgid "does not match - try again"
msgstr "一致しません - もう一度"
#. TRANSLATORS: The string is appended to an error message in
#. the pinentry. The %s is the actual error message, the
#. two %d give the current and maximum number of tries.
#, c-format
msgid "SETERROR %s (try %d of %d)"
msgstr "SETERROR %s (現在 %d / 最大 %d)"
msgid "Repeat:"
msgstr "繰り返し:"
msgid "PIN too long"
msgstr "PINが長すぎます"
msgid "Passphrase too long"
msgstr "パスフレーズが長すぎます"
msgid "Invalid characters in PIN"
msgstr "PINに無効な文字があります"
msgid "PIN too short"
msgstr "PINが短すぎます"
msgid "Bad PIN"
msgstr "不正なPINです"
msgid "Bad Passphrase"
msgstr "パスフレーズが不正です"
#, c-format
msgid "ssh keys greater than %d bits are not supported\n"
msgstr "ssh鍵で%dビットより大きいものはサポートされません\n"
#, c-format
msgid "can't create '%s': %s\n"
msgstr "'%s'が作成できません: %s\n"
#, c-format
msgid "can't open '%s': %s\n"
msgstr "'%s'が開けません: %s\n"
#, c-format
msgid "error getting serial number of card: %s\n"
msgstr "カード・シリアル番号の取得エラー: %s\n"
#, c-format
msgid "detected card with S/N: %s\n"
msgstr "カードを検出しました。シリアル番号: %s\n"
#, c-format
msgid "no authentication key for ssh on card: %s\n"
msgstr "カードにsshの認証鍵がありません: %s\n"
#, c-format
msgid "no suitable card key found: %s\n"
msgstr "適当なカードの鍵が見つかりません: %s\n"
#, c-format
msgid "error getting list of cards: %s\n"
msgstr "カードのリスト の取得エラー: %s\n"
#, c-format
msgid ""
"An ssh process requested the use of key%%0A %s%%0A (%s)%%0ADo you want to "
"allow this?"
msgstr ""
"sshプロセスが以下の鍵の使用を要求しました:%%0A %s%%0A (%s)%%0Aこの使用を認"
"めますか?"
msgid "Allow"
msgstr "許可する"
msgid "Deny"
msgstr "拒否する"
#, c-format
msgid "Please enter the passphrase for the ssh key%%0A %F%%0A (%c)"
msgstr "以下のssh鍵に対するパスフレーズを入力してください:%%0A %F%%0A (%c)"
msgid "Please re-enter this passphrase"
msgstr "このパスフレーズをもう一度入力してください"
#, c-format
msgid ""
"Please enter a passphrase to protect the received secret key%%0A %s%%0A "
"%s%%0Awithin gpg-agent's key storage"
msgstr ""
"パスフレーズを入力してください。gpg-agentの鍵の保管で受信した秘密鍵%%0A %s"
"%%0A %s%%0Aを保護します。"
#, c-format
msgid "failed to create stream from socket: %s\n"
msgstr "ソケットからストリームを作成するのに失敗しました: %s\n"
msgid "Please insert the card with serial number"
msgstr "以下のシリアル番号のカードを挿入してください"
msgid "Please remove the current card and insert the one with serial number"
msgstr "今のカードを抜き、以下のシリアル番号のカードを挿入してください"
msgid "Admin PIN"
msgstr "管理者PIN"
#. TRANSLATORS: A PUK is the Personal Unblocking Code
#. used to unblock a PIN.
msgid "PUK"
msgstr "PUK"
msgid "Reset Code"
msgstr "リセット・コード"
-#, c-format
-msgid "%s%%0A%%0AUse the reader's pinpad for input."
-msgstr "%s%%0A%%0Aリーダーのピンパッドを入力に使ってください。"
+msgid "Push ACK button on card/token."
+msgstr "カード/トークンのACKボタンを押してください。"
+
+msgid "Use the reader's pinpad for input."
+msgstr "リーダーのピンパッドを入力に使ってください。"
msgid "Repeat this Reset Code"
msgstr "このリセット・コードをもう一度入力してください"
msgid "Repeat this PUK"
msgstr "このPUKをもう一度入力してください"
msgid "Repeat this PIN"
msgstr "このPINをもう一度入力してください"
msgid "Reset Code not correctly repeated; try again"
msgstr "リセット・コードが正しく繰り返されていません。もう一度"
msgid "PUK not correctly repeated; try again"
msgstr "PUKが正しく繰り返されていません。もう一度"
msgid "PIN not correctly repeated; try again"
msgstr "PINが正しく繰り返されていません。もう一度"
#, c-format
msgid "Please enter the PIN%s%s%s to unlock the card"
msgstr "カードのロックを解除するためにPIN%s%s%sを入力してください"
#, c-format
msgid "error creating temporary file: %s\n"
msgstr "一時ファイルの作成エラー: %s\n"
#, c-format
msgid "error writing to temporary file: %s\n"
msgstr "一時ファイルの書き込みエラー: %s\n"
msgid "Enter new passphrase"
msgstr "新しいパスフレーズを入力してください"
-msgid "Take this one anyway"
-msgstr "それでもこれを使います"
-
#, c-format
msgid ""
"You have not entered a passphrase!%0AAn empty passphrase is not allowed."
msgstr ""
"パスフレーズが入力されませんでした!%0A空のパスフレーズは認められません。"
#, c-format
msgid ""
"You have not entered a passphrase - this is in general a bad idea!%0APlease "
"confirm that you do not want to have any protection on your key."
msgstr ""
"パスフレーズが入力されませんでした - 通常これは良くない考えです!%0A鍵に何の保"
"護も必要としないことを確認ください。"
msgid "Yes, protection is not needed"
msgstr "はい、保護は必要ありません"
#, c-format
msgid "A passphrase should be at least %u character long."
msgid_plural "A passphrase should be at least %u characters long."
msgstr[0] "パスフレーズは最低でも%u文字以上でなければなりません。"
#, c-format
msgid "A passphrase should contain at least %u digit or%%0Aspecial character."
msgid_plural ""
"A passphrase should contain at least %u digits or%%0Aspecial characters."
msgstr[0] "パスフレーズは最低でも%u文字の数字か特殊文字を含むべきです。"
#, c-format
msgid "A passphrase may not be a known term or match%%0Acertain pattern."
msgstr ""
"パスフレーズには、よく知られている用語や特定のパターンにマッチするものは%%0A"
"避けましょう。"
msgid "Warning: You have entered an insecure passphrase."
msgstr "警告: 安全とは言えないパスフレーズが入力されました。"
+msgid "Take this one anyway"
+msgstr "それでもこれを使います"
+
#, c-format
msgid "Please enter the passphrase to%0Aprotect your new key"
msgstr "新しい鍵を保護するために、%0Aパスフレーズを入力してください。"
msgid "Please enter the new passphrase"
msgstr "新しいパスフレーズを入力してください"
msgid ""
"@Options:\n"
" "
msgstr ""
"@オプション:\n"
" "
msgid "run in daemon mode (background)"
msgstr "デーモン・モードで実行 (バックグラウンド)"
msgid "run in server mode (foreground)"
msgstr "サーバ・モードで実行 (フォアグラウンド)"
msgid "run in supervised mode"
msgstr "スーパーバイズド・モードで実行"
msgid "verbose"
msgstr "冗長"
msgid "be somewhat more quiet"
msgstr "いくらかおとなしく"
msgid "sh-style command output"
msgstr "sh-形式のコマンド出力"
msgid "csh-style command output"
msgstr "csh-形式のコマンド出力"
msgid "|FILE|read options from FILE"
msgstr "|FILE|FILEからオプションを読み込みます"
msgid "do not detach from the console"
msgstr "コンソールからデタッチしない"
msgid "use a log file for the server"
msgstr "サーバのログ・ファイルを使う"
msgid "|PGM|use PGM as the PIN-Entry program"
msgstr "|PGM|PGMをPIN入力プログラムとして使う"
msgid "|PGM|use PGM as the SCdaemon program"
msgstr "|PGM|PGMをSCdaemonプログラムとして使う"
msgid "do not use the SCdaemon"
msgstr "SCdaemonを使わない"
msgid "|NAME|accept some commands via NAME"
msgstr "|NAME|NAMEからのコマンドを受け付ける"
msgid "ignore requests to change the TTY"
msgstr "TTYの変更要求を無視する"
msgid "ignore requests to change the X display"
msgstr "Xディスプレイの変更要求を無視する"
msgid "|N|expire cached PINs after N seconds"
msgstr "|N|N秒後に保持したPINを無効とする"
msgid "do not use the PIN cache when signing"
msgstr "署名に対してPINの保持を使わない"
msgid "disallow the use of an external password cache"
msgstr "外部のパスワードキャッシュの使用を認めない"
msgid "disallow clients to mark keys as \"trusted\""
msgstr "クライアントが鍵に\"trusted\"マークをつけることを認めない"
msgid "allow presetting passphrase"
msgstr "パスフレーズの事前設定を認める"
msgid "disallow caller to override the pinentry"
msgstr "pinentryより優先してパスフレーズ入力を認めない"
msgid "allow passphrase to be prompted through Emacs"
msgstr "Emacsを通じてパスフレーズを催促することを認める"
msgid "enable ssh support"
msgstr "sshサポートを有功にする"
msgid "|ALGO|use ALGO to show ssh fingerprints"
msgstr "|ALGO|ssh署名の表示にALGOを使う"
msgid "enable putty support"
msgstr "puttyサポートを有功にする"
#. TRANSLATORS: @EMAIL@ will get replaced by the actual bug
#. reporting address. This is so that we can change the
#. reporting address without breaking the translations.
msgid "Please report bugs to <@EMAIL@>.\n"
msgstr "バグは <@EMAIL@> までご報告ください。\n"
msgid "Usage: @GPG_AGENT@ [options] (-h for help)"
msgstr "使い方: @GPG_AGENT@ [オプション] (ヘルプは -h)"
msgid ""
"Syntax: @GPG_AGENT@ [options] [command [args]]\n"
"Secret key management for @GNUPG@\n"
msgstr ""
"形式: @GPG_AGENT@ [オプション] [コマンド [引数]]\n"
"@GnuPG@の秘密鍵の管理\n"
#, c-format
msgid "invalid debug-level '%s' given\n"
msgstr "無効なdebug-level '%s'が与えられました\n"
msgid "selected digest algorithm is invalid\n"
msgstr "選択されたダイジェスト・アルゴリズムは、無効です\n"
#, c-format
msgid "Note: no default option file '%s'\n"
msgstr "注意: デフォルトのオプション・ファイル '%s' がありません\n"
#, c-format
msgid "option file '%s': %s\n"
msgstr "オプション・ファイル '%s': %s\n"
#, c-format
msgid "reading options from '%s'\n"
msgstr "'%s' からオプションを読み込みます\n"
#, c-format
msgid "Note: '%s' is not considered an option\n"
msgstr "注意: '%s'はオプションとは考えられません\n"
#, c-format
msgid "can't create socket: %s\n"
msgstr "ソケットが作成できません: %s\n"
#, c-format
msgid "socket name '%s' is too long\n"
msgstr "ソケット名'%s'は長すぎます\n"
msgid "a gpg-agent is already running - not starting a new one\n"
msgstr "gpg-agentは既に実行されています - 新しいものをスタートさせません\n"
msgid "error getting nonce for the socket\n"
msgstr "ソケットのナンス取得エラー\n"
#, c-format
msgid "error binding socket to '%s': %s\n"
msgstr "'%s'でソケットのバインドのエラー: %s\n"
#, c-format
msgid "can't set permissions of '%s': %s\n"
msgstr "'%s'の許可が設定できません: %s\n"
#, c-format
msgid "listening on socket '%s'\n"
msgstr "ソケット'%s'でlisten\n"
#, c-format
msgid "can't create directory '%s': %s\n"
msgstr "ディレクトリ'%s'が作成できません: %s\n"
#, c-format
msgid "directory '%s' created\n"
msgstr "ディレクトリ'%s'が作成されました\n"
#, c-format
msgid "stat() failed for '%s': %s\n"
msgstr "'%s'でstat()が失敗しました: %s\n"
#, c-format
msgid "can't use '%s' as home directory\n"
msgstr "'%s'をホーム・ディレクトリに使えません\n"
#, c-format
msgid "error reading nonce on fd %d: %s\n"
msgstr "fd %dでナンスの読み込みエラー: %s\n"
#, c-format
msgid "handler 0x%lx for fd %d started\n"
msgstr "ハンドラ0x%lx (fd %d に対する)が開始\n"
#, c-format
msgid "handler 0x%lx for fd %d terminated\n"
msgstr "ハンドラ0x%lx (fd %d に対する)が終了\n"
#, c-format
msgid "ssh handler 0x%lx for fd %d started\n"
msgstr "ssh ハンドラ0x%lx (fd %d に対する)が開始\n"
#, c-format
msgid "ssh handler 0x%lx for fd %d terminated\n"
msgstr "ssh ハンドラ0x%lx (fd %d に対する)が終了\n"
#, c-format
msgid "npth_pselect failed: %s - waiting 1s\n"
msgstr "npth_pselectに失敗しました: %s - 一秒待ちます\n"
#, c-format
msgid "%s %s stopped\n"
msgstr "%s %s 停止しました\n"
msgid "no gpg-agent running in this session\n"
msgstr "このセッションでgpg-agentは実行されていません\n"
msgid "Usage: gpg-preset-passphrase [options] KEYGRIP (-h for help)\n"
msgstr "使い方: gpg-preset-passphrase [オプション] KEYGRIP (ヘルプは -h)\n"
msgid ""
"Syntax: gpg-preset-passphrase [options] KEYGRIP\n"
"Password cache maintenance\n"
msgstr ""
"形式: gpg-preset-passphrase [オプション] KEYGRIP\n"
"パスワードキャッシュの管理\n"
msgid ""
"@Commands:\n"
" "
msgstr ""
"@コマンド:\n"
" "
msgid ""
"@\n"
"Options:\n"
" "
msgstr ""
"@\n"
"オプション:\n"
" "
msgid "Usage: gpg-protect-tool [options] (-h for help)\n"
msgstr "使い方: gpg-protect-tool [オプション] (ヘルプは -h)\n"
msgid ""
"Syntax: gpg-protect-tool [options] [args]\n"
"Secret key maintenance tool\n"
msgstr ""
"形式: gpg-protect-tool [オプション] [引数]\n"
"秘密鍵管理ツール\n"
msgid "Please enter the passphrase to unprotect the PKCS#12 object."
msgstr "パスフレーズを入力してください。PKCS#12オブジェクトを解除します。"
msgid "Please enter the passphrase to protect the new PKCS#12 object."
msgstr ""
"パスフレーズを入力してください。新しいPKCS#12オブジェクトを解除します。"
msgid ""
"Please enter the passphrase to protect the imported object within the GnuPG "
"system."
msgstr ""
"GnuPGシステムにインポートされたオブジェクトを保護するためにパスフレーズを入力"
"してください"
msgid ""
"Please enter the passphrase or the PIN\n"
"needed to complete this operation."
msgstr ""
"パスフレーズまたはPINを入力してください。\n"
"この操作を完了するのに必要です。"
msgid "cancelled\n"
msgstr "キャンセルされました\n"
#, c-format
msgid "error while asking for the passphrase: %s\n"
msgstr "パスフレーズを問い合わせする際、エラー: %s\n"
#, c-format
msgid "error opening '%s': %s\n"
msgstr "'%s'を開く際、エラー: %s\n"
#, c-format
msgid "file '%s', line %d: %s\n"
msgstr "ファイル'%s'(行 %d): %s\n"
#, c-format
msgid "statement \"%s\" ignored in '%s', line %d\n"
msgstr "ステートメント \"%s\" は'%s'で無視されました(行 %d)\n"
#, c-format
msgid "system trustlist '%s' not available\n"
msgstr "システム信用リスト'%s'が得られません\n"
#, c-format
msgid "bad fingerprint in '%s', line %d\n"
msgstr "'%s'の不正なフィンガープリント (行 %d)\n"
#, c-format
msgid "invalid keyflag in '%s', line %d\n"
msgstr "'%s'の無効な鍵フラグ(行 %d)\n"
#, c-format
msgid "error reading '%s', line %d: %s\n"
msgstr "'%s'の読み込みエラー(行 %d): %s\n"
msgid "error reading list of trusted root certificates\n"
-msgstr "信用されたルート証明書のリストの読み込みエラ−\n"
+msgstr "信用されたルート証明書のリストの読み込みエラー\n"
#. TRANSLATORS: This prompt is shown by the Pinentry
#. and has one special property: A "%%0A" is used by
#. Pinentry to insert a line break. The double
#. percent sign is actually needed because it is also
#. a printf format string. If you need to insert a
#. plain % sign, you need to encode it as "%%25". The
#. "%s" gets replaced by the name as stored in the
#. certificate.
#, c-format
msgid ""
"Do you ultimately trust%%0A \"%s\"%%0Ato correctly certify user "
"certificates?"
msgstr "究極的にこれを信用し%%0A \"%s\"%%0A正にユーザの証明書と保証しますか?"
msgid "Yes"
msgstr "はい"
msgid "No"
msgstr "いいえ"
#. TRANSLATORS: This prompt is shown by the Pinentry and has
#. one special property: A "%%0A" is used by Pinentry to
#. insert a line break. The double percent sign is actually
#. needed because it is also a printf format string. If you
#. need to insert a plain % sign, you need to encode it as
#. "%%25". The second "%s" gets replaced by a hexdecimal
#. fingerprint string whereas the first one receives the name
#. as stored in the certificate.
#, c-format
msgid ""
"Please verify that the certificate identified as:%%0A \"%s\"%%0Ahas the "
"fingerprint:%%0A %s"
msgstr ""
"確認してください。証明書:%%0A \"%s\"%%0Aは下記のフィンガープリントを持つ:"
"%%0A %s"
#. TRANSLATORS: "Correct" is the label of a button and intended
#. to be hit if the fingerprint matches the one of the CA. The
#. other button is "the default "Cancel" of the Pinentry.
msgid "Correct"
msgstr "正しい"
msgid "Wrong"
msgstr "誤り"
#, c-format
msgid "Note: This passphrase has never been changed.%0APlease change it now."
msgstr "注意: パスフレーズは変更されていません。%0A今、変更してください。"
#, c-format
msgid ""
"This passphrase has not been changed%%0Asince %.4s-%.2s-%.2s. Please change "
"it now."
msgstr ""
"このパスフレーズは%.4s-%.2s-%.2sから変更されていません。%%0A今、変更してくだ"
"さい。"
msgid "Change passphrase"
msgstr "パスフレーズを変更する"
msgid "I'll change it later"
msgstr "後で変更する"
#, c-format
msgid ""
"Do you really want to delete the key identified by keygrip%%0A %s%%0A %%C"
"%%0A?"
msgstr "本当にこの鍵: keygrip%%0A %s%%0A %%C%%0Aを削除しますか?"
msgid "Delete key"
msgstr "鍵を削除する"
msgid ""
"Warning: This key is also listed for use with SSH!\n"
"Deleting the key might remove your ability to access remote machines."
msgstr ""
"警告: この鍵はSSHの使用にもリストされています!\n"
"この鍵を削除するとリモート・マシンのアクセスの能力を失うかもしれません。"
msgid "DSA requires the hash length to be a multiple of 8 bits\n"
msgstr "DSAは8ビットの倍数のハッシュ長を必要とします\n"
#, c-format
msgid "%s key uses an unsafe (%u bit) hash\n"
msgstr "%s 鍵は安全でない(%uビット)ハッシュを使用しています\n"
#, c-format
msgid "a %zu bit hash is not valid for a %u bit %s key\n"
msgstr "%zuビットのハッシュは%uビットの%s鍵には無効です\n"
#, c-format
msgid "checking created signature failed: %s\n"
msgstr "作成された署名の検査に失敗しました: %s\n"
msgid "secret key parts are not available\n"
-msgstr "秘密部分が得られません\n"
+msgstr "秘密鍵部分が利用できません\n"
#, c-format
msgid "public key algorithm %d (%s) is not supported\n"
msgstr "公開鍵アルゴリズム%d (%s)はサポートされていません\n"
#, c-format
msgid "protection algorithm %d (%s) is not supported\n"
msgstr "保護アルゴリズム%d (%s)はサポートされていません\n"
#, c-format
msgid "protection hash algorithm %d (%s) is not supported\n"
msgstr "保護ハッシュ・アルゴリズム%d (%s)はサポートされていません\n"
#, c-format
msgid "error creating a pipe: %s\n"
msgstr "パイプの作成エラー: %s\n"
#, c-format
msgid "error creating a stream for a pipe: %s\n"
msgstr "パイプのストリーム作成エラー: %s\n"
#, c-format
msgid "error forking process: %s\n"
msgstr "プロセスforkエラー: %s\n"
#, c-format
msgid "waiting for process %d to terminate failed: %s\n"
msgstr "プロセス%dの終了待ちが失敗: %s\n"
#, c-format
msgid "error running '%s': probably not installed\n"
msgstr "'%s'の実行エラー: おそらくインストールされていません\n"
#, c-format
msgid "error running '%s': exit status %d\n"
msgstr "'%s'の実行エラー: exitステイタス %d\n"
#, c-format
msgid "error running '%s': terminated\n"
msgstr "'%s'の実行エラー: 終了しました\n"
#, c-format
msgid "waiting for processes to terminate failed: %s\n"
msgstr "プロセスの終了待ちが失敗: %s\n"
#, c-format
msgid "error getting exit code of process %d: %s\n"
msgstr "プロセス %d のexitコード取得エラー: %s\n"
#, c-format
msgid "can't connect to '%s': %s\n"
msgstr "'%s'へ接続できません: %s\n"
msgid "problem setting the gpg-agent options\n"
-msgstr "gpg-agentオプションの設定の問題\n"
+msgstr "gpg-agentのオプション設定の問題\n"
#, c-format
msgid "can't disable core dumps: %s\n"
msgstr "コア・ダンプを無効にできません: %s\n"
#, c-format
msgid "Warning: unsafe ownership on %s \"%s\"\n"
msgstr "警告: '%s'の安全でない所有 \"%s\"\n"
#, c-format
msgid "Warning: unsafe permissions on %s \"%s\"\n"
msgstr "警告: '%s'の安全でない許可 \"%s\"\n"
#, c-format
msgid "waiting for file '%s' to become accessible ...\n"
msgstr "ファイル'%s'がアクセスできるのを待ちます...\n"
#, c-format
msgid "renaming '%s' to '%s' failed: %s\n"
msgstr "'%s'から'%s'へ名前変更に失敗: %s\n"
#. TRANSLATORS: See doc/TRANSLATE about this string.
msgid "yes"
msgstr "yes"
msgid "yY"
msgstr "yY"
#. TRANSLATORS: See doc/TRANSLATE about this string.
msgid "no"
msgstr "no"
msgid "nN"
msgstr "nN"
#. TRANSLATORS: See doc/TRANSLATE about this string.
msgid "quit"
msgstr "quit"
msgid "qQ"
msgstr "qQ"
#. TRANSLATORS: See doc/TRANSLATE about this string.
msgid "okay|okay"
msgstr "okay|okay"
#. TRANSLATORS: See doc/TRANSLATE about this string.
msgid "cancel|cancel"
msgstr "cancel|cancel"
msgid "oO"
msgstr "oO"
msgid "cC"
msgstr "cC"
#, c-format
msgid "out of core in secure memory while allocating %lu bytes"
msgstr "%luバイトの確保においてセキュア・メモリが足りません"
#, c-format
msgid "out of core while allocating %lu bytes"
msgstr "%luバイトの確保においてメモリが足りません"
#, c-format
msgid "error allocating enough memory: %s\n"
msgstr "十分なメモリの確保のエラー: %s\n"
#, c-format
msgid "%s:%u: obsolete option \"%s\" - it has no effect\n"
msgstr ""
"%s:%u: \"%s\"は、使われなくなったオプションです - なんの効果もありません\n"
#, c-format
msgid "WARNING: \"%s%s\" is an obsolete option - it has no effect\n"
msgstr ""
"*警告*: \"%s%s\"は、使われなくなったオプションです - なんの効果もありません\n"
#, c-format
msgid "unknown debug flag '%s' ignored\n"
msgstr "未知のdebugフラグ'%s'は無視されました\n"
#, c-format
-msgid "no running gpg-agent - starting '%s'\n"
-msgstr "gpg-agentが実行されていません - '%s'を開始します\n"
+msgid "waiting for the dirmngr to come up ... (%ds)\n"
+msgstr "dirmngrの起動のため、%d秒待ちます\n"
#, c-format
msgid "waiting for the agent to come up ... (%ds)\n"
msgstr "agentの起動のため、%d秒待ちます\n"
-msgid "connection to agent established\n"
+msgid "connection to the dirmngr established\n"
+msgstr "dirmngrへの接続が確立しました\n"
+
+msgid "connection to the agent established\n"
msgstr "エージェントへの接続が確立しました。\n"
-msgid "connection to agent is in restricted mode\n"
+#, c-format
+msgid "no running gpg-agent - starting '%s'\n"
+msgstr "gpg-agentが実行されていません - '%s'を開始します\n"
+
+msgid "connection to the agent is in restricted mode\n"
msgstr "エージェントへの接続は制限モードです。\n"
#, c-format
-msgid "no running Dirmngr - starting '%s'\n"
+msgid "no running dirmngr - starting '%s'\n"
msgstr "dirmngrが動いていません - 開始します'%s'\n"
-#, c-format
-msgid "waiting for the dirmngr to come up ... (%ds)\n"
-msgstr "dirmngrの起動のため、%d秒待ちます\n"
-
-msgid "connection to the dirmngr established\n"
-msgstr "dirmngrへの接続が確立しました\n"
-
#. TRANSLATORS: Copy the prefix between the vertical bars
#. verbatim. It will not be printed.
msgid "|audit-log-result|Good"
msgstr "|audit-log-result|良"
msgid "|audit-log-result|Bad"
msgstr "|audit-log-result|不良"
msgid "|audit-log-result|Not supported"
msgstr "|audit-log-result|サポートされてません"
msgid "|audit-log-result|No certificate"
msgstr "|audit-log-result|証明書がありません"
msgid "|audit-log-result|Not enabled"
msgstr "|audit-log-result|有効となってません"
msgid "|audit-log-result|Error"
msgstr "|audit-log-result|エラー"
msgid "|audit-log-result|Not used"
msgstr "|audit-log-result|使われていません"
msgid "|audit-log-result|Okay"
msgstr "|audit-log-result|Okay"
msgid "|audit-log-result|Skipped"
msgstr "|audit-log-result|スキップされました"
msgid "|audit-log-result|Some"
msgstr "|audit-log-result|一部"
msgid "Certificate chain available"
msgstr "証明書のチェインが利用可能"
msgid "root certificate missing"
msgstr "ルート証明書がありません"
msgid "Data encryption succeeded"
msgstr "データ暗号化に成功しました"
msgid "Data available"
msgstr "データが利用可能"
msgid "Session key created"
msgstr "セッション・キーが作成されました"
#, c-format
msgid "algorithm: %s"
msgstr "アルゴリズム: %s"
#, c-format
msgid "unsupported algorithm: %s"
msgstr "サポートされていないアルゴリズム: %s"
msgid "seems to be not encrypted"
msgstr "暗号化されていないようです"
msgid "Number of recipients"
msgstr "受取人の数"
#, c-format
msgid "Recipient %d"
msgstr "受取人 %d"
msgid "Data signing succeeded"
msgstr "データ署名に成功しました"
#, c-format
msgid "data hash algorithm: %s"
msgstr "データのハッシュ・アルゴリズム: %s"
#, c-format
msgid "Signer %d"
msgstr "署名人 %d"
#, c-format
msgid "attr hash algorithm: %s"
msgstr "属性のハッシュ・アルゴリズム: %s"
msgid "Data decryption succeeded"
msgstr "データ復号に成功しました"
msgid "Encryption algorithm supported"
msgstr "サポートされている暗号アルゴリズム"
msgid "Data verification succeeded"
msgstr "データ検証が成功しました"
msgid "Signature available"
msgstr "署名が利用可能です"
msgid "Parsing data succeeded"
msgstr "データの構文解析に成功しました"
#, c-format
msgid "bad data hash algorithm: %s"
msgstr "不正なデータのハッシュ・アルゴリズム: %s"
#, c-format
msgid "Signature %d"
msgstr "署名 %d"
msgid "Certificate chain valid"
msgstr "証明書のチェインは有効"
msgid "Root certificate trustworthy"
msgstr "信頼できるルート証明書"
msgid "no CRL found for certificate"
msgstr "証明書に対するCRLがありません"
msgid "the available CRL is too old"
msgstr "利用できるCRLは古すぎます"
msgid "CRL/OCSP check of certificates"
msgstr "証明書のCRL/OCSP確認"
msgid "Included certificates"
msgstr "含まれる証明書"
msgid "No audit log entries."
msgstr "監査ログのエントリはありません。"
msgid "Unknown operation"
msgstr "不明な操作"
msgid "Gpg-Agent usable"
msgstr "Gpg-Agent利用可能"
msgid "Dirmngr usable"
msgstr "Dirmngr利用可能"
#, c-format
msgid "No help available for '%s'."
msgstr "'%s'のヘルプはありません。"
msgid "ignoring garbage line"
msgstr "ガベージ行を無視します"
msgid "[none]"
msgstr "[未設定]"
#, c-format
msgid "invalid radix64 character %02x skipped\n"
msgstr "無効な64進文字%02Xをスキップしました\n"
msgid "argument not expected"
msgstr "引数は期待されていません"
msgid "read error"
msgstr "読み込みエラー"
msgid "keyword too long"
msgstr "キーワードが長すぎます"
msgid "missing argument"
msgstr "引数がありません"
msgid "invalid argument"
msgstr "無効な引数"
msgid "invalid command"
msgstr "無効なコマンド"
msgid "invalid alias definition"
msgstr "無効なエイリアス定義です"
msgid "out of core"
msgstr "メモリがありません"
msgid "invalid option"
msgstr "無効なオプション"
#, c-format
msgid "missing argument for option \"%.50s\"\n"
msgstr "オプション\"%.50s\"に引数がありません\n"
#, c-format
msgid "invalid argument for option \"%.50s\"\n"
msgstr "オプション\"%.50s\"には無効な引数です\n"
#, c-format
msgid "option \"%.50s\" does not expect an argument\n"
msgstr "オプション\"%.50s\"は引数をとりません\n"
#, c-format
msgid "invalid command \"%.50s\"\n"
msgstr "無効なコマンド \"%.50s\"\n"
#, c-format
msgid "option \"%.50s\" is ambiguous\n"
msgstr "オプション\"%.50s\"はあいまいです\n"
#, c-format
msgid "command \"%.50s\" is ambiguous\n"
msgstr "コマンド\"%.50s\"はあいまいです\n"
msgid "out of core\n"
msgstr "メモリがありません\n"
#, c-format
msgid "invalid option \"%.50s\"\n"
msgstr "無効なオプション \"%.50s\"\n"
#, c-format
msgid "conversion from '%s' to '%s' not available\n"
msgstr "'%s'から'%s'への変換は利用できません\n"
#, c-format
msgid "iconv_open failed: %s\n"
msgstr "iconv_openに失敗しました: %s\n"
#, c-format
msgid "conversion from '%s' to '%s' failed: %s\n"
msgstr "'%s'から'%s'への変換に失敗: %s\n"
#, c-format
msgid "failed to create temporary file '%s': %s\n"
msgstr "一時ファイル'%s'が作成できません: %s\n"
#, c-format
msgid "error writing to '%s': %s\n"
msgstr "'%s'の書き込みエラー: %s\n"
#, c-format
msgid "removing stale lockfile (created by %d)\n"
msgstr "古い lockfile (%d により作成)を除去します\n"
#, c-format
msgid "waiting for lock (held by %d%s) %s...\n"
msgstr "lockを待ちます (%d%s により保持) %s...\n"
msgid "(deadlock?) "
msgstr "(デッドロック?) "
#, c-format
msgid "lock '%s' not made: %s\n"
msgstr "lock '%s' は作成されませんでした: %s\n"
#, c-format
msgid "waiting for lock %s...\n"
msgstr "lock %s を待ちます...\n"
#, c-format
msgid "%s is too old (need %s, have %s)\n"
msgstr "%s が古すぎます (%s が必要、現在 %s)\n"
#, c-format
msgid "armor: %s\n"
msgstr "外装: %s\n"
msgid "invalid armor header: "
msgstr "無効な外装ヘッダー: "
msgid "armor header: "
msgstr "外装ヘッダー: "
msgid "invalid clearsig header\n"
msgstr "無効なクリア・テクスト署名ヘッダー\n"
msgid "unknown armor header: "
msgstr "不明の外装ヘッダー: "
msgid "nested clear text signatures\n"
msgstr "入れ子のクリア・テクスト署名\n"
msgid "unexpected armor: "
msgstr "予期せぬ外装: "
msgid "invalid dash escaped line: "
msgstr "無効なダッシュでエスケープされた行: "
#, c-format
msgid "invalid radix64 character %02X skipped\n"
msgstr "無効な64進文字%02Xをスキップしました\n"
msgid "premature eof (no CRC)\n"
msgstr "ファイル末尾が早すぎます (CRCがありません)\n"
msgid "premature eof (in CRC)\n"
msgstr "ファイル末尾が早すぎます (CRCの途中)\n"
msgid "malformed CRC\n"
msgstr "CRCの書式が正しくありません\n"
#, c-format
msgid "CRC error; %06lX - %06lX\n"
msgstr "CRCエラー。%06lX - %06lX\n"
msgid "premature eof (in trailer)\n"
-msgstr "ファイル末尾が早すぎます (後尾部の中にあります)\n"
+msgstr "ファイル終端が早すぎます (後尾部の中にあります)\n"
msgid "error in trailer line\n"
msgstr "後尾の行にエラーがあります\n"
msgid "no valid OpenPGP data found.\n"
msgstr "有効なOpenPGPデータが見つかりません。\n"
#, c-format
msgid "invalid armor: line longer than %d characters\n"
msgstr "無効な外装: 行の長さが%d文字を超えています\n"
msgid ""
"quoted printable character in armor - probably a buggy MTA has been used\n"
msgstr ""
"外装の中にquoted printable文字があります。おそらくバグのあるMTAが使われたので"
"しょう\n"
#, c-format
msgid "[ not human readable (%zu bytes: %s%s) ]"
msgstr "[ 人には読めません (%zuバイト: %s%s) ]"
msgid ""
"a notation name must have only printable characters or spaces, and end with "
"an '='\n"
msgstr ""
"注釈名には印字可能な文字か空白のみを使い、'='で終わらなければなりません\n"
msgid "a user notation name must contain the '@' character\n"
msgstr "ユーザ注釈名は、'@'文字を含まなければなりません\n"
msgid "a notation name must not contain more than one '@' character\n"
msgstr "ユーザ注釈名は、一つより大きい'@'文字を含んではなりません\n"
msgid "a notation value must not use any control characters\n"
msgstr "注釈名の値に制御文字を使ってはいけません\n"
msgid "a notation name may not contain an '=' character\n"
msgstr "ユーザ注釈名は、'='の文字を含んではなりません\n"
msgid "a notation name must have only printable characters or spaces\n"
msgstr "注釈名には印字可能な文字か空白のみを使わなければなりません\n"
msgid "WARNING: invalid notation data found\n"
msgstr "*警告*: 無効な注釈データを発見\n"
#, c-format
msgid "failed to proxy %s inquiry to client\n"
msgstr "プロキシ%sのクライアントへの問い合わせが失敗しました\n"
msgid "Enter passphrase: "
msgstr "パスフレーズを入力: "
#, c-format
msgid "error getting version from '%s': %s\n"
msgstr "%s'からバージョン取得エラー: %s\n"
#, c-format
msgid "server '%s' is older than us (%s < %s)"
msgstr "サーバ'%s'はこちらより古いです(%s < %s)"
#, c-format
msgid "WARNING: %s\n"
msgstr "*警告*: %s\n"
msgid "Note: Outdated servers may lack important security fixes.\n"
msgstr ""
"注意: 古いサーバは、重要なセキュリティの修正が欠如しているかもしれません。\n"
#, c-format
msgid "Note: Use the command \"%s\" to restart them.\n"
msgstr "注意: \"%s\"コマンドを使って再起動してください。\n"
#, c-format
msgid "%s is not compliant with %s mode\n"
msgstr "%sは%sモードに準拠しません\n"
+#, c-format
+msgid "problem with the agent: %s\n"
+msgstr "エージェントに問題: %s\n"
+
#, c-format
msgid "OpenPGP card not available: %s\n"
msgstr "OpenPGPカードが利用できません: %s\n"
#, c-format
msgid "OpenPGP card no. %s detected\n"
msgstr "OpenPGPカードno. %sを検出\n"
msgid "can't do this in batch mode\n"
msgstr "これはバッチ・モードではできません\n"
msgid "This command is only available for version 2 cards\n"
msgstr "このコマンドが使えるのはバージョン2のカードだけです\n"
msgid "Reset Code not or not anymore available\n"
msgstr "リセット・コードが(もはや)利用可能ではありません\n"
msgid "Your selection? "
msgstr "あなたの選択は? "
msgid "[not set]"
msgstr "[未設定]"
-msgid "male"
-msgstr "男"
+msgid "Mr."
+msgstr "Mr."
-msgid "female"
-msgstr "女"
-
-msgid "unspecified"
-msgstr "無指定"
+msgid "Mrs."
+msgstr "Mrs"
msgid "not forced"
msgstr "強制なし"
msgid "forced"
msgstr "強制"
msgid "Error: Only plain ASCII is currently allowed.\n"
msgstr "エラー: 普通のASCIIだけが今、許可されています。\n"
msgid "Error: The \"<\" character may not be used.\n"
msgstr "エラー: \"<\"文字は使えません。\n"
msgid "Error: Double spaces are not allowed.\n"
msgstr "エラー: 二重の空白は禁止です。\n"
msgid "Cardholder's surname: "
msgstr "カード所有者の姓 (surname): "
msgid "Cardholder's given name: "
msgstr "カード所有者の名 (given name): "
#, c-format
msgid "Error: Combined name too long (limit is %d characters).\n"
msgstr "エラー: つないだ名前が長すぎます (上限%d文字)。\n"
msgid "URL to retrieve public key: "
msgstr "公開鍵を取得するURL: "
#, c-format
msgid "error reading '%s': %s\n"
msgstr "'%s'の読み込みエラー: %s\n"
#, c-format
msgid "error writing '%s': %s\n"
msgstr "'%s'の書き込みエラー: %s\n"
msgid "Login data (account name): "
msgstr "ログイン・データ (アカウント名): "
msgid "Private DO data: "
msgstr "プライベート DO データ: "
msgid "Language preferences: "
msgstr "言語の優先指定: "
msgid "Error: invalid length of preference string.\n"
msgstr "エラー: 優先指定の文字列の長さが無効です。\n"
msgid "Error: invalid characters in preference string.\n"
msgstr "エラー: 優先指定の文字列に無効な文字があります。\n"
-msgid "Sex ((M)ale, (F)emale or space): "
-msgstr "性別 ((M)男、(F)女、または空白): "
+msgid "Salutation (M = Mr., F = Mrs., or space): "
+msgstr "敬称 (M = Mr., F = Mrs, あるいは空白): "
msgid "Error: invalid response.\n"
msgstr "エラー: 無効な応答。\n"
msgid "CA fingerprint: "
msgstr "CAのフィンガープリント: "
msgid "Error: invalid formatted fingerprint.\n"
msgstr "エラー: 無効な形式のフィンガープリント。\n"
#, c-format
msgid "key operation not possible: %s\n"
msgstr "鍵は操作できません: %s\n"
msgid "not an OpenPGP card"
msgstr "OpenPGPカードでありません"
#, c-format
msgid "error getting current key info: %s\n"
msgstr "現行鍵情報の取得エラー: %s\n"
msgid "Replace existing key? (y/N) "
msgstr "既存の鍵を置き換えしますか? (y/N) "
msgid ""
-"Note: There is no guarantee that the card supports the requested size.\n"
-" If the key generation does not succeed, please check the\n"
-" documentation of your card to see what sizes are allowed.\n"
+"Note: There is no guarantee that the card supports the requested\n"
+" key type or size. If the key generation does not succeed,\n"
+" please check the documentation of your card to see which\n"
+" key types and sizes are supported.\n"
msgstr ""
-"注意: カードが要求された鍵長をサポートしているという保証はありません。\n"
-" 鍵生成が成功しない場合、あなたのカードに関する技術文書を確認し、\n"
-" 利用できる鍵長について確認ください。\n"
+"注意: カードが要求された鍵のタイプもしくは鍵長をサポートしている保証は\n"
+" ありません。鍵生成が成功しない場合、あなたのカードに関する技術文書を\n"
+" 確認し、どの鍵のタイプと鍵長がサポートされているかについて確認して\n"
+" ください。\n"
#, c-format
msgid "What keysize do you want? (%u) "
msgstr "鍵長は? (%u) "
#, c-format
msgid "rounded up to %u bits\n"
msgstr "%uビットに切り上げます\n"
#, c-format
msgid "%s keysizes must be in the range %u-%u\n"
msgstr "%s 鍵長は %u-%u の範囲でなければなりません\n"
msgid "Changing card key attribute for: "
msgstr "こちらのカード鍵の属性を変更します: "
msgid "Signature key\n"
msgstr "署名鍵\n"
msgid "Encryption key\n"
msgstr "暗号化鍵\n"
msgid "Authentication key\n"
msgstr "認証鍵\n"
msgid "Please select what kind of key you want:\n"
msgstr "ご希望の鍵の種類を選択してください:\n"
#, c-format
msgid " (%d) RSA\n"
msgstr " (%d) RSA\n"
#, c-format
msgid " (%d) ECC\n"
msgstr " (%d) ECC\n"
msgid "Invalid selection.\n"
msgstr "無効な選択です。\n"
#, c-format
msgid "The card will now be re-configured to generate a key of %u bits\n"
msgstr "カードは、今、%uビットの鍵を生成するように再コンフィグされます\n"
#, c-format
msgid "The card will now be re-configured to generate a key of type: %s\n"
msgstr ""
"カードは、今、こちらのタイプの鍵を生成するように再コンフィグされます: %s\n"
#, c-format
msgid "error changing key attribute for key %d: %s\n"
msgstr "鍵%dの属性を変更する際にエラー: %s\n"
#, c-format
msgid "error getting card info: %s\n"
msgstr "カード情報の取得エラー: %s\n"
msgid "This command is not supported by this card\n"
msgstr "このカードでは、このコマンドはサポートされていません。\n"
msgid "Make off-card backup of encryption key? (Y/n) "
msgstr "暗号化鍵のカード外バックアップを作成しますか? (Y/n) "
msgid "Note: keys are already stored on the card!\n"
msgstr "注意: 秘密鍵はもうカードに保管してあります!\n"
msgid "Replace existing keys? (y/N) "
msgstr "既存の鍵を置き換えますか? (y/N) "
#, c-format
msgid ""
"Please note that the factory settings of the PINs are\n"
" PIN = '%s' Admin PIN = '%s'\n"
"You should change them using the command --change-pin\n"
msgstr ""
"工場設定のPINは下記のとおり\n"
" PIN = '%s' 管理者PIN = '%s'\n"
"次のコマンドを使って変更すべきです --change-pin\n"
msgid "Please select the type of key to generate:\n"
msgstr "生成する鍵の型を選択してください:\n"
msgid " (1) Signature key\n"
msgstr " (1) 署名鍵\n"
msgid " (2) Encryption key\n"
msgstr " (2) 暗号化鍵\n"
msgid " (3) Authentication key\n"
msgstr " (3) 認証鍵\n"
msgid "Please select where to store the key:\n"
msgstr "鍵を保管する場所を選択してください:\n"
#, c-format
msgid "KEYTOCARD failed: %s\n"
msgstr "KEYTOCARDが失敗しました: %s\n"
msgid "Note: This command destroys all keys stored on the card!\n"
msgstr "注意: このコマンドはカードに保管してあるすべての鍵を破壊します!\n"
msgid "Continue? (y/N) "
msgstr "続けますか? (y/N) "
msgid "Really do a factory reset? (enter \"yes\") "
msgstr "工場出荷リセットを行いますか? (本当なら \"yes\" と入力) "
#, c-format
msgid "error for setup KDF: %s\n"
msgstr "KDF設定のエラー: %s\n"
+#, c-format
+msgid "error for setup UIF: %s\n"
+msgstr "UIF設定のエラー: %s\n"
+
msgid "quit this menu"
msgstr "このメニューを終了"
msgid "show admin commands"
msgstr "管理コマンドを表示"
msgid "show this help"
msgstr "このヘルプを表示"
msgid "list all available data"
msgstr "全有効データを表示"
msgid "change card holder's name"
msgstr "カード所有者の名前の変更"
msgid "change URL to retrieve key"
msgstr "鍵を取得するURLの変更"
msgid "fetch the key specified in the card URL"
msgstr "カードURLで指定された鍵の取得"
msgid "change the login name"
msgstr "ログイン名の変更"
msgid "change the language preferences"
msgstr "言語の優先指定の変更"
-msgid "change card holder's sex"
-msgstr "カード所有者の性別の変更"
+msgid "change card holder's salutation"
+msgstr "カード所有者の敬称の変更"
msgid "change a CA fingerprint"
msgstr "CAフィンガープリントの変更"
msgid "toggle the signature force PIN flag"
msgstr "署名強制PINフラグを反転"
msgid "generate new keys"
msgstr "新しい鍵を生成"
msgid "menu to change or unblock the PIN"
msgstr "PINブロックの解除や変更のメニュー"
msgid "verify the PIN and list all data"
msgstr "PINを確認しすべてのデータを表示する"
msgid "unblock the PIN using a Reset Code"
msgstr "PINをリセット・コードでブロックを解除する"
msgid "destroy all keys and data"
msgstr "すべての鍵とデータを破壊します"
msgid "setup KDF for PIN authentication"
msgstr "PIN認証のKDFを設定する"
msgid "change the key attribute"
msgstr "鍵の属性の変更"
+msgid "change the User Interaction Flag"
+msgstr "ユーザ・インタラクション・フラグの変更"
+
msgid "gpg/card> "
msgstr "gpg/card> "
msgid "Admin-only command\n"
msgstr "管理者専用コマンド\n"
msgid "Admin commands are allowed\n"
msgstr "管理者コマンドが許可されています\n"
msgid "Admin commands are not allowed\n"
msgstr "管理者コマンドは禁止されています\n"
msgid "Invalid command (try \"help\")\n"
msgstr "無効なコマンド (\"help\"を参照)\n"
msgid "--output doesn't work for this command\n"
msgstr "このコマンドで--outputは機能しません\n"
#, c-format
msgid "can't open '%s'\n"
msgstr "'%s'が開けません\n"
#, c-format
msgid "key \"%s\" not found: %s\n"
msgstr "鍵\"%s\"が見つかりません: %s\n"
#, c-format
msgid "error reading keyblock: %s\n"
msgstr "鍵ブロックの読み込みエラー: %s\n"
#, c-format
msgid "key \"%s\" not found\n"
msgstr "鍵\"%s\"が見つかりません\n"
msgid "(unless you specify the key by fingerprint)\n"
msgstr "(フィンガー・プリントで鍵を指定してない限り)\n"
msgid "can't do this in batch mode without \"--yes\"\n"
-msgstr "\"--yes\"なしでバッチ・モードではできません\n"
+msgstr "これは\"--yes\"なしでバッチ・モードではできません\n"
+
+msgid "Note: The public primary key and all its subkeys will be deleted.\n"
+msgstr "注意: 主鍵とすべての副鍵の公開鍵が削除されます。\n"
+
+msgid "Note: Only the shown public subkey will be deleted.\n"
+msgstr "注意: 表示されている副鍵の公開鍵だけが削除されます。\n"
+
+msgid "Note: Only the secret part of the shown primary key will be deleted.\n"
+msgstr "注意: 表示されている主鍵の秘密鍵だけが削除されます。\n"
+
+msgid "Note: Only the secret part of the shown subkey will be deleted.\n"
+msgstr "注意: 表示されている副鍵の秘密鍵だけが削除されます。\n"
msgid "Delete this key from the keyring? (y/N) "
msgstr "この鍵を鍵リングから削除しますか? (y/N) "
msgid "This is a secret key! - really delete? (y/N) "
msgstr "これは秘密鍵です! 本当に削除しますか? (y/N) "
#, c-format
msgid "deleting secret %s failed: %s\n"
msgstr "秘密%sの削除に失敗しました: %s\n"
msgid "key"
msgstr "鍵"
msgid "subkey"
msgstr "副鍵: "
+#, c-format
+msgid "update failed: %s\n"
+msgstr "更新に失敗しました: %s\n"
+
#, c-format
msgid "deleting keyblock failed: %s\n"
msgstr "鍵ブロックの削除に失敗しました: %s\n"
msgid "ownertrust information cleared\n"
msgstr "所有者信用情報をクリアしました\n"
#, c-format
msgid "there is a secret key for public key \"%s\"!\n"
msgstr "この公開鍵に対する秘密鍵\"%s\"があります!\n"
msgid "use option \"--delete-secret-keys\" to delete it first.\n"
msgstr "まず\"--delete-secret-keys\"オプションでこれを削除してください。\n"
#, c-format
msgid "error creating passphrase: %s\n"
msgstr "パスフレーズの作成エラー: %s\n"
msgid "can't use a symmetric ESK packet due to the S2K mode\n"
msgstr "S2Kモードのため、共通鍵ESKパケットを使えません\n"
#, c-format
-msgid "using cipher %s\n"
-msgstr "暗号方式 %s を使います\n"
+msgid "using cipher %s.%s\n"
+msgstr "暗号方式 %s.%s を使います\n"
#, c-format
msgid "'%s' already compressed\n"
msgstr "'%s'はもう圧縮済みです\n"
#, c-format
msgid "WARNING: '%s' is an empty file\n"
msgstr "*警告*: '%s'は空のファイルです\n"
#, c-format
msgid "reading from '%s'\n"
msgstr "'%s'から読み込み\n"
#, c-format
msgid ""
"WARNING: forcing symmetric cipher %s (%d) violates recipient preferences\n"
msgstr ""
"*警告*: 共通鍵暗号方式 %s (%d) の強制が、受取人の優先指定をそむきます\n"
#, c-format
msgid "cipher algorithm '%s' may not be used in %s mode\n"
msgstr "暗号アルゴリズム'%s'を%sモードで使うことはできません\n"
#, c-format
msgid "WARNING: key %s is not suitable for encryption in %s mode\n"
msgstr "*警告*: 鍵%sは、%sモードでは、暗号化に適しません\n"
#, c-format
msgid ""
"WARNING: forcing compression algorithm %s (%d) violates recipient "
"preferences\n"
msgstr ""
"*警告*: 圧縮アルゴリズム %s (%d) の強制が、受取人の優先指定をそむきます\n"
#, c-format
msgid "forcing symmetric cipher %s (%d) violates recipient preferences\n"
msgstr "共通鍵暗号方式 %s (%d) の強制が、受取人の優先指定をそむきます\n"
#, c-format
-msgid "%s/%s encrypted for: \"%s\"\n"
-msgstr "%s/%s暗号化 受信者:\"%s\"\n"
+msgid "%s/%s.%s encrypted for: \"%s\"\n"
+msgstr "%s/%s.%s 暗号化 受信者:\"%s\"\n"
#, c-format
msgid "option '%s' may not be used in %s mode\n"
msgstr "オプション'%s'を%sモードで使うことはできません\n"
#, c-format
-msgid "%s encrypted data\n"
-msgstr "%s暗号化済みデータ\n"
+msgid "%s.%s encrypted data\n"
+msgstr "%s.%s 暗号化データ\n"
#, c-format
msgid "encrypted with unknown algorithm %d\n"
msgstr "不明のアルゴリズム%dによる暗号化\n"
msgid ""
"WARNING: message was encrypted with a weak key in the symmetric cipher.\n"
msgstr "*警告*: メッセージは共通鍵暗号方式の弱い鍵で暗号化されています。\n"
msgid "problem handling encrypted packet\n"
msgstr "暗号化パケットの取扱いで障害\n"
msgid "no remote program execution supported\n"
msgstr "遠隔プログラムの実行は、サポートしていません\n"
msgid ""
"external program calls are disabled due to unsafe options file permissions\n"
msgstr ""
"オプション・ファイルの許可モードが安全ではないので、外部プログラムの呼出しは"
"禁止となります。\n"
msgid "this platform requires temporary files when calling external programs\n"
msgstr ""
"このプラットホームだと、外部プログラムの呼出しには、一時ファイルが必要です\n"
#, c-format
msgid "unable to execute program '%s': %s\n"
msgstr "'%s'を実行できません: %s\n"
#, c-format
msgid "unable to execute shell '%s': %s\n"
msgstr "シェル'%s'を実行できません: %s\n"
#, c-format
msgid "system error while calling external program: %s\n"
msgstr "外部プログラムの呼出しでシステム・エラー: %s\n"
msgid "unnatural exit of external program\n"
msgstr "外部プログラムが、不自然に終了\n"
msgid "unable to execute external program\n"
msgstr "外部プログラムを実行できません\n"
#, c-format
msgid "unable to read external program response: %s\n"
msgstr "外部プログラムの応答を読み込めません: %s\n"
#, c-format
msgid "WARNING: unable to remove tempfile (%s) '%s': %s\n"
msgstr "*警告*: 一時ファイルを削除できません (%s) '%s': %s\n"
#, c-format
msgid "WARNING: unable to remove temp directory '%s': %s\n"
msgstr "*警告*: 一時ディレクトリ'%s'を削除できません: %s\n"
msgid "export signatures that are marked as local-only"
msgstr "ローカルのみと指定された署名をエクスポートします"
msgid "export attribute user IDs (generally photo IDs)"
msgstr "ユーザIDの属性(通常フォトID)をエクスポートします"
msgid "export revocation keys marked as \"sensitive\""
msgstr "\"sensitive\"(機密)と指定された失効鍵をエクスポートします"
msgid "remove unusable parts from key during export"
msgstr "エクスポートの際、利用できない部分を除去する"
msgid "remove as much as possible from key during export"
msgstr "エクスポートの際、できるだけ除去する"
+msgid "Do not export user id or attribute packets"
+msgstr "ユーザIDもしくは属性パケットをエクスポートしない"
+
msgid "use the GnuPG key backup format"
msgstr "GnuPGの鍵のバックアップフォーマットを使います"
msgid " - skipped"
msgstr " - スキップされました"
#, c-format
msgid "writing to '%s'\n"
msgstr "'%s'への書き込み\n"
#, c-format
msgid "key %s: key material on-card - skipped\n"
msgstr "鍵%s: 鍵はカード上にあります - スキップします\n"
msgid "exporting secret keys not allowed\n"
msgstr "秘密鍵のエクスポートは認められません\n"
#, c-format
msgid "key %s: PGP 2.x style key - skipped\n"
msgstr "鍵%s: PGP 2.x形式の鍵です - スキップします\n"
msgid "WARNING: nothing exported\n"
msgstr "*警告*: 何もエクスポートされていません\n"
#, c-format
msgid "error creating '%s': %s\n"
msgstr "'%s'の作成エラー: %s\n"
msgid "[User ID not found]"
msgstr "[ユーザIDが見つかりません]"
-#, c-format
-msgid "(check argument of option '%s')\n"
-msgstr "(オプション'%s'の引数を確認ください)\n"
-
-#, c-format
-msgid "Warning: '%s' should be a long key ID or a fingerprint\n"
-msgstr "警告: '%s'は長い鍵IDかフィンガープリントであるべきです。\n"
-
-#, c-format
-msgid "error looking up: %s\n"
-msgstr "検索のエラー: %s\n"
-
-#, c-format
-msgid "Warning: %s appears in the keyring %d times\n"
-msgstr "警告: %sは鍵リングに%d回出現します\n"
-
#, c-format
msgid "automatically retrieved '%s' via %s\n"
msgstr "'%s'を %s から自動取得\n"
#, c-format
msgid "error retrieving '%s' via %s: %s\n"
msgstr "'%s'を %s から取得する際のエラー: %s\n"
msgid "No fingerprint"
msgstr "フィンガープリントがありません"
+#, c-format
+msgid "checking for a fresh copy of an expired key via %s\n"
+msgstr "%s から失効した鍵の新しいコピーを確認します。\n"
+
#, c-format
msgid "secret key \"%s\" not found: %s\n"
msgstr "秘密鍵\"%s\"が見つかりません: %s\n"
+#, c-format
+msgid "(check argument of option '%s')\n"
+msgstr "(オプション'%s'の引数を確認ください)\n"
+
#, c-format
msgid "Warning: not using '%s' as default key: %s\n"
msgstr "警告: デフォルトの秘密鍵として '%s' を用いません: %s\n"
#, c-format
msgid "using \"%s\" as default secret key for signing\n"
msgstr "デフォルトの署名用の秘密鍵として\"%s\"を用います\n"
#, c-format
msgid "all values passed to '%s' ignored\n"
msgstr "'%s'に渡されたすべての値は無視されました\n"
#, c-format
msgid "Invalid key %s made valid by --allow-non-selfsigned-uid\n"
msgstr "--allow-non-selfsigned-uidで有効にされた無効な鍵%sです\n"
#, c-format
msgid "using subkey %s instead of primary key %s\n"
msgstr "副鍵%s(主鍵%sではなく)を用います\n"
#, c-format
msgid "valid values for option '%s':\n"
msgstr "オプション'%s'に有効な値:\n"
msgid "make a signature"
msgstr "署名を作成"
msgid "make a clear text signature"
msgstr "クリア・テクスト署名を作成"
msgid "make a detached signature"
msgstr "分遣署名を作成"
msgid "encrypt data"
msgstr "データを暗号化"
msgid "encryption only with symmetric cipher"
msgstr "暗号化には共通鍵暗号方式のみを使用"
msgid "decrypt data (default)"
msgstr "データを復号 (デフォルト)"
msgid "verify a signature"
msgstr "署名を検証"
msgid "list keys"
msgstr "鍵の一覧"
msgid "list keys and signatures"
msgstr "鍵と署名の一覧"
msgid "list and check key signatures"
msgstr "鍵署名の検査と一覧"
msgid "list keys and fingerprints"
msgstr "鍵とフィンガープリントの一覧"
msgid "list secret keys"
msgstr "秘密鍵の一覧"
msgid "generate a new key pair"
msgstr "新しい鍵ペアを生成"
msgid "quickly generate a new key pair"
msgstr "すばやく新しい鍵ペアを生成"
msgid "quickly add a new user-id"
msgstr "すばやく新しいユーザIDを追加"
msgid "quickly revoke a user-id"
msgstr "すばやくユーザIDを失効"
msgid "quickly set a new expiration date"
msgstr "すばやく新しい有効期限を設定"
msgid "full featured key pair generation"
msgstr "全機能の鍵ペアを生成"
msgid "generate a revocation certificate"
msgstr "失効証明書を生成"
msgid "remove keys from the public keyring"
msgstr "公開鍵リングから鍵を削除"
msgid "remove keys from the secret keyring"
msgstr "秘密鍵リングから鍵を削除"
msgid "quickly sign a key"
msgstr "鍵にすばやく署名"
msgid "quickly sign a key locally"
msgstr "鍵へすばやくローカルに署名"
msgid "sign a key"
msgstr "鍵に署名"
msgid "sign a key locally"
msgstr "鍵へローカルに署名"
msgid "sign or edit a key"
msgstr "鍵への署名や編集"
msgid "change a passphrase"
msgstr "パスフレーズの変更"
msgid "export keys"
msgstr "鍵をエクスポートする"
msgid "export keys to a keyserver"
msgstr "鍵サーバに鍵をエクスポートする"
msgid "import keys from a keyserver"
msgstr "鍵サーバから鍵をインポートする"
msgid "search for keys on a keyserver"
msgstr "鍵サーバの鍵を検索する"
msgid "update all keys from a keyserver"
msgstr "鍵サーバから鍵を全部更新する"
msgid "import/merge keys"
msgstr "鍵のインポート/マージ"
msgid "print the card status"
msgstr "カード・ステイタスを表示"
msgid "change data on a card"
msgstr "カードのデータを変更"
msgid "change a card's PIN"
msgstr "カードのPINを変更"
msgid "update the trust database"
msgstr "信用データベースを更新"
msgid "print message digests"
msgstr "メッセージ・ダイジェストを表示"
msgid "run in server mode"
msgstr "サーバ・モードで実行"
msgid "|VALUE|set the TOFU policy for a key"
msgstr "|VALUE|TOFUポリシーを鍵に設定する"
msgid "create ascii armored output"
msgstr "ASCII形式の外装を作成"
msgid "|USER-ID|encrypt for USER-ID"
msgstr "|USER-ID|USER-ID用に暗号化"
msgid "|USER-ID|use USER-ID to sign or decrypt"
msgstr "|USER-ID|署名や復号にこのUSER-IDを使用"
msgid "|N|set compress level to N (0 disables)"
msgstr "|N|圧縮レベルをNに設定 (0は非圧縮)"
msgid "use canonical text mode"
msgstr "正準テキスト・モードを使用"
msgid "|FILE|write output to FILE"
msgstr "|FILE|出力をFILEに書き出す"
msgid "do not make any changes"
msgstr "無変更"
msgid "prompt before overwriting"
msgstr "上書き前に確認"
msgid "use strict OpenPGP behavior"
msgstr "厳密なOpenPGPの振舞を採用"
msgid ""
"@\n"
"(See the man page for a complete listing of all commands and options)\n"
msgstr ""
"@\n"
"(コマンドとオプション全部の一覧は、マニュアル・ページをご覧ください)\n"
msgid ""
"@\n"
"Examples:\n"
"\n"
" -se -r Bob [file] sign and encrypt for user Bob\n"
" --clear-sign [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"
msgstr ""
"@\n"
"例:\n"
"\n"
" -se -r Bob [ファイル] ユーザBobへ署名と暗号化\n"
" --clear-sign [ファイル] クリア・テクスト署名を作成\n"
" --detach-sign [ファイル] 分遣署名を作成\n"
" --list-keys [名前] 鍵を表示\n"
" --fingerprint [名前] フィンガープリントを表示\n"
msgid "Usage: @GPG@ [options] [files] (-h for help)"
msgstr "使い方: @GPG@ [オプション] [ファイル] (ヘルプは -h)"
msgid ""
"Syntax: @GPG@ [options] [files]\n"
"Sign, check, encrypt or decrypt\n"
"Default operation depends on the input data\n"
msgstr ""
"形式: @GPG@ [オプション] [ファイル]\n"
"署名、検査、暗号化または復号\n"
"デフォルトの操作は、入力データに依存\n"
msgid ""
"\n"
"Supported algorithms:\n"
msgstr ""
"\n"
"サポートしているアルゴリズム:\n"
msgid "Pubkey: "
msgstr "公開鍵: "
msgid "Cipher: "
msgstr "暗号方式: "
msgid "Hash: "
msgstr "ハッシュ: "
msgid "Compression: "
msgstr "圧縮: "
#, c-format
msgid "usage: %s [options] %s\n"
msgstr "使い方: %s [オプション] %s\n"
msgid "conflicting commands\n"
msgstr "対立するコマンド\n"
#, c-format
msgid "no = sign found in group definition '%s'\n"
msgstr "=記号が、グループ定義'%s'内に見つかりません\n"
#, c-format
msgid "WARNING: unsafe ownership on homedir '%s'\n"
msgstr "*警告*: homedir '%s'の安全でない所有\n"
#, c-format
msgid "WARNING: unsafe ownership on configuration file '%s'\n"
msgstr "*警告*: コンフィグレーション・ファイル'%s'の安全でない所有\n"
#, c-format
msgid "WARNING: unsafe ownership on extension '%s'\n"
msgstr "*警告*: 拡張'%s'の安全でない所有\n"
#, c-format
msgid "WARNING: unsafe permissions on homedir '%s'\n"
msgstr "*警告*: homedir '%s'の安全でない許可\n"
#, c-format
msgid "WARNING: unsafe permissions on configuration file '%s'\n"
msgstr "*警告*: コンフィグレーション・ファイル'%s'の安全でない許可\n"
#, c-format
msgid "WARNING: unsafe permissions on extension '%s'\n"
msgstr "*警告*: 拡張'%s'の安全でない許可\n"
#, c-format
msgid "WARNING: unsafe enclosing directory ownership on homedir '%s'\n"
msgstr "*警告*: homedir '%s'の安全でない上位ディレクトリ所有\n"
#, c-format
msgid ""
"WARNING: unsafe enclosing directory ownership on configuration file '%s'\n"
msgstr ""
"*警告*: コンフィグレーション・ファイル'%s'の安全でない上位ディレクトリ所有\n"
#, c-format
msgid "WARNING: unsafe enclosing directory ownership on extension '%s'\n"
msgstr "*警告*: 拡張'%s'の安全でない上位ディレクトリ所有\n"
#, c-format
msgid "WARNING: unsafe enclosing directory permissions on homedir '%s'\n"
msgstr "*警告*: homedir '%s'の安全でない上位ディレクトリ許可\n"
#, c-format
msgid ""
"WARNING: unsafe enclosing directory permissions on configuration file '%s'\n"
msgstr ""
"*警告*: コンフィグレーション・ファイル'%s'の安全でない上位ディレクトリ許可\n"
#, c-format
msgid "WARNING: unsafe enclosing directory permissions on extension '%s'\n"
msgstr "*警告*: 拡張'%s'の安全でない上位ディレクトリ許可\n"
#, c-format
msgid "unknown configuration item '%s'\n"
msgstr "不明のコンフィグレーション項目'%s'\n"
msgid "display photo IDs during key listings"
msgstr "鍵の一覧時にフォトIDを表示する"
msgid "show key usage information during key listings"
msgstr "鍵の一覧時に鍵の使い方の情報を表示する"
msgid "show policy URLs during signature listings"
msgstr "署名の一覧時にポリシーURLを表示する"
msgid "show all notations during signature listings"
msgstr "署名の一覧時にすべての注釈を表示する"
msgid "show IETF standard notations during signature listings"
msgstr "署名の一覧時にIETF標準注釈を表示する"
msgid "show user-supplied notations during signature listings"
msgstr "署名の一覧時にユーザの注釈を表示する"
msgid "show preferred keyserver URLs during signature listings"
msgstr "署名の一覧時に優先鍵サーバURLを表示する"
msgid "show user ID validity during key listings"
msgstr "鍵の一覧時にユーザIDの有効性を表示する"
msgid "show revoked and expired user IDs in key listings"
msgstr "鍵の一覧に失効したユーザID、期限切れとなったユーザIDを表示する"
msgid "show revoked and expired subkeys in key listings"
msgstr "鍵の一覧に失効した副鍵、期限切れとなった副鍵を表示する"
msgid "show the keyring name in key listings"
msgstr "鍵の一覧に鍵リングの名前を表示する"
msgid "show expiration dates during signature listings"
msgstr "署名の一覧時に有効期限の日付を表示する"
#, c-format
msgid "unknown TOFU policy '%s'\n"
msgstr "不明のTOFUポリシー'%s'\n"
msgid "(use \"help\" to list choices)\n"
msgstr "(選択肢の一覧には\"help\"を使ってください)\n"
#, c-format
msgid "Note: old default options file '%s' ignored\n"
msgstr "注意: 以前デフォルトだったオプション・ファイル'%s'は、無視されます\n"
#, c-format
msgid "Note: %s is not for normal use!\n"
msgstr "注意: 普通%sは使いません!\n"
#, c-format
msgid "'%s' is not a valid signature expiration\n"
msgstr "'%s'は、有効な署名表現ではありません\n"
#, c-format
msgid "\"%s\" is not a proper mail address\n"
msgstr "\"%s\"は正しいメール・アドレスではありません\n"
#, c-format
msgid "invalid pinentry mode '%s'\n"
msgstr "無効な pinentry mode '%s'です\n"
#, c-format
msgid "invalid request origin '%s'\n"
msgstr "無効な送信元要求 '%s' です\n"
#, c-format
msgid "'%s' is not a valid character set\n"
msgstr "'%s'は、有効な文字集合ではありません\n"
msgid "could not parse keyserver URL\n"
msgstr "鍵サーバのURLを解析不能\n"
#, c-format
msgid "%s:%d: invalid keyserver options\n"
msgstr "%s:%d: 無効な鍵サーバ・オプションです\n"
msgid "invalid keyserver options\n"
msgstr "無効な鍵サーバ・オプションです\n"
#, c-format
msgid "%s:%d: invalid import options\n"
msgstr "%s:%d: 無効なインポート・オプションです\n"
msgid "invalid import options\n"
msgstr "無効なインポート・オプションです\n"
#, c-format
msgid "invalid filter option: %s\n"
msgstr "無効なフィルタ・オプションです: %s\n"
#, c-format
msgid "%s:%d: invalid export options\n"
msgstr "%s:%d: 無効なエクスポート・オプションです\n"
msgid "invalid export options\n"
msgstr "無効なエクスポート・オプションです\n"
#, c-format
msgid "%s:%d: invalid list options\n"
msgstr "%s:%d: 無効な一覧オプションです\n"
msgid "invalid list options\n"
msgstr "無効な一覧オプションです\n"
msgid "display photo IDs during signature verification"
msgstr "署名の検証時にフォトIDを表示する"
msgid "show policy URLs during signature verification"
msgstr "署名の検証時にポリシーURLを表示する"
msgid "show all notations during signature verification"
msgstr "署名の検証時にすべての注釈を表示する"
msgid "show IETF standard notations during signature verification"
msgstr "署名の検証時にIETF標準注釈を表示する"
msgid "show user-supplied notations during signature verification"
msgstr "署名の検証時にユーザの注釈を表示する"
msgid "show preferred keyserver URLs during signature verification"
msgstr "署名の検証時に優先鍵サーバURLを表示する"
msgid "show user ID validity during signature verification"
msgstr "署名の検証時にユーザIDの有効性を表示する"
msgid "show revoked and expired user IDs in signature verification"
msgstr "署名の検証時に失効したユーザID、期限切れとなったユーザIDを表示する"
msgid "show only the primary user ID in signature verification"
msgstr "署名の検証時にプライマリ・ユーザIDだけをを表示する"
msgid "validate signatures with PKA data"
msgstr "PKAデータで署名を検証する"
msgid "elevate the trust of signatures with valid PKA data"
msgstr "有効なPKAデータで署名の信用度を上昇させる"
#, c-format
msgid "%s:%d: invalid verify options\n"
msgstr "%s:%d: 無効な検証オプションです\n"
msgid "invalid verify options\n"
msgstr "無効な検証オプションです\n"
#, c-format
msgid "unable to set exec-path to %s\n"
msgstr "exec-pathを%sに設定不能\n"
#, c-format
msgid "%s:%d: invalid auto-key-locate list\n"
msgstr "%s:%d: 無効な auto-key-locate リストです\n"
msgid "invalid auto-key-locate list\n"
msgstr "無効な auto-key-locate リストです\n"
msgid "WARNING: program may create a core file!\n"
msgstr "*警告*: プログラムはcoreファイルを作成することがあります!\n"
#, c-format
msgid "WARNING: %s overrides %s\n"
msgstr "*警告*: %sは%sより優先\n"
#, c-format
msgid "%s not allowed with %s!\n"
msgstr "%sは%sとともに使うことはできません!\n"
#, c-format
msgid "%s makes no sense with %s!\n"
msgstr "%sは%sとともに使っても無意味です!\n"
msgid "WARNING: running with faked system time: "
msgstr "*警告*: ニセモノのシステム時刻で実行しています: "
#, c-format
msgid "will not run with insecure memory due to %s\n"
msgstr "%s のため、セキュアでないメモリで実行しません\n"
msgid "selected cipher algorithm is invalid\n"
-msgstr "選択された暗号アルゴリズムは、無効です\n"
+msgstr "選択された暗号アルゴリズムは無効です\n"
+
+msgid "selected AEAD algorithm is invalid\n"
+msgstr "選択された AEAD アルゴリズムは無効です\n"
msgid "selected compression algorithm is invalid\n"
msgstr "選択された圧縮アルゴリズムは、無効です\n"
msgid "selected certification digest algorithm is invalid\n"
msgstr "選択された証明書ダイジェスト・アルゴリズムは、無効です\n"
msgid "completes-needed must be greater than 0\n"
msgstr "completes-neededは正の値が必要です\n"
msgid "marginals-needed must be greater than 1\n"
msgstr "marginals-neededは1より大きな値が必要です\n"
msgid "max-cert-depth must be in the range from 1 to 255\n"
msgstr "max-cert-depthは1から255の範囲でなければなりません\n"
msgid "invalid default-cert-level; must be 0, 1, 2, or 3\n"
msgstr "無効なdefault-cert-level。0か1か2か3でなければなりません\n"
msgid "invalid min-cert-level; must be 1, 2, or 3\n"
msgstr "無効なmin-cert-level。0か1か2か3でなければなりません\n"
msgid "Note: simple S2K mode (0) is strongly discouraged\n"
msgstr "注意: 単純なS2Kモード(0)の使用には強く反対します\n"
msgid "invalid S2K mode; must be 0, 1 or 3\n"
msgstr "無効なS2Kモード。0か1か3でなければなりません\n"
msgid "invalid default preferences\n"
msgstr "無効なデフォルトの優先指定\n"
msgid "invalid personal cipher preferences\n"
msgstr "無効な個人用暗号方式の優先指定\n"
+msgid "invalid personal AEAD preferences\n"
+msgstr "無効な個人用 AEAD 方式の優先指定\n"
+
msgid "invalid personal digest preferences\n"
msgstr "無効な個人用ダイジェストの優先指定\n"
msgid "invalid personal compress preferences\n"
msgstr "無効な個人用圧縮の優先指定\n"
+#, c-format
+msgid "chunk size invalid - using %d\n"
+msgstr "無効なチャンク長です - %dにします\n"
+
#, c-format
msgid "%s does not yet work with %s\n"
msgstr "%sは%sではまだ機能しません\n"
+#, c-format
+msgid "AEAD algorithm '%s' may not be used in %s mode\n"
+msgstr "AEAD アルゴリズム'%s'を%sモードで使うことはできません\n"
+
#, c-format
msgid "digest algorithm '%s' may not be used in %s mode\n"
msgstr "ダイジェスト・アルゴリズム'%s'を%sモードで使うことはできません\n"
#, c-format
msgid "compression algorithm '%s' may not be used in %s mode\n"
msgstr "圧縮アルゴリズム'%s'を%sモードで使うことはできません\n"
#, c-format
msgid "failed to initialize the TrustDB: %s\n"
msgstr "信用データベースの初期化に失敗しました: %s\n"
msgid "WARNING: recipients (-r) given without using public key encryption\n"
msgstr "*警告*: 公開鍵暗号を使わずに、受取人 (-r) を指定しています\n"
#, c-format
msgid "symmetric encryption of '%s' failed: %s\n"
msgstr "'%s'の共通鍵暗号に失敗しました: %s\n"
msgid "you cannot use --symmetric --encrypt with --s2k-mode 0\n"
msgstr "--symmetric --encryptを--s2k-mode 0で使うことはできません\n"
#, c-format
msgid "you cannot use --symmetric --encrypt in %s mode\n"
msgstr "--symmetric --encryptを%sモードで使うことはできません\n"
msgid "you cannot use --symmetric --sign --encrypt with --s2k-mode 0\n"
msgstr "--symmetric --sign --encryptを--s2k-mode 0で使うことはできません\n"
#, c-format
msgid "you cannot use --symmetric --sign --encrypt in %s mode\n"
msgstr "--symmetric --sign --encryptを%sモードで使うことはできません\n"
#, c-format
msgid "keyserver send failed: %s\n"
msgstr "鍵サーバへの送信に失敗しました: %s\n"
#, c-format
msgid "keyserver receive failed: %s\n"
msgstr "鍵サーバからの受信に失敗しました: %s\n"
#, c-format
msgid "key export failed: %s\n"
msgstr "鍵のエクスポートに失敗しました: %s\n"
#, c-format
msgid "export as ssh key failed: %s\n"
msgstr "ssh鍵としてのエクスポートに失敗しました: %s\n"
#, c-format
msgid "keyserver search failed: %s\n"
msgstr "鍵サーバの検索に失敗しました: %s\n"
#, c-format
msgid "keyserver refresh failed: %s\n"
msgstr "鍵サーバの更新に失敗しました: %s\n"
#, c-format
msgid "dearmoring failed: %s\n"
msgstr "外装除去に失敗しました: %s\n"
#, c-format
msgid "enarmoring failed: %s\n"
msgstr "外装に失敗しました: %s\n"
#, c-format
msgid "invalid hash algorithm '%s'\n"
msgstr "無効なハッシュ・アルゴリズム'%s'です\n"
#, c-format
msgid "error parsing key specification '%s': %s\n"
msgstr "鍵指定'%s'の構文解析エラー: %s\n"
#, c-format
msgid "'%s' does not appear to be a valid key ID, fingerprint or keygrip\n"
msgstr "'%s'は有効な鍵ID, フィンガープリント、keygripではないようです。\n"
msgid "WARNING: no command supplied. Trying to guess what you mean ...\n"
msgstr ""
"*警告*: コマンドが指定されていません。なにを意味しているのか当ててみま"
"す ...\n"
msgid "Go ahead and type your message ...\n"
msgstr "開始します。メッセージを打ってください ...\n"
msgid "the given certification policy URL is invalid\n"
msgstr "あたえられた証明書ポリシーURLは無効です\n"
msgid "the given signature policy URL is invalid\n"
msgstr "あたえられた署名ポリシーURLは無効です\n"
msgid "the given preferred keyserver URL is invalid\n"
msgstr "指定された優先鍵サーバURLは無効です\n"
msgid "|FILE|take the keys from the keyring FILE"
msgstr "|FILE|鍵リングFILEの鍵を扱います"
msgid "make timestamp conflicts only a warning"
msgstr "日時の矛盾を警告だけにします"
msgid "|FD|write status info to this FD"
msgstr "|FD|このFDにステイタス情報を書き出す"
msgid "|ALGO|reject signatures made with ALGO"
msgstr "|ALGO|ALGOで作成された署名を拒絶する"
msgid "Usage: gpgv [options] [files] (-h for help)"
msgstr "使い方: gpgv [オプション] [ファイル] (ヘルプは -h)"
msgid ""
"Syntax: gpgv [options] [files]\n"
"Check signatures against known trusted keys\n"
msgstr ""
"形式: gpgv [オプション] [ファイル]\n"
"既知の信用した鍵で署名を検査\n"
msgid "No help available"
msgstr "ヘルプはありません"
#, c-format
msgid "No help available for '%s'"
msgstr "'%s'のヘルプはありません"
msgid "import signatures that are marked as local-only"
msgstr "ローカルだけとマークされた署名をインポートします"
msgid "repair damage from the pks keyserver during import"
msgstr "インポートの際、にpksキーサーバからのダメージを修正します"
msgid "do not clear the ownertrust values during import"
msgstr "インポートの際、所有者信用の値をクリアしない"
msgid "do not update the trustdb after import"
msgstr "インポート後、信用データベースを更新しない"
msgid "show key during import"
msgstr "インポートの際、鍵を表示"
msgid "only accept updates to existing keys"
msgstr "既存の鍵に対する更新のみ認めます"
msgid "remove unusable parts from key after import"
msgstr "インポート後、利用できない部分を鍵から除去します"
msgid "remove as much as possible from key after import"
msgstr "インポート後、できるだけ除去します"
+msgid "Do not import user id or attribute packets"
+msgstr "ユーザIDもしくは属性パケットをインポートしない"
+
msgid "run import filters and export key immediately"
msgstr "インポート・フィルタを実行し鍵をすぐにエクスポートします"
msgid "assume the GnuPG key backup format"
msgstr "GnuPGの鍵のバックアップフォーマットを仮定します"
msgid "repair keys on import"
msgstr "インポートの際、鍵を修復する"
#, c-format
msgid "skipping block of type %d\n"
msgstr "型%dのブロックをスキップします\n"
#, c-format
msgid "%lu keys processed so far\n"
msgstr "これまで%lu個の鍵を処理\n"
#, c-format
msgid "Total number processed: %lu\n"
msgstr " 処理数の合計: %lu\n"
#, c-format
msgid " skipped PGP-2 keys: %lu\n"
msgstr " スキップしたPGP-2鍵: %lu\n"
#, c-format
msgid " skipped new keys: %lu\n"
msgstr " スキップした新しい鍵: %lu\n"
#, c-format
msgid " w/o user IDs: %lu\n"
msgstr " ユーザIDなし: %lu\n"
#, c-format
msgid " imported: %lu"
msgstr " インポート: %lu"
#, c-format
msgid " unchanged: %lu\n"
msgstr " 変更なし: %lu\n"
#, c-format
msgid " new user IDs: %lu\n"
msgstr " 新しいユーザID: %lu\n"
#, c-format
msgid " new subkeys: %lu\n"
msgstr " 新しい副鍵: %lu\n"
#, c-format
msgid " new signatures: %lu\n"
msgstr " 新しい署名: %lu\n"
#, c-format
msgid " new key revocations: %lu\n"
msgstr " 新しい鍵の失効: %lu\n"
#, c-format
msgid " secret keys read: %lu\n"
msgstr " 秘密鍵の読み込み: %lu\n"
#, c-format
msgid " secret keys imported: %lu\n"
msgstr " 秘密鍵のインポート: %lu\n"
#, c-format
msgid " secret keys unchanged: %lu\n"
msgstr " 無変更の秘密鍵: %lu\n"
#, c-format
msgid " not imported: %lu\n"
msgstr " 未インポート: %lu\n"
#, c-format
msgid " signatures cleaned: %lu\n"
msgstr " 掃除された署名: %lu\n"
#, c-format
msgid " user IDs cleaned: %lu\n"
msgstr " 掃除されたユーザID: %lu\n"
#, c-format
msgid ""
"WARNING: key %s contains preferences for unavailable\n"
"algorithms on these user IDs:\n"
msgstr ""
"*警告*: 鍵%sには、これらのユーザIDに対して使用不可のアルゴリズムの優先指定が"
"あります\n"
#, c-format
msgid " \"%s\": preference for cipher algorithm %s\n"
msgstr " \"%s\": 暗号アルゴリズムの優先指定 %s\n"
+#, c-format
+msgid " \"%s\": preference for AEAD algorithm %s\n"
+msgstr " \"%s\": AEADアルゴリズムの優先指定 %s\n"
+
#, c-format
msgid " \"%s\": preference for digest algorithm %s\n"
msgstr " \"%s\": ダイジェスト・アルゴリズムの優先指定 %s\n"
#, c-format
msgid " \"%s\": preference for compression algorithm %s\n"
msgstr " \"%s\": 圧縮アルゴリズムの優先指定 %s\n"
msgid "it is strongly suggested that you update your preferences and\n"
msgstr "あなたの優先指定を更新し、この鍵を再配布することが強く推奨されます\n"
msgid "re-distribute this key to avoid potential algorithm mismatch problems\n"
msgstr "それによって、潜在的なアルゴリズム不一致の問題を避けられます\n"
#, c-format
msgid "you can update your preferences with: gpg --edit-key %s updpref save\n"
msgstr "以下で、優先指定を更新できます: gpg --edit-key %s updpref save\n"
#, c-format
msgid "key %s: no user ID\n"
msgstr "鍵%s: ユーザIDがありません\n"
#, c-format
msgid "key %s: %s\n"
msgstr "鍵 %s: %s\n"
msgid "rejected by import screener"
msgstr "インポートの検査で拒否されました"
#, c-format
msgid "key %s: PKS subkey corruption repaired\n"
msgstr "鍵%s: PKSの副鍵変造を修復\n"
#, c-format
msgid "key %s: accepted non self-signed user ID \"%s\"\n"
msgstr "鍵%s: 受理した未自己署名のユーザID\"%s\"\n"
#, c-format
msgid "key %s: no valid user IDs\n"
msgstr "鍵%s: 有効なユーザIDがありません\n"
msgid "this may be caused by a missing self-signature\n"
msgstr "これはおそらく自己署名のないせいでしょう\n"
#, c-format
msgid "key %s: public key not found: %s\n"
msgstr "鍵%s: 公開鍵が見つかりません: %s\n"
#, c-format
msgid "key %s: new key - skipped\n"
msgstr "鍵%s: 新しい鍵です - スキップします\n"
#, c-format
msgid "no writable keyring found: %s\n"
msgstr "書き込み可能な鍵リングが見つかりません: %s\n"
#, c-format
msgid "error writing keyring '%s': %s\n"
msgstr "鍵リング'%s'の書き込みエラー: %s\n"
#, c-format
msgid "key %s: public key \"%s\" imported\n"
msgstr "鍵%s: 公開鍵\"%s\"をインポートしました\n"
#, c-format
msgid "key %s: doesn't match our copy\n"
msgstr "鍵%s: こちらの複製と合いません\n"
#, c-format
msgid "key %s: \"%s\" 1 new user ID\n"
msgstr "鍵%s: \"%s\" 新しいユーザIDを1個\n"
#, c-format
msgid "key %s: \"%s\" %d new user IDs\n"
msgstr "鍵%s: \"%s\" 新しいユーザIDを%d個\n"
#, c-format
msgid "key %s: \"%s\" 1 new signature\n"
msgstr "鍵%s: \"%s\" 新しい署名を1個\n"
#, c-format
msgid "key %s: \"%s\" %d new signatures\n"
msgstr "鍵%s: \"%s\" 新しい署名を%d個\n"
#, c-format
msgid "key %s: \"%s\" 1 new subkey\n"
msgstr "鍵%s: \"%s\" 新しい副鍵を1個\n"
#, c-format
msgid "key %s: \"%s\" %d new subkeys\n"
msgstr "鍵%s: \"%s\" 新しい副鍵を%d個\n"
#, c-format
msgid "key %s: \"%s\" %d signature cleaned\n"
msgstr "鍵%s: \"%s\" %d個の署名をきれいにしました\n"
#, c-format
msgid "key %s: \"%s\" %d signatures cleaned\n"
msgstr "鍵%s: \"%s\" %d個の署名をきれいにしました\n"
#, c-format
msgid "key %s: \"%s\" %d user ID cleaned\n"
msgstr "鍵%s: \"%s\" %d個のユーザIDをきれいにしました\n"
#, c-format
msgid "key %s: \"%s\" %d user IDs cleaned\n"
msgstr "鍵%s: \"%s\" %d個のユーザIDをきれいにしました\n"
#, c-format
msgid "key %s: \"%s\" not changed\n"
msgstr "鍵%s:\"%s\"変更なし\n"
#, c-format
msgid "key %s: secret key imported\n"
msgstr "鍵%s: 秘密鍵をインポートしました\n"
#, c-format
msgid "key %s: secret key already exists\n"
msgstr "鍵 %s: 秘密鍵はもうあります\n"
#, c-format
msgid "key %s: error sending to agent: %s\n"
msgstr "鍵 %s: エージェントへの送信エラー: %s\n"
+#. TRANSLATORS: For a smartcard, each private key on host has a
+#. * reference (stub) to a smartcard and actual private key data
+#. * is stored on the card. A single smartcard can have up to
+#. * three private key data. Importing private key stub is always
+#. * skipped in 2.1, and it returns GPG_ERR_NOT_PROCESSED.
+#. * Instead, user should be suggested to run 'gpg --card-status',
+#. * then, references to a card will be automatically created
+#. * again.
+#, c-format
+msgid "To migrate '%s', with each smartcard, run: %s\n"
+msgstr "'%s'の移行には、スマードカードそれぞれで、以下を実行してください: %s\n"
+
#, c-format
msgid "secret key %s: %s\n"
msgstr "秘密鍵 %s: %s\n"
msgid "importing secret keys not allowed\n"
msgstr "秘密鍵のインポートは禁止です\n"
#, c-format
msgid "key %s: secret key with invalid cipher %d - skipped\n"
msgstr "鍵%s: 無効な暗号方式%dの秘密鍵です - スキップします\n"
-#. TRANSLATORS: For smartcard, each private key on
-#. host has a reference (stub) to a smartcard and
-#. actual private key data is stored on the card. A
-#. single smartcard can have up to three private key
-#. data. Importing private key stub is always
-#. skipped in 2.1, and it returns
-#. GPG_ERR_NOT_PROCESSED. Instead, user should be
-#. suggested to run 'gpg --card-status', then,
-#. references to a card will be automatically
-#. created again.
-#, c-format
-msgid "To migrate '%s', with each smartcard, run: %s\n"
-msgstr "'%s'の移行には、スマードカードそれぞれで、以下を実行してください: %s\n"
+msgid "No reason specified"
+msgstr "理由は指定されていません"
+
+msgid "Key is superseded"
+msgstr "鍵がとりかわっています"
+
+msgid "Key has been compromised"
+msgstr "鍵(の信頼性)が損なわれています"
+
+msgid "Key is no longer used"
+msgstr "鍵はもはや使われていません"
+
+msgid "User ID is no longer valid"
+msgstr "ユーザIDがもはや有効でありません"
+
+msgid "reason for revocation: "
+msgstr "失効理由: "
+
+msgid "revocation comment: "
+msgstr "失効のコメント: "
#, c-format
msgid "key %s: no public key - can't apply revocation certificate\n"
msgstr "鍵%s: 公開鍵がありません - 失効証明書を適用できません\n"
#, c-format
msgid "key %s: can't locate original keyblock: %s\n"
msgstr "鍵%s: 元の鍵ブロックに位置づけできません: %s\n"
#, c-format
msgid "key %s: can't read original keyblock: %s\n"
msgstr "鍵%s: 元の鍵ブロックを読み込めません: %s\n"
#, c-format
msgid "key %s: invalid revocation certificate: %s - rejected\n"
msgstr "鍵%s: 無効な失効証明書: %s - 拒否\n"
#, c-format
msgid "key %s: \"%s\" revocation certificate imported\n"
msgstr "鍵%s:\"%s\"失効証明書をインポートしました\n"
#, c-format
msgid "key %s: no user ID for signature\n"
msgstr "鍵%s: 署名に対応するユーザIDがありません\n"
#, c-format
msgid "key %s: unsupported public key algorithm on user ID \"%s\"\n"
msgstr "鍵%s: ユーザID\"%s\"のサポートしていない公開鍵アルゴリズムです\n"
#, c-format
msgid "key %s: invalid self-signature on user ID \"%s\"\n"
msgstr "鍵%s: ユーザID\"%s\"の自己署名が、無効です\n"
#, c-format
msgid "key %s: unsupported public key algorithm\n"
msgstr "鍵%s: サポートしていない公開鍵アルゴリズムです\n"
#, c-format
msgid "key %s: invalid direct key signature\n"
msgstr "鍵%s: 無効な直接鍵署名\n"
#, c-format
msgid "key %s: no subkey for key binding\n"
msgstr "鍵%s: 鍵に対応する副鍵がありません\n"
#, c-format
msgid "key %s: invalid subkey binding\n"
msgstr "鍵%s: 無効な副鍵の対応です\n"
#, c-format
msgid "key %s: removed multiple subkey binding\n"
msgstr "鍵%s: 多重副鍵の対応を削除します\n"
#, c-format
msgid "key %s: no subkey for key revocation\n"
msgstr "鍵%s: 鍵失効に対する副鍵がありません\n"
#, c-format
msgid "key %s: invalid subkey revocation\n"
msgstr "鍵%s: 無効な副鍵失効です\n"
#, c-format
msgid "key %s: removed multiple subkey revocation\n"
msgstr "鍵%s: 無効な副鍵の多重失効を削除します\n"
#, c-format
msgid "key %s: skipped user ID \"%s\"\n"
msgstr "鍵%s: スキップしたユーザID\"%s\"\n"
#, c-format
msgid "key %s: skipped subkey\n"
msgstr "鍵%s: スキップした副鍵\n"
#, c-format
msgid "key %s: non exportable signature (class 0x%02X) - skipped\n"
msgstr "鍵%s: エクスポート不可な署名 (クラス0x%02X) - スキップします\n"
#, c-format
msgid "key %s: revocation certificate at wrong place - skipped\n"
msgstr "鍵%s: 失効証明書が誤って設定されています - スキップします\n"
#, c-format
msgid "key %s: invalid revocation certificate: %s - skipped\n"
msgstr "鍵%s: 無効な失効証明書: %s - スキップします\n"
#, c-format
msgid "key %s: subkey signature in wrong place - skipped\n"
msgstr "鍵%s: 副鍵署名の場所が、誤っています - スキップします\n"
#, c-format
msgid "key %s: unexpected signature class (0x%02X) - skipped\n"
msgstr "鍵%s: 予期せぬ署名クラス (0x%02X) - スキップします\n"
#, c-format
msgid "key %s: duplicated user ID detected - merged\n"
msgstr "鍵%s: 重複したユーザIDの検出 - マージ\n"
#, c-format
msgid "WARNING: key %s may be revoked: fetching revocation key %s\n"
msgstr "*警告*: 鍵%sは失効されたかもしれません: 失効鍵%sを取ってきます\n"
#, c-format
msgid "WARNING: key %s may be revoked: revocation key %s not present.\n"
msgstr "*警告*: 鍵%sは失効されたかもしれません: 失効鍵%sが存在しません。\n"
#, c-format
msgid "key %s: \"%s\" revocation certificate added\n"
msgstr "鍵%s:\"%s\"失効証明書の追加\n"
#, c-format
msgid "key %s: direct key signature added\n"
msgstr "鍵%s: 直接鍵署名を追加\n"
#, c-format
msgid "error creating keybox '%s': %s\n"
msgstr "keybox'%s'の作成エラー: %s\n"
#, c-format
msgid "error creating keyring '%s': %s\n"
msgstr "鍵リング'%s'の作成エラー: %s\n"
#, c-format
msgid "keybox '%s' created\n"
msgstr "keybox'%s'が作成されました\n"
#, c-format
msgid "keyring '%s' created\n"
msgstr "鍵リング'%s'ができました\n"
#, c-format
msgid "keyblock resource '%s': %s\n"
msgstr "keyblock リソース'%s': %s\n"
#, c-format
msgid "error opening key DB: %s\n"
msgstr "鍵DBを開く際のエラー: %s\n"
#, c-format
msgid "failed to rebuild keyring cache: %s\n"
msgstr "鍵リング・キャッシュの再構築に失敗しました: %s\n"
msgid "[revocation]"
msgstr "[失効]"
msgid "[self-signature]"
msgstr "[自己署名]"
msgid ""
"Please decide how far you trust this user to correctly verify other users' "
"keys\n"
"(by looking at passports, checking fingerprints from different sources, "
"etc.)\n"
msgstr ""
"他のユーザの鍵を正しく検証するために、このユーザの信用度を決めてください\n"
"(パスポートを見せてもらったり、他から得たフィンガープリントを検査したり、など"
"など)\n"
#, c-format
msgid " %d = I trust marginally\n"
msgstr " %d = まぁまぁ信用する\n"
#, c-format
msgid " %d = I trust fully\n"
msgstr " %d = 充分に信用する\n"
msgid ""
"Please enter the depth of this trust signature.\n"
"A depth greater than 1 allows the key you are signing to make\n"
"trust signatures on your behalf.\n"
msgstr ""
"信用署名の深さを入力してください。\n"
"深さが1より大きいと、署名しようとしている鍵で信用署名を作れます。\n"
msgid "Please enter a domain to restrict this signature, or enter for none.\n"
msgstr "署名を制限するドメインを入力するか、空行を入力してください。\n"
#, c-format
msgid "Skipping user ID \"%s\", which is not a text ID.\n"
msgstr "ユーザID\"%s\"をスキップします、テキストのIDではありません。\n"
#, c-format
msgid "User ID \"%s\" is revoked."
msgstr "ユーザID\"%s\"は、失効されています。"
msgid "Are you sure you still want to sign it? (y/N) "
msgstr "それでもこの鍵に署名したいですか? (y/N) "
msgid " Unable to sign.\n"
msgstr " 署名不能。\n"
#, c-format
msgid "User ID \"%s\" is expired."
msgstr "ユーザID \"%s\"は、期限切れです。"
#, c-format
msgid "User ID \"%s\" is not self-signed."
msgstr "ユーザID \"%s\"は、自己署名されていません。"
#, c-format
msgid "User ID \"%s\" is signable. "
msgstr "ユーザID \"%s\"は署名可能です。 "
msgid "Sign it? (y/N) "
msgstr "署名しますか? (y/N) "
#, c-format
msgid ""
"The self-signature on \"%s\"\n"
"is a PGP 2.x-style signature.\n"
msgstr ""
"\"%s\"に対する自己署名は、\n"
"PGP 2.x形式の署名です。\n"
msgid "Do you want to promote it to an OpenPGP self-signature? (y/N) "
msgstr "OpenPGPの自己署名に格上げしたいですか? (y/N) "
#, c-format
msgid ""
"Your current signature on \"%s\"\n"
"has expired.\n"
msgstr ""
"\"%s\"に対するあなたの今の署名\n"
"は期限切れです。\n"
msgid "Do you want to issue a new signature to replace the expired one? (y/N) "
msgstr "新しい署名を発行し、期限切れ署名と置き換えたいですか? (y/N) "
#, c-format
msgid ""
"Your current signature on \"%s\"\n"
"is a local signature.\n"
msgstr ""
"\"%s\"に対するあなたの今の署名\n"
"はローカルな署名です。\n"
msgid "Do you want to promote it to a full exportable signature? (y/N) "
msgstr "エクスポート可能な署名に格上げしたいですか? (y/N) "
#, c-format
msgid "\"%s\" was already locally signed by key %s\n"
msgstr "\"%s\"は鍵%sでもうローカルに署名してあります\n"
#, c-format
msgid "\"%s\" was already signed by key %s\n"
msgstr "\"%s\"は鍵%sでもう署名してあります\n"
msgid "Do you want to sign it again anyway? (y/N) "
msgstr "それでも再署名したいですか? (y/N) "
#, c-format
msgid "Nothing to sign with key %s\n"
msgstr "鍵%sで署名すべきものはありません\n"
msgid "This key has expired!"
msgstr "この鍵は期限切れです!"
#, c-format
msgid "This key is due to expire on %s.\n"
msgstr "この鍵は%sで期限が切れます。\n"
msgid "Do you want your signature to expire at the same time? (Y/n) "
msgstr "同時に署名も期限切れとしたいですか? (Y/n) "
msgid ""
"How carefully have you verified the key you are about to sign actually "
"belongs\n"
"to the person named above? If you don't know what to answer, enter \"0\".\n"
msgstr ""
"署名しようとしている鍵が実際に上記の名前の人のものかどうか、どの程度\n"
"注意して検証しましたか? 答がわからなければ、\"0\"を入力してください。\n"
#, c-format
msgid " (0) I will not answer.%s\n"
msgstr " (0) 答えません。%s\n"
#, c-format
msgid " (1) I have not checked at all.%s\n"
msgstr " (1) 全然、検査していません。%s\n"
#, c-format
msgid " (2) I have done casual checking.%s\n"
msgstr " (2) 一応、検査しました。%s\n"
#, c-format
msgid " (3) I have done very careful checking.%s\n"
msgstr " (3) かなり注意して検査しました。%s\n"
msgid "Your selection? (enter '?' for more information): "
msgstr "選択は? (詳細は '?'): "
#, c-format
msgid ""
"Are you sure that you want to sign this key with your\n"
"key \"%s\" (%s)\n"
msgstr ""
"本当にこの鍵にあなたの鍵\"%s\"で署名してよいですか\n"
"(%s)\n"
msgid "This will be a self-signature.\n"
msgstr "自己署名になるでしょう。\n"
msgid "WARNING: the signature will not be marked as non-exportable.\n"
msgstr "*警告*: 署名は、エクスポート不可に設定されません。\n"
msgid "WARNING: the signature will not be marked as non-revocable.\n"
msgstr "*警告*: 署名は、失効不可に設定されません。\n"
msgid "The signature will be marked as non-exportable.\n"
msgstr "署名は、エクスポート不可に設定されます。\n"
msgid "The signature will be marked as non-revocable.\n"
msgstr "署名は、失効不可に設定されます。\n"
msgid "I have not checked this key at all.\n"
msgstr "この鍵は全然、検査していません。\n"
msgid "I have checked this key casually.\n"
msgstr "この鍵は一応、検査しました。\n"
msgid "I have checked this key very carefully.\n"
msgstr "この鍵は、かなり注意して検査しました。\n"
msgid "Really sign? (y/N) "
msgstr "本当に署名しますか? (y/N) "
#, c-format
msgid "signing failed: %s\n"
msgstr "署名に失敗しました: %s\n"
msgid "Key has only stub or on-card key items - no passphrase to change.\n"
msgstr ""
"鍵にはスタブあるいはカード上の項目しかありません - パスフレーズは変更されませ"
"ん。\n"
#, c-format
msgid "key %s: error changing passphrase: %s\n"
msgstr "鍵 %s: パスフレーズの変更エラー: %s\n"
msgid "save and quit"
msgstr "保存して終了"
msgid "show key fingerprint"
msgstr "鍵のフィンガープリントを表示"
msgid "show the keygrip"
msgstr "keygripを表示"
msgid "list key and user IDs"
msgstr "鍵とユーザIDの一覧"
msgid "select user ID N"
msgstr "ユーザID Nの選択"
msgid "select subkey N"
msgstr "副鍵Nの選択"
msgid "check signatures"
msgstr "署名の確認"
msgid "sign selected user IDs [* see below for related commands]"
msgstr "選択したユーザIDに署名する [* 以下の関連コマンドを参照 ]"
msgid "sign selected user IDs locally"
msgstr "選択したユーザIDにローカルに署名"
msgid "sign selected user IDs with a trust signature"
msgstr "選択したユーザIDに信用署名を署名する"
msgid "sign selected user IDs with a non-revocable signature"
msgstr "選択したユーザIDに失効不可の署名をする"
msgid "add a user ID"
msgstr "ユーザIDの追加"
msgid "add a photo ID"
msgstr "フォトIDの追加"
msgid "delete selected user IDs"
msgstr "選択したユーザIDの削除"
msgid "add a subkey"
msgstr "副鍵を追加"
msgid "add a key to a smartcard"
msgstr "スマートカードへ鍵の追加"
msgid "move a key to a smartcard"
msgstr "鍵をスマートカードへ移動"
msgid "move a backup key to a smartcard"
msgstr "バックアップ鍵をスマートカードへ移動"
msgid "delete selected subkeys"
msgstr "選択した副鍵の削除"
msgid "add a revocation key"
msgstr "失効鍵の追加"
msgid "delete signatures from the selected user IDs"
msgstr "選択したユーザIDから署名を削除する"
msgid "change the expiration date for the key or selected subkeys"
msgstr "鍵または選択した副鍵の有効期限を変更する"
msgid "flag the selected user ID as primary"
msgstr "選択したユーザIDを主にする"
msgid "list preferences (expert)"
msgstr "優先指定の一覧 (エキスパート)"
msgid "list preferences (verbose)"
msgstr "優先指定の一覧 (冗長)"
msgid "set preference list for the selected user IDs"
msgstr "選択したユーザIDに優先指定リストを設定"
msgid "set the preferred keyserver URL for the selected user IDs"
msgstr "選択したユーザIDに優先鍵サーバのURLを設定"
msgid "set a notation for the selected user IDs"
msgstr "選択したユーザIDに注釈を設定する"
msgid "change the passphrase"
msgstr "パスフレーズの変更"
msgid "change the ownertrust"
msgstr "所有者信用の変更"
msgid "revoke signatures on the selected user IDs"
msgstr "選択したユーザIDの署名を失効"
msgid "revoke selected user IDs"
msgstr "選択したユーザIDの失効"
msgid "revoke key or selected subkeys"
msgstr "鍵の失効または選択した副鍵の失効"
msgid "enable key"
msgstr "鍵を有効にする"
msgid "disable key"
msgstr "鍵を無効にする"
msgid "show selected photo IDs"
msgstr "選択したフォトIDを表示"
msgid "compact unusable user IDs and remove unusable signatures from key"
msgstr "使えないユーザIDをコンパクトにし、使えない署名を鍵から除去"
msgid "compact unusable user IDs and remove all signatures from key"
msgstr "使えないユーザIDをコンパクトにし、すべての署名を鍵から除去"
msgid "Secret key is available.\n"
msgstr "秘密鍵が利用できます。\n"
msgid "Secret subkeys are available.\n"
msgstr "秘密副鍵が利用できます。\n"
msgid "Need the secret key to do this.\n"
msgstr "この実行には秘密鍵がいります。\n"
msgid ""
"* The 'sign' command may be prefixed with an 'l' for local signatures "
"(lsign),\n"
" a 't' for trust signatures (tsign), an 'nr' for non-revocable signatures\n"
" (nrsign), or any combination thereof (ltsign, tnrsign, etc.).\n"
msgstr ""
"* 'sign' コマンドは 'l' で始まると、ローカルの署名で (lsign)、\n"
" 't' で始まると信用署名 (tsign)、'nr' で始まると失効不可署名\n"
" (nrsign)、もしくはこれらの組み合わせ (ltsign, tnrsign, など)となります。\n"
msgid "Key is revoked."
msgstr "鍵は、失効されています。"
msgid "Really sign all text user IDs? (y/N) "
msgstr "本当に全てのテキストユーザIDに署名しますか? (y/N) "
msgid "Really sign all user IDs? (y/N) "
msgstr "本当に全ユーザIDに署名しますか? (y/N) "
msgid "Hint: Select the user IDs to sign\n"
msgstr "ヒント: まず署名するユーザIDを選択します\n"
#, c-format
msgid "Unknown signature type '%s'\n"
msgstr "不明の署名タイプ'%s'\n"
#, c-format
msgid "This command is not allowed while in %s mode.\n"
msgstr "%sモードでこのコマンドは禁止です。\n"
msgid "You must select at least one user ID.\n"
msgstr "ユーザIDを少なくともひとつ選択してください。\n"
#, c-format
msgid "(Use the '%s' command.)\n"
msgstr "('%s'コマンドを使用してください。)\n"
msgid "You can't delete the last user ID!\n"
msgstr "最後のユーザIDは削除できません!\n"
msgid "Really remove all selected user IDs? (y/N) "
msgstr "選択した全ユーザIDを本当に削除しますか? (y/N) "
msgid "Really remove this user ID? (y/N) "
msgstr "このユーザIDを本当に削除しますか? (y/N) "
#. TRANSLATORS: Please take care: This is about
#. moving the key and not about removing it.
msgid "Really move the primary key? (y/N) "
msgstr "この主鍵を本当に移動しますか? (y/N) "
msgid "You must select exactly one key.\n"
msgstr "鍵をきっかり1つ選択してください。\n"
msgid "Command expects a filename argument\n"
msgstr "コマンドはファイル名の引数を期待します\n"
#, c-format
msgid "Can't open '%s': %s\n"
msgstr "'%s'が開けません: %s\n"
#, c-format
msgid "Error reading backup key from '%s': %s\n"
msgstr "バックアップ鍵を'%s'から読み込みする際のエラー: %s\n"
msgid "You must select at least one key.\n"
msgstr "鍵を少なくとも1本選択してください。\n"
msgid "Do you really want to delete the selected keys? (y/N) "
msgstr "選択した鍵を本当に削除しますか? (y/N) "
msgid "Do you really want to delete this key? (y/N) "
msgstr "この鍵を本当に削除しますか? (y/N) "
msgid "Really revoke all selected user IDs? (y/N) "
msgstr "選択した全ユーザIDを本当に失効しますか? (y/N) "
msgid "Really revoke this user ID? (y/N) "
msgstr "このユーザIDを本当に失効しますか? (y/N) "
msgid "Do you really want to revoke the entire key? (y/N) "
msgstr "この鍵全体を本当に失効しますか? (y/N) "
msgid "Do you really want to revoke the selected subkeys? (y/N) "
msgstr "選択した副鍵を本当に失効しますか? (y/N) "
msgid "Do you really want to revoke this subkey? (y/N) "
msgstr "この副鍵を本当に失効しますか? (y/N) "
msgid "Owner trust may not be set while using a user provided trust database\n"
msgstr ""
"ユーザが指定した信用データベースを利用中、所有者信用は設定できません。\n"
msgid "Set preference list to:\n"
msgstr "優先指定の一覧を設定:\n"
msgid "Really update the preferences for the selected user IDs? (y/N) "
msgstr "選択したユーザIDの優先指定を本当に更新しますか? (y/N) "
msgid "Really update the preferences? (y/N) "
msgstr "優先指定を本当に更新しますか? (y/N) "
msgid "Save changes? (y/N) "
msgstr "変更を保存しますか? (y/N) "
msgid "Quit without saving? (y/N) "
msgstr "保存せずに終了しますか? (y/N) "
-#, c-format
-msgid "update failed: %s\n"
-msgstr "更新に失敗しました: %s\n"
-
msgid "Key not changed so no update needed.\n"
msgstr "鍵は無変更なので更新は不要です。\n"
msgid "cannot revoke the last valid user ID.\n"
msgstr "最後の有効なユーザIDは失効できません。\n"
#, c-format
msgid "revoking the user ID failed: %s\n"
msgstr "ユーザIDの失効に失敗しました: %s\n"
#, c-format
msgid "setting the primary user ID failed: %s\n"
msgstr "プライマリ・ユーザIDの設定に失敗しました: %s\n"
#, c-format
msgid "\"%s\" is not a fingerprint\n"
msgstr "\"%s\"はフィンガープリントではありません\n"
#, c-format
msgid "\"%s\" is not the primary fingerprint\n"
msgstr "\"%s\" はプライマリ・フィンガープリントではありません\n"
#, c-format
msgid "Invalid user ID '%s': %s\n"
msgstr "無効なユーザID '%s': %s\n"
msgid "No matching user IDs."
msgstr "マッチするユーザIDはありません。"
msgid "Nothing to sign.\n"
msgstr "署名するものがありません。\n"
#, c-format
msgid "'%s' is not a valid expiration time\n"
msgstr "'%s'は、有効な有効期限ではありません\n"
#, c-format
msgid "\"%s\" is not a proper fingerprint\n"
-msgstr "\"%s\"はフ正しいィンガープリントではありません\n"
+msgstr "\"%s\"は正しいフィンガープリントではありません\n"
#, c-format
msgid "subkey \"%s\" not found\n"
msgstr "副鍵\"%s\"が見つかりません\n"
+msgid "AEAD: "
+msgstr "AEAD: "
+
msgid "Digest: "
msgstr "ダイジェスト: "
msgid "Features: "
msgstr "機能: "
msgid "Keyserver no-modify"
msgstr "鍵サーバ 修正しない"
msgid "Preferred keyserver: "
msgstr "優先鍵サーバ: "
msgid "Notations: "
msgstr "注釈: "
msgid "There are no preferences on a PGP 2.x-style user ID.\n"
msgstr "PGP 2.x形式ユーザIDの優先指定が、ありません。\n"
#, c-format
msgid "The following key was revoked on %s by %s key %s\n"
msgstr "%sで%s鍵%sによって以下の鍵は、失効されました\n"
#, c-format
msgid "This key may be revoked by %s key %s"
msgstr "この鍵は、%s鍵%sによって失効可能です"
msgid "(sensitive)"
msgstr "(機密指定)"
#, c-format
msgid "created: %s"
msgstr "作成: %s"
#, c-format
msgid "revoked: %s"
msgstr "失効: %s"
#, c-format
msgid "expired: %s"
msgstr "期限切れ: %s"
#, c-format
msgid "expires: %s"
msgstr "有効期限: %s"
#, c-format
msgid "usage: %s"
msgstr "利用法: %s"
msgid "card-no: "
msgstr "カード番号: "
#, c-format
msgid "trust: %s"
msgstr "信用: %s"
#, c-format
msgid "validity: %s"
msgstr "有効性: %s"
msgid "This key has been disabled"
msgstr "この鍵は使用禁止に設定されています"
msgid ""
"Please note that the shown key validity is not necessarily correct\n"
"unless you restart the program.\n"
msgstr ""
"プログラムを再起動するまで、表示された鍵の有効性は正しくないかもしれない、\n"
"ということを念頭においてください。\n"
msgid "revoked"
msgstr "失効"
msgid "expired"
msgstr "期限切れ"
msgid ""
"WARNING: no user ID has been marked as primary. This command may\n"
" cause a different user ID to become the assumed primary.\n"
msgstr ""
"*警告*: 主たるユーザIDがありません。このコマンドは、別な\n"
" ユーザIDが主になると仮定する場合があります。\n"
msgid "WARNING: Your encryption subkey expires soon.\n"
msgstr "*警告*: あなたの暗号副鍵はもうすぐ期限切れとなります。\n"
msgid "You may want to change its expiration date too.\n"
msgstr "その有効期限も変更したいでしょう\n"
msgid ""
"WARNING: This is a PGP2-style key. Adding a photo ID may cause some "
"versions\n"
" of PGP to reject this key.\n"
msgstr ""
"*警告*: これはPGP2形式の鍵です。フォトIDの追加で、一部のバージョンのPGPで"
"は、\n"
" この鍵を拒否するかもしれません。\n"
msgid "Are you sure you still want to add it? (y/N) "
msgstr "それでも追加したいですか? (y/N) "
msgid "You may not add a photo ID to a PGP2-style key.\n"
msgstr "PGP2形式の鍵にはフォトIDを追加できません。\n"
msgid "Such a user ID already exists on this key!\n"
msgstr "そういったユーザIDはすでにこの鍵に存在しています!\n"
msgid "Delete this good signature? (y/N/q)"
msgstr "この正しい署名を削除しますか? (y/N/q)"
msgid "Delete this invalid signature? (y/N/q)"
msgstr "この無効な署名を削除しますか? (y/N/q)"
msgid "Delete this unknown signature? (y/N/q)"
msgstr "この不明の署名を削除しますか? (y/N/q)"
msgid "Really delete this self-signature? (y/N)"
msgstr "この自己署名を本当に削除しますか? (y/N)"
#, c-format
msgid "Deleted %d signature.\n"
msgid_plural "Deleted %d signatures.\n"
msgstr[0] "%d個の署名を削除しました。\n"
msgid "Nothing deleted.\n"
msgstr "何も削除していません。\n"
msgid "invalid"
msgstr "無効"
#, c-format
msgid "User ID \"%s\" compacted: %s\n"
msgstr "ユーザID \"%s\" は、コンパクトになりました: %s\n"
#, c-format
msgid "User ID \"%s\": %d signature removed\n"
msgid_plural "User ID \"%s\": %d signatures removed\n"
msgstr[0] "ユーザID \"%s\": %d の署名が除去されました\n"
#, c-format
msgid "User ID \"%s\": already minimized\n"
msgstr "ユーザID \"%s\": 既に最小化されています\n"
#, c-format
msgid "User ID \"%s\": already clean\n"
msgstr "ユーザID \"%s\": 既にクリーンとなっています\n"
msgid ""
"WARNING: This is a PGP 2.x-style key. Adding a designated revoker may "
"cause\n"
" some versions of PGP to reject this key.\n"
msgstr ""
"*警告*: これはPGP 2.x形式の鍵です。指名失効者の追加で、一部のバージョンのPGP"
"では、\n"
" この鍵を拒否するかもしれません。\n"
msgid "You may not add a designated revoker to a PGP 2.x-style key.\n"
msgstr "PGP 2.x形式の鍵には指名失効者を追加できません。\n"
msgid "Enter the user ID of the designated revoker: "
msgstr "指名失効者のユーザIDを入力してください: "
msgid "cannot appoint a PGP 2.x style key as a designated revoker\n"
msgstr "PGP 2.x形式の鍵は、指名失効者に任命できません\n"
msgid "you cannot appoint a key as its own designated revoker\n"
msgstr "指名失効者には、その鍵自体を任命できません\n"
msgid "this key has already been designated as a revoker\n"
msgstr "この鍵は失効者としてもう指名されています\n"
msgid "WARNING: appointing a key as a designated revoker cannot be undone!\n"
msgstr "*警告*: ある鍵を指名失効者に設定すると、元に戻せません!\n"
msgid ""
"Are you sure you want to appoint this key as a designated revoker? (y/N) "
msgstr "本当にこの鍵を指名失効者に任命しますか? (y/N) "
msgid ""
"Are you sure you want to change the expiration time for multiple subkeys? (y/"
"N) "
msgstr "本当に複数の副鍵の失効期限を変更しますか? (y/N) "
msgid "Changing expiration time for a subkey.\n"
msgstr "副鍵の有効期限を変更します。\n"
msgid "Changing expiration time for the primary key.\n"
msgstr "主鍵の有効期限を変更します。\n"
msgid "You can't change the expiration date of a v3 key\n"
msgstr "v3鍵の有効期限は変更できません\n"
msgid "Changing usage of a subkey.\n"
msgstr "副鍵の使用法を変更します。\n"
msgid "Changing usage of the primary key.\n"
msgstr "主鍵の使用法を変更します。\n"
#, c-format
msgid "signing subkey %s is already cross-certified\n"
msgstr "署名する副鍵%sはすでに相互証明されています\n"
#, c-format
msgid "subkey %s does not sign and so does not need to be cross-certified\n"
msgstr "副鍵 %s は署名をしないので、相互証明の必要はありません\n"
msgid "Please select exactly one user ID.\n"
msgstr "ユーザIDをきっかりひとつ選択してください。\n"
#, c-format
msgid "skipping v3 self-signature on user ID \"%s\"\n"
msgstr "ユーザID\"%s\"のv3自己署名をスキップします\n"
msgid "Enter your preferred keyserver URL: "
msgstr "優先鍵サーバURLを入力してください: "
msgid "Are you sure you want to replace it? (y/N) "
msgstr "本当に置き換えたいですか? (y/N) "
msgid "Are you sure you want to delete it? (y/N) "
msgstr "本当に削除したいですか? (y/N) "
msgid "Enter the notation: "
msgstr "注釈を入力: "
msgid "Proceed? (y/N) "
msgstr "進みますか? (y/N) "
#, c-format
msgid "No user ID with index %d\n"
msgstr "%d番のユーザIDはありません\n"
#, c-format
msgid "No user ID with hash %s\n"
msgstr "ハッシュ%sのユーザIDはありません\n"
#, c-format
msgid "No subkey with key ID '%s'.\n"
msgstr "鍵ID'%s'の副鍵はありません\n"
#, c-format
msgid "No subkey with index %d\n"
msgstr "%d番の副鍵はありません\n"
#, c-format
msgid "user ID: \"%s\"\n"
msgstr "ユーザID:\"%s\"\n"
#, c-format
msgid "signed by your key %s on %s%s%s\n"
msgstr "%sで%s%s%sに署名されています\n"
msgid " (non-exportable)"
msgstr " (エクスポート不可)"
#, c-format
msgid "This signature expired on %s.\n"
msgstr "この署名は%sで期限切れです。\n"
msgid "Are you sure you still want to revoke it? (y/N) "
msgstr "それでも本当に失効したいですか? (y/N) "
msgid "Create a revocation certificate for this signature? (y/N) "
msgstr "この署名に対する失効証明書を作成しますか? (y/N) "
msgid "Not signed by you.\n"
msgstr "あなたによって署名されていません。\n"
#, c-format
msgid "You have signed these user IDs on key %s:\n"
msgstr "これらのユーザIDに鍵%sで署名しました:\n"
msgid " (non-revocable)"
msgstr " (失効不可)"
#, c-format
msgid "revoked by your key %s on %s\n"
msgstr "あなたの鍵%sで%sに失効されています\n"
msgid "You are about to revoke these signatures:\n"
msgstr "これらの署名を失効しようとしています:\n"
msgid "Really create the revocation certificates? (y/N) "
msgstr "失効証明書を本当に作成しますか? (y/N) "
msgid "no secret key\n"
msgstr "秘密鍵がありません\n"
#, c-format
msgid "tried to revoke a non-user ID: %s\n"
msgstr "ユーザIDでないものを失効しようとしました: %s\n"
#, c-format
msgid "user ID \"%s\" is already revoked\n"
msgstr "ユーザID\"%s\"は、もう失効されています\n"
#, c-format
msgid "WARNING: a user ID signature is dated %d seconds in the future\n"
msgstr "*警告*: ユーザID署名が、%d秒未来です\n"
msgid "Cannot revoke the last valid user ID.\n"
msgstr "最後の有効なユーザIDは失効できません。\n"
#, c-format
msgid "Key %s is already revoked.\n"
msgstr "鍵 %s は、もう失効されています。\n"
#, c-format
msgid "Subkey %s is already revoked.\n"
msgstr "副鍵 %s は、もう失効されています。\n"
#, c-format
msgid "Displaying %s photo ID of size %ld for key %s (uid %d)\n"
msgstr "%s (大きさ%ld) の鍵%s (uid %d) のフォトIDとして表示\n"
#, c-format
msgid "invalid value for option '%s'\n"
msgstr "オプション'%s'に無効な値です\n"
#, c-format
msgid "preference '%s' duplicated\n"
msgstr "優先指定'%s'の重複\n"
msgid "too many cipher preferences\n"
msgstr "暗号方式の優先指定が多すぎます\n"
msgid "too many digest preferences\n"
msgstr "ダイジェストの優先指定が多すぎます\n"
msgid "too many compression preferences\n"
msgstr "圧縮の優先指定が多すぎます\n"
+msgid "too many AEAD preferences\n"
+msgstr "AEAD方式の優先指定が多すぎます\n"
+
#, c-format
msgid "invalid item '%s' in preference string\n"
msgstr "優先指定の文字列に無効な項目'%s'があります\n"
msgid "writing direct signature\n"
msgstr "直接署名を書き込みます\n"
msgid "writing self signature\n"
msgstr "自己署名を書き込みます\n"
msgid "writing key binding signature\n"
msgstr "鍵対応への署名を書き込みます\n"
#, c-format
msgid "keysize invalid; using %u bits\n"
msgstr "無効な鍵長。%uビットにします\n"
#, c-format
msgid "keysize rounded up to %u bits\n"
msgstr "鍵長を%uビットに丸めます\n"
msgid ""
"WARNING: some OpenPGP programs can't handle a DSA key with this digest size\n"
msgstr ""
"*警告*: いくつかのOpenPGPプログラムはこのダイジェスト長のDSA鍵を扱うことがで"
"きません\n"
msgid "Sign"
msgstr "Sign"
msgid "Certify"
msgstr "Certify"
msgid "Encrypt"
msgstr "Encrypt"
msgid "Authenticate"
msgstr "Authenticate"
#. TRANSLATORS: Please use only plain ASCII characters for the
-#. translation. If this is not possible use single digits. The
-#. string needs to 8 bytes long. Here is a description of the
-#. functions:
-#.
-#. s = Toggle signing capability
-#. e = Toggle encryption capability
-#. a = Toggle authentication capability
-#. q = Finish
+#. * translation. If this is not possible use single digits. The
+#. * string needs to 8 bytes long. Here is a description of the
+#. * functions:
+#. *
+#. * s = Toggle signing capability
+#. * e = Toggle encryption capability
+#. * a = Toggle authentication capability
+#. * q = Finish
#.
msgid "SsEeAaQq"
msgstr "SsEeAaQq"
#, c-format
-msgid "Possible actions for a %s key: "
-msgstr "鍵%sに認められた操作: "
+msgid "Possible actions for this %s key: "
+msgstr "この鍵%sにありうる操作: "
msgid "Current allowed actions: "
msgstr "現在の認められた操作: "
#, c-format
msgid " (%c) Toggle the sign capability\n"
msgstr " (%c) 署名機能を反転する\n"
#, c-format
msgid " (%c) Toggle the encrypt capability\n"
msgstr " (%c) 暗号機能を反転する\n"
#, c-format
msgid " (%c) Toggle the authenticate capability\n"
msgstr " (%c) 認証機能を反転する\n"
#, c-format
msgid " (%c) Finished\n"
msgstr " (%c) 完了\n"
#, c-format
msgid " (%d) RSA and RSA (default)\n"
msgstr " (%d) RSA と RSA (デフォルト)\n"
#, c-format
msgid " (%d) DSA and Elgamal\n"
msgstr " (%d) DSA と Elgamal\n"
#, c-format
msgid " (%d) DSA (sign only)\n"
msgstr " (%d) DSA (署名のみ)\n"
#, c-format
msgid " (%d) RSA (sign only)\n"
msgstr " (%d) RSA (署名のみ)\n"
#, c-format
msgid " (%d) Elgamal (encrypt only)\n"
msgstr " (%d) Elgamal (暗号化のみ)\n"
#, c-format
msgid " (%d) RSA (encrypt only)\n"
msgstr " (%d) RSA (暗号化のみ)\n"
#, c-format
msgid " (%d) DSA (set your own capabilities)\n"
msgstr " (%d) DSA (機能をあなた自身で設定)\n"
#, c-format
msgid " (%d) RSA (set your own capabilities)\n"
msgstr " (%d) RSA (機能をあなた自身で設定)\n"
#, c-format
msgid " (%d) ECC and ECC\n"
msgstr " (%d) ECC と ECC\n"
#, c-format
msgid " (%d) ECC (sign only)\n"
msgstr " (%d) ECC (署名のみ)\n"
#, c-format
msgid " (%d) ECC (set your own capabilities)\n"
msgstr " (%d) ECC (機能をあなた自身で設定)\n"
#, c-format
msgid " (%d) ECC (encrypt only)\n"
msgstr " (%d) ECC (暗号化のみ)\n"
#, c-format
msgid " (%d) Existing key\n"
msgstr " (%d) 既存の鍵\n"
+#, c-format
+msgid " (%d) Existing key from card\n"
+msgstr " (%d) カードに存在する鍵\n"
+
msgid "Enter the keygrip: "
msgstr "keygripを入力: "
msgid "Not a valid keygrip (expecting 40 hex digits)\n"
msgstr "有効なkeygrip (40桁の16進数字)ではありません\n"
msgid "No key with this keygrip\n"
msgstr "このkeygripの鍵はありません\n"
+#, c-format
+msgid "error reading the card: %s\n"
+msgstr "カードの読み込みエラー: %s\n"
+
+#, c-format
+msgid "Serial number of the card: %s\n"
+msgstr "カードのシリアル番号: %s\n"
+
+msgid "Available keys:\n"
+msgstr "利用可能な鍵:\n"
+
#, c-format
msgid "rounded to %u bits\n"
msgstr "%uビットに切り上げます\n"
#, c-format
msgid "%s keys may be between %u and %u bits long.\n"
msgstr "%s 鍵は %u から %u ビットの長さで可能です。\n"
#, c-format
msgid "What keysize do you want for the subkey? (%u) "
msgstr "副鍵の鍵長は? (%u) "
#, c-format
msgid "Requested keysize is %u bits\n"
msgstr "要求された鍵長は%uビット\n"
msgid "Please select which elliptic curve you want:\n"
msgstr "ご希望の楕円曲線を選択してください:\n"
msgid ""
"Please specify how long the key should be valid.\n"
" 0 = key does not expire\n"
" <n> = key expires in n days\n"
" <n>w = key expires in n weeks\n"
" <n>m = key expires in n months\n"
" <n>y = key expires in n years\n"
msgstr ""
"鍵の有効期限を指定してください。\n"
" 0 = 鍵は無期限\n"
" <n> = 鍵は n 日間で期限切れ\n"
" <n>w = 鍵は n 週間で期限切れ\n"
" <n>m = 鍵は n か月間で期限切れ\n"
" <n>y = 鍵は n 年間で期限切れ\n"
msgid ""
"Please specify how long the signature should be valid.\n"
" 0 = signature does not expire\n"
" <n> = signature expires in n days\n"
" <n>w = signature expires in n weeks\n"
" <n>m = signature expires in n months\n"
" <n>y = signature expires in n years\n"
msgstr ""
"署名の有効期限を指定してください。\n"
" 0 = 署名は無期限\n"
" <n> = 署名は n 日間で期限切れ\n"
" <n>w = 署名は n 週間で期限切れ\n"
" <n>m = 署名は n か月間で期限切れ\n"
" <n>y = 署名は n 年間で期限切れ\n"
msgid "Key is valid for? (0) "
msgstr "鍵の有効期間は? (0)"
#, c-format
msgid "Signature is valid for? (%s) "
msgstr "署名の有効期間は? (%s)"
msgid "invalid value\n"
msgstr "無効な値\n"
msgid "Key does not expire at all\n"
msgstr "鍵は無期限です\n"
msgid "Signature does not expire at all\n"
msgstr "署名は無期限です\n"
#, c-format
msgid "Key expires at %s\n"
msgstr "鍵は%sで期限切れとなります\n"
#, c-format
msgid "Signature expires at %s\n"
msgstr "署名は%sで期限切れとなります\n"
msgid ""
"Your system can't display dates beyond 2038.\n"
"However, it will be correctly handled up to 2106.\n"
msgstr ""
"このシステムでは、2038年以降の日付を表示することはできませんが、\n"
"2106年まで正しく処理されます。\n"
msgid "Is this correct? (y/N) "
msgstr "これで正しいですか? (y/N) "
msgid ""
"\n"
"GnuPG needs to construct a user ID to identify your key.\n"
"\n"
msgstr ""
"\n"
"GnuPGはあなたの鍵を識別するためにユーザIDを構成する必要があります。\n"
"\n"
#. TRANSLATORS: This string is in general not anymore used
#. but you should keep your existing translation. In case
#. the new string is not translated this old string will
#. be used.
msgid ""
"\n"
"You need a user ID to identify your key; the software constructs the user "
"ID\n"
"from the Real Name, Comment and Email Address in this form:\n"
" \"Heinrich Heine (Der Dichter) <heinrichh@duesseldorf.de>\"\n"
"\n"
msgstr ""
"\n"
"あなたの鍵を識別するためにユーザIDが必要です。\n"
"このソフトは本名、コメント、電子メール・アドレスから\n"
"次の書式でユーザIDを構成します:\n"
" \"Heinrich Heine (Der Dichter) <heinrichh@duesseldorf.de>\"\n"
"\n"
msgid "Real name: "
msgstr "本名: "
msgid "Invalid character in name\n"
msgstr "名前に無効な文字があります\n"
#, c-format
msgid "The characters '%s' and '%s' may not appear in name\n"
msgstr "キャラクタ'%s'と'%s'は名前に使えません\n"
msgid "Name may not start with a digit\n"
msgstr "名前を数字で始めてはいけません\n"
msgid "Name must be at least 5 characters long\n"
msgstr "名前は5文字以上でなければなりません\n"
msgid "Email address: "
msgstr "電子メール・アドレス: "
msgid "Not a valid email address\n"
msgstr "有効な電子メール・アドレスではありません\n"
msgid "Comment: "
msgstr "コメント: "
msgid "Invalid character in comment\n"
msgstr "コメントに無効な文字があります\n"
#, c-format
msgid "You are using the '%s' character set.\n"
msgstr "あなたは文字集合'%s'を使っています。\n"
#, c-format
msgid ""
"You selected this USER-ID:\n"
" \"%s\"\n"
"\n"
msgstr ""
"次のユーザIDを選択しました:\n"
" \"%s\"\n"
"\n"
msgid "Please don't put the email address into the real name or the comment\n"
msgstr "電子メールのアドレスを本名やコメントに入れないように\n"
#. TRANSLATORS: These are the allowed answers in
#. lower and uppercase. Below you will find the matching
#. string which should be translated accordingly and the
#. letter changed to match the one in the answer string.
#.
#. n = Change name
#. c = Change comment
#. e = Change email
#. o = Okay (ready, continue)
#. q = Quit
#.
msgid "NnCcEeOoQq"
msgstr "NnCcEeOoQq"
msgid "Change (N)ame, (C)omment, (E)mail or (Q)uit? "
msgstr "名前(N)、コメント(C)、電子メール(E)の変更、または終了(Q)? "
msgid "Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? "
msgstr "名前(N)、コメント(C)、電子メール(E)の変更、またはOK(O)か終了(Q)? "
msgid "Change (N)ame, (E)mail, or (Q)uit? "
msgstr "名前(N)、電子メール(E)の変更、または終了(Q)? "
msgid "Change (N)ame, (E)mail, or (O)kay/(Q)uit? "
msgstr "名前(N)、電子メール(E)の変更、またはOK(O)か終了(Q)? "
msgid "Please correct the error first\n"
msgstr "まずエラーを修正してください\n"
msgid ""
"We need to generate a lot of random bytes. It is a good idea to perform\n"
"some other action (type on the keyboard, move the mouse, utilize the\n"
"disks) during the prime generation; this gives the random number\n"
"generator a better chance to gain enough entropy.\n"
msgstr ""
"たくさんのランダム・バイトの生成が必要です。キーボードを打つ、マウスを動か\n"
"す、ディスクにアクセスするなどの他の操作を素数生成の間に行うことで、乱数生\n"
"成器に十分なエントロピーを供給する機会を与えることができます。\n"
#, c-format
msgid "Key generation failed: %s\n"
msgstr "鍵の生成に失敗しました: %s\n"
#, c-format
msgid ""
"About to create a key for:\n"
" \"%s\"\n"
"\n"
msgstr ""
"鍵を作成します:\n"
" \"%s\"\n"
"\n"
msgid "Continue? (Y/n) "
msgstr "続けますか? (Y/n) "
#, c-format
msgid "A key for \"%s\" already exists\n"
msgstr "\"%s\" の鍵はもうあります\n"
msgid "Create anyway? (y/N) "
msgstr "それでも鍵を作成しますか? (y/N) "
msgid "creating anyway\n"
msgstr "いずれにしろ鍵を作成\n"
#, c-format
msgid "Note: Use \"%s %s\" for a full featured key generation dialog.\n"
msgstr "注意: 全機能の鍵生成には \"%s %s\" を使います。\n"
msgid "Key generation canceled.\n"
msgstr "鍵の生成が取り消されました。\n"
#, c-format
msgid "can't create backup file '%s': %s\n"
msgstr "バックアップ・ファイル'%s'が作成できません: %s\n"
#, c-format
msgid "Note: backup of card key saved to '%s'\n"
msgstr "注意: カード鍵のバックアップが'%s'へ保存されます\n"
#, c-format
msgid "writing public key to '%s'\n"
msgstr "'%s'へ公開鍵を書き込みます\n"
#, c-format
msgid "no writable public keyring found: %s\n"
msgstr "書き込み可能な公開鍵リングが見つかりません: %s\n"
#, c-format
msgid "error writing public keyring '%s': %s\n"
msgstr "公開鍵リング'%s'の書き込みエラー: %s\n"
msgid "public and secret key created and signed.\n"
msgstr "公開鍵と秘密鍵を作成し、署名しました。\n"
msgid ""
"Note that this key cannot be used for encryption. You may want to use\n"
"the command \"--edit-key\" to generate a subkey for this purpose.\n"
msgstr ""
"この鍵は暗号化には使用できないことに注意してください。暗号化を行うには、\n"
"\"--edit-key\"コマンドを使って副鍵を生成してください。\n"
#, c-format
msgid ""
"key has been created %lu second in future (time warp or clock problem)\n"
msgstr "鍵は%lu秒未来にできました (時間歪曲か時計の障害でしょう)\n"
#, c-format
msgid ""
"key has been created %lu seconds in future (time warp or clock problem)\n"
msgstr "鍵は%lu秒未来にできました (時間歪曲か時計の障害でしょう)\n"
msgid "Note: creating subkeys for v3 keys is not OpenPGP compliant\n"
msgstr "注意: v3鍵に対する副鍵の作成は、OpenPGPに適合しません\n"
msgid "Secret parts of primary key are not available.\n"
msgstr "主鍵の秘密部分が利用できません。\n"
msgid "Secret parts of primary key are stored on-card.\n"
msgstr "主鍵の秘密部分はカード上に保存されています。\n"
msgid "Really create? (y/N) "
msgstr "本当に作成しますか? (y/N) "
msgid "never "
msgstr "無期限 "
msgid "Critical signature policy: "
msgstr "クリティカルな署名ポリシー: "
msgid "Signature policy: "
msgstr "署名ポリシー: "
msgid "Critical preferred keyserver: "
msgstr "クリティカルな優先鍵サーバ: "
msgid "Critical signature notation: "
msgstr "クリティカルな署名注釈: "
msgid "Signature notation: "
msgstr "署名注釈: "
#, c-format
msgid "%d good signature\n"
msgid_plural "%d good signatures\n"
msgstr[0] "正しい署名%d個\n"
#, c-format
msgid "%d bad signature\n"
msgid_plural "%d bad signatures\n"
msgstr[0] "%d個の不正な署名\n"
#, c-format
msgid "%d signature not checked due to a missing key\n"
msgid_plural "%d signatures not checked due to missing keys\n"
msgstr[0] "鍵がないため%d個の署名は検査しません\n"
#, c-format
msgid "%d signature not checked due to an error\n"
msgid_plural "%d signatures not checked due to errors\n"
msgstr[0] "エラーのため%d個の署名を検査しません\n"
#, c-format
msgid "Warning: %lu key skipped due to its large size\n"
msgid_plural "Warning: %lu keys skipped due to their large sizes\n"
msgstr[0] "警告: %lu個の鍵がその大きさのためスキップされました\n"
msgid "Keyring"
msgstr "鍵リング"
msgid "Primary key fingerprint:"
msgstr " 主鍵フィンガープリント:"
msgid " Subkey fingerprint:"
msgstr " 副鍵フィンガープリント:"
#. TRANSLATORS: this should fit into 24 bytes so that the
#. * fingerprint data is properly aligned with the user ID
msgid " Primary key fingerprint:"
msgstr " 主鍵フィンガープリント:"
msgid " Subkey fingerprint:"
msgstr " 副鍵フィンガープリント:"
msgid " Key fingerprint ="
msgstr " フィンガープリント ="
msgid " Card serial no. ="
msgstr " カードシリアル番号 ="
#, c-format
msgid "caching keyring '%s'\n"
msgstr "鍵リング'%s'をキャッシュします\n"
#, c-format
msgid "%lu keys cached so far (%lu signature)\n"
msgid_plural "%lu keys cached so far (%lu signatures)\n"
msgstr[0] "これまで%lu個の鍵をキャッシュしました (%lu個の署名)\n"
#, c-format
msgid "%lu key cached"
msgid_plural "%lu keys cached"
msgstr[0] "%lu個の鍵をキャッシュしました"
#, c-format
msgid " (%lu signature)\n"
msgid_plural " (%lu signatures)\n"
msgstr[0] " (%lu個の不正な署名)\n"
#, c-format
msgid "%s: keyring created\n"
msgstr "%s: 鍵リングができました\n"
msgid "override proxy options set for dirmngr"
msgstr "dirmngrのプロキシ・オプション設定を押し切る"
msgid "include revoked keys in search results"
msgstr "失効した鍵を検索結果に含める"
msgid "include subkeys when searching by key ID"
msgstr "key IDによる検索に副鍵も含める"
msgid "override timeout options set for dirmngr"
msgstr "dirmngrのタイムアウト・オプション設定を押し切る"
msgid "automatically retrieve keys when verifying signatures"
msgstr "署名の検証時に自動的に鍵を取得する"
msgid "honor the preferred keyserver URL set on the key"
msgstr "鍵に設定される優先鍵サーバURLを与える"
msgid "honor the PKA record set on a key when retrieving keys"
msgstr "鍵に設定されたPKAレコードを鍵の取得時に与える"
msgid "disabled"
msgstr "使用禁止"
msgid "Enter number(s), N)ext, or Q)uit > "
msgstr "番号(s)、N)次、またはQ)中止を入力してください >"
#, c-format
msgid "invalid keyserver protocol (us %d!=handler %d)\n"
msgstr "無効な鍵サーバ・プロトコルです (us %d!=handler %d)\n"
#, c-format
msgid "\"%s\" not a key ID: skipping\n"
msgstr "\"%s\"鍵IDではありません: スキップします\n"
#, c-format
msgid "refreshing %d key from %s\n"
msgid_plural "refreshing %d keys from %s\n"
msgstr[0] "%d本の鍵を%sから更新\n"
#, c-format
msgid "WARNING: unable to refresh key %s via %s: %s\n"
msgstr "*警告*: 鍵%sを%s経由で更新できません: %s\n"
#, c-format
msgid "key \"%s\" not found on keyserver\n"
msgstr "鍵\"%s\"が鍵サーバに見つかりません\n"
msgid "key not found on keyserver\n"
msgstr "鍵が鍵サーバに見つかりません\n"
-msgid "no keyserver known (use option --keyserver)\n"
-msgstr "既知の鍵サーバがありません (オプション--keyserverを使いましょう)\n"
-
#, c-format
msgid "requesting key %s from %s server %s\n"
msgstr "鍵%sを%sからサーバ%sに要求\n"
#, c-format
msgid "requesting key %s from %s\n"
msgstr "鍵%sを%sに要求\n"
msgid "no keyserver known\n"
msgstr "鍵サーバがわかりません\n"
#, c-format
msgid "skipped \"%s\": %s\n"
msgstr "\"%s\"をスキップしました: %s\n"
#, c-format
msgid "sending key %s to %s\n"
msgstr "鍵%sを%sへ送信\n"
#, c-format
msgid "requesting key from '%s'\n"
msgstr "鍵を'%s'から要求\n"
#, c-format
msgid "WARNING: unable to fetch URI %s: %s\n"
msgstr "*警告*: URI %s からデータを取れません: %s\n"
#, c-format
msgid "weird size for an encrypted session key (%d)\n"
-msgstr "変な長さの暗号化済みセッション鍵 (%d)\n"
+msgstr "変な長さの暗号化セッション鍵 (%d)\n"
#, c-format
-msgid "%s encrypted session key\n"
-msgstr "%s 暗号化済みセッション鍵\n"
+msgid "%s.%s encrypted session key\n"
+msgstr "%s.%s 暗号化セッション鍵\n"
+
+#, c-format
+msgid "encrypted with unknown algorithm %d.%s\n"
+msgstr "不明のアルゴリズム%d.%sによる暗号化\n"
#, c-format
msgid "passphrase generated with unknown digest algorithm %d\n"
msgstr "不明のダイジェスト・アルゴリズムで生成されたパスフレーズ %d\n"
#, c-format
msgid "public key is %s\n"
msgstr "公開鍵は%sです\n"
-msgid "public key encrypted data: good DEK\n"
-msgstr "公開鍵による暗号化済みデータ: 正しいDEKです\n"
-
#, c-format
-msgid "encrypted with %u-bit %s key, ID %s, created %s\n"
-msgstr "%u-ビット%s鍵, ID %s, 日付%sに暗号化されました\n"
+msgid "encrypted with %s key, ID %s, created %s\n"
+msgstr "%s鍵, ID %s, 作成日付%s により暗号化されました\n"
#, c-format
msgid " \"%s\"\n"
msgstr " \"%s\"\n"
#, c-format
msgid "encrypted with %s key, ID %s\n"
msgstr "%s鍵, ID %sで暗号化されました\n"
-#, c-format
-msgid "public key decryption failed: %s\n"
-msgstr "公開鍵の復号に失敗しました: %s\n"
+msgid "WARNING: multiple plaintexts seen\n"
+msgstr "*警告*: 複数のプレインテクストが見られます\n"
#, c-format
msgid "encrypted with %lu passphrases\n"
msgstr "%lu 個のパスフレーズで暗号化\n"
msgid "encrypted with 1 passphrase\n"
msgstr "1 個のパスフレーズで暗号化\n"
+#, c-format
+msgid "public key decryption failed: %s\n"
+msgstr "公開鍵の復号に失敗しました: %s\n"
+
+msgid "public key encrypted data: good DEK\n"
+msgstr "公開鍵による暗号化データ: 正しいDEKです\n"
+
#, c-format
msgid "assuming %s encrypted data\n"
-msgstr "%s暗号化済みデータを仮定\n"
+msgstr "%s暗号化データを仮定\n"
#, c-format
msgid "IDEA cipher unavailable, optimistically attempting to use %s instead\n"
msgstr "IDEA暗号方式は利用不能なので、楽天的ですが%sで代用しようとしています\n"
msgid "WARNING: message was not integrity protected\n"
msgstr "*警告*: メッセージの完全性は保護されていません\n"
+msgid ""
+"Hint: If this message was created before the year 2003 it is\n"
+"likely that this message is legitimate. This is because back\n"
+"then integrity protection was not widely used.\n"
+msgstr ""
+"ヒント: もし、このメッセージが2003年以前に作成されたのであれば、\n"
+"このメッセージはおそらく正当でしょう。当時、整合性の保護機能は\n"
+"広く使われてはいなかったためです。\n"
+
+#, c-format
+msgid "Use the option '%s' to decrypt anyway.\n"
+msgstr "それでも復号するにはオプション '%s' を使います。\n"
+
+msgid "decryption forced to fail!\n"
+msgstr "復号は強制的に失敗とされました!\n"
+
msgid "decryption okay\n"
msgstr "復号に成功\n"
msgid "WARNING: encrypted message has been manipulated!\n"
msgstr "*警告*: 暗号化されたメッセージは改竄されています!\n"
#, c-format
msgid "decryption failed: %s\n"
msgstr "復号に失敗しました: %s\n"
msgid "Note: sender requested \"for-your-eyes-only\"\n"
msgstr "注意: 送信者は\"極秘とする\"ように求めています\n"
#, c-format
msgid "original file name='%.*s'\n"
msgstr "元のファイル名='%.*s'\n"
-msgid "WARNING: multiple plaintexts seen\n"
-msgstr "*警告*: 複数のプレインテクストが見られます\n"
-
msgid "standalone revocation - use \"gpg --import\" to apply\n"
msgstr "スタンドアロン失効 - \"gpg --import\"を使って適用してください\n"
msgid "no signature found\n"
msgstr "署名が見つかりません\n"
#, c-format
msgid "BAD signature from \"%s\""
msgstr "\"%s\"からの*不正な*署名"
#, c-format
msgid "Expired signature from \"%s\""
msgstr "\"%s\"からの期限切れの署名"
#, c-format
msgid "Good signature from \"%s\""
msgstr "\"%s\"からの正しい署名"
msgid "signature verification suppressed\n"
msgstr "署名の検証を省略\n"
msgid "can't handle this ambiguous signature data\n"
msgstr "このあいまいな署名データは取り扱えません\n"
#, c-format
msgid "Signature made %s\n"
msgstr "%sに施された署名\n"
#, c-format
msgid " using %s key %s\n"
msgstr " %s鍵%sを使用\n"
#, c-format
msgid "Signature made %s using %s key ID %s\n"
msgstr "%sに%s鍵ID %sで施された署名\n"
#, c-format
msgid " issuer \"%s\"\n"
msgstr " 発行者\"%s\"\n"
msgid "Key available at: "
msgstr "以下に鍵があります: "
msgid "[uncertain]"
msgstr "[不確定]"
#, c-format
msgid " aka \"%s\""
msgstr " 別名\"%s\""
#, c-format
msgid "WARNING: This key is not suitable for signing in %s mode\n"
msgstr "*警告*: この鍵は%sモードでの署名に適しません!\n"
#, c-format
msgid "Signature expired %s\n"
msgstr "期限切れの署名 %s\n"
#, c-format
msgid "Signature expires %s\n"
msgstr "この署名は%sで期限切れとなります\n"
#, c-format
msgid "%s signature, digest algorithm %s%s%s\n"
msgstr "%s署名、ダイジェスト・アルゴリズム %s%s%s\n"
msgid "binary"
msgstr "バイナリ"
msgid "textmode"
msgstr "テキストモード"
msgid "unknown"
msgstr "不明の"
msgid ", key algorithm "
msgstr "、鍵アルゴリズム "
#, c-format
msgid "WARNING: not a detached signature; file '%s' was NOT verified!\n"
msgstr ""
"*警告*: 分遣署名ではありません。ファイル「%s」は検証され*ませんでした*!\n"
#, c-format
msgid "Can't check signature: %s\n"
msgstr "署名を検査できません: %s\n"
msgid "not a detached signature\n"
msgstr "分遣署名でありません\n"
msgid ""
"WARNING: multiple signatures detected. Only the first will be checked.\n"
msgstr "*警告*: 多重署名の検出。最初のものだけ検査します。\n"
#, c-format
msgid "standalone signature of class 0x%02x\n"
msgstr "クラス0x%02xのスタンドアロン署名\n"
msgid "old style (PGP 2.x) signature\n"
msgstr "古い形式 (PGP 2.x) の署名\n"
#, c-format
msgid "fstat of '%s' failed in %s: %s\n"
msgstr "'%s'のfstatが%sで失敗しました: %s\n"
#, c-format
msgid "fstat(%d) failed in %s: %s\n"
msgstr "fstat(%d)が%sで失敗しました: %s\n"
#, c-format
msgid "WARNING: using experimental public key algorithm %s\n"
msgstr "*警告*: 実験的公開鍵アルゴリズム%sを使用します\n"
msgid "WARNING: Elgamal sign+encrypt keys are deprecated\n"
msgstr "*警告*: Elgamal署名+暗号化鍵は廃止されています\n"
#, c-format
msgid "WARNING: using experimental cipher algorithm %s\n"
msgstr "*警告*: 実験的暗号アルゴリズム %s を使用します\n"
#, c-format
msgid "WARNING: using experimental digest algorithm %s\n"
msgstr "*警告*: 実験的ダイジェスト・アルゴリズム %sを使用\n"
#, c-format
msgid "WARNING: digest algorithm %s is deprecated\n"
msgstr "*警告*: ダイジェスト・アルゴリズム %s は廃止されています\n"
#, c-format
msgid "Note: signatures using the %s algorithm are rejected\n"
msgstr "注意: アルゴリズム %s を用いた署名は拒否されました\n"
#, c-format
msgid "(reported error: %s)\n"
msgstr "(報告されたエラー: %s)\n"
#, c-format
msgid "(reported error: %s <%s>)\n"
msgstr "(報告されたエラー: %s <%s>)\n"
msgid "(further info: "
msgstr "(より詳細な情報: "
#, c-format
msgid "%s:%d: deprecated option \"%s\"\n"
msgstr "%s:%d: 廃止されたオプション\"%s\"\n"
#, c-format
msgid "WARNING: \"%s\" is a deprecated option\n"
msgstr "*警告*: \"%s\"は、廃止されたオプションです\n"
#, c-format
msgid "please use \"%s%s\" instead\n"
msgstr "\"%s%s\"を代わりに使ってください\n"
#, c-format
msgid "WARNING: \"%s\" is a deprecated command - do not use it\n"
msgstr "*警告*: \"%s\" は、廃止されているコマンドです - 使わないでください\n"
#, c-format
msgid "%s:%u: \"%s\" is obsolete in this file - it only has effect in %s\n"
msgstr ""
"%s:%u: \"%s\"は、このファイルで使われなくなりました - %sになんの効果もありま"
"せん\n"
#, c-format
msgid ""
"WARNING: \"%s%s\" is an obsolete option - it has no effect except on %s\n"
msgstr ""
"*警告*: \"%s%s\"は、使われなくなったオプションです - %s以外になんの効果もあり"
"ません\n"
msgid "Uncompressed"
msgstr "無圧縮"
#. TRANSLATORS: See doc/TRANSLATE about this string.
msgid "uncompressed|none"
msgstr "無圧縮|なし"
#, c-format
msgid "this message may not be usable by %s\n"
msgstr "このメッセージは、%sでは使用できません\n"
#, c-format
msgid "ambiguous option '%s'\n"
msgstr "あいまいなオプション'%s'\n"
#, c-format
msgid "unknown option '%s'\n"
msgstr "不明のオプション'%s'\n"
msgid "ECDSA public key is expected to be in SEC encoding multiple of 8 bits\n"
msgstr "ECDSAの公開鍵は8ビットの倍数のSECエンコーディングを期待します\n"
#, c-format
msgid "unknown weak digest '%s'\n"
msgstr "不明の弱いダイジェスト'%s'\n"
#, c-format
msgid "File '%s' exists. "
msgstr "ファイル'%s'は既に存在します。"
msgid "Overwrite? (y/N) "
msgstr "上書きしますか? (y/N) "
#, c-format
msgid "%s: unknown suffix\n"
msgstr "%s: 不明の拡張子\n"
msgid "Enter new filename"
msgstr "新しいファイル名を入力してください"
msgid "writing to stdout\n"
msgstr "標準出力に書き込みます\n"
#, c-format
msgid "assuming signed data in '%s'\n"
msgstr "署名されたデータが'%s'にあると想定します\n"
#, c-format
msgid "can't handle public key algorithm %d\n"
msgstr "公開鍵のアルゴリズム%dは、取り扱えません\n"
msgid "WARNING: potentially insecure symmetrically encrypted session key\n"
msgstr "*警告*: 潜在的にセキュアでない共通鍵暗号化セッション鍵です\n"
+msgid "Unknown critical signature notation: "
+msgstr "不明なクリティカルな署名注釈: "
+
#, c-format
msgid "subpacket of type %d has critical bit set\n"
msgstr "型%dの下位パケットにクリティカル・ビットを発見\n"
-#, c-format
-msgid "problem with the agent: %s\n"
-msgstr "エージェントに問題: %s\n"
-
msgid "Enter passphrase\n"
msgstr "パスフレーズを入力\n"
msgid "cancelled by user\n"
msgstr "ユーザによる取消し\n"
#, c-format
msgid " (main key ID %s)"
msgstr " (主鍵ID %s)"
msgid "Please enter the passphrase to unlock the OpenPGP secret key:"
msgstr "OpenPGPの秘密鍵のロックを解除するためにパスフレーズを入力してください:"
msgid "Please enter the passphrase to import the OpenPGP secret key:"
msgstr "OpenPGPの秘密鍵をインポートするためにパスフレーズを入力してください:"
msgid "Please enter the passphrase to export the OpenPGP secret subkey:"
msgstr ""
"OpenPGPの秘密副鍵をエクスポートするためにパスフレーズを入力してください:"
msgid "Please enter the passphrase to export the OpenPGP secret key:"
msgstr "OpenPGPの秘密鍵をエクスポートするためにパスフレーズを入力してください:"
msgid "Do you really want to permanently delete the OpenPGP secret subkey key:"
msgstr "選択したOpenPGP副鍵を本当に永久に削除しますか? (y/N) "
msgid "Do you really want to permanently delete the OpenPGP secret key:"
msgstr "選択したOpenPGP秘密鍵を本当に永久に削除しますか? (y/N) "
#, c-format
msgid ""
"%s\n"
"\"%.*s\"\n"
"%u-bit %s key, ID %s,\n"
"created %s%s.\n"
"%s"
msgstr ""
"%s\n"
"\"%.*s\"\n"
"%uビット%s鍵, ID %s,\n"
"作成日付 %s%s.\n"
"%s"
msgid ""
"\n"
"Pick an image to use for your photo ID. The image must be a JPEG file.\n"
"Remember that the image is stored within your public key. If you use a\n"
"very large picture, your key will become very large as well!\n"
"Keeping the image close to 240x288 is a good size to use.\n"
msgstr ""
"\n"
"あなたのフォトIDに使う画像を決めてください。画像はJPEGファイルである必\n"
"要があります。画像は公開鍵といっしょに格納される、ということを念頭にお\n"
"いておきましょう。もし大きな写真を使うと、あなたの鍵も同様に大きくなり\n"
"ます! 240x288くらいにおさまる大きさの画像は、使いよいでしょう。\n"
msgid "Enter JPEG filename for photo ID: "
msgstr "フォトID用のJPEGファイル名を入力してください: "
#, c-format
msgid "unable to open JPEG file '%s': %s\n"
msgstr "JPEGファイル'%s'が開けません: %s\n"
#, c-format
msgid "This JPEG is really large (%d bytes) !\n"
msgstr "このJPEGは、本当に大きい (%dバイト) !\n"
msgid "Are you sure you want to use it? (y/N) "
msgstr "本当に使いたいですか? (y/N) "
#, c-format
msgid "'%s' is not a JPEG file\n"
msgstr "'%s'は、JPEGファイルではありません\n"
msgid "Is this photo correct (y/N/q)? "
msgstr "この写真は正しいですか (y/N/q)? "
msgid "unable to display photo ID!\n"
msgstr "フォトIDが表示不能!\n"
-msgid "No reason specified"
-msgstr "理由は指定されていません"
-
-msgid "Key is superseded"
-msgstr "鍵がとりかわっています"
-
-msgid "Key has been compromised"
-msgstr "鍵(の信頼性)が損なわれています"
-
-msgid "Key is no longer used"
-msgstr "鍵はもはや使われていません"
-
-msgid "User ID is no longer valid"
-msgstr "ユーザIDがもはや有効でありません"
-
-msgid "reason for revocation: "
-msgstr "失効理由: "
-
-msgid "revocation comment: "
-msgstr "失効のコメント: "
-
#. TRANSLATORS: These are the allowed answers in lower and
#. uppercase. Below you will find the matching strings which
#. should be translated accordingly and the letter changed to
#. match the one in the answer string.
#.
#. i = please show me more information
#. m = back to the main menu
#. s = skip this key
#. q = quit
#.
msgid "iImMqQsS"
msgstr "iImMqQsS"
msgid "No trust value assigned to:\n"
msgstr "信用度が指定されていません:\n"
#, c-format
msgid " aka \"%s\"\n"
msgstr " 別名\"%s\"\n"
msgid ""
"How much do you trust that this key actually belongs to the named user?\n"
msgstr ""
"この鍵がこのユーザをなのる本人のものかどうか、どれくらい信用できますか?\n"
#, c-format
msgid " %d = I don't know or won't say\n"
msgstr " %d = 知らない、または何とも言えない\n"
#, c-format
msgid " %d = I do NOT trust\n"
msgstr " %d = 信用し ない\n"
#, c-format
msgid " %d = I trust ultimately\n"
msgstr " %d = 究極的に信用する\n"
msgid " m = back to the main menu\n"
msgstr " m = メーン・メニューに戻る\n"
msgid " s = skip this key\n"
msgstr " s = この鍵はとばす\n"
msgid " q = quit\n"
msgstr " q = 終了\n"
#, c-format
msgid ""
"The minimum trust level for this key is: %s\n"
"\n"
msgstr "この鍵の最小信用レベル: %s\n"
msgid "Your decision? "
msgstr "あなたの決定は? "
msgid "Do you really want to set this key to ultimate trust? (y/N) "
msgstr "本当にこの鍵を究極的に信用しますか? (y/N) "
msgid "Certificates leading to an ultimately trusted key:\n"
msgstr "究極的に信用した鍵への証明書:\n"
#, c-format
msgid "%s: There is no assurance this key belongs to the named user\n"
msgstr "%s: この鍵が本当に本人のものである、という兆候が、ありません\n"
#, c-format
msgid "%s: There is limited assurance this key belongs to the named user\n"
msgstr "%s: この鍵が本当に本人のものである、という兆候が、少ししかありません\n"
msgid "This key probably belongs to the named user\n"
msgstr "この鍵はたぶん本人のものです\n"
msgid "This key belongs to us\n"
msgstr "この鍵は自分のものです\n"
#, c-format
msgid "%s: This key is bad! It has been marked as untrusted!\n"
msgstr "%s: この鍵はダメです! 信用できないとマークされています!\n"
msgid ""
"This key is bad! It has been marked as untrusted! If you\n"
"*really* know what you are doing, you may answer the next\n"
"question with yes.\n"
msgstr ""
"この鍵は、ダメです! 信用できないとマークされています! *本当に*\n"
"なにをしているのか分かっている場合には、次の質問には yes と\n"
"答えてください。\n"
msgid ""
"It is NOT certain that the key belongs to the person named\n"
"in the user ID. If you *really* know what you are doing,\n"
"you may answer the next question with yes.\n"
msgstr ""
"この鍵は、このユーザIDをなのる本人のものかどうか確信でき\n"
"ません。今から行うことを*本当に*理解していない場合には、\n"
"次の質問にはnoと答えてください。\n"
msgid "Use this key anyway? (y/N) "
msgstr "それでもこの鍵を使いますか? (y/N) "
msgid "WARNING: Using untrusted key!\n"
msgstr "*警告*: 信用できない鍵を使っています!\n"
msgid "WARNING: this key might be revoked (revocation key not present)\n"
msgstr "*警告*: この鍵は失効されたようです (失効鍵は不在)\n"
msgid "WARNING: This key has been revoked by its designated revoker!\n"
msgstr "*警告*: この鍵は指名失効者によって失効されています!\n"
msgid "WARNING: This key has been revoked by its owner!\n"
msgstr "*警告*: この鍵は所有者によって失効されています!\n"
msgid " This could mean that the signature is forged.\n"
msgstr " 署名が偽物なこともある、ということです。\n"
msgid "WARNING: This subkey has been revoked by its owner!\n"
msgstr "*警告*: この副鍵は所有者によって失効されています!\n"
msgid "Note: This key has been disabled.\n"
msgstr "注意: この鍵は使用禁止に設定されています。\n"
#, c-format
msgid "Note: Verified signer's address is '%s'\n"
msgstr "注意: 確認された署名者のアドレスは'%s'です\n"
#, c-format
msgid "Note: Signer's address '%s' does not match DNS entry\n"
msgstr "注意: 署名者のアドレス'%s'がDNSのエントリと一致しません\n"
msgid "trustlevel adjusted to FULL due to valid PKA info\n"
msgstr "PKA情報が有効のため、信用レベルがFULLに調整されました\n"
msgid "trustlevel adjusted to NEVER due to bad PKA info\n"
msgstr "PKA情報が無効のため、信用レベルがNEVERに調整されました\n"
msgid "Note: This key has expired!\n"
msgstr "注意: この鍵は期限切れです!\n"
msgid "WARNING: This key is not certified with a trusted signature!\n"
msgstr "*警告*: この鍵は信用できる署名で証明されていません!\n"
msgid ""
" There is no indication that the signature belongs to the owner.\n"
msgstr " この署名が所有者のものかどうかの検証手段がありません。\n"
msgid "WARNING: We do NOT trust this key!\n"
msgstr "*警告*: この鍵は信用できません!\n"
msgid " The signature is probably a FORGERY.\n"
msgstr " この署名はおそらく 偽物 です。\n"
msgid ""
"WARNING: This key is not certified with sufficiently trusted signatures!\n"
msgstr "*警告*: この鍵は十分に信用できる署名で証明されていません!\n"
msgid " It is not certain that the signature belongs to the owner.\n"
msgstr " この署名が所有者のものかどうか確信できません。\n"
#, c-format
msgid "%s: skipped: %s\n"
msgstr "%s: スキップ: %s\n"
#, c-format
msgid "%s: skipped: public key is disabled\n"
msgstr "%s: スキップ: 公開鍵は使用禁止です\n"
#, c-format
msgid "%s: skipped: public key already present\n"
msgstr "%s: スキップ: 公開鍵はもうあります\n"
#, c-format
msgid "can't encrypt to '%s'\n"
msgstr "'%s'に暗号化できません\n"
#, c-format
msgid "option '%s' given, but no valid default keys given\n"
msgstr ""
"オプション'%s'が与えられましたが、有効なデフォルト鍵が与えられていません\n"
#, c-format
msgid "option '%s' given, but option '%s' not given\n"
msgstr "オプション'%s'が与えられましたが、オプション'%s'は与えられていません\n"
msgid "You did not specify a user ID. (you may use \"-r\")\n"
msgstr "ユーザIDを指定していません (\"-r\"を使いましょう) 。\n"
msgid "Current recipients:\n"
msgstr "今の受取人:\n"
msgid ""
"\n"
"Enter the user ID. End with an empty line: "
msgstr ""
"\n"
"ユーザIDを入力。空行で終了: "
msgid "No such user ID.\n"
msgstr "そのユーザIDはありません。\n"
msgid "skipped: public key already set as default recipient\n"
msgstr "スキップ: 公開鍵はデフォルトの受取人としてもう設定済みです\n"
msgid "Public key is disabled.\n"
msgstr "公開鍵は使用禁止です。\n"
msgid "skipped: public key already set\n"
msgstr "スキップ: 公開鍵はもう設定済みです\n"
#, c-format
msgid "unknown default recipient \"%s\"\n"
msgstr "デフォルトの受取人\"%s\"が不明です\n"
msgid "no valid addressees\n"
msgstr "有効な宛先がありません\n"
#, c-format
msgid "Note: key %s has no %s feature\n"
msgstr "注意: 鍵%sには %s の機能がありません\n"
#, c-format
msgid "Note: key %s has no preference for %s\n"
msgstr "注意: 鍵%sには%sに対する優先指定がありません\n"
msgid "data not saved; use option \"--output\" to save it\n"
msgstr ""
"データは保存されていません。保存には\"--output\"オプションを使ってください\n"
msgid "Detached signature.\n"
msgstr "分遣署名。\n"
msgid "Please enter name of data file: "
msgstr "データ・ファイルの名前を入力: "
msgid "reading stdin ...\n"
msgstr "標準入力より読み込み中 ...\n"
msgid "no signed data\n"
msgstr "署名されたデータがありません\n"
#, c-format
msgid "can't open signed data '%s'\n"
msgstr "署名されたデータ'%s'が開けません\n"
#, c-format
msgid "can't open signed data fd=%d: %s\n"
msgstr "署名されたデータ fd=%d が開けません: %s\n"
#, c-format
msgid "key %s is not suitable for decryption in %s mode\n"
msgstr "鍵%sは%sモードでの復号化のために適しません\n"
#, c-format
msgid "anonymous recipient; trying secret key %s ...\n"
msgstr "匿名の受取人用です。秘密鍵%sを試します ...\n"
msgid "okay, we are the anonymous recipient.\n"
msgstr "終了。匿名の受取人用です。\n"
msgid "old encoding of the DEK is not supported\n"
msgstr "DEKの旧式エンコーディングは、サポートしていません\n"
#, c-format
msgid "cipher algorithm %d%s is unknown or disabled\n"
msgstr "暗号アルゴリズム%d%sは不明か使用禁止です\n"
#, c-format
msgid "WARNING: cipher algorithm %s not found in recipient preferences\n"
msgstr "*警告*: 暗号アルゴリズム%sは受取人の優先指定に入っていません\n"
#, c-format
msgid "Note: secret key %s expired at %s\n"
msgstr "注意: 秘密鍵%sは%sで期限切れとなります\n"
msgid "Note: key has been revoked"
msgstr "注意: 鍵は失効済みです"
#, c-format
msgid "build_packet failed: %s\n"
msgstr "build_packet に失敗しました: %s\n"
#, c-format
msgid "key %s has no user IDs\n"
msgstr "鍵%sにはユーザIDがありません\n"
msgid "To be revoked by:\n"
msgstr "失効者:\n"
msgid "(This is a sensitive revocation key)\n"
msgstr "(これは、機密指定の失効鍵です)\n"
msgid "Secret key is not available.\n"
msgstr "秘密鍵が利用できません。\n"
msgid "Create a designated revocation certificate for this key? (y/N) "
msgstr "この鍵に対する指名失効証明書を作成しますか? (y/N) "
msgid "ASCII armored output forced.\n"
msgstr "ASCII外装出力を強制します。\n"
#, c-format
msgid "make_keysig_packet failed: %s\n"
msgstr "make_keysig_packet に失敗しました: %s\n"
msgid "Revocation certificate created.\n"
msgstr "失効証明書を作成。\n"
#, c-format
msgid "no revocation keys found for \"%s\"\n"
msgstr "\"%s\"用の失効鍵が見つかりません\n"
msgid "This is a revocation certificate for the OpenPGP key:"
msgstr "これは失効証明書でこちらのOpenPGP鍵に対するものです:"
msgid ""
"A revocation certificate is a kind of \"kill switch\" to publicly\n"
"declare that a key shall not anymore be used. It is not possible\n"
"to retract such a revocation certificate once it has been published."
msgstr ""
"失効証明書は \"殺すスイッチ\" のようなもので、鍵がそれ以上使えない\n"
"ように公に宣言するものです。一度発行されると、そのような失効証明書は\n"
"撤回することはできません。"
msgid ""
"Use it to revoke this key in case of a compromise or loss of\n"
"the secret key. However, if the secret key is still accessible,\n"
"it is better to generate a new revocation certificate and give\n"
"a reason for the revocation. For details see the description of\n"
"of the gpg command \"--generate-revocation\" in the GnuPG manual."
msgstr ""
"秘密鍵のコンプロマイズや紛失の場合、これを使ってこの鍵を失効させます。\n"
"しかし、秘密鍵がまだアクセス可能である場合、新しい失効証明書を生成し、\n"
"失効の理由をつける方がよいでしょう。詳細は、GnuPGマニュアルのgpgコマンド \"--"
"generate-revocation\"の記述をご覧ください。"
msgid ""
"To avoid an accidental use of this file, a colon has been inserted\n"
"before the 5 dashes below. Remove this colon with a text editor\n"
"before importing and publishing this revocation certificate."
msgstr ""
"このファイルを誤って使うのを避けるため、以下ではコロンが5つのダッシュ\n"
"の前に挿入されます。この失効証明書をインポートして公開する前に、テク\n"
"スト・エディタでこのコロンを削除してください。"
#, c-format
msgid "revocation certificate stored as '%s.rev'\n"
msgstr "失効証明書を '%s.rev' に保管しました。\n"
#, c-format
msgid "secret key \"%s\" not found\n"
msgstr "秘密鍵\"%s\"が見つかりません\n"
#. TRANSLATORS: The %s prints a key specification which
#. for example has been given at the command line. Several lines
#. lines with secret key infos are printed after this message.
#, c-format
msgid "'%s' matches multiple secret keys:\n"
msgstr "'%s'は以下の複数の秘密鍵にマッチします:\n"
#, c-format
msgid "error searching the keyring: %s\n"
msgstr "鍵リング探索エラー: %s\n"
msgid "Create a revocation certificate for this key? (y/N) "
msgstr "この鍵に対する失効証明書を作成しますか? (y/N) "
msgid ""
"Revocation certificate created.\n"
"\n"
"Please move it to a medium which you can hide away; if Mallory gets\n"
"access to this certificate he can use it to make your key unusable.\n"
"It is smart to print this certificate and store it away, just in case\n"
"your media become unreadable. But have some caution: The print system of\n"
"your machine might store the data and make it available to others!\n"
msgstr ""
"失効証明書を作成しました。\n"
"\n"
"みつからないように隠せるような媒体に移してください。もし_悪者_がこの証明書へ"
"の\n"
"アクセスを得ると、あなたの鍵を使えなくすることができます。\n"
"媒体が読出し不能になった場合に備えて、この証明書を印刷して保管するのが賢明で"
"す。\n"
"しかし、ご注意ください。あなたのマシンの印字システムは、他の人がアクセスでき"
"る\n"
"場所にデータをおくことがあります!\n"
msgid "Please select the reason for the revocation:\n"
msgstr "失効の理由を選択してください:\n"
msgid "Cancel"
msgstr "キャンセル"
#, c-format
msgid "(Probably you want to select %d here)\n"
msgstr "(ここではたぶん%dを選びたいでしょう)\n"
msgid "Enter an optional description; end it with an empty line:\n"
msgstr "予備の説明を入力。空行で終了:\n"
#, c-format
msgid "Reason for revocation: %s\n"
msgstr "失効理由: %s\n"
msgid "(No description given)\n"
msgstr "(説明はありません)\n"
msgid "Is this okay? (y/N) "
msgstr "よろしいですか? (y/N) "
msgid "weak key created - retrying\n"
msgstr "弱い鍵ができました - 再実行\n"
#, c-format
msgid "cannot avoid weak key for symmetric cipher; tried %d times!\n"
msgstr "共通鍵暗号方式の弱い鍵を回避することができません。%d回試みました!\n"
#, c-format
msgid "%s key %s uses an unsafe (%zu bit) hash\n"
msgstr "%s 鍵 %s は安全でない(%zuビット)ハッシュを使用しています\n"
#, c-format
msgid "%s key %s requires a %zu bit or larger hash (hash is %s)\n"
msgstr ""
"%s鍵%sは%zuビットあるいはより大きいハッシュを必要とします(今のハッシュは%s)\n"
msgid "WARNING: signature digest conflict in message\n"
msgstr "*警告*: 署名のダイジェストが、メッセージと衝突します\n"
#, c-format
msgid "key %s may not be used for signing in %s mode\n"
msgstr "鍵%sを署名のために%sモードで使うことはできません\n"
#, c-format
msgid "WARNING: signing subkey %s is not cross-certified\n"
msgstr "*警告*: 署名副鍵%sは、相互証明されてません\n"
#, c-format
msgid "please see %s for more information\n"
msgstr "詳細は%sをご覧ください\n"
#, c-format
msgid "WARNING: signing subkey %s has an invalid cross-certification\n"
msgstr "*警告*: 無効な相互証明が、署名副鍵%sにあります\n"
#, c-format
msgid "public key %s is %lu second newer than the signature\n"
msgid_plural "public key %s is %lu seconds newer than the signature\n"
msgstr[0] "公開鍵%sは、署名よりも%lu秒、新しいものです\n"
#, c-format
msgid "public key %s is %lu day newer than the signature\n"
msgid_plural "public key %s is %lu days newer than the signature\n"
msgstr[0] "公開鍵%sは、署名よりも%lu日、新しいものです\n"
#, c-format
msgid ""
"key %s was created %lu second in the future (time warp or clock problem)\n"
msgid_plural ""
"key %s was created %lu seconds in the future (time warp or clock problem)\n"
msgstr[0] "鍵%sは%lu秒、未来にできました (時間歪曲か時計の障害でしょう)\n"
#, c-format
msgid "key %s was created %lu day in the future (time warp or clock problem)\n"
msgid_plural ""
"key %s was created %lu days in the future (time warp or clock problem)\n"
msgstr[0] "鍵%sは%lu日、未来にできました (時間歪曲か時計の障害でしょう)\n"
#, c-format
msgid "Note: signature key %s expired %s\n"
msgstr "注意: 署名鍵%sは%sに期限切れとなります\n"
#, c-format
msgid "Note: signature key %s has been revoked\n"
msgstr "注意: 鍵 %s は失効済みです\n"
#, c-format
msgid "bad key signature from key %s: %s (0x%02x, 0x%x)\n"
msgstr "鍵%sによる不正な鍵への署名: %s (0x%02x, 0x%x)\n"
+#, c-format
+msgid "bad data signature from key %s: %s (0x%02x, 0x%x)\n"
+msgstr "鍵%sによる不正なデータへの署名: %s (0x%02x, 0x%x)\n"
+
#, c-format
msgid "assuming bad signature from key %s due to an unknown critical bit\n"
msgstr "不明のクリティカル・ビットのため、鍵%sによる署名を不正とみなします\n"
#, c-format
msgid "key %s: no subkey for subkey revocation signature\n"
msgstr "鍵%s: 副鍵失効署名に対する副鍵がありません\n"
#, c-format
msgid "key %s: no subkey for subkey binding signature\n"
msgstr "鍵%s: 副鍵対応への署名に対する副鍵がありません\n"
#, c-format
msgid "WARNING: unable to %%-expand notation (too large). Using unexpanded.\n"
msgstr "*警告*: 表記を%%拡張不能 (大きすぎ)。拡張せずに使用。\n"
#, c-format
msgid ""
"WARNING: unable to %%-expand policy URL (too large). Using unexpanded.\n"
msgstr "*警告*: ポリシーURLを%%拡張不能 (大きすぎ)。拡張せずに使用。\n"
#, c-format
msgid ""
"WARNING: unable to %%-expand preferred keyserver URL (too large). Using "
"unexpanded.\n"
msgstr "*警告*: 優先鍵サーバURLを%%拡張不能 (大きすぎ)。拡張せずに使用。\n"
#, c-format
msgid "%s/%s signature from: \"%s\"\n"
msgstr "%s/%s署名。署名者:\"%s\"\n"
#, c-format
msgid ""
"WARNING: forcing digest algorithm %s (%d) violates recipient preferences\n"
msgstr ""
"*警告*: ダイジェスト・アルゴリズム %s (%d) の強制が、受取人の優先指定と対立し"
"ます\n"
msgid "signing:"
msgstr "署名:"
#, c-format
-msgid "%s encryption will be used\n"
-msgstr "%s暗号化を使用します\n"
+msgid "%s.%s encryption will be used\n"
+msgstr "%s.%s 暗号化を使用します\n"
msgid "key is not flagged as insecure - can't use it with the faked RNG!\n"
msgstr ""
"セキュアでないというフラグが鍵には設定されていません。\n"
"偽物乱数生成器とはいっしょに使えません!\n"
#, c-format
msgid "skipped \"%s\": duplicated\n"
msgstr "\"%s\"をスキップします: 重複\n"
msgid "skipped: secret key already present\n"
msgstr "スキップ: 秘密鍵はもうあります\n"
msgid "this is a PGP generated Elgamal key which is not secure for signatures!"
msgstr "これはPGPの生成したElgamal鍵で、署名用には安全ではありません!"
#, c-format
msgid "trust record %lu, type %d: write failed: %s\n"
msgstr "信用レコード%lu, 型%d: 書き込みに失敗しました: %s\n"
#, c-format
msgid ""
"# List of assigned trustvalues, created %s\n"
"# (Use \"gpg --import-ownertrust\" to restore them)\n"
msgstr ""
"# 指定された信用度の一覧です 作成日時: %s\n"
"# (\"gpg --import-ownertrust\" で復旧することができます)\n"
#, c-format
msgid "error in '%s': %s\n"
msgstr "'%s'でエラー: %s\n"
msgid "line too long"
msgstr "行が長すぎます"
msgid "colon missing"
msgstr "コロンがありません"
msgid "invalid fingerprint"
msgstr "無効なフィンガープリント"
msgid "ownertrust value missing"
msgstr "所有者信用度がありません"
#, c-format
msgid "error finding trust record in '%s': %s\n"
msgstr "'%s'で信用レコードの検索エラー: %s\n"
#, c-format
msgid "read error in '%s': %s\n"
msgstr "'%s'で読み込みエラー: %s\n"
#, c-format
msgid "trustdb: sync failed: %s\n"
msgstr "信用データベース: 同期に失敗しました: %s\n"
#, c-format
msgid "can't create lock for '%s'\n"
msgstr "'%s'のロックを作成できません\n"
#, c-format
msgid "can't lock '%s'\n"
msgstr "'%s'がロックできません\n"
#, c-format
msgid "trustdb rec %lu: lseek failed: %s\n"
msgstr "信用データベース レコード%lu: シークに失敗しました: %s\n"
#, c-format
msgid "trustdb rec %lu: write failed (n=%d): %s\n"
msgstr "信用データベース レコード%lu: 書き込みに失敗しました (n=%d): %s\n"
msgid "trustdb transaction too large\n"
msgstr "信用データベースのトランザクションが大きすぎます\n"
#, c-format
msgid "%s: directory does not exist!\n"
msgstr "%s: ディレクトリがありません!\n"
#, c-format
msgid "can't access '%s': %s\n"
msgstr "'%s'にアクセスできません: %s\n"
#, c-format
msgid "%s: failed to create version record: %s"
msgstr "%s: バージョン・レコードの作成に失敗しました: %s"
#, c-format
msgid "%s: invalid trustdb created\n"
msgstr "%s: 無効な信用データベースを作成\n"
#, c-format
msgid "%s: trustdb created\n"
msgstr "%s: 信用データベースができました\n"
msgid "Note: trustdb not writable\n"
msgstr "注意: 信用データベースが、書き込み不能です\n"
#, c-format
msgid "%s: invalid trustdb\n"
msgstr "%s: 無効な信用データベース\n"
#, c-format
msgid "%s: failed to create hashtable: %s\n"
msgstr "%s: ハッシュ表の作成に失敗しました: %s\n"
#, c-format
msgid "%s: error updating version record: %s\n"
msgstr "%s: バージョン・レコードの更新エラー: %s\n"
#, c-format
msgid "%s: error reading version record: %s\n"
msgstr "%s: バージョン・レコードの読み込みエラー: %s\n"
#, c-format
msgid "%s: error writing version record: %s\n"
msgstr "%s: バージョン・レコードの書き込みエラー: %s\n"
#, c-format
msgid "trustdb: lseek failed: %s\n"
msgstr "信用データベース: シークに失敗しました: %s\n"
#, c-format
msgid "trustdb: read failed (n=%d): %s\n"
msgstr "信用データベース: 読み込みに失敗しました (n=%d): %s\n"
#, c-format
msgid "%s: not a trustdb file\n"
msgstr "%s: 信用データベース・ファイルではありません\n"
#, c-format
msgid "%s: version record with recnum %lu\n"
msgstr "%s: レコード番号%lu番のバージョン・レコード\n"
#, c-format
msgid "%s: invalid file version %d\n"
msgstr "%s: 無効なファイル・バージョン%d\n"
#, c-format
msgid "%s: error reading free record: %s\n"
msgstr "%s: 空きレコードの読み込みエラー: %s\n"
#, c-format
msgid "%s: error writing dir record: %s\n"
msgstr "%s: ディレクトリ・レコードの書き込みエラー: %s\n"
#, c-format
msgid "%s: failed to zero a record: %s\n"
msgstr "%s: レコードの初期化に失敗しました: %s\n"
#, c-format
msgid "%s: failed to append a record: %s\n"
msgstr "%s: レコードの追加に失敗しました: %s\n"
msgid "Error: The trustdb is corrupted.\n"
msgstr "エラー: 信用データベースが壊れています。\n"
#, c-format
msgid "can't handle text lines longer than %d characters\n"
msgstr "%d文字以上の長さのテキスト行は、取り扱えません\n"
#, c-format
msgid "input line longer than %d characters\n"
msgstr "入力行の長さが%d文字を超えています\n"
#, c-format
msgid "error beginning transaction on TOFU database: %s\n"
msgstr "TOFUデータベースのトランザクション開始エラー: %s\n"
#, c-format
msgid "error committing transaction on TOFU database: %s\n"
msgstr "TOFUデータベースのトランザクションコミットのエラー: %s\n"
#, c-format
msgid "error rolling back transaction on TOFU database: %s\n"
msgstr "TOFUデータベースのトランザクションのロールバックのエラー: %s\n"
#, c-format
msgid "unsupported TOFU database version: %s\n"
msgstr "サポートされていないTOFUデータベースバージョン: %s\n"
#, c-format
-msgid "error creating 'ultimately_trusted_keys' TOFU table: %s\n"
-msgstr "'ultimately_trusted_keys' TOFUテーブル作成エラー: %s\n"
-
msgid "TOFU DB error"
msgstr "TOFU DBエラー"
#, c-format
msgid "error reading TOFU database: %s\n"
msgstr "TOFUデータベースの読み込みエラー: %s\n"
#, c-format
msgid "error determining TOFU database's version: %s\n"
msgstr "TOFUデータベースのバージョン判定エラー: %s\n"
#, c-format
msgid "error initializing TOFU database: %s\n"
msgstr "TOFUデータベースの初期化エラー: %s\n"
-#, c-format
-msgid "error creating 'encryptions' TOFU table: %s\n"
-msgstr "'encryptions' TOFUデータベースの作成エラー: %s\n"
-
-#, c-format
-msgid "adding column effective_policy to bindings DB: %s\n"
-msgstr "バインディングDBにカラムeffective_policyを追加: %s\n"
-
#, c-format
msgid "error opening TOFU database '%s': %s\n"
msgstr "TOFUデータベースのオープンでエラー '%s': %s\n"
#, c-format
msgid "error updating TOFU database: %s\n"
msgstr "TOFUデータベースの更新エラー: %s\n"
#, c-format
msgid ""
"This is the first time the email address \"%s\" is being used with key %s."
msgstr "電子メールアドレス\"%s\"が鍵%sに使われたのはこれが最初です。"
#, c-format
msgid "The email address \"%s\" is associated with %d key!"
msgid_plural "The email address \"%s\" is associated with %d keys!"
msgstr[0] "電子メールアドレス\"%s\"は%d個の鍵に結びつけられます!"
msgid " Since this binding's policy was 'auto', it has been changed to 'ask'."
msgstr ""
" このバインディングポリシーは'auto'だったので、'ask'に変更されました。"
#, c-format
msgid ""
"Please indicate whether this email address should be associated with key %s "
"or whether you think someone is impersonating \"%s\"."
msgstr ""
"この電子メールアドレスが鍵 %s に結びつけられるべきか、あるいは誰かが\"%s\" に"
"なりすましていると思うか指定してください。"
#, c-format
msgid "error gathering other user IDs: %s\n"
msgstr "ほかのユーザIDの収集エラー: %s\n"
msgid "This key's user IDs:\n"
msgstr "この鍵のユーザID:\n"
#, c-format
msgid "policy: %s"
msgstr "ポリシー: %s"
#, c-format
msgid "error gathering signature stats: %s\n"
msgstr "署名の統計の収集エラー: %s\n"
#, c-format
msgid "The email address \"%s\" is associated with %d key:\n"
msgid_plural "The email address \"%s\" is associated with %d keys:\n"
msgstr[0] "電子メールアドレス\"%s\"は%d個の鍵に結びつけられます:\n"
#, c-format
msgid "Statistics for keys with the email address \"%s\":\n"
msgstr "この電子メールアドレス\"%s\"の鍵の統計:\n"
msgid "this key"
msgstr "この鍵"
#, c-format
msgid "Verified %d message."
msgid_plural "Verified %d messages."
msgstr[0] "%d個のメッセージを検証しました。"
#, c-format
msgid "Encrypted %d message."
msgid_plural "Encrypted %d messages."
msgstr[0] "%d個のメッセージを暗号化しました。"
#, c-format
msgid "Verified %d message in the future."
msgid_plural "Verified %d messages in the future."
msgstr[0] "%d個のメッセージが未来に検証されました。"
#, c-format
msgid "Encrypted %d message in the future."
msgid_plural "Encrypted %d messages in the future."
msgstr[0] "%d個のメッセージが未来に暗号化されました。"
#, c-format
msgid "Messages verified over the past %d day: %d."
msgid_plural "Messages verified over the past %d days: %d."
msgstr[0] "過去%d日間に検証されたメッセージの数: %d."
#, c-format
msgid "Messages encrypted over the past %d day: %d."
msgid_plural "Messages encrypted over the past %d days: %d."
msgstr[0] "過去%d日間に暗号化されたメッセージの数: %d."
#, c-format
msgid "Messages verified over the past %d month: %d."
msgid_plural "Messages verified over the past %d months: %d."
msgstr[0] "過去%d月の間に検証されたメッセージの数: %d."
#, c-format
msgid "Messages encrypted over the past %d month: %d."
msgid_plural "Messages encrypted over the past %d months: %d."
msgstr[0] "過去%d月の間に暗号化されたメッセージの数: %d."
#, c-format
msgid "Messages verified over the past %d year: %d."
msgid_plural "Messages verified over the past %d years: %d."
msgstr[0] "過去%d年間に検証されたメッセージの数: %d."
#, c-format
msgid "Messages encrypted over the past %d year: %d."
msgid_plural "Messages encrypted over the past %d years: %d."
msgstr[0] "過去%d年間に暗号化されたメッセージの数: %d."
#, c-format
msgid "Messages verified in the past: %d."
msgstr "これまでに検証されたメッセージの数: %d."
#, c-format
msgid "Messages encrypted in the past: %d."
msgstr "これまでに暗号化されたメッセージの数: %d."
#. TRANSLATORS: Please translate the text found in the source
#. * file below. We don't directly internationalize that text so
#. * that we can tweak it without breaking translations.
msgid "TOFU detected a binding conflict"
msgstr "TOFUはバインディングの衝突を検出しました"
#. TRANSLATORS: Two letters (normally the lower and upper case
#. * version of the hotkey) for each of the five choices. If
#. * there is only one choice in your language, repeat it.
msgid "gGaAuUrRbB"
msgstr "gGaAuUrRbB"
msgid "(G)ood, (A)ccept once, (U)nknown, (R)eject once, (B)ad? "
msgstr ""
"(G)ood-良, (A)ccept once-一度だけ認める, (U)nknown-不明, (R)eject once-一度だ"
"け否, (B)ad-ダメ? "
msgid "Defaulting to unknown.\n"
msgstr "不明をデフォルトとします。\n"
msgid "TOFU db corruption detected.\n"
msgstr "TOFU dbが壊れていることが検出されました。\n"
-#, c-format
-msgid "resetting keydb: %s\n"
-msgstr "keydbをリセット: %s\n"
-
-#, c-format
-msgid "error setting TOFU binding's policy to %s\n"
-msgstr "TOFUバインディングのポリシーを %s に設定エラー\n"
-
#, c-format
msgid "error changing TOFU policy: %s\n"
msgstr "TOFUポリシーの作成エラー: %s\n"
#, c-format
msgid "%lld~year"
msgid_plural "%lld~years"
msgstr[0] "%lldå¹´"
#, c-format
msgid "%lld~month"
msgid_plural "%lld~months"
msgstr[0] "%lld月"
#, c-format
msgid "%lld~week"
msgid_plural "%lld~weeks"
msgstr[0] "%lld週"
#, c-format
msgid "%lld~day"
msgid_plural "%lld~days"
msgstr[0] "%lld日"
#, c-format
msgid "%lld~hour"
msgid_plural "%lld~hours"
msgstr[0] "%lld時間"
#, c-format
msgid "%lld~minute"
msgid_plural "%lld~minutes"
msgstr[0] "%lld分"
#, c-format
msgid "%lld~second"
msgid_plural "%lld~seconds"
msgstr[0] "%lld秒"
#, c-format
msgid "%s: Verified 0~signatures and encrypted 0~messages."
msgstr "%s: 0個の署名を検証、0個のメッセージを暗号化しました。"
#, c-format
msgid "%s: Verified 0 signatures."
msgstr "%s: 0個の署名を検証しました。"
-#, c-format
-msgid "%s: Verified %ld~signature in the past %s."
-msgid_plural "%s: Verified %ld~signatures in the past %s."
-msgstr[0] "%s: 署名を%ld個検証しました(これまで %s に)。"
-
msgid "Encrypted 0 messages."
msgstr "0 個のメッセージを暗号化しました。"
-#, c-format
-msgid "Encrypted %ld~message in the past %s."
-msgid_plural "Encrypted %ld~messages in the past %s."
-msgstr[0] "メッセージを%ld個暗号化しました(これまで %s に)。"
-
#, c-format
msgid "(policy: %s)"
msgstr "(ポリシー: %s)"
msgid ""
"Warning: we have yet to see a message signed using this key and user id!\n"
msgstr "警告: この鍵とユーザIDで署名されたメッセージは一度も見てません!\n"
msgid ""
"Warning: we've only seen one message signed using this key and user id!\n"
msgstr "警告: この鍵とユーザIDで署名されたメッセージは一度しか見てません!\n"
msgid "Warning: you have yet to encrypt a message to this key!\n"
msgstr "警告: この鍵へは一つもメッセージを暗号化したことがありません!\n"
msgid "Warning: you have only encrypted one message to this key!\n"
msgstr "警告: この鍵へは一つのメッセージしか暗号化したことがありません!\n"
#, c-format
msgid ""
"Warning: if you think you've seen more signatures by this key and user id, "
"then this key might be a forgery! Carefully examine the email address for "
"small variations. If the key is suspect, then use\n"
" %s\n"
"to mark it as being bad.\n"
msgid_plural ""
"Warning: if you think you've seen more signatures by this key and these user "
"ids, then this key might be a forgery! Carefully examine the email "
"addresses for small variations. If the key is suspect, then use\n"
" %s\n"
"to mark it as being bad.\n"
msgstr[0] ""
"警告: この鍵とユーザIDによる署名をもっと見たと思う場合、この鍵は偽物の可能性"
"があります! 少数のバリエーションでこの電子メールアドレスを注意深く検査してく"
"ださい。この鍵が疑われる場合、\n"
" %s\n"
"でダメとマークしてください。\n"
#, c-format
msgid "error opening TOFU database: %s\n"
msgstr "TOFUデータベースのオープンでエラー: %s\n"
#, c-format
msgid "WARNING: Encrypting to %s, which has no non-revoked user ids\n"
msgstr ""
"*警告*: %s に暗号化します。失効していないユーザIDが一つもないものです\n"
-#, c-format
-msgid "error setting policy for key %s, user id \"%s\": %s"
-msgstr "鍵%s, ユーザID \"%s\"のポリシーの設定エラー: %s"
-
#, c-format
msgid "'%s' is not a valid long keyID\n"
msgstr "'%s'は、有効な大型鍵IDでありません\n"
#, c-format
msgid "key %s: accepted as trusted key\n"
msgstr "鍵%s: 信用する鍵として受理しました\n"
#, c-format
msgid "key %s occurs more than once in the trustdb\n"
msgstr "鍵%sが信用データベースに複数あります\n"
#, c-format
msgid "key %s: no public key for trusted key - skipped\n"
msgstr "鍵%s: 信用される鍵の公開鍵がありません - スキップします\n"
#, c-format
msgid "key %s marked as ultimately trusted\n"
msgstr "鍵%sを究極的に信用するよう記録しました\n"
#, c-format
msgid "trust record %lu, req type %d: read failed: %s\n"
msgstr "信用レコード%lu, リクエスト型%d: 読み込みに失敗しました: %s\n"
#, c-format
msgid "trust record %lu is not of requested type %d\n"
msgstr "信用レコード%luが要求された型%dではありません\n"
msgid "You may try to re-create the trustdb using the commands:\n"
msgstr "trustdbを下記のコマンドで再生成することを試すことができます:\n"
msgid "If that does not work, please consult the manual\n"
msgstr "もし、それがうまくいかなかったら、マニュアルをご覧ください\n"
#, c-format
msgid "unable to use unknown trust model (%d) - assuming %s trust model\n"
msgstr "不明の信用モデル (%d) は使えません - %s信用モデルを仮定\n"
#, c-format
msgid "using %s trust model\n"
msgstr "%s信用モデルを使用\n"
msgid "no need for a trustdb check\n"
msgstr "信用データベースの検査は、不要です\n"
#, c-format
msgid "next trustdb check due at %s\n"
msgstr "次回の信用データベース検査は、%sです\n"
#, c-format
msgid "no need for a trustdb check with '%s' trust model\n"
msgstr "信用モデル'%s'で信用データベースの検査は、不要です\n"
#, c-format
msgid "no need for a trustdb update with '%s' trust model\n"
msgstr "信用モデル'%s'で信用データベースの更新は、不要です\n"
#, c-format
msgid "public key %s not found: %s\n"
msgstr "公開鍵%sが見つかりません: %s\n"
msgid "please do a --check-trustdb\n"
msgstr "--check-trustdbを実行してください\n"
msgid "checking the trustdb\n"
msgstr "信用データベースの検査\n"
#, c-format
msgid "%d key processed"
msgid_plural "%d keys processed"
msgstr[0] "%d個の鍵を処理"
#, c-format
msgid " (%d validity count cleared)\n"
msgid_plural " (%d validity counts cleared)\n"
msgstr[0] " (うち%d本の有効性数をクリア)\n"
msgid "no ultimately trusted keys found\n"
msgstr "究極的に信用する鍵が見つかりません\n"
#, c-format
msgid "public key of ultimately trusted key %s not found\n"
msgstr "究極的に信用する鍵%sの公開鍵が見つかりません\n"
#, c-format
msgid ""
"depth: %d valid: %3d signed: %3d trust: %d-, %dq, %dn, %dm, %df, %du\n"
msgstr "深さ: %d 有効性: %3d 署名: %3d 信用: %d-, %dq, %dn, %dm, %df, %du\n"
#, c-format
msgid "unable to update trustdb version record: write failed: %s\n"
msgstr ""
"信用データベースのバージョン・レコードが更新できません: 書き込みに失敗しまし"
"た: %s\n"
msgid "undefined"
msgstr "未定義"
msgid "never"
msgstr "全くなし"
msgid "marginal"
msgstr "まぁまぁ"
msgid "full"
msgstr "充分"
msgid "ultimate"
msgstr "究極"
#. TRANSLATORS: these strings are similar to those in
#. trust_value_to_string(), but are a fixed length. This is needed to
#. make attractive information listings where columns line up
#. properly. The value "10" should be the length of the strings you
#. choose to translate to. This is the length in printable columns.
#. It gets passed to atoi() so everything after the number is
#. essentially a comment and need not be translated. Either key and
#. uid are both NULL, or neither are NULL.
msgid "10 translator see trust.c:uid_trust_string_fixed"
msgstr "10 translator see trust.c:uid_trust_string_fixed"
msgid "[ revoked]"
msgstr "[ 失効 ]"
msgid "[ expired]"
msgstr "[期限切れ]"
msgid "[ unknown]"
msgstr "[ 不明 ]"
msgid "[ undef ]"
msgstr "[ 未定義 ]"
msgid "[ never ]"
msgstr "[全くなし]"
msgid "[marginal]"
msgstr "[まぁまぁ]"
msgid "[ full ]"
msgstr "[ 充分 ]"
msgid "[ultimate]"
msgstr "[ 究極 ]"
msgid ""
"the signature could not be verified.\n"
"Please remember that the signature file (.sig or .asc)\n"
"should be the first file given on the command line.\n"
msgstr ""
"署名を検証できませんでした。署名ファイル\n"
"(.sigや.asc)がコマンド行の最初でなければ\n"
"ならないことを念頭においてください。\n"
#, c-format
msgid "input line %u too long or missing LF\n"
msgstr "入力の%u行目が長すぎるか、LFがないようです\n"
#, c-format
msgid "can't open fd %d: %s\n"
msgstr "fd %dが開けません: %s\n"
+msgid "WARNING: encrypting without integrity protection is dangerous\n"
+msgstr "*警告*: 完全性保護なしでの暗号化は危険です\n"
+
+#, c-format
+msgid "Hint: Do not use option %s\n"
+msgstr "ヒント: オプション %s を使わない\n"
+
msgid "set debugging flags"
msgstr "デバッグ・フラグを設定"
msgid "enable full debugging"
msgstr "フル・デバッグを有効にする"
msgid "Usage: kbxutil [options] [files] (-h for help)"
msgstr "使い方: kbxutil [オプション] [ファイル] (ヘルプは -h)"
msgid ""
"Syntax: kbxutil [options] [files]\n"
"List, export, import Keybox data\n"
msgstr ""
"形式: kbxutil [オプション] [ファイル]\n"
"Keyboxデータを一覧、エクスポート、インポート\n"
#, c-format
msgid "RSA modulus missing or not of size %d bits\n"
msgstr "RSAのモジュラスがないか、%dビットのものではありません\n"
#, c-format
msgid "RSA public exponent missing or larger than %d bits\n"
msgstr "RSA公開指数がないか %d ビットより大きすぎます\n"
#, c-format
msgid "PIN callback returned error: %s\n"
msgstr "PINコールバックがエラーを返しました: %s\n"
msgid "the NullPIN has not yet been changed\n"
msgstr "NullPINが変更されていません\n"
msgid "|N|Please enter a new PIN for the standard keys."
msgstr "|N|新しいPINを標準の鍵のために入力してください。"
msgid "||Please enter the PIN for the standard keys."
msgstr "||PINを標準の鍵のために入力してください。"
msgid "|NP|Please enter a new PIN Unblocking Code (PUK) for the standard keys."
msgstr "|NP|標準の鍵の新しいPIN Unblocking Code (PUK)を入力してください。"
msgid "|P|Please enter the PIN Unblocking Code (PUK) for the standard keys."
msgstr "|P|標準の鍵のPIN Unblocking Code (PUK)を入力してください。"
msgid "|N|Please enter a new PIN for the key to create qualified signatures."
msgstr "|N|新しいPINを認定署名を生成する鍵のために入力してください。"
msgid "||Please enter the PIN for the key to create qualified signatures."
msgstr "||新しいPINを認定署名を生成する鍵のために入力してください。"
msgid ""
"|NP|Please enter a new PIN Unblocking Code (PUK) for the key to create "
"qualified signatures."
msgstr ""
"|NP|認定署名の鍵のために新しいPINブロック解除コード(PUK)を入力してください。"
msgid ""
"|P|Please enter the PIN Unblocking Code (PUK) for the key to create "
"qualified signatures."
msgstr ""
"|P|認定署名の鍵のために新しいPINブロック解除コード(PUK)を入力してください。"
#, c-format
msgid "error getting new PIN: %s\n"
msgstr "新しいPINの取得エラー: %s\n"
#, c-format
msgid "failed to store the fingerprint: %s\n"
msgstr "指紋の保管に失敗しました: %s\n"
#, c-format
msgid "failed to store the creation date: %s\n"
msgstr "生成日の保管に失敗しました: %s\n"
msgid "error retrieving CHV status from card\n"
msgstr "カードからCHVステイタスの取得でエラー\n"
msgid "response does not contain the RSA modulus\n"
msgstr "応答にRSAのモジュラスが含まれていません\n"
msgid "response does not contain the RSA public exponent\n"
msgstr "応答にRSA公開指数が含まれていません\n"
msgid "response does not contain the EC public key\n"
msgstr "応答に楕円曲線の公開鍵が含まれていません\n"
msgid "response does not contain the public key data\n"
msgstr "応答に公開鍵データが含まれていません\n"
#, c-format
msgid "reading public key failed: %s\n"
msgstr "公開鍵の読み込みに失敗しました: %s\n"
#. TRANSLATORS: Put a \x1f right before a colon. This can be
#. * used by pinentry to nicely align the names and values. Keep
#. * the %s at the start and end of the string.
#, c-format
msgid "%sNumber: %s%%0AHolder: %s%%0ACounter: %lu%s"
msgstr "%s番号: %s%%0A保持者: %s%%0Aカウンタ: %lu%s"
#, c-format
msgid "%sNumber: %s%%0AHolder: %s%s"
msgstr "%s番号: %s%%0A保持者: %s%s"
#. TRANSLATORS: This is the number of remaining attempts to
#. * enter a PIN. Use %%0A (double-percent,0A) for a linefeed.
#, c-format
msgid "Remaining attempts: %d"
msgstr "残された試行回数: %d"
#, c-format
msgid "using default PIN as %s\n"
msgstr "デフォルトPINを%sとして使います\n"
#, c-format
msgid "failed to use default PIN as %s: %s - disabling further default use\n"
msgstr ""
"デフォルトのPIN %s を使うのに失敗しました: %s - これ以上デフォルトとしての使"
"用を無効とします\n"
msgid "||Please unlock the card"
msgstr "||カードをアンロックしてください"
#, c-format
msgid "PIN for CHV%d is too short; minimum length is %d\n"
msgstr "CHV%dのPINが短すぎます。最短で%dです\n"
#, c-format
msgid "verify CHV%d failed: %s\n"
msgstr "CHV%dの認証に失敗しました: %s\n"
msgid "card is permanently locked!\n"
msgstr "カードが永久にロックされてます!\n"
#, c-format
msgid "%d Admin PIN attempt remaining before card is permanently locked\n"
msgid_plural ""
"%d Admin PIN attempts remaining before card is permanently locked\n"
msgstr[0] "カードの永久ロック前に%d回の管理者PINの試行が残っています\n"
#. TRANSLATORS: Do not translate the "|A|" prefix but keep it at
#. the start of the string. Use %0A (single percent) for a linefeed.
msgid "|A|Please enter the Admin PIN"
msgstr "|A|管理者PINを入力してください"
msgid "access to admin commands is not configured\n"
msgstr "管理コマンドへのアクセスが設定されていません\n"
msgid "||Please enter the PIN"
msgstr "||PINを入力してください"
msgid "||Please enter the Reset Code for the card"
msgstr "||カードのリセット・コードを入力してください"
#, c-format
msgid "Reset Code is too short; minimum length is %d\n"
msgstr "リセット・コードが短すぎます。最短の長さは%dです。\n"
#. TRANSLATORS: Do not translate the "|*|" prefixes but
#. keep it at the start of the string. We need this elsewhere
#. to get some infos on the string.
msgid "|RN|New Reset Code"
msgstr "|RN|新しいリセット・コード"
msgid "|AN|New Admin PIN"
msgstr "|AN|新しい管理者PIN"
msgid "|N|New PIN"
msgstr "|N|新しいPIN"
msgid "||Please enter the Admin PIN and New Admin PIN"
msgstr "||管理者PINと新しい管理者PINを入力してください"
msgid "||Please enter the PIN and New PIN"
msgstr "||PINと新しいPINを入力してください"
msgid "error reading application data\n"
msgstr "アプリケーション・データの読み込みエラー\n"
msgid "error reading fingerprint DO\n"
msgstr "フィンガープリントのデータ・オブジェクトの読み込みエラー\n"
msgid "key already exists\n"
msgstr "鍵はもうあります\n"
msgid "existing key will be replaced\n"
msgstr "既存の鍵は置き換えられます\n"
msgid "generating new key\n"
msgstr "新しい鍵を生成\n"
msgid "writing new key\n"
msgstr "新しい鍵を書き込み\n"
msgid "creation timestamp missing\n"
msgstr "作成時刻スタンプがありません\n"
#, c-format
msgid "RSA prime %s missing or not of size %d bits\n"
msgstr "RSA素数 %s がありません、または%dビットのものではありません\n"
#, c-format
msgid "failed to store the key: %s\n"
msgstr "鍵の保管に失敗しました: %s\n"
msgid "unsupported curve\n"
msgstr "サポートされていない曲線\n"
msgid "please wait while key is being generated ...\n"
msgstr "鍵生成の間、お待ちください ...\n"
msgid "generating key failed\n"
msgstr "鍵の生成に失敗しました\n"
#, c-format
msgid "key generation completed (%d second)\n"
msgid_plural "key generation completed (%d seconds)\n"
msgstr[0] "鍵の生成が完了しました (%d秒)\n"
msgid "invalid structure of OpenPGP card (DO 0x93)\n"
msgstr "OpenPGPカードに無効な構造 (データ・オブジェクト 0x93)\n"
msgid "fingerprint on card does not match requested one\n"
msgstr "カードのフィンガープリントが要求されたものと一致しません\n"
#, c-format
msgid "card does not support digest algorithm %s\n"
msgstr "カードはダイジェスト・アルゴリズム %s をサポートしていません\n"
#, c-format
msgid "signatures created so far: %lu\n"
msgstr "これまでに作成された署名: %lu\n"
msgid ""
"verification of Admin PIN is currently prohibited through this command\n"
msgstr "管理者PINの確認はこのコマンドでは今のところ禁止されています\n"
#, c-format
msgid "can't access %s - invalid OpenPGP card?\n"
msgstr "%sにアクセスできません - 無効なOpenPGPカード?\n"
msgid "||Please enter your PIN at the reader's pinpad"
msgstr "||PINをリーダのピンパッドで入力してください"
#. TRANSLATORS: Do not translate the "|*|" prefixes but
#. keep it at the start of the string. We need this elsewhere
#. to get some infos on the string.
msgid "|N|Initial New PIN"
msgstr "|N|初期の新しいPIN"
msgid "run in multi server mode (foreground)"
msgstr "マルチ・サーバ・モード(フォアグラウンド)で実行"
msgid "|LEVEL|set the debugging level to LEVEL"
msgstr "|LEVEL|デバッグ・レベルをLEVELとします"
msgid "|FILE|write a log to FILE"
msgstr "|FILE|FILEにログを書き出します"
msgid "|N|connect to reader at port N"
msgstr "|N|ポートNのリーダに接続します"
msgid "|NAME|use NAME as ct-API driver"
msgstr "|NAME|ct-APIドライバとしてNAMEを用います"
msgid "|NAME|use NAME as PC/SC driver"
msgstr "|NAME|PC/SCドライバとしてNAMEを用います"
msgid "do not use the internal CCID driver"
msgstr "内蔵CCIDドライバを使いません"
msgid "|N|disconnect the card after N seconds of inactivity"
msgstr "|N|N秒アクティブでない場合、カードへの接続を切ります"
msgid "do not use a reader's pinpad"
msgstr "リーダのピンパッドを使わない"
msgid "deny the use of admin card commands"
msgstr "管理カード・コマンドの使用を拒否"
+msgid "|LIST|Change the application priority to LIST"
+msgstr "|LIST|アプリケーションの優先順位をLISTに変更します"
+
msgid "use variable length input for pinpad"
msgstr "ピンパッドの可変長入力を使う"
msgid "Usage: @SCDAEMON@ [options] (-h for help)"
msgstr "使い方: @SCDAEMON@ [オプション] (ヘルプは -h)"
msgid ""
"Syntax: scdaemon [options] [command [args]]\n"
"Smartcard daemon for @GNUPG@\n"
msgstr ""
"形式: scdaemon [オプション] [コマンド [引数]]\n"
"@GNUPG@のSmartcardデーモン\n"
msgid "please use the option '--daemon' to run the program in the background\n"
msgstr ""
"'--daemon'オプションを使って、プログラムをバックグラウンドで実行してくださ"
"い\n"
#, c-format
msgid "handler for fd %d started\n"
msgstr "fd %dのハンドラが開始されました\n"
#, c-format
msgid "handler for fd %d terminated\n"
msgstr "fd %dのハンドラが終了しました\n"
msgid "no dirmngr running in this session\n"
msgstr "このセッションでdirmngrは実行されていません\n"
#, c-format
msgid "validation model requested by certificate: %s"
msgstr "証明書から以下の検証モデルが要求されました: %s"
msgid "chain"
msgstr "chain"
msgid "shell"
msgstr "shell"
#, c-format
msgid "critical certificate extension %s is not supported"
msgstr "クリティカルな証明書の拡張%sはサポートされていません"
msgid "issuer certificate is not marked as a CA"
msgstr "発行者の証明書がCAとしてマークされていません"
msgid "critical marked policy without configured policies"
msgstr "コンフィグされたポリシーなしにクリティカルにマークされたポリシー"
#, c-format
msgid "failed to open '%s': %s\n"
msgstr "'%s'が開けません: %s\n"
msgid "Note: non-critical certificate policy not allowed"
msgstr "注意: クリティカルでない証明書ポリシーは認められません"
msgid "certificate policy not allowed"
msgstr "証明書ポリシーは認められません"
msgid "looking up issuer at external location\n"
msgstr "発行者の外部ロケーションを調べています\n"
#, c-format
msgid "number of issuers matching: %d\n"
msgstr "マッチする発行者の数: %d\n"
msgid "looking up issuer from the Dirmngr cache\n"
msgstr "Dirmngrキャッシュから発行者を調べています\n"
#, c-format
msgid "number of matching certificates: %d\n"
msgstr "マッチする証明書の数: %d\n"
#, c-format
msgid "dirmngr cache-only key lookup failed: %s\n"
msgstr "dirmngrのキャッシュだけの鍵探索に失敗しました: %s\n"
msgid "failed to allocate keyDB handle\n"
msgstr "keyDBハンドルの確保に失敗しました\n"
msgid "certificate has been revoked"
msgstr "証明書は失効済みです"
msgid "the status of the certificate is unknown"
msgstr "証明書のステイタスは不明です"
msgid "please make sure that the \"dirmngr\" is properly installed\n"
msgstr "\"dirmngr\" が正しくインストールされていることを確認してください\n"
#, c-format
msgid "checking the CRL failed: %s"
msgstr "CRLの検査に失敗しました: %s"
#, c-format
msgid "certificate with invalid validity: %s"
msgstr "無効の妥当性の証明書: %s"
msgid "certificate not yet valid"
msgstr "証明書はまだ有効ではありません"
msgid "root certificate not yet valid"
msgstr "ルート証明書がまだ有効ではありません"
msgid "intermediate certificate not yet valid"
msgstr "中間証明書はまだ有効ではありません"
msgid "certificate has expired"
msgstr "証明書が有効期限を過ぎています"
msgid "root certificate has expired"
msgstr "ルート証明書が有効期限を過ぎています"
msgid "intermediate certificate has expired"
msgstr "中間証明書が有効期限を過ぎています"
#, c-format
msgid "required certificate attributes missing: %s%s%s"
msgstr "必要な証明書の属性がありません: %s%s%s"
msgid "certificate with invalid validity"
msgstr "妥当性が無効な証明書"
msgid "signature not created during lifetime of certificate"
msgstr "証明書のライフタイムの間に署名が作られていません"
msgid "certificate not created during lifetime of issuer"
msgstr "発行者のライフタイムの間に証明書が作られていません"
msgid "intermediate certificate not created during lifetime of issuer"
msgstr "発行者のライフタイムの間に中間証明書が作られていません"
msgid " ( signature created at "
msgstr " ( 署名、作成"
msgid " (certificate created at "
msgstr " (証明書、作成"
msgid " (certificate valid from "
msgstr " ( 証明書、有効"
msgid " ( issuer valid from "
msgstr " ( 発行者、有効"
#, c-format
msgid "fingerprint=%s\n"
msgstr "フィンガープリント=%s\n"
msgid "root certificate has now been marked as trusted\n"
msgstr "ルート証明書は信用すると今、マークされました\n"
msgid "interactive marking as trusted not enabled in gpg-agent\n"
msgstr ""
"インタラクティブに信用するとマークすることがgpg-agentで有効となっていません\n"
msgid "interactive marking as trusted disabled for this session\n"
msgstr ""
"インタラクティブに信用するとマークすることはこのセッションでは無効となってい"
"ます\n"
msgid "WARNING: creation time of signature not known - assuming current time"
msgstr "*警告*: 署名の作成時間が不明です - 現在時刻を仮定します"
msgid "no issuer found in certificate"
msgstr "証明書の発行者がありません"
msgid "self-signed certificate has a BAD signature"
msgstr "自己署名証明書に*不正な*署名があります"
msgid "root certificate is not marked trusted"
msgstr "ルート証明書が信用できるとマークされていません"
#, c-format
msgid "checking the trust list failed: %s\n"
msgstr "信用リストの検査に失敗しました: %s\n"
msgid "certificate chain too long\n"
msgstr "証明書のチェインが長すぎます\n"
msgid "issuer certificate not found"
msgstr "発行者証明書が見つかりません"
msgid "certificate has a BAD signature"
msgstr "証明書に*不正な*署名があります"
msgid "found another possible matching CA certificate - trying again"
msgstr "別の一致する可能性があるCA証明書が見つかりました - 再度試します"
#, c-format
msgid "certificate chain longer than allowed by CA (%d)"
msgstr "証明書のチェインがCAにより認められたもの(%d)より長くなっています"
msgid "certificate is good\n"
msgstr "証明書は正しいです\n"
msgid "intermediate certificate is good\n"
msgstr "中間証明書は正しいです\n"
msgid "root certificate is good\n"
msgstr "ルート証明書は正しいです\n"
msgid "switching to chain model"
msgstr "チェイン・モデルに切り替えました"
#, c-format
msgid "validation model used: %s"
msgstr "使用した検証モデル: %s"
#, c-format
msgid "a %u bit hash is not valid for a %u bit %s key\n"
msgstr "%uビットハッシュは%uビットの%s鍵には無効です\n"
msgid "(this is the MD2 algorithm)\n"
msgstr "(これはMD2アルゴリズムです)\n"
msgid "none"
msgstr "none"
msgid "[Error - invalid encoding]"
msgstr "[エラー: 無効なエンコーディング]"
msgid "[Error - out of core]"
msgstr "[エラー - メモリがありません]"
msgid "[Error - No name]"
msgstr "[エラー - 名前なし]"
msgid "[Error - invalid DN]"
msgstr "[エラー: 無効な DN]"
#, c-format
msgid ""
"Please enter the passphrase to unlock the secret key for the X.509 "
"certificate:\n"
"\"%s\"\n"
"S/N %s, ID 0x%08lX,\n"
"created %s, expires %s.\n"
msgstr ""
"X.509証明書のための秘密鍵のロックを解除するためにパスフレーズを入力してくださ"
"い:\n"
"\"%s\"\n"
"S/N %s, ID 0x%08lX,\n"
"作成 %s, 有効期限 %s.\n"
msgid "no key usage specified - assuming all usages\n"
msgstr "鍵の使い方が指定されていません - すべての使い道を仮定します\n"
#, c-format
msgid "error getting key usage information: %s\n"
msgstr "鍵使用情報の取得エラー: %s\n"
msgid "certificate should not have been used for certification\n"
msgstr "証明書は証明のために使われるべきではありませんでした\n"
msgid "certificate should not have been used for OCSP response signing\n"
msgstr "証明書はOCSP応答の署名のために使われるべきではありませんでした\n"
msgid "certificate should not have been used for encryption\n"
msgstr "証明書は暗号化のために使われるべきではありませんでした\n"
msgid "certificate should not have been used for signing\n"
msgstr "証明書は署名のために使われるべきではありませんでした\n"
msgid "certificate is not usable for encryption\n"
msgstr "証明書は暗号化のために使えません\n"
msgid "certificate is not usable for signing\n"
msgstr "証明書は署名のために使えません\n"
+msgid "looking for another certificate\n"
+msgstr "別の証明書を探索する\n"
+
#, c-format
msgid "line %d: invalid algorithm\n"
msgstr "行 %d: 無効なアルゴリズムです\n"
#, c-format
msgid "line %d: invalid key length %u (valid are %d to %d)\n"
msgstr "行 %d: 無効な鍵長 %u (%d から %dが有効)です\n"
#, c-format
msgid "line %d: no subject name given\n"
msgstr "行 %d: サブジェクト名がありません\n"
#, c-format
msgid "line %d: invalid subject name label '%.*s'\n"
msgstr "行 %d: 無効なサブジェクト名ラベル'%.*s'です\n"
#, c-format
msgid "line %d: invalid subject name '%s' at pos %d\n"
msgstr "行 %d: 無効なサブジェクト名'%s'(位置: %d)です\n"
#, c-format
msgid "line %d: not a valid email address\n"
msgstr "行 %d: 有効な電子メール・アドレスではありません\n"
#, c-format
msgid "line %d: invalid serial number\n"
msgstr "行 %d: 無効なシリアル番号です\n"
#, c-format
msgid "line %d: invalid issuer name label '%.*s'\n"
msgstr "行 %d: 無効なサブジェクト名ラベル'%.*s'です\n"
#, c-format
msgid "line %d: invalid issuer name '%s' at pos %d\n"
msgstr "行 %d: 無効なサブジェクト名'%s'(位置: %d)です\n"
#, c-format
msgid "line %d: invalid date given\n"
msgstr "行 %d: 無効な日付が与えられました\n"
#, c-format
msgid "line %d: error getting signing key by keygrip '%s': %s\n"
msgstr "行 %d: keygrip'%s'から鍵の取得エラー: %s\n"
#, c-format
msgid "line %d: invalid hash algorithm given\n"
msgstr "行 %d: 無効なハッシュ・アルゴリズムです\n"
#, c-format
msgid "line %d: invalid authority-key-id\n"
msgstr "行 %d: 無効なauthority-key-idです\n"
#, c-format
msgid "line %d: invalid subject-key-id\n"
msgstr "行 %d: 無効なsubject-key-idです\n"
#, c-format
msgid "line %d: invalid extension syntax\n"
msgstr "行 %d: 無効な拡張構文です\n"
#, c-format
msgid "line %d: error reading key '%s' from card: %s\n"
msgstr "行 %d: カードから鍵'%s'の読み込みエラー: %s\n"
#, c-format
msgid "line %d: error getting key by keygrip '%s': %s\n"
msgstr "行 %d: keygrip'%s'から鍵の取得エラー: %s\n"
#, c-format
msgid "line %d: key generation failed: %s <%s>\n"
msgstr "行 %d: 鍵の生成に失敗しました: %s <%s>\n"
msgid ""
"To complete this certificate request please enter the passphrase for the key "
"you just created once more.\n"
msgstr ""
"この証明書要求を完成するために今作った鍵のパスフレーズをもう一度入力してくだ"
"さい。\n"
#, c-format
msgid " (%d) Existing key\n"
msgstr " (%d) 既存の鍵\n"
#, c-format
msgid " (%d) Existing key from card\n"
msgstr " (%d) カードに存在する鍵\n"
-#, c-format
-msgid "error reading the card: %s\n"
-msgstr "カードの読み込みエラー: %s\n"
-
-#, c-format
-msgid "Serial number of the card: %s\n"
-msgstr "カードのシリアル番号: %s\n"
-
-msgid "Available keys:\n"
-msgstr "利用可能な鍵:\n"
-
#, c-format
msgid "Possible actions for a %s key:\n"
msgstr "%s鍵に可能な操作:\n"
#, c-format
msgid " (%d) sign, encrypt\n"
msgstr " (%d) 署名、暗号化\n"
#, c-format
msgid " (%d) sign\n"
msgstr " (%d) 署名\n"
#, c-format
msgid " (%d) encrypt\n"
msgstr " (%d) 暗号化\n"
msgid "Enter the X.509 subject name: "
msgstr "X.509のサブジェクト名を入力: "
msgid "No subject name given\n"
msgstr "サブジェクト名がありません\n"
#, c-format
msgid "Invalid subject name label '%.*s'\n"
msgstr "無効なサブジェクト名ラベル'%.*s'です\n"
#. TRANSLATORS: The 22 in the second string is the
#. length of the first string up to the "%s". Please
#. adjust it do the length of your translation. The
#. second string is merely passed to atoi so you can
#. drop everything after the number.
#, c-format
msgid "Invalid subject name '%s'\n"
msgstr "無効なサブジェクト名'%s'です\n"
msgid "22 translator: see certreg-ui.c:gpgsm_gencertreq_tty"
msgstr "33"
msgid "Enter email addresses"
msgstr "電子メール・アドレスを入力"
msgid " (end with an empty line):\n"
msgstr " (空行で終了):\n"
msgid "Enter DNS names"
msgstr "DNS名を入力"
msgid " (optional; end with an empty line):\n"
msgstr " (オプションです。空行で終了):\n"
msgid "Enter URIs"
msgstr "URIを入力"
msgid "Create self-signed certificate? (y/N) "
msgstr "自己署名証明書を作成しますか? (y/N) "
msgid "These parameters are used:\n"
msgstr "下記のパラメータが使われます:\n"
msgid "Now creating self-signed certificate. "
msgstr "今、自己署名証明書を作成しています。 "
msgid "Now creating certificate request. "
msgstr "今、証明書要求を作成しています。 "
msgid "This may take a while ...\n"
msgstr "しばらくかかります...\n"
msgid "Ready.\n"
msgstr "準備完了。\n"
msgid "Ready. You should now send this request to your CA.\n"
msgstr "準備ができました。今、この要求をあなたのCAに送るべきです。\n"
msgid "resource problem: out of core\n"
msgstr "リソースの問題: メモリがありません\n"
msgid "(this is the RC2 algorithm)\n"
msgstr "(RC2アルゴリズムです)\n"
msgid "(this does not seem to be an encrypted message)\n"
msgstr "(暗号化されたメッセージではないようです)\n"
#, c-format
msgid "certificate '%s' not found: %s\n"
msgstr "証明書'%s'が見つかりません: %s\n"
#, c-format
msgid "error locking keybox: %s\n"
msgstr "keyboxのロックのエラー: %s\n"
#, c-format
msgid "duplicated certificate '%s' deleted\n"
msgstr "重複した証明書'%s'を削除しました\n"
#, c-format
msgid "certificate '%s' deleted\n"
msgstr "証明書'%s'を削除しました\n"
#, c-format
msgid "deleting certificate \"%s\" failed: %s\n"
msgstr "証明書'%s'の削除に失敗しました: %s\n"
msgid "no valid recipients given\n"
msgstr "有効な受け取り手が指定されていません\n"
msgid "list external keys"
msgstr "外部鍵を一覧する"
msgid "list certificate chain"
msgstr "証明書のチェインを表示する"
msgid "import certificates"
msgstr "証明書をインポートする"
msgid "export certificates"
msgstr "証明書をエクスポートする"
msgid "register a smartcard"
msgstr "スマートカードを登録する"
msgid "pass a command to the dirmngr"
msgstr "dirmngrにコマンドを渡す"
msgid "invoke gpg-protect-tool"
msgstr "gpg-protect-toolを起動する"
msgid "create base-64 encoded output"
msgstr "base-64形式の出力を作成"
msgid "assume input is in PEM format"
msgstr "PEMフォーマットの入力を仮定する"
msgid "assume input is in base-64 format"
msgstr "base-64フォーマットの入力を仮定する"
msgid "assume input is in binary format"
msgstr "バイナリ・フォーマットの入力を仮定する"
msgid "never consult a CRL"
msgstr "決してCRLを調べない"
msgid "check validity using OCSP"
msgstr "OCSPを用いて有効性を確認する"
msgid "|N|number of certificates to include"
msgstr "|N|インクルードする証明書の数"
msgid "|FILE|take policy information from FILE"
msgstr "|FILE|ポリシー情報をFILEから取得する"
msgid "do not check certificate policies"
msgstr "証明書ポリシーをチェックしない"
msgid "fetch missing issuer certificates"
msgstr "紛失している発行者証明書を取得する"
msgid "don't use the terminal at all"
msgstr "端末をまったく使わない"
msgid "|FILE|write a server mode log to FILE"
msgstr "|FILE|サーバ・モードのログをFILEに書き出す"
msgid "|FILE|write an audit log to FILE"
msgstr "|FILE|監査ログをFILEに書き出す"
msgid "batch mode: never ask"
msgstr "バッチ・モード: なにもユーザに問い合わせない"
msgid "assume yes on most questions"
msgstr "ほとんどの設問にyesを仮定する"
msgid "assume no on most questions"
msgstr "ほとんどの設問にnoを仮定する"
msgid "|FILE|add keyring to the list of keyrings"
msgstr "|FILE|鍵リングを鍵リングのリストに追加"
msgid "|USER-ID|use USER-ID as default secret key"
msgstr "|USER-ID|USER-IDをデフォルトの秘密鍵として使う"
msgid "|SPEC|use this keyserver to lookup keys"
msgstr "|SPEC|このキーサーバを鍵の検索に使う"
msgid "|NAME|use cipher algorithm NAME"
msgstr "|NAME|暗号アルゴリズムにNAMEを使用"
msgid "|NAME|use message digest algorithm NAME"
msgstr "|NAME|ダイジェスト・アルゴリズムにNAMEを使用"
msgid "Usage: @GPGSM@ [options] [files] (-h for help)"
msgstr "使い方: @GPGSM@ [オプション] [ファイル] (ヘルプは -h)"
msgid ""
"Syntax: @GPGSM@ [options] [files]\n"
"Sign, check, encrypt or decrypt using the S/MIME protocol\n"
"Default operation depends on the input data\n"
msgstr ""
"形式: @GPGSM@ [オプション] [ファイル]\n"
"S/MIMEプロトコルを用いて、署名、検査、暗号化や復号を行います\n"
"デフォルトの操作は、入力データに依存します\n"
#, c-format
msgid "Note: won't be able to encrypt to '%s': %s\n"
msgstr "注意:'%s'に対して暗号化できません: %s\n"
#, c-format
msgid "unknown validation model '%s'\n"
msgstr "不明の検証モデル '%s'\n"
#, c-format
msgid "%s:%u: no hostname given\n"
msgstr "%s:%u: ホスト名が指定されていません\n"
#, c-format
msgid "%s:%u: password given without user\n"
msgstr "%s:%u: ユーザなしに与えられたパスワード\n"
#, c-format
msgid "%s:%u: skipping this line\n"
msgstr "%s:%u: この行はスキップ\n"
msgid "could not parse keyserver\n"
msgstr "鍵サーバのURLを解析不能\n"
#, c-format
msgid "importing common certificates '%s'\n"
msgstr "共通証明書のインポート・エラー: %s\n"
#, c-format
msgid "can't sign using '%s': %s\n"
msgstr "'%s'を用いて署名できません: %s\n"
msgid "invalid command (there is no implicit command)\n"
msgstr "無効なコマンド (暗黙のコマンドはありません)\n"
#, c-format
msgid "total number processed: %lu\n"
msgstr " 処理数の合計: %lu\n"
msgid "error storing certificate\n"
msgstr "証明書の保存に失敗しました\n"
msgid "basic certificate checks failed - not imported\n"
msgstr "基本証明書チェックが失敗しました - インポートされませんでした\n"
#, c-format
msgid "error getting stored flags: %s\n"
msgstr "保存されたフラグの取得エラー: %s\n"
#, c-format
msgid "error importing certificate: %s\n"
msgstr "証明書のインポート・エラー: %s\n"
#, c-format
msgid "error reading input: %s\n"
msgstr "入力読み込みエラー: %s\n"
msgid "failed to get the fingerprint\n"
msgstr "フィンガープリントの取得に失敗しました\n"
#, c-format
msgid "problem looking for existing certificate: %s\n"
msgstr "既存の証明書の検索の問題: %s\n"
#, c-format
msgid "error finding writable keyDB: %s\n"
msgstr "書き込み可能keyDBの判定エラー: %s\n"
#, c-format
msgid "error storing certificate: %s\n"
msgstr "証明書保存エラー: %s\n"
#, c-format
msgid "problem re-searching certificate: %s\n"
msgstr "証明書の再検索の問題: %s\n"
#, c-format
msgid "error storing flags: %s\n"
msgstr "フラグの保存エラー: %s\n"
msgid "Error - "
msgstr "エラー - "
msgid "GPG_TTY has not been set - using maybe bogus default\n"
msgstr "GPG_TTY が設定されていません - 少々疑問のデフォルトを使います\n"
#, c-format
msgid "invalid formatted fingerprint in '%s', line %d\n"
msgstr "'%s'(行 %d) 無効な形式のフィンガープリント\n"
#, c-format
msgid "invalid country code in '%s', line %d\n"
msgstr "'%s' (行 %d)で無効な国識別コード\n"
#, c-format
msgid ""
"You are about to create a signature using your certificate:\n"
"\"%s\"\n"
"This will create a qualified signature by law equated to a handwritten "
"signature.\n"
"\n"
"%s%sAre you really sure that you want to do this?"
msgstr ""
"あなたの以下の証明書を用いて今、認定署名を作ろうとしています:\n"
":\n"
"\"%s\"\n"
"これにより手書きの署名と法的に同等とされる署名が作成されます。\n"
"\n"
"%s%s本当にこれを望みますか?"
msgid ""
"Note, that this software is not officially approved to create or verify such "
"signatures.\n"
msgstr ""
"注意してください、このような署名を作成したり、検証したりすることについてこの"
"ソフトウェアは公式に承認されていません。\n"
#, c-format
msgid ""
"You are about to create a signature using your certificate:\n"
"\"%s\"\n"
"Note, that this certificate will NOT create a qualified signature!"
msgstr ""
"あなたの以下の証明書を用いて今、認定署名を作ろうとしています:\n"
"\"%s\"\n"
"注意してください: この証明書は署名を作るために作成されていません!"
#, c-format
msgid "hash algorithm %d (%s) for signer %d not supported; using %s\n"
msgstr ""
"ハッシュ・アルゴリズム %d (%s)(署名人 %d へ)はサポートされていません。%s を使"
"います\n"
#, c-format
msgid "hash algorithm used for signer %d: %s (%s)\n"
msgstr "署名者 %dのために使われたハッシュアルゴリズム: %s (%s)\n"
#, c-format
msgid "checking for qualified certificate failed: %s\n"
msgstr "適正な認定証明書の検査に失敗しました: %s\n"
msgid "Signature made "
msgstr "施された署名 "
msgid "[date not given]"
msgstr "[日時指定なし]"
#, c-format
msgid " using certificate ID 0x%08lX\n"
msgstr " 証明書 ID 0x%08lXを用います\n"
msgid ""
"invalid signature: message digest attribute does not match computed one\n"
msgstr ""
"無効な署名: メッセージ・ダイジェストの属性が計算されたものと一致しません\n"
msgid "Good signature from"
msgstr "正しい署名"
msgid " aka"
msgstr " 別名"
msgid "This is a qualified signature\n"
msgstr "これは認定署名です\n"
#, c-format
msgid "can't initialize certificate cache lock: %s\n"
msgstr "証明書キャッシュのロックが初期化できません: %s\n"
#, c-format
msgid "can't acquire read lock on the certificate cache: %s\n"
msgstr "証明書キャッシュの読み出しロックが取得できません: %s\n"
#, c-format
msgid "can't acquire write lock on the certificate cache: %s\n"
msgstr "証明書キャッシュの書き込みロックが取得できません: %s\n"
#, c-format
msgid "can't release lock on the certificate cache: %s\n"
msgstr "証明書キャッシュのロックが解除できません: %s\n"
#, c-format
msgid "dropping %u certificates from the cache\n"
msgstr "%uの証明書をキャッシュからはずします\n"
#, c-format
msgid "can't parse certificate '%s': %s\n"
msgstr "証明書'%s'が解析できません: %s\n"
#, c-format
msgid "certificate '%s' already cached\n"
msgstr "証明書'%s'はすでにキャッシュされています\n"
#, c-format
msgid "trusted certificate '%s' loaded\n"
msgstr "信用される証明書'%s'をロードしました\n"
#, c-format
msgid "certificate '%s' loaded\n"
msgstr "証明書'%s'をロードしました\n"
#, c-format
msgid " SHA1 fingerprint = %s\n"
msgstr " SHA1フィンガープリント = %s\n"
msgid " issuer ="
msgstr " 発行者 ="
msgid " subject ="
msgstr "サブジェクト ="
#, c-format
msgid "error loading certificate '%s': %s\n"
msgstr "証明書'%s'の読み込みエラー: %s\n"
#, c-format
msgid "permanently loaded certificates: %u\n"
msgstr "永続的にロードされる証明書: %u\n"
#, c-format
msgid " runtime cached certificates: %u\n"
msgstr "実行時キャッシュ証明書の数: %u\n"
#, c-format
msgid " trusted certificates: %u (%u,%u,%u,%u)\n"
msgstr " 信用された証明書の数: %u (%u,%u,%u,%u)\n"
msgid "certificate already cached\n"
msgstr " すでにキャッシュされた証明書\n"
msgid "certificate cached\n"
msgstr "キャッシュされた証明書\n"
#, c-format
msgid "error caching certificate: %s\n"
msgstr "証明書のキャッシュのエラー: %s\n"
#, c-format
msgid "invalid SHA1 fingerprint string '%s'\n"
msgstr "無効なSHA1フィンガープリント文字列'%s'\n"
#, c-format
msgid "error fetching certificate by S/N: %s\n"
msgstr "S/Nでの証明書取得エラー : %s\n"
#, c-format
msgid "error fetching certificate by subject: %s\n"
msgstr "サブジェクトでの証明書取得エラー: %s\n"
msgid "no issuer found in certificate\n"
msgstr "証明書に発行者がみつかりません\n"
#, c-format
msgid "error getting authorityKeyIdentifier: %s\n"
msgstr "authorityKeyIdentifierの取得エラー: %s\n"
#, c-format
msgid "creating directory '%s'\n"
msgstr "ディレクトリ'%s'の作成\n"
#, c-format
msgid "error creating directory '%s': %s\n"
msgstr "ディレクトリ'%s'の作成エラー: %s\n"
#, c-format
msgid "ignoring database dir '%s'\n"
msgstr "データベース・ディレクトリ'%s'を無視します\n"
#, c-format
msgid "error reading directory '%s': %s\n"
msgstr "ディレクトリ'%s'の読み込みエラー: %s\n"
#, c-format
msgid "removing cache file '%s'\n"
msgstr "キャッシュ・ファイル' %s'の削除\n"
#, c-format
msgid "not removing file '%s'\n"
msgstr "ファイル'%s'を削除しません\n"
#, c-format
msgid "error closing cache file: %s\n"
msgstr "キャッシュ・ファイルでクローズのエラー: %s\n"
#, c-format
msgid "failed to open cache dir file '%s': %s\n"
msgstr "キャッシュ・ディレクトリ・ファイル'%s'が開けません: %s\n"
#, c-format
msgid "error creating new cache dir file '%s': %s\n"
msgstr "新しいキャッシュ・ディレクトリ・ファイル'%s'の作成エラー: %s\n"
#, c-format
msgid "error writing new cache dir file '%s': %s\n"
msgstr "新しいキャッシュ・ディレクトリ・ファイル'%s'の書き込みエラー: %s\n"
#, c-format
msgid "error closing new cache dir file '%s': %s\n"
msgstr "新しいキャッシュ・ディレクトリ・ファイル'%s'でクローズのエラー: %s\n"
#, c-format
msgid "new cache dir file '%s' created\n"
msgstr "新しいキャッシュ・ディレクトリ・ファイル'%s'ができました\n"
#, c-format
msgid "failed to re-open cache dir file '%s': %s\n"
msgstr "キャッシュ・ディレクトリ・ファイル'%s'が再オープンできません: %s\n"
#, c-format
msgid "first record of '%s' is not the version\n"
msgstr "最初のレコード'%s'はそのバージョンではありません。\n"
msgid "old version of cache directory - cleaning up\n"
msgstr "古いバージョンのキャッシュ・ディレクトリ - きれいにします\n"
msgid "old version of cache directory - giving up\n"
msgstr "古いバージョンのキャッシュ・ディレクトリ - あきらめます\n"
#, c-format
msgid "extra field detected in crl record of '%s' line %u\n"
msgstr "crlレコードの'%s'に行 %u で余分なフィールドが検出されました\n"
#, c-format
msgid "invalid line detected in '%s' line %u\n"
msgstr "'%s' (行 %u)で無効な行\n"
#, c-format
msgid "duplicate entry detected in '%s' line %u\n"
msgstr "'%s' (行 %u)で重複したエントリ\n"
#, c-format
msgid "unsupported record type in '%s' line %u skipped\n"
msgstr "サポートしていないレコード型 '%s' を行 %u でスキップしました\n"
#, c-format
msgid "invalid issuer hash in '%s' line %u\n"
msgstr "'%s'の無効な発行者ハッシュ(行 %u)\n"
#, c-format
msgid "no issuer DN in '%s' line %u\n"
msgstr "'%s'で発行者DNがありません(行 %u)\n"
#, c-format
msgid "invalid timestamp in '%s' line %u\n"
msgstr "'%s'の無効なタイムスタンプ(行 %u)\n"
#, c-format
msgid "WARNING: invalid cache file hash in '%s' line %u\n"
msgstr "*警告*: '%s'で無効なキャッシュ・ファイル・ハッシュ(行 %u)\n"
msgid "detected errors in cache dir file\n"
msgstr "キャッシュ・ディレクトリ・ファイルの検出エラー\n"
msgid "please check the reason and manually delete that file\n"
msgstr "理由を確認し、手動でそのファイルを削除してください\n"
#, c-format
msgid "failed to create temporary cache dir file '%s': %s\n"
msgstr "一時キャッシュ・ディレクトリ・ファイル'%s'が作成できません: %s\n"
#, c-format
msgid "error closing '%s': %s\n"
msgstr "'%s'でクローズのエラー: %s\n"
#, c-format
msgid "error renaming '%s' to '%s': %s\n"
msgstr "'%s'から'%s'へ名前変更のエラー: %s\n"
#, c-format
msgid "can't hash '%s': %s\n"
msgstr "'%s'をハッシュできません: %s\n"
#, c-format
msgid "error setting up MD5 hash context: %s\n"
msgstr "MD5ハッシュ・コンテクストの設定エラー: %s\n"
#, c-format
msgid "error hashing '%s': %s\n"
msgstr "'%s'でハッシュのエラー: %s\n"
#, c-format
msgid "invalid formatted checksum for '%s'\n"
msgstr "'%s'に対する無効な形式のチェックサム\n"
msgid "too many open cache files; can't open anymore\n"
msgstr ""
"キャッシュ・ファイルを多くオープンしすぎです。これ以上オープンできません\n"
#, c-format
msgid "opening cache file '%s'\n"
msgstr "キャッシュ・ファイル'%s'を開く\n"
#, c-format
msgid "error opening cache file '%s': %s\n"
msgstr "キャッシュ・ファイル'%s'を開く際、エラー: %s\n"
#, c-format
msgid "error initializing cache file '%s' for reading: %s\n"
msgstr "キャッシュ・ファイル '%s' の初期化エラー、読み込み: %s\n"
msgid "calling unlock_db_file on a closed file\n"
msgstr "unlock_db_file のクローズしたファイルへの呼び出し\n"
msgid "calling unlock_db_file on an unlocked file\n"
msgstr "unlock_db_fileのロックしてないファイルへの呼び出し\n"
#, c-format
msgid "failed to create a new cache object: %s\n"
msgstr "新しいキャッシュ・オブジェクトを作成するのに失敗しました: %s\n"
#, c-format
msgid "no CRL available for issuer id %s\n"
msgstr "発行者ID%sに対してCRLはありません\n"
#, c-format
msgid "cached CRL for issuer id %s too old; update required\n"
msgstr "キャッシュされたCRLの発行者ID %s が古すぎます。更新が必要です\n"
#, c-format
msgid ""
"force-crl-refresh active and %d minutes passed for issuer id %s; update "
"required\n"
msgstr ""
"force-crl-refreshが有効で%d分が発行者ID%sに経過しました。更新が必要です\n"
#, c-format
msgid "force-crl-refresh active for issuer id %s; update required\n"
msgstr "force-crl-refreshが発行者ID%sに対し有効です。更新が必要です\n"
#, c-format
msgid "available CRL for issuer ID %s can't be used\n"
msgstr "発行者ID%sに対する利用可能なCRLが使用できません\n"
#, c-format
msgid "cached CRL for issuer id %s tampered; we need to update\n"
msgstr ""
"発行者ID%sに対するキャッシュされたCRLが変更されています。更新が必要です\n"
msgid "WARNING: invalid cache record length for S/N "
msgstr "**警告**: S/Nに対する無効なキャッシュ・レコード長"
#, c-format
msgid "problem reading cache record for S/N %s: %s\n"
msgstr "S/N %sに対するキャッシュ・レコードを読み込む際の問題: %s\n"
#, c-format
msgid "S/N %s is not valid; reason=%02X date=%.15s\n"
msgstr "S/N %s は無効です。理由=%02X 日付=%.15s\n"
#, c-format
msgid "S/N %s is valid, it is not listed in the CRL\n"
msgstr "S/N %sは有効です。CRLに載っていません\n"
#, c-format
msgid "error getting data from cache file: %s\n"
msgstr "キャッシュ・ファイルからデータの取得エラー: %s\n"
#, c-format
msgid "unknown hash algorithm '%s'\n"
msgstr "不明なハッシュ・アルゴリズム'%s'\n"
#, c-format
msgid "gcry_md_open for algorithm %d failed: %s\n"
msgstr "アルゴリズム%dのgcry_md_openが失敗: %s\n"
msgid "got an invalid S-expression from libksba\n"
msgstr "libksbaから無効なS-式を取得しました\n"
#, c-format
msgid "converting S-expression failed: %s\n"
msgstr "S式の変換に失敗しました: %s\n"
#, c-format
msgid "creating S-expression failed: %s\n"
msgstr "S式の作成に失敗しました: %s\n"
#, c-format
msgid "ksba_crl_parse failed: %s\n"
msgstr "ksba_crl_parse に失敗しました: %s\n"
#, c-format
msgid "error getting update times of CRL: %s\n"
msgstr "CRLの更新時刻の取得エラー: %s\n"
#, c-format
msgid "update times of this CRL: this=%s next=%s\n"
msgstr "このCRLの更新時刻: これ=%s 次=%s\n"
msgid "nextUpdate not given; assuming a validity period of one day\n"
msgstr "nextUpdateが与えられていません。一日の有効期間を仮定します\n"
#, c-format
msgid "error getting CRL item: %s\n"
msgstr "CRL項目の取得エラー: %s\n"
#, c-format
msgid "error inserting item into temporary cache file: %s\n"
msgstr "一時キャッシュ・ファイルに項目の挿入エラー: %s\n"
#, c-format
msgid "no CRL issuer found in CRL: %s\n"
msgstr "CRLに発行者がありません: %s\n"
msgid "locating CRL issuer certificate by authorityKeyIdentifier\n"
msgstr "CRL発行証明書をauthorityKeyIdentifierで見つけます\n"
#, c-format
msgid "CRL signature verification failed: %s\n"
msgstr "CRL署名の検証に失敗しました: %s\n"
#, c-format
msgid "error checking validity of CRL issuer certificate: %s\n"
msgstr "CRL発行者証明書の検証検査エラ−: %s\n"
#, c-format
msgid "ksba_crl_new failed: %s\n"
msgstr "ksba_crl_new が失敗しました: %s\n"
#, c-format
msgid "ksba_crl_set_reader failed: %s\n"
msgstr "ksba_crl_set_reader が失敗しました: %s\n"
#, c-format
msgid "removed stale temporary cache file '%s'\n"
msgstr "古い一時キャッシュ・ファイル'%s'を削除しました\n"
#, c-format
msgid "problem removing stale temporary cache file '%s': %s\n"
msgstr "古い一時キャッシュ・ファイル'%s'が削除の問題: %s\n"
#, c-format
msgid "error creating temporary cache file '%s': %s\n"
msgstr "一時キャッシュ・ファイル'%s'の作成エラー: %s\n"
#, c-format
msgid "crl_parse_insert failed: %s\n"
msgstr "crl_parse_insert が失敗しました: %s\n"
#, c-format
msgid "error finishing temporary cache file '%s': %s\n"
msgstr "一時キャッシュ・ファイル'%s'の終了エラー: %s\n"
#, c-format
msgid "error closing temporary cache file '%s': %s\n"
msgstr "一時キャッシュ・ファイル'%s'のクローズ・エラー: %s\n"
#, c-format
msgid "WARNING: new CRL still too old; it expired on %s - loading anyway\n"
msgstr ""
"**警告**: 新しいCRLはまだ古すぎます。%sに期限がきています - それでも読み込み"
"ます\n"
#, c-format
msgid "new CRL still too old; it expired on %s\n"
msgstr "新しいCRLはまだ古すぎます。%sに期限がきています\n"
#, c-format
msgid "unknown critical CRL extension %s\n"
msgstr "不明のクリティカルCRLの拡張 %s\n"
#, c-format
msgid "error reading CRL extensions: %s\n"
msgstr "CRL拡張の読み込みエラー: %s\n"
#, c-format
msgid "creating cache file '%s'\n"
msgstr "キャッシュ・ファイル'%s'の作成\n"
#, c-format
msgid "problem renaming '%s' to '%s': %s\n"
msgstr "'%s'から'%s'へ名前変更の問題: %s\n"
msgid ""
"updating the DIR file failed - cache entry will get lost with the next "
"program start\n"
msgstr ""
"DIRファイルの更新の失敗 - キャッシュ・エントリは次のプログラムの開始で失われ"
"ます\n"
#, c-format
msgid "Begin CRL dump (retrieved via %s)\n"
msgstr "CRLダンプの開始 (%s から取得)\n"
msgid ""
" ERROR: The CRL will not be used because it was still too old after an "
"update!\n"
msgstr "*エラー*: CRLは使用されません。更新後でも、古すぎるからです!\n"
msgid ""
" ERROR: The CRL will not be used due to an unknown critical extension!\n"
msgstr "*エラー*: CRLは不明なクリティカル拡張のため使用されません!\n"
msgid " ERROR: The CRL will not be used\n"
msgstr "*エラー*: CRLは使用されません\n"
msgid " ERROR: This cached CRL may have been tampered with!\n"
msgstr "*エラー*: このキャッシュされたCRLは変更されているかもしれません!\n"
msgid " WARNING: invalid cache record length\n"
msgstr "*警告*: 無効なキャッシュ・レコード長\n"
#, c-format
msgid "problem reading cache record: %s\n"
msgstr "キャッシュ・レコードの読み込みの問題: %s\n"
#, c-format
msgid "problem reading cache key: %s\n"
msgstr "キャッシュ鍵の再読み込みの問題: %s\n"
#, c-format
msgid "error reading cache entry from db: %s\n"
msgstr "dbからキャッシュ・エントリの読み込みエラー: %s\n"
msgid "End CRL dump\n"
msgstr "CRLダンプの終了\n"
#, c-format
msgid "crl_fetch via DP failed: %s\n"
msgstr "DPからcrl_fetchが失敗しました: %s\n"
#, c-format
msgid "crl_cache_insert via DP failed: %s\n"
msgstr "DPからcrl_cache_insertが失敗しました: %s\n"
#, c-format
msgid "crl_cache_insert via issuer failed: %s\n"
msgstr "発行者からcrl_cache_insertが失敗しました: %s\n"
msgid "reader to file mapping table full - waiting\n"
msgstr "readerからファイル・マッピングのテーブルがいっぱいです - 待ちます\n"
#, c-format
msgid "CRL access not possible due to disabled %s\n"
msgstr "CRLアクセスは停止された%sのため不可能です\n"
#, c-format
msgid "error retrieving '%s': %s\n"
msgstr "'%s'を取得する際のエラー: %s\n"
#, c-format
msgid "error initializing reader object: %s\n"
msgstr "リーダ・オブジェクトの初期化エラー: %s\n"
msgid "CRL access not possible due to Tor mode\n"
msgstr "CRLアクセスはTorモードのため不可能です\n"
#, c-format
msgid "certificate search not possible due to disabled %s\n"
msgstr "禁止されているため、証明書の探索ができません: %s\n"
msgid "use OCSP instead of CRLs"
msgstr "OCSPをCRLの代わりに使います"
msgid "check whether a dirmngr is running"
msgstr "dirmngrが動いているかどうか確認します"
msgid "add a certificate to the cache"
msgstr "証明書をキャッシュに追加します"
msgid "validate a certificate"
msgstr "証明書を検証する"
msgid "lookup a certificate"
msgstr "証明書を探索する"
msgid "lookup only locally stored certificates"
msgstr "ローカルに保持された証明書だけを探索します"
msgid "expect an URL for --lookup"
msgstr "--lookupにはURLがきます"
msgid "load a CRL into the dirmngr"
msgstr "dirmngrにCRLをロードする"
msgid "special mode for use by Squid"
msgstr "Squidのための特別なモード"
msgid "expect certificates in PEM format"
msgstr "証明書はPEM形式を期待します"
msgid "force the use of the default OCSP responder"
msgstr "デフォルトOCSP応答の使用を強制します"
msgid "Usage: dirmngr-client [options] [certfile|pattern] (-h for help)\n"
msgstr ""
"使い方: dirmngr-client [オプション] [証明書ファイル|パターン] (ヘルプは -h)\n"
msgid ""
"Syntax: dirmngr-client [options] [certfile|pattern]\n"
"Test an X.509 certificate against a CRL or do an OCSP check\n"
"The process returns 0 if the certificate is valid, 1 if it is\n"
"not valid and other error codes for general failures\n"
msgstr ""
"形式: dirmngr-client [オプション] [証明書ファイル|パターン]\n"
"X.509証明書をCRLに対してテストする、あるいはOCSPチェックを行う\n"
"プロセスは証明書が有効の場合、0を返し、有効でない場合、1 を返す。\n"
"一般の失敗の場合、そのほかのエラーコードを返す\n"
#, c-format
msgid "error reading certificate from stdin: %s\n"
msgstr "stdinから証明書読み込みエラー: %s\n"
#, c-format
msgid "error reading certificate from '%s': %s\n"
msgstr "'%s'から証明書の読み込みエラー: %s\n"
msgid "certificate too large to make any sense\n"
msgstr "証明書は意味のあるものとしては大きすぎます\n"
#, c-format
msgid "can't connect to the dirmngr: %s\n"
msgstr "dirmngrへ接続できません: %s\n"
#, c-format
msgid "lookup failed: %s\n"
msgstr "検索に失敗しました: %s\n"
#, c-format
msgid "loading CRL '%s' failed: %s\n"
msgstr "CRL'%s'の読み込みが失敗しました: %s\n"
msgid "a dirmngr daemon is up and running\n"
msgstr "dirmngr daemonが起動され動いています\n"
#, c-format
msgid "validation of certificate failed: %s\n"
msgstr "証明書の検証に失敗しました: %s\n"
msgid "certificate is valid\n"
msgstr "証明書は正しいです\n"
msgid "certificate has been revoked\n"
msgstr "証明書は失効済みです\n"
#, c-format
msgid "certificate check failed: %s\n"
msgstr "証明書の検査に失敗しました: %s\n"
#, c-format
msgid "got status: '%s'\n"
msgstr "ステイタス'%s'を取得しました\n"
#, c-format
msgid "error writing base64 encoding: %s\n"
msgstr "base64エンコーディングの書き込みエラー: %s\n"
#, c-format
msgid "unsupported inquiry '%s'\n"
msgstr "サポートされていない問い合わせ: '%s'\n"
msgid "absolute file name expected\n"
msgstr "絶対ファイル名がきます\n"
#, c-format
msgid "looking up '%s'\n"
msgstr "'%s'を検索します\n"
msgid "list the contents of the CRL cache"
msgstr "CRLキャッシュの内容をリストします"
msgid "|FILE|load CRL from FILE into cache"
msgstr "|FILE|FILEからCRLをキャッシュにロードする"
msgid "|URL|fetch a CRL from URL"
msgstr "|URL|URLからCRLを取得します"
msgid "shutdown the dirmngr"
msgstr "dirmngrをシャットダウンする"
msgid "flush the cache"
msgstr "キャッシュをフラッシュします"
msgid "|FILE|write server mode logs to FILE"
msgstr "|FILE|FILEにサーバ・モードのログを書き出す"
msgid "run without asking a user"
msgstr "ユーザに問い合わせせずに実行"
msgid "force loading of outdated CRLs"
msgstr "期日の過ぎたCRLのロードを強制する"
msgid "allow sending OCSP requests"
msgstr "OCSP要求の送信を認める"
msgid "allow online software version check"
msgstr "オンラインのソフトウェア・バージョン・チェックを許す"
msgid "inhibit the use of HTTP"
msgstr "HTTPの使用を禁止する"
msgid "inhibit the use of LDAP"
msgstr "LDAPの使用を禁止する"
msgid "ignore HTTP CRL distribution points"
msgstr "HTTP CRL配布ポイントを無視する"
msgid "ignore LDAP CRL distribution points"
msgstr "LDAP CRL配布ポイントを無視する"
msgid "ignore certificate contained OCSP service URLs"
msgstr "OCSPサービスURLに入っている証明書を無視する"
msgid "|URL|redirect all HTTP requests to URL"
msgstr "|URL|すべてのHTTPリクエストをURLにリダイレクトする"
msgid "|HOST|use HOST for LDAP queries"
msgstr "|HOST|LDAPの問い合わせにHOSTを使う"
msgid "do not use fallback hosts with --ldap-proxy"
msgstr "--ldap-proxy にフォールバック・ホストを使わない"
msgid "|FILE|read LDAP server list from FILE"
msgstr "|FILE|FILEからLDAPサーバリストを読み込みます"
msgid "add new servers discovered in CRL distribution points to serverlist"
msgstr "CRL配布ポイントに発見された新しいサーバを serverlist に追加する"
msgid "|N|set LDAP timeout to N seconds"
msgstr "|N|LDAPのタイムアウトをN秒とする"
msgid "|URL|use OCSP responder at URL"
msgstr "|URL|OCSP応答としてURLを使用"
msgid "|FPR|OCSP response signed by FPR"
msgstr "|FPR|FPRで署名されたOCSPレスポンス"
msgid "|N|do not return more than N items in one query"
msgstr "|N|一つのクエリでNを越えるのアイテムを返さない"
msgid "|FILE|use the CA certificates in FILE for HKP over TLS"
msgstr "|FILE|FILEにあるCA証明書をTLSでのHKPに使う"
msgid "route all network traffic via Tor"
msgstr "ネットワーク・トラフィックをすべてTor経由にする"
msgid ""
"@\n"
"(See the \"info\" manual for a complete listing of all commands and "
"options)\n"
msgstr ""
"@\n"
"(コマンドとオプション全部の一覧は、\"info\" マニュアルをご覧ください)\n"
msgid "Usage: @DIRMNGR@ [options] (-h for help)"
msgstr "使い方: @DIRMNGR@ [オプション] (ヘルプは -h)"
msgid ""
"Syntax: @DIRMNGR@ [options] [command [args]]\n"
"Keyserver, CRL, and OCSP access for @GNUPG@\n"
msgstr ""
"形式: @DIRMNGR@ [オプション] [コマンド [引数]]\n"
"@GnuPG@の鍵サーバ、CRLとOCSPアクセス\n"
#, c-format
msgid "valid debug levels are: %s\n"
msgstr "有効なdebugレベルは: %s\n"
#, c-format
msgid "usage: %s [options] "
msgstr "使い方: %s [オプション] "
msgid "colons are not allowed in the socket name\n"
msgstr "コロンはソケット名に許されません\n"
#, c-format
msgid "fetching CRL from '%s' failed: %s\n"
msgstr "'%s'からCRLの取得の失敗: %s\n"
#, c-format
msgid "processing CRL from '%s' failed: %s\n"
msgstr "'%s'からCRLの処理に失敗: %s\n"
#, c-format
msgid "%s:%u: line too long - skipped\n"
msgstr "%s:%u: 行が長すぎます - スキップされました\n"
#, c-format
msgid "%s:%u: invalid fingerprint detected\n"
msgstr "%s:%u: 無効なフィンガープリントが検出されました\n"
#, c-format
msgid "%s:%u: read error: %s\n"
msgstr "%s:%u: 読み込みエラー: %s\n"
#, c-format
msgid "%s:%u: garbage at end of line ignored\n"
msgstr "%s:%u: 行末のゴミを無視\n"
msgid "SIGHUP received - re-reading configuration and flushing caches\n"
msgstr "SIGHUPを受け取り - 設定を読み直し、キャッシュをフラッシュ\n"
msgid "SIGUSR2 received - no action defined\n"
msgstr "SIGUSR2を受け取り - 動作は定義されない\n"
msgid "SIGTERM received - shutting down ...\n"
msgstr "SIGTERMを受け取り - シャットダウン...\n"
#, c-format
msgid "SIGTERM received - still %d active connections\n"
msgstr "SIGTERMを受け取り - %d本のアクティブな接続がまだあります\n"
msgid "shutdown forced\n"
msgstr "強制的にシャットダウンする\n"
msgid "SIGINT received - immediate shutdown\n"
msgstr "SIGINTを受け取り - すぐにシャットダウン\n"
#, c-format
msgid "signal %d received - no action defined\n"
msgstr "シグナル%dを受け取り - アクションは定義されない\n"
msgid "return all values in a record oriented format"
msgstr "レコード形式ですべての値を返す"
msgid "|NAME|ignore host part and connect through NAME"
msgstr "|NAME|host部分を無視してNAMEをとおして接続する"
msgid "|NAME|connect to host NAME"
msgstr "|NAME|ホストNAMEに接続する"
msgid "|N|connect to port N"
msgstr "|N|ポートNに接続します"
msgid "|NAME|use user NAME for authentication"
msgstr "|NAME|ユーザNAMEを認証に使う"
msgid "|PASS|use password PASS for authentication"
msgstr "|PASS|パスワードPASSを認証に使う"
msgid "take password from $DIRMNGR_LDAP_PASS"
msgstr "パスワードを$DIRMNGR_LDAP_PASSから取ってくる"
msgid "|STRING|query DN STRING"
msgstr "|STRING|DN STRINGをクエリする"
msgid "|STRING|use STRING as filter expression"
msgstr "|STRING|STRINGをフィルタ式に使う"
msgid "|STRING|return the attribute STRING"
msgstr "|STRING|STRINGの属性を返す"
msgid "Usage: dirmngr_ldap [options] [URL] (-h for help)\n"
msgstr "使い方: dirmngr_ldap [オプション] [URL] (ヘルプは -h)\n"
msgid ""
"Syntax: dirmngr_ldap [options] [URL]\n"
"Internal LDAP helper for Dirmngr\n"
"Interface and options may change without notice\n"
msgstr ""
"形式: dirmngr_ldap [オプション] [URL]\n"
"Dirmngrの内部LDAPヘルパー\n"
"インタフェースとオプションは事前の通知なく変更されることがあります\n"
#, c-format
msgid "invalid port number %d\n"
msgstr "無効なポート番号 %d\n"
#, c-format
msgid "scanning result for attribute '%s'\n"
msgstr "属性'%s'のスキャン結果\n"
#, c-format
msgid "error writing to stdout: %s\n"
msgstr "stdoutへの書き込みエラー: %s\n"
#, c-format
msgid " available attribute '%s'\n"
msgstr " 利用可能な属性'%s'\n"
#, c-format
msgid "attribute '%s' not found\n"
msgstr "属性'%s'が見つかりません\n"
#, c-format
msgid "found attribute '%s'\n"
msgstr "属性'%s'が見つかりました\n"
#, c-format
msgid "processing url '%s'\n"
msgstr "url'%s'を処理\n"
#, c-format
msgid " user '%s'\n"
msgstr " ユーザ '%s'\n"
#, c-format
msgid " pass '%s'\n"
msgstr " パスワード '%s'\n"
#, c-format
msgid " host '%s'\n"
msgstr " ホスト '%s'\n"
#, c-format
msgid " port %d\n"
msgstr " ポート %d\n"
#, c-format
msgid " DN '%s'\n"
msgstr " DN '%s'\n"
#, c-format
msgid " filter '%s'\n"
msgstr " フィルタ '%s'\n"
#, c-format
msgid " attr '%s'\n"
msgstr " 属性 '%s'\n"
#, c-format
msgid "no host name in '%s'\n"
msgstr "'%s'にホスト名がありません\n"
#, c-format
msgid "no attribute given for query '%s'\n"
msgstr "クエリ '%s' に属性が指定されていません\n"
msgid "WARNING: using first attribute only\n"
msgstr "*警告*: 最初の属性だけを使っています\n"
#, c-format
msgid "LDAP init to '%s:%d' failed: %s\n"
msgstr "'%s:%d'のLDAP初期化に失敗: %s\n"
#, c-format
msgid "binding to '%s:%d' failed: %s\n"
msgstr "'%s:%d'のバインドに失敗: %s\n"
#, c-format
msgid "searching '%s' failed: %s\n"
msgstr "'%s'の探索に失敗しました: %s\n"
#, c-format
msgid "'%s' is not an LDAP URL\n"
msgstr "'%s'は、LDAP URLではありません\n"
#, c-format
msgid "'%s' is an invalid LDAP URL\n"
msgstr "'%s' は無効なLDAP URLです\n"
#, c-format
msgid "error accessing '%s': http status %u\n"
msgstr "'%s'へアクセスのエラー: httpステイタス %u\n"
+#, c-format
+msgid "URL '%s' redirected to '%s' (%u)\n"
+msgstr "URL'%s' は '%s' (%u) へリダイレクトされました\n"
+
+msgid "too many redirections\n"
+msgstr "リダイレクトが多すぎます\n"
+
+#, c-format
+msgid "redirection changed to '%s'\n"
+msgstr "リダイレクトが'%s'に変更されました\n"
+
#, c-format
msgid "error allocating memory: %s\n"
msgstr "メモリの確保のエラー: %s\n"
#, c-format
msgid "error printing log line: %s\n"
msgstr "log出力エラー: %s\n"
#, c-format
msgid "error reading log from ldap wrapper %d: %s\n"
msgstr "ldap wrapper %dからのログの読み込みエラー: %s\n"
#, c-format
msgid "ldap wrapper %d ready"
msgstr "ldap wrapper %d が準備完了"
#, c-format
msgid "ldap wrapper %d ready: timeout\n"
msgstr "ldap wrapper %d が準備完了: タイムアウト\n"
#, c-format
msgid "ldap wrapper %d ready: exitcode=%d\n"
msgstr "ldap wrapper %d が準備完了: exitcode=%d\n"
#, c-format
msgid "waiting for ldap wrapper %d failed: %s\n"
msgstr "ldap wrapper %dの待ちが失敗: %s\n"
#, c-format
msgid "ldap wrapper %d stalled - killing\n"
msgstr "ldap wrapper %d が止まりました - killしています\n"
#, c-format
msgid "invalid char 0x%02x in host name - not added\n"
msgstr "ホスト名に無効な文字 0x%02x - 加えません\n"
#, c-format
msgid "adding '%s:%d' to the ldap server list\n"
msgstr "'%s:%d'をLDAPサーバ・リストに追加\n"
#, c-format
msgid "malloc failed: %s\n"
msgstr "mallocが失敗しました: %s\n"
#, c-format
msgid "start_cert_fetch: invalid pattern '%s'\n"
msgstr "start_cert_fetch: 無効なパターン '%s'\n"
msgid "ldap_search hit the size limit of the server\n"
msgstr "ldap_search がサーバのサイズ限界を越えました\n"
msgid "invalid canonical S-expression found\n"
msgstr "無効な正規S式が見つかりました\n"
#, c-format
msgid "gcry_md_open failed: %s\n"
msgstr "gcry_md_openに失敗しました: %s\n"
#, c-format
msgid "oops: ksba_cert_hash failed: %s\n"
msgstr "おっと: ksba_cert_hashが失敗しました: %s\n"
msgid "bad URL encoding detected\n"
msgstr "不正なURLエンコーディングが検出されました\n"
#, c-format
msgid "error reading from responder: %s\n"
msgstr "応答からの読み込みエラー: %s\n"
#, c-format
msgid "response from server too large; limit is %d bytes\n"
msgstr "サーバからの応答がが長すぎます (上限%dバイト)。\n"
msgid "OCSP request not possible due to Tor mode\n"
msgstr "TorモードのためOCSPリクエストが不可能です\n"
msgid "OCSP request not possible due to disabled HTTP\n"
msgstr "HTTPが停止されているためOCSPリクエストが不可能です\n"
#, c-format
msgid "error setting OCSP target: %s\n"
msgstr "OCSPターゲットの設定エラー: %s\n"
#, c-format
msgid "error building OCSP request: %s\n"
msgstr "OCSP要求のビルド・エラー: %s\n"
#, c-format
msgid "error connecting to '%s': %s\n"
msgstr "'%s'の接続エラー: %s\n"
#, c-format
msgid "error reading HTTP response for '%s': %s\n"
msgstr "'%s'のHTTP応答の読み込みエラー: %s\n"
-#, c-format
-msgid "URL '%s' redirected to '%s' (%u)\n"
-msgstr "URL'%s' は '%s' (%u) へリダイレクトされました\n"
-
-msgid "too many redirections\n"
-msgstr "リダイレクトが多すぎます\n"
-
#, c-format
msgid "error parsing OCSP response for '%s': %s\n"
msgstr "'%s'に対するOCSP応答構文解析エラー: %s\n"
#, c-format
msgid "OCSP responder at '%s' status: %s\n"
msgstr "OSCP応答が '%s' でステイタス: %s\n"
+#, c-format
+msgid "failed to establish a hashing context for OCSP: %s\n"
+msgstr "OCSPのハッシュ・コンテクストを確立するのに失敗しました: %s\n"
+
#, c-format
msgid "hashing the OCSP response for '%s' failed: %s\n"
msgstr "'%s'に対するOCSP応答のハッシングに失敗しました: %s\n"
msgid "not signed by a default OCSP signer's certificate"
msgstr "デフォルトOCSP署名者の証明で署名されていません"
-msgid "only SHA-1 is supported for OCSP responses\n"
-msgstr "SHA-1だけがOCSPレスポンスとしてサポートされています\n"
-
#, c-format
msgid "allocating list item failed: %s\n"
msgstr "リスト項目の確保に失敗しました: %s\n"
#, c-format
msgid "error getting responder ID: %s\n"
msgstr "応答IDの取得エラー: %s\n"
msgid "no suitable certificate found to verify the OCSP response\n"
msgstr "OCSP応答を検証する適切な証明書がありませんでした\n"
#, c-format
msgid "issuer certificate not found: %s\n"
msgstr "発行者証明書が見つかりません: %s\n"
msgid "caller did not return the target certificate\n"
msgstr "呼出側が対象の証明書を返しませんでした\n"
msgid "caller did not return the issuing certificate\n"
msgstr "呼出側が発行される証明書を返しませんでした\n"
#, c-format
msgid "failed to allocate OCSP context: %s\n"
msgstr "OCSPコンテクストの確保に失敗しました: %s\n"
#, c-format
msgid "can't get authorityInfoAccess: %s\n"
msgstr "authorityInfoAccessを取得できません: %s\n"
msgid "no default OCSP responder defined\n"
msgstr "デフォルトOCSPレスポンダが定義されていません\n"
msgid "no default OCSP signer defined\n"
msgstr "デフォルトのOCSP署名者が定義されていません\n"
#, c-format
msgid "using default OCSP responder '%s'\n"
msgstr "デフォルトOCSP応答'%s'を使います\n"
#, c-format
msgid "using OCSP responder '%s'\n"
msgstr "OCSP応答'%s'を使います\n"
-#, c-format
-msgid "failed to establish a hashing context for OCSP: %s\n"
-msgstr "OCSPのハッシュ・コンテクストを確立するのに失敗しました: %s\n"
-
#, c-format
msgid "error getting OCSP status for target certificate: %s\n"
msgstr "対象の証明書のOCSPステイタスの取得エラー: %s\n"
#, c-format
msgid "certificate status is: %s (this=%s next=%s)\n"
msgstr "証明書ステイタスは: %s (これ=%s 次=%s)\n"
msgid "good"
msgstr "良好"
#, c-format
msgid "certificate has been revoked at: %s due to: %s\n"
msgstr "証明書は失効済みです: %s (理由: %s)\n"
msgid "OCSP responder returned a status in the future\n"
msgstr "OSCPレスポンダは未来のステイタスを返しました\n"
msgid "OCSP responder returned a non-current status\n"
msgstr "OSCPレスポンダは現在でないステイタスを返しました\n"
msgid "OCSP responder returned an too old status\n"
msgstr "OSCPレスポンダは古すぎるステイタスを返しました\n"
#, c-format
msgid "assuan_inquire(%s) failed: %s\n"
msgstr "assuan_inquire(%s)が失敗しました: %s\n"
msgid "ldapserver missing"
msgstr "ldapserverがありません"
msgid "serialno missing in cert ID"
msgstr "serialnoがcert IDにありません"
#, c-format
msgid "assuan_inquire failed: %s\n"
msgstr "assuan_inquireに失敗しました: %s\n"
#, c-format
msgid "fetch_cert_by_url failed: %s\n"
msgstr "fetch_cert_by_url が失敗しました: %s\n"
#, c-format
msgid "error sending data: %s\n"
msgstr "データ送信エラー: %s\n"
#, c-format
msgid "start_cert_fetch failed: %s\n"
msgstr "start_cert_fetch が失敗しました: %s\n"
#, c-format
msgid "fetch_next_cert failed: %s\n"
msgstr "fetch_next_cert が失敗しました: %s\n"
#, c-format
msgid "max_replies %d exceeded\n"
msgstr "max_replies %d を越えました\n"
#, c-format
msgid "can't allocate control structure: %s\n"
msgstr "制御構造を確保できません: %s\n"
#, c-format
msgid "failed to allocate assuan context: %s\n"
msgstr "assuanコンテクストの確保に失敗しました: %s\n"
#, c-format
msgid "failed to initialize the server: %s\n"
msgstr "サーバの初期化に失敗しました: %s\n"
#, c-format
msgid "failed to the register commands with Assuan: %s\n"
msgstr "Assuanで登録コマンドに失敗しました: %s\n"
#, c-format
msgid "Assuan accept problem: %s\n"
msgstr "Assuan accept の問題: %s\n"
#, c-format
msgid "Assuan processing failed: %s\n"
msgstr "Assuanの処理が失敗しました: %s\n"
msgid "accepting root CA not marked as a CA"
msgstr "CAとしてマークされていないルートCAを受領します"
msgid "CRL checking too deeply nested\n"
msgstr "CRL検査のネストが深すぎです\n"
msgid "not checking CRL for"
msgstr "CRL を確認しません"
msgid "checking CRL for"
msgstr "CRLの検査をしています"
msgid "selfsigned certificate has a BAD signature"
msgstr "自己署名証明書に*不正な*署名があります"
#, c-format
msgid "checking trustworthiness of root certificate failed: %s\n"
msgstr "ルート証明書の信用検査に失敗しました: %s\n"
msgid "certificate chain is good\n"
msgstr "証明書チェインは正しいです\n"
msgid "certificate should not have been used for CRL signing\n"
msgstr "証明書はCRL署名のために使われるべきではありませんでした\n"
msgid "quiet"
msgstr "おとなしく"
msgid "print data out hex encoded"
msgstr "16進でエンコードしてデータ出力を表示する"
msgid "decode received data lines"
msgstr "受信したデータ行をデコードする"
msgid "connect to the dirmngr"
msgstr "dirmngrへ接続"
msgid "|NAME|connect to Assuan socket NAME"
msgstr "|NAME|Assuanのソケット名NAMEに接続する"
msgid "|ADDR|connect to Assuan server at ADDR"
msgstr "|ADDR|ADDRのAssuanサーバに接続する"
msgid "run the Assuan server given on the command line"
msgstr "コマンド・ラインで与えられたAssuanサーバを実行する"
msgid "do not use extended connect mode"
msgstr "拡張接続モードを使わない"
msgid "|FILE|run commands from FILE on startup"
msgstr "|FILE|起動時にFILEからコマンドを実行する"
msgid "run /subst on startup"
msgstr "起動時に /subst を実行する"
msgid "Usage: @GPG@-connect-agent [options] (-h for help)"
msgstr "使い方: @GPG@-connect-agent [オプション] (ヘルプは -h)"
msgid ""
"Syntax: @GPG@-connect-agent [options]\n"
"Connect to a running agent and send commands\n"
msgstr ""
"形式: @GPG@-connect-agent [オプション]\n"
"実行中のagentに接続し、コマンドを送る\n"
#, c-format
msgid "option \"%s\" requires a program and optional arguments\n"
msgstr "オプション\"%s\"はプログラムとオプショナルの引数を要します\n"
#, c-format
msgid "option \"%s\" ignored due to \"%s\"\n"
msgstr "オプション\"%s\"は\"%s\"のため無視されました\n"
#, c-format
msgid "receiving line failed: %s\n"
msgstr "行の受信に失敗しました: %s\n"
msgid "line too long - skipped\n"
msgstr "行が長すぎます - スキップされました\n"
msgid "line shortened due to embedded Nul character\n"
msgstr "組込みのNulキャラクタのため行は短くされました\n"
#, c-format
msgid "unknown command '%s'\n"
msgstr "不明のコマンド'%s'\n"
#, c-format
msgid "sending line failed: %s\n"
msgstr "行の送信に失敗しました: %s\n"
#, c-format
msgid "error sending standard options: %s\n"
msgstr "標準オプションを送信エラー: %s\n"
msgid "Options controlling the diagnostic output"
msgstr "診断出力を制御するオプション"
msgid "Options controlling the configuration"
msgstr "コンフィグレーションを制御するオプション"
msgid "Options useful for debugging"
msgstr "デバッグのために有用なオプション"
msgid "Options controlling the security"
msgstr "セキュリティを制御するオプション"
msgid "|N|expire SSH keys after N seconds"
msgstr "|N|N秒後にSSH鍵を無効とする"
msgid "|N|set maximum PIN cache lifetime to N seconds"
msgstr "|N|最大PINキャッシュ存続時間をN秒とする"
msgid "|N|set maximum SSH key lifetime to N seconds"
msgstr "|N|最大SSH鍵存続時間をN秒とする"
msgid "Options enforcing a passphrase policy"
msgstr "パスワード・ポリシーの強制オプション"
msgid "do not allow bypassing the passphrase policy"
msgstr "パスワード・ポリシーを迂回することを認めない"
msgid "|N|set minimal required length for new passphrases to N"
msgstr "|N|新しいパスフレーズの必要とする最低長をNとする"
msgid "|N|require at least N non-alpha characters for a new passphrase"
msgstr ""
"|N|新しいパスフレーズとしてアルファベットでないキャラクタを最低N必要とする"
msgid "|FILE|check new passphrases against pattern in FILE"
msgstr "|FILE|新しいパスフレーズをFILEのパターンに対してチェックする"
msgid "|N|expire the passphrase after N days"
msgstr "|N|N日後にパスフレーズを期限切れとする"
msgid "do not allow the reuse of old passphrases"
msgstr "古いパスフレーズを再使用することを認めない"
msgid "|N|set the Pinentry timeout to N seconds"
msgstr "|N|PinentryのタイムアウトをN秒とする"
msgid "|NAME|use NAME as default secret key"
msgstr "|NAME|デフォルトの秘密鍵としてNAMEを用いる"
msgid "|NAME|encrypt to user ID NAME as well"
msgstr "|NAME|ユーザID NAMEにも暗号化する"
msgid "|SPEC|set up email aliases"
msgstr "|SPEC|電子メールエイリアスを設定する"
msgid "Configuration for Keyservers"
msgstr "キーサーバのコンフィグレーション"
msgid "|URL|use keyserver at URL"
msgstr "|URL|鍵サーバとしてURLを使用"
msgid "allow PKA lookups (DNS requests)"
msgstr "PKA検索(DNS要求)を認める"
msgid "|MECHANISMS|use MECHANISMS to locate keys by mail address"
msgstr "|MECHANISMS|メールアドレスによって鍵を特定する際、MECHANISMSを使用する"
msgid "disable all access to the dirmngr"
msgstr "dirmngrへのすべてのアクセスを無効とする"
msgid "|NAME|use encoding NAME for PKCS#12 passphrases"
msgstr "|NAME|PKCS#12のパスフレーズにNAMEのエンコーディングを使う"
msgid "do not check CRLs for root certificates"
msgstr "ルート証明書のCRLをチェックしない"
msgid "Options controlling the format of the output"
msgstr "出力フォーマットを制御するオプション"
msgid "Options controlling the interactivity and enforcement"
msgstr "インタラクティビティと強制を制御するオプション"
msgid "Options controlling the use of Tor"
msgstr "Torの使用を制御するオプション"
msgid "Configuration for HTTP servers"
msgstr "HTTPサーバのコンフィグレーション"
msgid "use system's HTTP proxy setting"
msgstr "システムのHTTPプロキシ設定を用います"
msgid "Configuration of LDAP servers to use"
msgstr "使用するLDAPサーバのコンフィグレーション"
msgid "LDAP server list"
msgstr "LDAPサーバ・リスト"
msgid "Configuration for OCSP"
msgstr "OCSPのコンフィグレーション"
msgid "OpenPGP"
msgstr "OpenPGP"
msgid "Private Keys"
msgstr "プライベート鍵"
msgid "Smartcards"
msgstr "スマートカード"
msgid "S/MIME"
msgstr "S/MIME"
msgid "Network"
msgstr "ネットワーク"
msgid "Passphrase Entry"
msgstr "パスフレーズ入力"
msgid "Component not suitable for launching"
msgstr "コンポーネントが起動するために適切ではありません"
+#, c-format
+msgid "Configuration file of component %s is broken\n"
+msgstr "コンポーネント%sのコンフィグレーション・ファイルが壊れています\n"
+
+#, c-format
+msgid "Note: Use the command \"%s%s\" to get details.\n"
+msgstr "注意: \"%s%s\"コマンドを使って詳細を得てください。\n"
+
#, c-format
msgid "External verification of component %s failed"
msgstr "コンポーネント%sの外部の検証が失敗しました"
msgid "Note that group specifications are ignored\n"
msgstr "グループ仕様は無視されていることに注意してください\n"
#, c-format
msgid "error closing '%s'\n"
msgstr "'%s'でクローズのエラー\n"
#, c-format
msgid "error parsing '%s'\n"
msgstr "'%s'でパーズのエラー\n"
msgid "list all components"
msgstr "すべてのコンポーネントをリストする"
msgid "check all programs"
msgstr "すべてのプログラムをチェックする"
msgid "|COMPONENT|list options"
msgstr "|COMPONENT|オプションをリストする"
msgid "|COMPONENT|change options"
msgstr "|COMPONENT|オプションを変更する"
msgid "|COMPONENT|check options"
msgstr "|COMPONENT|オプションをチェックする"
msgid "apply global default values"
msgstr "グローバル・デフォルト値を適用する"
msgid "|FILE|update configuration files using FILE"
msgstr "|FILE|FILEを使ってコンフィグレーション・ファイルを更新する"
msgid "get the configuration directories for @GPGCONF@"
msgstr "@GPGCONF@のためにコンフィグレーション・ディレクトリを取得する"
msgid "list global configuration file"
msgstr "グローバルのコンフィグレーション・ファイルをリストする"
msgid "check global configuration file"
msgstr "グローバルのコンフィグレーション・ファイルをチェックする"
msgid "query the software version database"
msgstr "ソフトウェア・バージョン・データベースに問い合わせる"
msgid "reload all or a given component"
msgstr "すべて、あるいは指定されたコンポーネントをリロードする"
msgid "launch a given component"
msgstr "指定されたコンポーネントを起動する"
msgid "kill a given component"
msgstr "指定されたコンポーネントをkillする"
msgid "use as output file"
msgstr "出力ファイルとして使用"
msgid "activate changes at runtime, if possible"
msgstr "可能な場合、実行時に変更を有効とする"
msgid "Usage: @GPGCONF@ [options] (-h for help)"
msgstr "使い方: @GPGCONF@ [オプション] (ヘルプは -h)"
msgid ""
"Syntax: @GPGCONF@ [options]\n"
"Manage configuration options for tools of the @GNUPG@ system\n"
msgstr ""
"形式: @GPGCONF@ [オプション]\n"
"@GNUPG@システムのツールに対しコンフィグレーション・オプションを管理する\n"
msgid "Need one component argument"
msgstr "一つコンポーネント引数が必要です"
msgid "Component not found"
msgstr "コンポーネントが見つかりません"
msgid "No argument allowed"
msgstr "引数は許可されていません"
msgid ""
"@\n"
"Commands:\n"
" "
msgstr ""
"@\n"
"@コマンド:\n"
" "
msgid "decryption modus"
msgstr "復号方式"
msgid "encryption modus"
msgstr "暗号方式"
msgid "tool class (confucius)"
msgstr "ツール・クラス (confucius)"
msgid "program filename"
msgstr "program [ファイル名]"
msgid "secret key file (required)"
msgstr "秘密鍵ファイル (必須)"
msgid "input file name (default stdin)"
msgstr "入力ファイル名 (デフォルト stdin)"
msgid "Usage: symcryptrun [options] (-h for help)"
msgstr "使い方: symcryption [オプション] (ヘルプは -h)"
msgid ""
"Syntax: symcryptrun --class CLASS --program PROGRAM --keyfile KEYFILE "
"[options...] COMMAND [inputfile]\n"
"Call a simple symmetric encryption tool\n"
msgstr ""
"形式: symcryptrun --class CLASS --program PROGRAM --keyfile KEYFILE [オプショ"
"ン...] COMMAND [入力ファイル]\n"
"シンプルな共通鍵暗号ツールを呼び出す\n"
#, c-format
msgid "%s on %s aborted with status %i\n"
msgstr "%s (%s の)がステイタス%iで中止されました\n"
#, c-format
msgid "%s on %s failed with status %i\n"
msgstr "%s (%s の)がステイタス%iで失敗しました\n"
#, c-format
msgid "can't create temporary directory '%s': %s\n"
msgstr "一時ディレクトリ'%s'が作成できません: %s\n"
#, c-format
msgid "could not open %s for writing: %s\n"
msgstr "%sを書き込みでオープンできませんでした: %s\n"
#, c-format
msgid "error writing to %s: %s\n"
msgstr "'%s'の書き込みエラー: %s\n"
#, c-format
msgid "error reading from %s: %s\n"
msgstr "'%s'の読み込みエラー: %s\n"
#, c-format
msgid "error closing %s: %s\n"
msgstr "'%s'でクローズのエラー: %s\n"
msgid "no --program option provided\n"
msgstr "--programオプションが指定されていません\n"
msgid "only --decrypt and --encrypt are supported\n"
msgstr "--decryptと--encryptだけがサポートされています\n"
msgid "no --keyfile option provided\n"
msgstr "--keyfileオプションが与えられていません\n"
msgid "cannot allocate args vector\n"
msgstr "引数ベクタが確保できません\n"
#, c-format
msgid "could not create pipe: %s\n"
msgstr "パイプが作成できませんでした: %s\n"
#, c-format
msgid "could not create pty: %s\n"
msgstr "ptyが作成できませんでした: %s\n"
#, c-format
msgid "could not fork: %s\n"
msgstr "fork できませんでした: %s\n"
#, c-format
msgid "execv failed: %s\n"
msgstr "execv が失敗しました: %s\n"
#, c-format
msgid "select failed: %s\n"
msgstr "select が失敗しました: %s\n"
#, c-format
msgid "read failed: %s\n"
msgstr "read が失敗しました: %s\n"
#, c-format
msgid "pty read failed: %s\n"
msgstr "pty read が失敗しました: %s\n"
#, c-format
msgid "waitpid failed: %s\n"
msgstr "waitpid が失敗しました: %s\n"
#, c-format
msgid "child aborted with status %i\n"
msgstr "子プロセスがステイタス %i で中止されました\n"
#, c-format
msgid "cannot allocate infile string: %s\n"
msgstr "infileの文字列が確保できません: %s\n"
#, c-format
msgid "cannot allocate outfile string: %s\n"
msgstr "outfileの文字列を確保できません: %s\n"
#, c-format
msgid "either %s or %s must be given\n"
msgstr "%s か %s のどちらかが与えられる必要があります\n"
msgid "no class provided\n"
msgstr "クラスが与えられていません\n"
#, c-format
msgid "class %s is not supported\n"
msgstr "クラス%sはサポートされていません\n"
msgid "Usage: gpg-check-pattern [options] patternfile (-h for help)\n"
msgstr "使い方: gpg-check-pattern [オプション] patternfile (ヘルプは -h)\n"
msgid ""
"Syntax: gpg-check-pattern [options] patternfile\n"
"Check a passphrase given on stdin against the patternfile\n"
msgstr ""
"形式: gpg-check-pattern [オプション] パターンファイル\n"
"パターンファイルに対して標準入力のパスフレーズを確認する\n"
+#, c-format
+msgid "%s card no. %s detected\n"
+msgstr "%s カード番号 %sを検出しました\n"
+
+msgid "authenticate to the card"
+msgstr "カードに対して認証します"
+
+msgid "send a reset to the card daemon"
+msgstr "リセットをカードデーモンに送ります"
+
+msgid "change a private data object"
+msgstr "プライベート・データオブジェクトを変更します"
+
+msgid "read a certificate from a data object"
+msgstr "証明書をデータオブジェクトから読み出します"
+
+msgid "store a certificate to a data object"
+msgstr "証明書をデータオブジェクトに保管します"
+
+msgid "store a private key to a data object"
+msgstr "プライベート鍵をデータオブジェクトに保管します"
+
+msgid "Yubikey management commands"
+msgstr "Yubikey管理コマンド"
+
+#~ msgid "no keyserver known (use option --keyserver)\n"
+#~ msgstr "既知の鍵サーバがありません (オプション--keyserverを使いましょう)\n"
+
+#~ msgid "error creating 'ultimately_trusted_keys' TOFU table: %s\n"
+#~ msgstr "'ultimately_trusted_keys' TOFUテーブル作成エラー: %s\n"
+
+#~ msgid "error creating 'encryptions' TOFU table: %s\n"
+#~ msgstr "'encryptions' TOFUデータベースの作成エラー: %s\n"
+
+#~ msgid "adding column effective_policy to bindings DB: %s\n"
+#~ msgstr "バインディングDBにカラムeffective_policyを追加: %s\n"
+
+#~ msgid "resetting keydb: %s\n"
+#~ msgstr "keydbをリセット: %s\n"
+
+#~ msgid "error setting TOFU binding's policy to %s\n"
+#~ msgstr "TOFUバインディングのポリシーを %s に設定エラー\n"
+
+#~ msgid "%s: Verified %ld~signature in the past %s."
+#~ msgid_plural "%s: Verified %ld~signatures in the past %s."
+#~ msgstr[0] "%s: 署名を%ld個検証しました(これまで %s に)。"
+
+#~ msgid "Encrypted %ld~message in the past %s."
+#~ msgid_plural "Encrypted %ld~messages in the past %s."
+#~ msgstr[0] "メッセージを%ld個暗号化しました(これまで %s に)。"
+
+#~ msgid "error setting policy for key %s, user id \"%s\": %s"
+#~ msgstr "鍵%s, ユーザID \"%s\"のポリシーの設定エラー: %s"
+
+#~ msgid "only SHA-1 is supported for OCSP responses\n"
+#~ msgstr "SHA-1だけがOCSPレスポンスとしてサポートされています\n"
+
+#~ msgid "male"
+#~ msgstr "男"
+
+#~ msgid "female"
+#~ msgstr "女"
+
+#~ msgid "unspecified"
+#~ msgstr "無指定"
+
+#~ msgid "Sex ((M)ale, (F)emale or space): "
+#~ msgstr "性別 ((M)男、(F)女、または空白): "
+
+#~ msgid "Warning: '%s' should be a long key ID or a fingerprint\n"
+#~ msgstr "警告: '%s'は長い鍵IDかフィンガープリントであるべきです。\n"
+
+#~ msgid "error looking up: %s\n"
+#~ msgstr "検索のエラー: %s\n"
+
+#~ msgid "Warning: %s appears in the keyring %d times\n"
+#~ msgstr "警告: %sは鍵リングに%d回出現します\n"
+
#~ msgid "using \"http\" instead of \"https\"\n"
#~ msgstr "\"http\" を \"https\" の代わりに使います\n"
#~ msgid "error retrieving '%s': http status %u\n"
#~ msgstr "'%s'の取得エラー: httpステイタス %u\n"
#~ msgid "npth_select failed: %s - waiting 1s\n"
#~ msgstr "npth_selectに失敗しました: %s - 一秒待ちます\n"
#~ msgid "error spawning ldap wrapper reaper thread: %s\n"
#~ msgstr "ldap wrapperのスレッドの起動でエラー: %s\n"
#~ msgid "reading from ldap wrapper %d failed: %s\n"
#~ msgstr "ldap wrapper %d からの読み込みに失敗しました: %s\n"
#~ msgid "No change."
#~ msgstr "変更なし。"
#~ msgid "What keysize do you want for the Signature key? (%u) "
#~ msgstr "署名鍵の鍵長は? (%u) "
#~ msgid "What keysize do you want for the Encryption key? (%u) "
#~ msgstr "暗号化鍵の鍵長は? (%u) "
#~ msgid "What keysize do you want for the Authentication key? (%u) "
#~ msgstr "認証鍵の鍵長は? (%u) "
#~ msgid "listen() failed: %s\n"
#~ msgstr "listen() に失敗しました: %s\n"
#~ msgid "do not grab keyboard and mouse"
#~ msgstr "キーボードとマウスを占有しない"
#~ msgid "Error: URL too long (limit is %d characters).\n"
#~ msgstr "エラー: URLが長すぎます (上限%d文字)。\n"
#~ msgid "Error: Login data too long (limit is %d characters).\n"
#~ msgstr "エラー: ログイン・データが長すぎます (上限%d文字)。\n"
#~ msgid "Error: Private DO too long (limit is %d characters).\n"
#~ msgstr "エラー: プライベート DOが長すぎます (上限%d文字)。\n"
#~ msgid ""
#~ "can't check signature with unsupported public-key algorithm (%d): %s.\n"
#~ msgstr ""
#~ "サポートしていない公開鍵アルゴリズム(%d)の署名は確認できません: %s.\n"
#~ msgid ""
#~ "can't check signature with unsupported message-digest algorithm %d: %s.\n"
#~ msgstr ""
#~ "サポートしていないメッセージ・ダイジェスト(%d)の署名は確認できません: "
#~ "%s.\n"
#~ msgid " (reordered signatures follow)"
#~ msgstr "(順番を変えた署名が続きます)"
#~ msgid "key %s:\n"
#~ msgstr "鍵 %s:\n"
#~ msgid "%d duplicate signature removed\n"
#~ msgid_plural "%d duplicate signatures removed\n"
#~ msgstr[0] "%d個の重複した署名が除去されました\n"
#~ msgid "%d signature reordered\n"
#~ msgid_plural "%d signatures reordered\n"
#~ msgstr[0] "%d個の正しい署名\n"
#~ msgid ""
#~ "Warning: errors found and only checked self-signatures, run '%s' to check "
#~ "all signatures.\n"
#~ msgstr ""
#~ "警告: エラーがあり、自己署名だけ確認しました。'%s'を実行してすべての署名を"
#~ "確認ください。\n"
#~ msgid ", "
#~ msgstr ", "
#~ msgid "new configuration file '%s' created\n"
#~ msgstr "新しいコンフィグレーション・ファイル'%s'ができました\n"
#~ msgid "WARNING: options in '%s' are not yet active during this run\n"
#~ msgstr "*警告*: '%s'のオプションはこの実行では、まだ有効になりません\n"
#~ msgid "User ID revocation failed: %s\n"
#~ msgstr "ユーザIDの失効に失敗しました: %s\n"
#~ msgid "||Please enter the PIN%%0A[sigs done: %lu]"
#~ msgstr "||PINを入力してください%%0A[署名数: %lu]"
#~ msgid "|A|Please enter the Admin PIN%%0A[remaining attempts: %d]"
#~ msgstr "|A|管理者PINを入力してください%%0A[残り回数: %d]"
#~ msgid "DSA requires the use of a 160 bit hash algorithm\n"
#~ msgstr "DSAは160ビットののハッシュアルゴリズムの使用を必要とします\n"
#~ msgid ""
#~ "@\n"
#~ "Examples:\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"
#~ msgstr ""
#~ "@\n"
#~ "例:\n"
#~ "\n"
#~ " -se -r Bob [ファイル] ユーザBobへ署名と暗号化\n"
#~ " --clearsign [ファイル] クリア・テクスト署名を作成\n"
#~ " --detach-sign [ファイル] 分遣署名を作成\n"
#~ " --list-keys [名前] 鍵を表示\n"
#~ " --fingerprint [名前] フィンガープリントを表示\n"
#~ msgid "--store [filename]"
#~ msgstr "--store [ファイル名]"
#~ msgid "--symmetric [filename]"
#~ msgstr "--symmetric [ファイル名]"
#~ msgid "--encrypt [filename]"
#~ msgstr "--encrypt [ファイル名]"
#~ msgid "--symmetric --encrypt [filename]"
#~ msgstr "--symmetric --encrypt [ファイル名]"
#~ msgid "--sign [filename]"
#~ msgstr "--sign [ファイル名]"
#~ msgid "--sign --encrypt [filename]"
#~ msgstr "--sign --encrypt [ファイル名]"
#~ msgid "--symmetric --sign --encrypt [filename]"
#~ msgstr "--symmetric --sign --encrypt [ファイル名]"
#~ msgid "--sign --symmetric [filename]"
#~ msgstr "--sign --symmetric [ファイル名]"
#~ msgid "--clear-sign [filename]"
#~ msgstr "--clear-sign [ファイル名]"
#~ msgid "--decrypt [filename]"
#~ msgstr "--decrypt [ファイル名]"
#~ msgid "--sign-key user-id"
#~ msgstr "--sign-key ユーザid"
#~ msgid "--lsign-key user-id"
#~ msgstr "--lsign-key ユーザid"
#~ msgid "--edit-key user-id [commands]"
#~ msgstr "--edit-key ユーザid [コマンド]"
#~ msgid "--passwd <user-id>"
#~ msgstr "--passwd <ユーザid>"
#~ msgid "[filename]"
#~ msgstr "[ファイル名]"
#~ msgid "shadowing the key failed: %s\n"
#~ msgstr "鍵のシャドウ化に失敗しました: %s\n"
#~ msgid "available TOFU policies:\n"
#~ msgstr "利用可能なTOFUポリシー:\n"
#~ msgid "The binding %s is NOT known."
#~ msgstr "%sのバインディングは不明です。"
#~ msgid ""
#~ "Please indicate whether you believe the binding %s%sis legitimate (the "
#~ "key belongs to the stated owner) or a forgery (bad)."
#~ msgstr ""
#~ "バインディング%s%sが適切(鍵は述べられた所有者に属する)か、偽られたものか"
#~ "(ダメ)かを指示してください。"
#~ msgid "Known user IDs associated with this key:\n"
#~ msgstr "この鍵に結びつけられた知られているユーザID:\n"
#~ msgid "%ld message signed"
#~ msgid_plural "%ld messages signed"
#~ msgstr[0] "%ld個のメッセージに署名しました"
#~ msgid " over the past %ld week."
#~ msgid_plural " over the past %ld weeks."
#~ msgstr[0] "過去%ld週間に。"
#~ msgid "Have never verified a message signed by key %s!\n"
#~ msgstr "鍵%sで署名されたメッセージを検証したことは一度もありません!\n"
#~ msgid ""
#~ "Failed to collect signature statistics for \"%s\"\n"
#~ "(key %s)\n"
#~ msgstr ""
#~ "\"%s\"の署名の統計を収集することに失敗しました\n"
#~ "(鍵 %s)\n"
#~ msgid "The most recent message was verified %s ago."
#~ msgstr "もっとも最近のメッセージは%s前に検証されました。"
#~ msgid "GPG Agent"
#~ msgstr "GPG Agent"
#~ msgid "Key Acquirer"
#~ msgstr "キー取得プログラム"
#~ msgid "communication problem with gpg-agent\n"
#~ msgstr "gpg-agentとの通信障害\n"
#~ msgid "canceled by user\n"
#~ msgstr "ユーザによる取消し\n"
#~ msgid "problem with the agent\n"
#~ msgstr "エージェントに障害\n"
#~ msgid "problem with the agent (unexpected response \"%s\")\n"
#~ msgstr "エージェントに問題 (予期しない応答 \"%s\")\n"
#~ msgid "unknown TOFU DB format '%s'\n"
#~ msgstr "不明のTOFU DBフォーマット'%s'\n"
#~ msgid "libgcrypt is too old (need %s, have %s)\n"
#~ msgstr "libgcrypt が古すぎます (必要 %s, 現在 %s)\n"
#~ msgid ""
#~ "Please enter the passphrase to unlock the secret key for the OpenPGP "
#~ "certificate:\n"
#~ "\"%.*s\"\n"
#~ "%u-bit %s key, ID %s,\n"
#~ "created %s%s.\n"
#~ msgstr ""
#~ "OpenPGP証明書の秘密鍵のロックを解除するためにパスフレーズを入力してくださ"
#~ "い:\n"
#~ "\"%.*s\"\n"
#~ "%uビット %s 鍵, ID %s,\n"
#~ "作成日付 %s%s。\n"
#~ msgid ""
#~ "You need a passphrase to unlock the secret key for\n"
#~ "user: \"%s\"\n"
#~ msgstr ""
#~ "次のユーザの秘密鍵のロックを解除するには\n"
#~ "パスフレーズがいります:\"%s\"\n"
#~ msgid "%u-bit %s key, ID %s, created %s"
#~ msgstr "%uビット%s鍵, ID %s作成日付は%s"
#~ msgid " (subkey on main key ID %s)"
#~ msgstr " (主鍵ID %s の副鍵)"
#~ msgid "Warning: Home directory contains both tofu.db and tofu.d.\n"
#~ msgstr "警告: tofu.db と tofu.d の両方がホームディレクトリにあります。\n"
#~ msgid "Using split format for TOFU database\n"
#~ msgstr "TOFUデータベースに分割フォーマットを使用\n"
#~ msgid "can't access directory '%s': %s\n"
#~ msgstr "ディレクトリ'%s'が作成できません: %s\n"
#~ msgid "run as windows service (background)"
#~ msgstr "ウィンドウズ・サービスとして実行 (バックグラウンド)"
#~ msgid "running in compatibility mode - certificate chain not checked!\n"
#~ msgstr "コンパチ・モードで実行します - 証明書チェインは確認しません!\n"
#~ msgid "you found a bug ... (%s:%d)\n"
#~ msgstr "あなたはバグを発見しました ... (%s:%d)\n"
#~ msgid "moving a key signature to the correct place\n"
#~ msgstr "鍵の署名を正しい場所に移動します\n"
#~ msgid "key specification '%s' is ambiguous\n"
#~ msgstr "鍵の指定'%s'はあいまいです\n"
#~ msgid "'%s' matches at least:\n"
#~ msgstr "'%s'は最低、以下にマッチします:\n"
#~ msgid "%d signatures not checked due to missing keys\n"
#~ msgstr "鍵がないため%d個の署名を検査しません\n"
#~ msgid "%d signatures not checked due to errors\n"
#~ msgstr "エラーのため%d個の署名を検査しません\n"
#~ msgid "1 user ID without valid self-signature detected\n"
#~ msgstr "有効な自己署名のないユーザIDを1個検出\n"
#~ msgid "User ID \"%s\": %d signatures removed\n"
#~ msgstr "ユーザID \"%s\": %d の署名が除去されました\n"
#~ msgid ""
#~ "You need a Passphrase to protect your secret key.\n"
#~ "\n"
#~ msgstr ""
#~ "秘密鍵を保護するためにパスフレーズがいります。\n"
#~ "\n"
#~ msgid ""
#~ "Please enter a passphrase to protect the off-card backup of the new "
#~ "encryption key."
#~ msgstr ""
#~ "パスフレーズを入力してください。これは新しく作られる暗号化鍵のカード外の"
#~ "バックアップを保護するものです。"
#~ msgid "passphrase not correctly repeated; try again"
#~ msgstr "パスフレーズをちゃんと繰り返していません。再入力してください"
#~ msgid "%s.\n"
#~ msgstr "%s.\n"
#~ msgid ""
#~ "You don't want a passphrase - this is probably a *bad* idea!\n"
#~ "I will do it anyway. You can change your passphrase at any time,\n"
#~ "using this program with the option \"--edit-key\".\n"
#~ "\n"
#~ msgstr ""
#~ "パスフレーズを必要としないようですが、おそらくそれは良くない考えです!\n"
#~ "続けますが、パスフレーズを設定することを検討ください。パスフレーズは、\n"
#~ "このプログラムの\"--edit-key\"オプションでいつでも変更できます。\n"
#~ "\n"
#~ msgid "storing key onto card failed: %s\n"
#~ msgstr "カードへの鍵の保管に失敗しました: %s\n"
#~ msgid "1 good signature\n"
#~ msgstr "正しい署名1個\n"
#~ msgid "%lu keys cached (%lu signatures)\n"
#~ msgstr "%lu個の鍵をキャッシュ済 (%lu個の署名)\n"
#~ msgid "refreshing 1 key from %s\n"
#~ msgstr "1本の鍵を%sから更新\n"
#~ msgid "sending key %s to %s server %s\n"
#~ msgstr "鍵%sを%sサーバ%sへ送信\n"
#~ msgid "public key %s is %lu seconds newer than the signature\n"
#~ msgstr "公開鍵%sは、署名よりも%lu秒新しいものです\n"
#~ msgid ""
#~ "key %s was created %lu seconds in the future (time warp or clock "
#~ "problem)\n"
#~ msgstr "鍵%sは%lu秒未来にできました (時間歪曲か時計の障害でしょう)\n"
#~ msgid "%d marginal(s) needed, %d complete(s) needed, %s trust model\n"
#~ msgstr "'まぁまぁの信用'%d、'全面的信用'%d、%s信用モデル\n"
#~ msgid "cleared passphrase cached with ID: %s\n"
#~ msgstr "保持したパスフレーズをクリアしました ID: %s\n"
#~ msgid "Please select at most one subkey.\n"
#~ msgstr "高々1個の副鍵を選択してください。\n"
#~ msgid "apparently no running dirmngr\n"
#~ msgstr "あきらかにdirmngrが動いていません\n"
#~ msgid "no running dirmngr - starting one\n"
#~ msgstr "dirmngrが動いていません - 開始します\n"
#~ msgid "malformed %s environment variable\n"
#~ msgstr "環境変数%sが破壊されています\n"
#~ msgid "dirmngr protocol version %d is not supported\n"
#~ msgstr "dirmngrプロトコル・バージョン%dはサポートされていません\n"
#~ msgid "can't connect to the dirmngr - trying fall back\n"
#~ msgstr "dirmngrに接続できません - フォールバックを試します\n"
#~ msgid "export keys in an S-expression based format"
#~ msgstr "S式ベースのフォーマットで鍵をエクスポートする"
#~ msgid "Directory Manager"
#~ msgstr "ディレクトリ・マネージャ"
#~ msgid "toggle between the secret and public key listings"
#~ msgstr "秘密鍵と公開鍵の一覧の反転"
#~ msgid "Passphrase"
#~ msgstr "パスフレーズ"
#~ msgid "use temporary files to pass data to keyserver helpers"
#~ msgstr "キーサーバ・ヘルパーにデータを与える際、一時ファイルを使う"
#~ msgid "do not delete temporary files after using them"
#~ msgstr "一時ファイルを使用後、それを削除しない"
#~ msgid "WARNING: keyserver option '%s' is not used on this platform\n"
#~ msgstr ""
#~ "*警告*: 鍵サーバのオプション'%s'は、このプラットホームでは使われません\n"
#~ msgid "name of socket too long\n"
#~ msgstr "ソケット名が長すぎます\n"
#~ msgid "gpg-agent is not available in this session\n"
#~ msgstr "このセッションでgpg-agentは無効です\n"
diff --git a/scd/apdu.c b/scd/apdu.c
index 816938ac5..2df113c5e 100644
--- a/scd/apdu.c
+++ b/scd/apdu.c
@@ -1,3331 +1,3324 @@
/* apdu.c - ISO 7816 APDU functions and low level I/O
* Copyright (C) 2003, 2004, 2008, 2009, 2010,
* 2011 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
/* NOTE: This module is also used by other software, thus the use of
the macro USE_NPTH is mandatory. For GnuPG this macro is
guaranteed to be defined true. */
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <signal.h>
#ifdef USE_NPTH
# include <unistd.h>
# include <fcntl.h>
# include <npth.h>
#endif
/* If requested include the definitions for the remote APDU protocol
code. */
#ifdef USE_G10CODE_RAPDU
#include "rapdu.h"
#endif /*USE_G10CODE_RAPDU*/
-#if defined(GNUPG_SCD_MAIN_HEADER)
-#include GNUPG_SCD_MAIN_HEADER
-#elif GNUPG_MAJOR_VERSION == 1
-/* This is used with GnuPG version < 1.9. The code has been source
- copied from the current GnuPG >= 1.9 and is maintained over
- there. */
-#include "../common/options.h"
-#include "errors.h"
-#include "memory.h"
-#include "../common/util.h"
-#include "../common/i18n.h"
-#include "dynload.h"
-#include "cardglue.h"
-#else /* GNUPG_MAJOR_VERSION != 1 */
-#include "scdaemon.h"
-#include "../common/exechelp.h"
-#endif /* GNUPG_MAJOR_VERSION != 1 */
+#if defined(GNUPG_MAJOR_VERSION)
+# include "scdaemon.h"
+# include "../common/exechelp.h"
+#endif /*GNUPG_MAJOR_VERSION*/
+
#include "../common/host2net.h"
#include "iso7816.h"
#include "apdu.h"
#define CCID_DRIVER_INCLUDE_USB_IDS 1
#include "ccid-driver.h"
struct dev_list {
struct ccid_dev_table *ccid_table;
const char *portstr;
int idx;
int idx_max;
};
#define MAX_READER 4 /* Number of readers we support concurrently. */
#if defined(_WIN32) || defined(__CYGWIN__)
#define DLSTDCALL __stdcall
#else
#define DLSTDCALL
#endif
#if defined(__APPLE__) || defined(_WIN32) || defined(__CYGWIN__)
typedef unsigned int pcsc_dword_t;
#else
typedef unsigned long pcsc_dword_t;
#endif
/* A structure to collect information pertaining to one reader
slot. */
struct reader_table_s {
int used; /* True if slot is used. */
unsigned short port; /* Port number: 0 = unused, 1 - dev/tty */
/* Function pointers initialized to the various backends. */
int (*connect_card)(int);
int (*disconnect_card)(int);
int (*close_reader)(int);
int (*reset_reader)(int);
int (*get_status_reader)(int, unsigned int *, int);
int (*send_apdu_reader)(int,unsigned char *,size_t,
unsigned char *, size_t *, pininfo_t *);
int (*check_pinpad)(int, int, pininfo_t *);
void (*dump_status_reader)(int);
int (*set_progress_cb)(int, gcry_handler_progress_t, void*);
int (*set_prompt_cb)(int, void (*) (void *, int), void*);
int (*pinpad_verify)(int, int, int, int, int, pininfo_t *);
int (*pinpad_modify)(int, int, int, int, int, pininfo_t *);
struct {
ccid_driver_t handle;
} ccid;
struct {
long context;
long card;
pcsc_dword_t protocol;
pcsc_dword_t verify_ioctl;
pcsc_dword_t modify_ioctl;
int pinmin;
int pinmax;
pcsc_dword_t current_state;
} pcsc;
#ifdef USE_G10CODE_RAPDU
struct {
rapdu_t handle;
} rapdu;
#endif /*USE_G10CODE_RAPDU*/
char *rdrname; /* Name of the connected reader or NULL if unknown. */
unsigned int is_t0:1; /* True if we know that we are running T=0. */
unsigned int is_spr532:1; /* True if we know that the reader is a SPR532. */
unsigned int pinpad_varlen_supported:1; /* True if we know that the reader
supports variable length pinpad
input. */
unsigned int require_get_status:1;
unsigned char atr[33];
size_t atrlen; /* A zero length indicates that the ATR has
not yet been read; i.e. the card is not
ready for use. */
#ifdef USE_NPTH
npth_mutex_t lock;
#endif
};
typedef struct reader_table_s *reader_table_t;
/* A global table to keep track of active readers. */
static struct reader_table_s reader_table[MAX_READER];
#ifdef USE_NPTH
static npth_mutex_t reader_table_lock;
#endif
/* PC/SC constants and function pointer. */
#define PCSC_SCOPE_USER 0
#define PCSC_SCOPE_TERMINAL 1
#define PCSC_SCOPE_SYSTEM 2
#define PCSC_SCOPE_GLOBAL 3
#define PCSC_PROTOCOL_T0 1
#define PCSC_PROTOCOL_T1 2
#ifdef HAVE_W32_SYSTEM
# define PCSC_PROTOCOL_RAW 0x00010000 /* The active protocol. */
#else
# define PCSC_PROTOCOL_RAW 4
#endif
#define PCSC_SHARE_EXCLUSIVE 1
#define PCSC_SHARE_SHARED 2
#define PCSC_SHARE_DIRECT 3
#define PCSC_LEAVE_CARD 0
#define PCSC_RESET_CARD 1
#define PCSC_UNPOWER_CARD 2
#define PCSC_EJECT_CARD 3
#ifdef HAVE_W32_SYSTEM
# define PCSC_UNKNOWN 0x0000 /* The driver is not aware of the status. */
# define PCSC_ABSENT 0x0001 /* Card is absent. */
# define PCSC_PRESENT 0x0002 /* Card is present. */
# define PCSC_SWALLOWED 0x0003 /* Card is present and electrical connected. */
# define PCSC_POWERED 0x0004 /* Card is powered. */
# define PCSC_NEGOTIABLE 0x0005 /* Card is awaiting PTS. */
# define PCSC_SPECIFIC 0x0006 /* Card is ready for use. */
#else
# define PCSC_UNKNOWN 0x0001
# define PCSC_ABSENT 0x0002 /* Card is absent. */
# define PCSC_PRESENT 0x0004 /* Card is present. */
# define PCSC_SWALLOWED 0x0008 /* Card is present and electrical connected. */
# define PCSC_POWERED 0x0010 /* Card is powered. */
# define PCSC_NEGOTIABLE 0x0020 /* Card is awaiting PTS. */
# define PCSC_SPECIFIC 0x0040 /* Card is ready for use. */
#endif
#define PCSC_STATE_UNAWARE 0x0000 /* Want status. */
#define PCSC_STATE_IGNORE 0x0001 /* Ignore this reader. */
#define PCSC_STATE_CHANGED 0x0002 /* State has changed. */
#define PCSC_STATE_UNKNOWN 0x0004 /* Reader unknown. */
#define PCSC_STATE_UNAVAILABLE 0x0008 /* Status unavailable. */
#define PCSC_STATE_EMPTY 0x0010 /* Card removed. */
#define PCSC_STATE_PRESENT 0x0020 /* Card inserted. */
#define PCSC_STATE_ATRMATCH 0x0040 /* ATR matches card. */
#define PCSC_STATE_EXCLUSIVE 0x0080 /* Exclusive Mode. */
#define PCSC_STATE_INUSE 0x0100 /* Shared mode. */
#define PCSC_STATE_MUTE 0x0200 /* Unresponsive card. */
#ifdef HAVE_W32_SYSTEM
# define PCSC_STATE_UNPOWERED 0x0400 /* Card not powerred up. */
#endif
/* Some PC/SC error codes. */
#define PCSC_E_CANCELLED 0x80100002
#define PCSC_E_CANT_DISPOSE 0x8010000E
#define PCSC_E_INSUFFICIENT_BUFFER 0x80100008
#define PCSC_E_INVALID_ATR 0x80100015
#define PCSC_E_INVALID_HANDLE 0x80100003
#define PCSC_E_INVALID_PARAMETER 0x80100004
#define PCSC_E_INVALID_TARGET 0x80100005
#define PCSC_E_INVALID_VALUE 0x80100011
#define PCSC_E_NO_MEMORY 0x80100006
#define PCSC_E_UNKNOWN_READER 0x80100009
#define PCSC_E_TIMEOUT 0x8010000A
#define PCSC_E_SHARING_VIOLATION 0x8010000B
#define PCSC_E_NO_SMARTCARD 0x8010000C
#define PCSC_E_UNKNOWN_CARD 0x8010000D
#define PCSC_E_PROTO_MISMATCH 0x8010000F
#define PCSC_E_NOT_READY 0x80100010
#define PCSC_E_SYSTEM_CANCELLED 0x80100012
#define PCSC_E_NOT_TRANSACTED 0x80100016
#define PCSC_E_READER_UNAVAILABLE 0x80100017
#define PCSC_E_NO_SERVICE 0x8010001D
#define PCSC_E_SERVICE_STOPPED 0x8010001E
#define PCSC_W_RESET_CARD 0x80100068
#define PCSC_W_REMOVED_CARD 0x80100069
/* Fix pcsc-lite ABI incompatibility. */
#ifndef SCARD_CTL_CODE
#ifdef _WIN32
#include <winioctl.h>
#define SCARD_CTL_CODE(code) CTL_CODE(FILE_DEVICE_SMARTCARD, (code), \
METHOD_BUFFERED, FILE_ANY_ACCESS)
#else
#define SCARD_CTL_CODE(code) (0x42000000 + (code))
#endif
#endif
#define CM_IOCTL_GET_FEATURE_REQUEST SCARD_CTL_CODE(3400)
#define CM_IOCTL_VENDOR_IFD_EXCHANGE SCARD_CTL_CODE(1)
#define FEATURE_VERIFY_PIN_DIRECT 0x06
#define FEATURE_MODIFY_PIN_DIRECT 0x07
#define FEATURE_GET_TLV_PROPERTIES 0x12
#define PCSCv2_PART10_PROPERTY_bEntryValidationCondition 2
#define PCSCv2_PART10_PROPERTY_bTimeOut2 3
#define PCSCv2_PART10_PROPERTY_bMinPINSize 6
#define PCSCv2_PART10_PROPERTY_bMaxPINSize 7
#define PCSCv2_PART10_PROPERTY_wIdVendor 11
#define PCSCv2_PART10_PROPERTY_wIdProduct 12
/* The PC/SC error is defined as a long as per specs. Due to left
shifts bit 31 will get sign extended. We use this mask to fix
it. */
#define PCSC_ERR_MASK(a) ((a) & 0xffffffff)
struct pcsc_io_request_s
{
+#if defined(_WIN32) || defined(__CYGWIN__)
+ pcsc_dword_t protocol;
+ pcsc_dword_t pci_len;
+#else
unsigned long protocol;
unsigned long pci_len;
+#endif
};
typedef struct pcsc_io_request_s *pcsc_io_request_t;
#ifdef __APPLE__
#pragma pack(1)
#endif
struct pcsc_readerstate_s
{
const char *reader;
void *user_data;
pcsc_dword_t current_state;
pcsc_dword_t event_state;
pcsc_dword_t atrlen;
unsigned char atr[33];
};
#ifdef __APPLE__
#pragma pack()
#endif
typedef struct pcsc_readerstate_s *pcsc_readerstate_t;
long (* DLSTDCALL pcsc_establish_context) (pcsc_dword_t scope,
const void *reserved1,
const void *reserved2,
long *r_context);
long (* DLSTDCALL pcsc_release_context) (long context);
long (* DLSTDCALL pcsc_list_readers) (long context,
const char *groups,
char *readers, pcsc_dword_t*readerslen);
long (* DLSTDCALL pcsc_get_status_change) (long context,
pcsc_dword_t timeout,
pcsc_readerstate_t readerstates,
pcsc_dword_t nreaderstates);
long (* DLSTDCALL pcsc_connect) (long context,
const char *reader,
pcsc_dword_t share_mode,
pcsc_dword_t preferred_protocols,
long *r_card,
pcsc_dword_t *r_active_protocol);
long (* DLSTDCALL pcsc_reconnect) (long card,
pcsc_dword_t share_mode,
pcsc_dword_t preferred_protocols,
pcsc_dword_t initialization,
pcsc_dword_t *r_active_protocol);
long (* DLSTDCALL pcsc_disconnect) (long card,
pcsc_dword_t disposition);
long (* DLSTDCALL pcsc_status) (long card,
char *reader, pcsc_dword_t *readerlen,
pcsc_dword_t *r_state,
pcsc_dword_t *r_protocol,
unsigned char *atr, pcsc_dword_t *atrlen);
long (* DLSTDCALL pcsc_begin_transaction) (long card);
long (* DLSTDCALL pcsc_end_transaction) (long card,
pcsc_dword_t disposition);
long (* DLSTDCALL pcsc_transmit) (long card,
const pcsc_io_request_t send_pci,
const unsigned char *send_buffer,
pcsc_dword_t send_len,
pcsc_io_request_t recv_pci,
unsigned char *recv_buffer,
pcsc_dword_t *recv_len);
long (* DLSTDCALL pcsc_set_timeout) (long context,
pcsc_dword_t timeout);
long (* DLSTDCALL pcsc_control) (long card,
pcsc_dword_t control_code,
const void *send_buffer,
pcsc_dword_t send_len,
void *recv_buffer,
pcsc_dword_t recv_len,
pcsc_dword_t *bytes_returned);
/* Prototypes. */
static int pcsc_vendor_specific_init (int slot);
static int pcsc_get_status (int slot, unsigned int *status, int on_wire);
static int reset_pcsc_reader (int slot);
static int apdu_get_status_internal (int slot, int hang, unsigned int *status,
int on_wire);
static int check_pcsc_pinpad (int slot, int command, pininfo_t *pininfo);
static int pcsc_pinpad_verify (int slot, int class, int ins, int p0, int p1,
pininfo_t *pininfo);
static int pcsc_pinpad_modify (int slot, int class, int ins, int p0, int p1,
pininfo_t *pininfo);
/*
Helper
*/
static int
lock_slot (int slot)
{
#ifdef USE_NPTH
int err;
err = npth_mutex_lock (&reader_table[slot].lock);
if (err)
{
log_error ("failed to acquire apdu lock: %s\n", strerror (err));
return SW_HOST_LOCKING_FAILED;
}
#endif /*USE_NPTH*/
return 0;
}
static int
trylock_slot (int slot)
{
#ifdef USE_NPTH
int err;
err = npth_mutex_trylock (&reader_table[slot].lock);
if (err == EBUSY)
return SW_HOST_BUSY;
else if (err)
{
log_error ("failed to acquire apdu lock: %s\n", strerror (err));
return SW_HOST_LOCKING_FAILED;
}
#endif /*USE_NPTH*/
return 0;
}
static void
unlock_slot (int slot)
{
#ifdef USE_NPTH
int err;
err = npth_mutex_unlock (&reader_table[slot].lock);
if (err)
log_error ("failed to release apdu lock: %s\n", strerror (errno));
#endif /*USE_NPTH*/
}
/* Find an unused reader slot for PORTSTR and put it into the reader
table. Return -1 on error or the index into the reader table.
Acquire slot's lock on successful return. Caller needs to unlock it. */
static int
new_reader_slot (void)
{
int i, reader = -1;
for (i=0; i < MAX_READER; i++)
if (!reader_table[i].used)
{
reader = i;
reader_table[reader].used = 1;
break;
}
if (reader == -1)
{
log_error ("new_reader_slot: out of slots\n");
return -1;
}
if (lock_slot (reader))
{
reader_table[reader].used = 0;
return -1;
}
reader_table[reader].connect_card = NULL;
reader_table[reader].disconnect_card = NULL;
reader_table[reader].close_reader = NULL;
reader_table[reader].reset_reader = NULL;
reader_table[reader].get_status_reader = NULL;
reader_table[reader].send_apdu_reader = NULL;
reader_table[reader].check_pinpad = check_pcsc_pinpad;
reader_table[reader].dump_status_reader = NULL;
reader_table[reader].set_progress_cb = NULL;
reader_table[reader].set_prompt_cb = NULL;
reader_table[reader].pinpad_verify = pcsc_pinpad_verify;
reader_table[reader].pinpad_modify = pcsc_pinpad_modify;
reader_table[reader].is_t0 = 1;
reader_table[reader].is_spr532 = 0;
reader_table[reader].pinpad_varlen_supported = 0;
reader_table[reader].require_get_status = 1;
reader_table[reader].pcsc.verify_ioctl = 0;
reader_table[reader].pcsc.modify_ioctl = 0;
reader_table[reader].pcsc.pinmin = -1;
reader_table[reader].pcsc.pinmax = -1;
reader_table[reader].pcsc.current_state = PCSC_STATE_UNAWARE;
return reader;
}
static void
dump_reader_status (int slot)
{
if (!opt.verbose)
return;
if (reader_table[slot].dump_status_reader)
reader_table[slot].dump_status_reader (slot);
if (reader_table[slot].atrlen)
{
log_info ("slot %d: ATR=", slot);
log_printhex (reader_table[slot].atr, reader_table[slot].atrlen, "");
}
}
static const char *
host_sw_string (long err)
{
switch (err)
{
case 0: return "okay";
case SW_HOST_OUT_OF_CORE: return "out of core";
case SW_HOST_INV_VALUE: return "invalid value";
case SW_HOST_NO_DRIVER: return "no driver";
case SW_HOST_NOT_SUPPORTED: return "not supported";
case SW_HOST_LOCKING_FAILED: return "locking failed";
case SW_HOST_BUSY: return "busy";
case SW_HOST_NO_CARD: return "no card";
case SW_HOST_CARD_INACTIVE: return "card inactive";
case SW_HOST_CARD_IO_ERROR: return "card I/O error";
case SW_HOST_GENERAL_ERROR: return "general error";
case SW_HOST_NO_READER: return "no reader";
case SW_HOST_ABORTED: return "aborted";
case SW_HOST_NO_PINPAD: return "no pinpad";
case SW_HOST_ALREADY_CONNECTED: return "already connected";
case SW_HOST_CANCELLED: return "cancelled";
default: return "unknown host status error";
}
}
const char *
apdu_strerror (int rc)
{
switch (rc)
{
case SW_EOF_REACHED : return "eof reached";
case SW_EEPROM_FAILURE : return "eeprom failure";
case SW_WRONG_LENGTH : return "wrong length";
case SW_CHV_WRONG : return "CHV wrong";
case SW_CHV_BLOCKED : return "CHV blocked";
case SW_REF_DATA_INV : return "referenced data invalidated";
case SW_USE_CONDITIONS : return "use conditions not satisfied";
case SW_BAD_PARAMETER : return "bad parameter";
case SW_NOT_SUPPORTED : return "not supported";
case SW_FILE_NOT_FOUND : return "file not found";
case SW_RECORD_NOT_FOUND:return "record not found";
case SW_REF_NOT_FOUND : return "reference not found";
case SW_NOT_ENOUGH_MEMORY: return "not enough memory space in the file";
case SW_INCONSISTENT_LC: return "Lc inconsistent with TLV structure.";
case SW_INCORRECT_P0_P1: return "incorrect parameters P0,P1";
case SW_BAD_LC : return "Lc inconsistent with P0,P1";
case SW_BAD_P0_P1 : return "bad P0,P1";
case SW_INS_NOT_SUP : return "instruction not supported";
case SW_CLA_NOT_SUP : return "class not supported";
case SW_SUCCESS : return "success";
default:
if ((rc & ~0x00ff) == SW_MORE_DATA)
return "more data available";
if ( (rc & 0x10000) )
return host_sw_string (rc);
return "unknown status error";
}
}
/*
PC/SC Interface
*/
static const char *
pcsc_error_string (long err)
{
const char *s;
if (!err)
return "okay";
if ((err & 0x80100000) != 0x80100000)
return "invalid PC/SC error code";
err &= 0xffff;
switch (err)
{
case 0x0002: s = "cancelled"; break;
case 0x000e: s = "can't dispose"; break;
case 0x0008: s = "insufficient buffer"; break;
case 0x0015: s = "invalid ATR"; break;
case 0x0003: s = "invalid handle"; break;
case 0x0004: s = "invalid parameter"; break;
case 0x0005: s = "invalid target"; break;
case 0x0011: s = "invalid value"; break;
case 0x0006: s = "no memory"; break;
case 0x0013: s = "comm error"; break;
case 0x0001: s = "internal error"; break;
case 0x0014: s = "unknown error"; break;
case 0x0007: s = "waited too long"; break;
case 0x0009: s = "unknown reader"; break;
case 0x000a: s = "timeout"; break;
case 0x000b: s = "sharing violation"; break;
case 0x000c: s = "no smartcard"; break;
case 0x000d: s = "unknown card"; break;
case 0x000f: s = "proto mismatch"; break;
case 0x0010: s = "not ready"; break;
case 0x0012: s = "system cancelled"; break;
case 0x0016: s = "not transacted"; break;
case 0x0017: s = "reader unavailable"; break;
case 0x0065: s = "unsupported card"; break;
case 0x0066: s = "unresponsive card"; break;
case 0x0067: s = "unpowered card"; break;
case 0x0068: s = "reset card"; break;
case 0x0069: s = "removed card"; break;
case 0x006a: s = "inserted card"; break;
case 0x001f: s = "unsupported feature"; break;
case 0x0019: s = "PCI too small"; break;
case 0x001a: s = "reader unsupported"; break;
case 0x001b: s = "duplicate reader"; break;
case 0x001c: s = "card unsupported"; break;
case 0x001d: s = "no service"; break;
case 0x001e: s = "service stopped"; break;
default: s = "unknown PC/SC error code"; break;
}
return s;
}
/* Map PC/SC error codes to our special host status words. */
static int
pcsc_error_to_sw (long ec)
{
int rc;
switch ( PCSC_ERR_MASK (ec) )
{
case 0: rc = 0; break;
case PCSC_E_CANCELLED: rc = SW_HOST_CANCELLED; break;
case PCSC_E_NO_MEMORY: rc = SW_HOST_OUT_OF_CORE; break;
case PCSC_E_TIMEOUT: rc = SW_HOST_CARD_IO_ERROR; break;
case PCSC_E_NO_SERVICE:
case PCSC_E_SERVICE_STOPPED:
case PCSC_E_UNKNOWN_READER: rc = SW_HOST_NO_READER; break;
case PCSC_E_SHARING_VIOLATION: rc = SW_HOST_LOCKING_FAILED; break;
case PCSC_E_NO_SMARTCARD: rc = SW_HOST_NO_CARD; break;
case PCSC_W_REMOVED_CARD: rc = SW_HOST_NO_CARD; break;
case PCSC_E_INVALID_TARGET:
case PCSC_E_INVALID_VALUE:
case PCSC_E_INVALID_HANDLE:
case PCSC_E_INVALID_PARAMETER:
case PCSC_E_INSUFFICIENT_BUFFER: rc = SW_HOST_INV_VALUE; break;
default: rc = SW_HOST_GENERAL_ERROR; break;
}
return rc;
}
static void
dump_pcsc_reader_status (int slot)
{
if (reader_table[slot].pcsc.card)
{
log_info ("reader slot %d: active protocol:", slot);
if ((reader_table[slot].pcsc.protocol & PCSC_PROTOCOL_T0))
log_printf (" T0");
else if ((reader_table[slot].pcsc.protocol & PCSC_PROTOCOL_T1))
log_printf (" T1");
else if ((reader_table[slot].pcsc.protocol & PCSC_PROTOCOL_RAW))
log_printf (" raw");
log_printf ("\n");
}
else
log_info ("reader slot %d: not connected\n", slot);
}
static int
pcsc_get_status (int slot, unsigned int *status, int on_wire)
{
long err;
struct pcsc_readerstate_s rdrstates[1];
(void)on_wire;
memset (rdrstates, 0, sizeof *rdrstates);
rdrstates[0].reader = reader_table[slot].rdrname;
rdrstates[0].current_state = reader_table[slot].pcsc.current_state;
err = pcsc_get_status_change (reader_table[slot].pcsc.context,
0,
rdrstates, 1);
if (err == PCSC_E_TIMEOUT)
err = 0; /* Timeout is no error here. */
if (err)
{
log_error ("pcsc_get_status_change failed: %s (0x%lx)\n",
pcsc_error_string (err), err);
return pcsc_error_to_sw (err);
}
if ((rdrstates[0].event_state & PCSC_STATE_CHANGED))
reader_table[slot].pcsc.current_state =
(rdrstates[0].event_state & ~PCSC_STATE_CHANGED);
if (DBG_CARD_IO)
log_debug
("pcsc_get_status_change: %s%s%s%s%s%s%s%s%s%s\n",
(rdrstates[0].event_state & PCSC_STATE_IGNORE)? " ignore":"",
(rdrstates[0].event_state & PCSC_STATE_CHANGED)? " changed":"",
(rdrstates[0].event_state & PCSC_STATE_UNKNOWN)? " unknown":"",
(rdrstates[0].event_state & PCSC_STATE_UNAVAILABLE)?" unavail":"",
(rdrstates[0].event_state & PCSC_STATE_EMPTY)? " empty":"",
(rdrstates[0].event_state & PCSC_STATE_PRESENT)? " present":"",
(rdrstates[0].event_state & PCSC_STATE_ATRMATCH)? " atr":"",
(rdrstates[0].event_state & PCSC_STATE_EXCLUSIVE)? " excl":"",
(rdrstates[0].event_state & PCSC_STATE_INUSE)? " inuse":"",
(rdrstates[0].event_state & PCSC_STATE_MUTE)? " mute":"" );
*status = 0;
if ( (reader_table[slot].pcsc.current_state & PCSC_STATE_PRESENT) )
{
*status |= APDU_CARD_PRESENT;
if ( !(reader_table[slot].pcsc.current_state & PCSC_STATE_MUTE) )
*status |= APDU_CARD_ACTIVE;
}
#ifndef HAVE_W32_SYSTEM
/* We indicate a useful card if it is not in use by another
application. This is because we only use exclusive access
mode. */
if ( (*status & (APDU_CARD_PRESENT|APDU_CARD_ACTIVE))
== (APDU_CARD_PRESENT|APDU_CARD_ACTIVE)
&& !(reader_table[slot].pcsc.current_state & PCSC_STATE_INUSE) )
*status |= APDU_CARD_USABLE;
#else
/* Some winscard drivers may set EXCLUSIVE and INUSE at the same
time when we are the only user (SCM SCR335) under Windows. */
if ((*status & (APDU_CARD_PRESENT|APDU_CARD_ACTIVE))
== (APDU_CARD_PRESENT|APDU_CARD_ACTIVE))
*status |= APDU_CARD_USABLE;
#endif
if (!on_wire && (rdrstates[0].event_state & PCSC_STATE_CHANGED))
/* Event like sleep/resume occurs, which requires RESET. */
return SW_HOST_NO_READER;
else
return 0;
}
/* Send the APDU of length APDULEN to SLOT and return a maximum of
*BUFLEN data in BUFFER, the actual returned size will be stored at
BUFLEN. Returns: A status word. */
static int
pcsc_send_apdu (int slot, unsigned char *apdu, size_t apdulen,
unsigned char *buffer, size_t *buflen,
pininfo_t *pininfo)
{
long err;
struct pcsc_io_request_s send_pci;
pcsc_dword_t recv_len;
(void)pininfo;
if (!reader_table[slot].atrlen
&& (err = reset_pcsc_reader (slot)))
return err;
if (DBG_CARD_IO)
log_printhex (apdu, apdulen, " PCSC_data:");
if ((reader_table[slot].pcsc.protocol & PCSC_PROTOCOL_T1))
send_pci.protocol = PCSC_PROTOCOL_T1;
else
send_pci.protocol = PCSC_PROTOCOL_T0;
send_pci.pci_len = sizeof send_pci;
recv_len = *buflen;
err = pcsc_transmit (reader_table[slot].pcsc.card,
&send_pci, apdu, apdulen,
NULL, buffer, &recv_len);
*buflen = recv_len;
if (err)
log_error ("pcsc_transmit failed: %s (0x%lx)\n",
pcsc_error_string (err), err);
/* Handle fatal errors which require shutdown of reader. */
if (err == PCSC_E_NOT_TRANSACTED || err == PCSC_W_RESET_CARD
|| err == PCSC_W_REMOVED_CARD)
{
reader_table[slot].pcsc.current_state = PCSC_STATE_UNAWARE;
scd_kick_the_loop ();
}
return pcsc_error_to_sw (err);
}
/* Do some control with the value of IOCTL_CODE to the card inserted
to SLOT. Input buffer is specified by CNTLBUF of length LEN.
Output buffer is specified by BUFFER of length *BUFLEN, and the
actual output size will be stored at BUFLEN. Returns: A status word.
This routine is used for PIN pad input support. */
static int
control_pcsc (int slot, pcsc_dword_t ioctl_code,
const unsigned char *cntlbuf, size_t len,
unsigned char *buffer, pcsc_dword_t *buflen)
{
long err;
err = pcsc_control (reader_table[slot].pcsc.card, ioctl_code,
cntlbuf, len, buffer, buflen? *buflen:0, buflen);
if (err)
{
log_error ("pcsc_control failed: %s (0x%lx)\n",
pcsc_error_string (err), err);
return pcsc_error_to_sw (err);
}
return 0;
}
static int
close_pcsc_reader (int slot)
{
pcsc_release_context (reader_table[slot].pcsc.context);
return 0;
}
/* Connect a PC/SC card. */
static int
connect_pcsc_card (int slot)
{
long err;
assert (slot >= 0 && slot < MAX_READER);
if (reader_table[slot].pcsc.card)
return SW_HOST_ALREADY_CONNECTED;
reader_table[slot].atrlen = 0;
reader_table[slot].is_t0 = 0;
err = pcsc_connect (reader_table[slot].pcsc.context,
reader_table[slot].rdrname,
PCSC_SHARE_EXCLUSIVE,
PCSC_PROTOCOL_T0|PCSC_PROTOCOL_T1,
&reader_table[slot].pcsc.card,
&reader_table[slot].pcsc.protocol);
if (err)
{
reader_table[slot].pcsc.card = 0;
if (err != PCSC_E_NO_SMARTCARD)
log_error ("pcsc_connect failed: %s (0x%lx)\n",
pcsc_error_string (err), err);
}
else
{
char reader[250];
pcsc_dword_t readerlen, atrlen;
pcsc_dword_t card_state, card_protocol;
pcsc_vendor_specific_init (slot);
atrlen = DIM (reader_table[0].atr);
readerlen = sizeof reader -1 ;
err = pcsc_status (reader_table[slot].pcsc.card,
reader, &readerlen,
&card_state, &card_protocol,
reader_table[slot].atr, &atrlen);
if (err)
log_error ("pcsc_status failed: %s (0x%lx) %lu\n",
pcsc_error_string (err), err, (long unsigned int)readerlen);
else
{
if (atrlen > DIM (reader_table[0].atr))
log_bug ("ATR returned by pcsc_status is too large\n");
reader_table[slot].atrlen = atrlen;
reader_table[slot].is_t0 = !!(card_protocol & PCSC_PROTOCOL_T0);
}
}
dump_reader_status (slot);
return pcsc_error_to_sw (err);
}
static int
disconnect_pcsc_card (int slot)
{
long err;
assert (slot >= 0 && slot < MAX_READER);
if (!reader_table[slot].pcsc.card)
return 0;
err = pcsc_disconnect (reader_table[slot].pcsc.card, PCSC_LEAVE_CARD);
if (err)
{
log_error ("pcsc_disconnect failed: %s (0x%lx)\n",
pcsc_error_string (err), err);
return SW_HOST_CARD_IO_ERROR;
}
reader_table[slot].pcsc.card = 0;
return 0;
}
/* Send an PC/SC reset command and return a status word on error or 0
on success. */
static int
reset_pcsc_reader (int slot)
{
int sw;
sw = disconnect_pcsc_card (slot);
if (!sw)
sw = connect_pcsc_card (slot);
return sw;
}
/* Examine reader specific parameters and initialize. This is mostly
for pinpad input. Called at opening the connection to the reader. */
static int
pcsc_vendor_specific_init (int slot)
{
unsigned char buf[256];
pcsc_dword_t len;
int sw;
int vendor = 0;
int product = 0;
pcsc_dword_t get_tlv_ioctl = (pcsc_dword_t)-1;
unsigned char *p;
len = sizeof (buf);
sw = control_pcsc (slot, CM_IOCTL_GET_FEATURE_REQUEST, NULL, 0, buf, &len);
if (sw)
{
log_error ("pcsc_vendor_specific_init: GET_FEATURE_REQUEST failed: %d\n",
sw);
return SW_NOT_SUPPORTED;
}
else
{
p = buf;
while (p < buf + len)
{
unsigned char code = *p++;
int l = *p++;
unsigned int v = 0;
if (l == 1)
v = p[0];
else if (l == 2)
v = buf16_to_uint (p);
else if (l == 4)
v = buf32_to_uint (p);
if (code == FEATURE_VERIFY_PIN_DIRECT)
reader_table[slot].pcsc.verify_ioctl = v;
else if (code == FEATURE_MODIFY_PIN_DIRECT)
reader_table[slot].pcsc.modify_ioctl = v;
else if (code == FEATURE_GET_TLV_PROPERTIES)
get_tlv_ioctl = v;
if (DBG_CARD_IO)
log_debug ("feature: code=%02X, len=%d, v=%02X\n", code, l, v);
p += l;
}
}
if (get_tlv_ioctl == (pcsc_dword_t)-1)
{
/*
* For system which doesn't support GET_TLV_PROPERTIES,
* we put some heuristics here.
*/
if (reader_table[slot].rdrname)
{
if (strstr (reader_table[slot].rdrname, "SPRx32"))
{
reader_table[slot].is_spr532 = 1;
reader_table[slot].pinpad_varlen_supported = 1;
}
else if (strstr (reader_table[slot].rdrname, "ST-2xxx"))
{
reader_table[slot].pcsc.pinmax = 15;
reader_table[slot].pinpad_varlen_supported = 1;
}
else if (strstr (reader_table[slot].rdrname, "cyberJack")
|| strstr (reader_table[slot].rdrname, "DIGIPASS")
|| strstr (reader_table[slot].rdrname, "Gnuk")
|| strstr (reader_table[slot].rdrname, "KAAN")
|| strstr (reader_table[slot].rdrname, "Trustica"))
reader_table[slot].pinpad_varlen_supported = 1;
}
return 0;
}
len = sizeof (buf);
sw = control_pcsc (slot, get_tlv_ioctl, NULL, 0, buf, &len);
if (sw)
{
log_error ("pcsc_vendor_specific_init: GET_TLV_IOCTL failed: %d\n", sw);
return SW_NOT_SUPPORTED;
}
p = buf;
while (p < buf + len)
{
unsigned char tag = *p++;
int l = *p++;
unsigned int v = 0;
/* Umm... here is little endian, while the encoding above is big. */
if (l == 1)
v = p[0];
else if (l == 2)
v = (((unsigned int)p[1] << 8) | p[0]);
else if (l == 4)
v = (((unsigned int)p[3] << 24) | (p[2] << 16) | (p[1] << 8) | p[0]);
if (tag == PCSCv2_PART10_PROPERTY_bMinPINSize)
reader_table[slot].pcsc.pinmin = v;
else if (tag == PCSCv2_PART10_PROPERTY_bMaxPINSize)
reader_table[slot].pcsc.pinmax = v;
else if (tag == PCSCv2_PART10_PROPERTY_wIdVendor)
vendor = v;
else if (tag == PCSCv2_PART10_PROPERTY_wIdProduct)
product = v;
if (DBG_CARD_IO)
log_debug ("TLV properties: tag=%02X, len=%d, v=%08X\n", tag, l, v);
p += l;
}
if (vendor == VENDOR_VEGA && product == VEGA_ALPHA)
{
/*
* Please read the comment of ccid_vendor_specific_init in
* ccid-driver.c.
*/
const unsigned char cmd[] = { '\xb5', '\x01', '\x00', '\x03', '\x00' };
sw = control_pcsc (slot, CM_IOCTL_VENDOR_IFD_EXCHANGE,
cmd, sizeof (cmd), NULL, 0);
if (sw)
return SW_NOT_SUPPORTED;
}
else if (vendor == VENDOR_SCM && product == SCM_SPR532) /* SCM SPR532 */
{
reader_table[slot].is_spr532 = 1;
reader_table[slot].pinpad_varlen_supported = 1;
}
else if (vendor == 0x046a)
{
/* Cherry ST-2xxx (product == 0x003e) supports TPDU level
* exchange. Other products which only support short APDU level
* exchange only work with shorter keys like RSA 1024.
*/
reader_table[slot].pcsc.pinmax = 15;
reader_table[slot].pinpad_varlen_supported = 1;
}
else if (vendor == 0x0c4b /* Tested with Reiner cyberJack GO */
|| vendor == 0x1a44 /* Tested with Vasco DIGIPASS 920 */
|| vendor == 0x234b /* Tested with FSIJ Gnuk Token */
|| vendor == 0x0d46 /* Tested with KAAN Advanced??? */
|| (vendor == 0x1fc9 && product == 0x81e6) /* Tested with Trustica Cryptoucan */)
reader_table[slot].pinpad_varlen_supported = 1;
return 0;
}
/* Open the PC/SC reader without using the wrapper. Returns -1 on
error or a slot number for the reader. */
static int
open_pcsc_reader (const char *portstr)
{
long err;
int slot;
char *list = NULL;
char *rdrname = NULL;
pcsc_dword_t nreader;
char *p;
slot = new_reader_slot ();
if (slot == -1)
return -1;
/* Fixme: Allocating a context for each slot is not required. One
global context should be sufficient. */
err = pcsc_establish_context (PCSC_SCOPE_SYSTEM, NULL, NULL,
&reader_table[slot].pcsc.context);
if (err)
{
log_error ("pcsc_establish_context failed: %s (0x%lx)\n",
pcsc_error_string (err), err);
reader_table[slot].used = 0;
unlock_slot (slot);
return -1;
}
err = pcsc_list_readers (reader_table[slot].pcsc.context,
NULL, NULL, &nreader);
if (!err)
{
list = xtrymalloc (nreader+1); /* Better add 1 for safety reasons. */
if (!list)
{
log_error ("error allocating memory for reader list\n");
pcsc_release_context (reader_table[slot].pcsc.context);
reader_table[slot].used = 0;
unlock_slot (slot);
return -1 /*SW_HOST_OUT_OF_CORE*/;
}
err = pcsc_list_readers (reader_table[slot].pcsc.context,
NULL, list, &nreader);
}
if (err)
{
log_error ("pcsc_list_readers failed: %s (0x%lx)\n",
pcsc_error_string (err), err);
pcsc_release_context (reader_table[slot].pcsc.context);
reader_table[slot].used = 0;
xfree (list);
unlock_slot (slot);
return -1;
}
p = list;
while (nreader)
{
if (!*p && !p[1])
break;
log_info ("detected reader '%s'\n", p);
if (nreader < (strlen (p)+1))
{
log_error ("invalid response from pcsc_list_readers\n");
break;
}
if (!rdrname && portstr && !strncmp (p, portstr, strlen (portstr)))
rdrname = p;
nreader -= strlen (p)+1;
p += strlen (p) + 1;
}
if (!rdrname)
rdrname = list;
reader_table[slot].rdrname = xtrystrdup (rdrname);
if (!reader_table[slot].rdrname)
{
log_error ("error allocating memory for reader name\n");
pcsc_release_context (reader_table[slot].pcsc.context);
reader_table[slot].used = 0;
unlock_slot (slot);
return -1;
}
xfree (list);
list = NULL;
reader_table[slot].pcsc.card = 0;
reader_table[slot].atrlen = 0;
reader_table[slot].connect_card = connect_pcsc_card;
reader_table[slot].disconnect_card = disconnect_pcsc_card;
reader_table[slot].close_reader = close_pcsc_reader;
reader_table[slot].reset_reader = reset_pcsc_reader;
reader_table[slot].get_status_reader = pcsc_get_status;
reader_table[slot].send_apdu_reader = pcsc_send_apdu;
reader_table[slot].dump_status_reader = dump_pcsc_reader_status;
dump_reader_status (slot);
unlock_slot (slot);
return slot;
}
/* Check whether the reader supports the ISO command code COMMAND
on the pinpad. Return 0 on success. */
static int
check_pcsc_pinpad (int slot, int command, pininfo_t *pininfo)
{
int r;
if (reader_table[slot].pcsc.pinmin >= 0)
pininfo->minlen = reader_table[slot].pcsc.pinmin;
if (reader_table[slot].pcsc.pinmax >= 0)
pininfo->maxlen = reader_table[slot].pcsc.pinmax;
if (!pininfo->minlen)
pininfo->minlen = 1;
if (!pininfo->maxlen)
pininfo->maxlen = 15;
if ((command == ISO7816_VERIFY && reader_table[slot].pcsc.verify_ioctl != 0)
|| (command == ISO7816_CHANGE_REFERENCE_DATA
&& reader_table[slot].pcsc.modify_ioctl != 0))
r = 0; /* Success */
else
r = SW_NOT_SUPPORTED;
if (DBG_CARD_IO)
log_debug ("check_pcsc_pinpad: command=%02X, r=%d\n",
(unsigned int)command, r);
if (reader_table[slot].pinpad_varlen_supported)
pininfo->fixedlen = 0;
return r;
}
#define PIN_VERIFY_STRUCTURE_SIZE 24
static int
pcsc_pinpad_verify (int slot, int class, int ins, int p0, int p1,
pininfo_t *pininfo)
{
int sw;
unsigned char *pin_verify;
int len = PIN_VERIFY_STRUCTURE_SIZE + pininfo->fixedlen;
/*
* The result buffer is only expected to have two-byte result on
* return. However, some implementation uses this buffer for lower
* layer too and it assumes that there is enough space for lower
* layer communication. Such an implementation fails for TPDU
* readers with "insufficient buffer", as it needs header and
* trailer. Six is the number for header + result + trailer (TPDU).
*/
unsigned char result[6];
pcsc_dword_t resultlen = 6;
int no_lc;
if (!reader_table[slot].atrlen
&& (sw = reset_pcsc_reader (slot)))
return sw;
if (pininfo->fixedlen < 0 || pininfo->fixedlen >= 16)
return SW_NOT_SUPPORTED;
pin_verify = xtrymalloc (len);
if (!pin_verify)
return SW_HOST_OUT_OF_CORE;
no_lc = (!pininfo->fixedlen && reader_table[slot].is_spr532);
pin_verify[0] = 0x00; /* bTimeOut */
pin_verify[1] = 0x00; /* bTimeOut2 */
pin_verify[2] = 0x82; /* bmFormatString: Byte, pos=0, left, ASCII. */
pin_verify[3] = pininfo->fixedlen; /* bmPINBlockString */
pin_verify[4] = 0x00; /* bmPINLengthFormat */
pin_verify[5] = pininfo->maxlen; /* wPINMaxExtraDigit */
pin_verify[6] = pininfo->minlen; /* wPINMaxExtraDigit */
pin_verify[7] = 0x02; /* bEntryValidationCondition: Validation key pressed */
if (pininfo->minlen && pininfo->maxlen && pininfo->minlen == pininfo->maxlen)
pin_verify[7] |= 0x01; /* Max size reached. */
pin_verify[8] = 0x01; /* bNumberMessage: One message */
pin_verify[9] = 0x09; /* wLangId: 0x0409: US English */
pin_verify[10] = 0x04; /* wLangId: 0x0409: US English */
pin_verify[11] = 0x00; /* bMsgIndex */
pin_verify[12] = 0x00; /* bTeoPrologue[0] */
pin_verify[13] = 0x00; /* bTeoPrologue[1] */
pin_verify[14] = pininfo->fixedlen + 0x05 - no_lc; /* bTeoPrologue[2] */
pin_verify[15] = pininfo->fixedlen + 0x05 - no_lc; /* ulDataLength */
pin_verify[16] = 0x00; /* ulDataLength */
pin_verify[17] = 0x00; /* ulDataLength */
pin_verify[18] = 0x00; /* ulDataLength */
pin_verify[19] = class; /* abData[0] */
pin_verify[20] = ins; /* abData[1] */
pin_verify[21] = p0; /* abData[2] */
pin_verify[22] = p1; /* abData[3] */
pin_verify[23] = pininfo->fixedlen; /* abData[4] */
if (pininfo->fixedlen)
memset (&pin_verify[24], 0xff, pininfo->fixedlen);
else if (no_lc)
len--;
if (DBG_CARD_IO)
log_debug ("send secure: c=%02X i=%02X p1=%02X p2=%02X len=%d pinmax=%d\n",
class, ins, p0, p1, len, pininfo->maxlen);
sw = control_pcsc (slot, reader_table[slot].pcsc.verify_ioctl,
pin_verify, len, result, &resultlen);
xfree (pin_verify);
if (sw || resultlen < 2)
{
log_error ("control_pcsc failed: %d\n", sw);
return sw? sw: SW_HOST_INCOMPLETE_CARD_RESPONSE;
}
sw = (result[resultlen-2] << 8) | result[resultlen-1];
if (DBG_CARD_IO)
log_debug (" response: sw=%04X datalen=%d\n", sw, (unsigned int)resultlen);
return sw;
}
#define PIN_MODIFY_STRUCTURE_SIZE 29
static int
pcsc_pinpad_modify (int slot, int class, int ins, int p0, int p1,
pininfo_t *pininfo)
{
int sw;
unsigned char *pin_modify;
int len = PIN_MODIFY_STRUCTURE_SIZE + 2 * pininfo->fixedlen;
unsigned char result[6]; /* See the comment at pinpad_verify. */
pcsc_dword_t resultlen = 6;
int no_lc;
if (!reader_table[slot].atrlen
&& (sw = reset_pcsc_reader (slot)))
return sw;
if (pininfo->fixedlen < 0 || pininfo->fixedlen >= 16)
return SW_NOT_SUPPORTED;
pin_modify = xtrymalloc (len);
if (!pin_modify)
return SW_HOST_OUT_OF_CORE;
no_lc = (!pininfo->fixedlen && reader_table[slot].is_spr532);
pin_modify[0] = 0x00; /* bTimeOut */
pin_modify[1] = 0x00; /* bTimeOut2 */
pin_modify[2] = 0x82; /* bmFormatString: Byte, pos=0, left, ASCII. */
pin_modify[3] = pininfo->fixedlen; /* bmPINBlockString */
pin_modify[4] = 0x00; /* bmPINLengthFormat */
pin_modify[5] = 0x00; /* bInsertionOffsetOld */
pin_modify[6] = pininfo->fixedlen; /* bInsertionOffsetNew */
pin_modify[7] = pininfo->maxlen; /* wPINMaxExtraDigit */
pin_modify[8] = pininfo->minlen; /* wPINMaxExtraDigit */
pin_modify[9] = (p0 == 0 ? 0x03 : 0x01);
/* bConfirmPIN
* 0x00: new PIN once
* 0x01: new PIN twice (confirmation)
* 0x02: old PIN and new PIN once
* 0x03: old PIN and new PIN twice (confirmation)
*/
pin_modify[10] = 0x02; /* bEntryValidationCondition: Validation key pressed */
if (pininfo->minlen && pininfo->maxlen && pininfo->minlen == pininfo->maxlen)
pin_modify[10] |= 0x01; /* Max size reached. */
pin_modify[11] = 0x03; /* bNumberMessage: Three messages */
pin_modify[12] = 0x09; /* wLangId: 0x0409: US English */
pin_modify[13] = 0x04; /* wLangId: 0x0409: US English */
pin_modify[14] = 0x00; /* bMsgIndex1 */
pin_modify[15] = 0x01; /* bMsgIndex2 */
pin_modify[16] = 0x02; /* bMsgIndex3 */
pin_modify[17] = 0x00; /* bTeoPrologue[0] */
pin_modify[18] = 0x00; /* bTeoPrologue[1] */
pin_modify[19] = 2 * pininfo->fixedlen + 0x05 - no_lc; /* bTeoPrologue[2] */
pin_modify[20] = 2 * pininfo->fixedlen + 0x05 - no_lc; /* ulDataLength */
pin_modify[21] = 0x00; /* ulDataLength */
pin_modify[22] = 0x00; /* ulDataLength */
pin_modify[23] = 0x00; /* ulDataLength */
pin_modify[24] = class; /* abData[0] */
pin_modify[25] = ins; /* abData[1] */
pin_modify[26] = p0; /* abData[2] */
pin_modify[27] = p1; /* abData[3] */
pin_modify[28] = 2 * pininfo->fixedlen; /* abData[4] */
if (pininfo->fixedlen)
memset (&pin_modify[29], 0xff, 2 * pininfo->fixedlen);
else if (no_lc)
len--;
if (DBG_CARD_IO)
log_debug ("send secure: c=%02X i=%02X p1=%02X p2=%02X len=%d pinmax=%d\n",
class, ins, p0, p1, len, (int)pininfo->maxlen);
sw = control_pcsc (slot, reader_table[slot].pcsc.modify_ioctl,
pin_modify, len, result, &resultlen);
xfree (pin_modify);
if (sw || resultlen < 2)
{
log_error ("control_pcsc failed: %d\n", sw);
return sw? sw : SW_HOST_INCOMPLETE_CARD_RESPONSE;
}
sw = (result[resultlen-2] << 8) | result[resultlen-1];
if (DBG_CARD_IO)
log_debug (" response: sw=%04X datalen=%d\n", sw, (unsigned int)resultlen);
return sw;
}
#ifdef HAVE_LIBUSB
/*
Internal CCID driver interface.
*/
static void
dump_ccid_reader_status (int slot)
{
log_info ("reader slot %d: using ccid driver\n", slot);
}
static int
close_ccid_reader (int slot)
{
ccid_close_reader (reader_table[slot].ccid.handle);
return 0;
}
static int
reset_ccid_reader (int slot)
{
int err;
reader_table_t slotp = reader_table + slot;
unsigned char atr[33];
size_t atrlen;
err = ccid_get_atr (slotp->ccid.handle, atr, sizeof atr, &atrlen);
if (err)
return err;
/* If the reset was successful, update the ATR. */
assert (sizeof slotp->atr >= sizeof atr);
slotp->atrlen = atrlen;
memcpy (slotp->atr, atr, atrlen);
dump_reader_status (slot);
return 0;
}
static int
set_progress_cb_ccid_reader (int slot, gcry_handler_progress_t cb, void *cb_arg)
{
reader_table_t slotp = reader_table + slot;
return ccid_set_progress_cb (slotp->ccid.handle, cb, cb_arg);
}
static int
set_prompt_cb_ccid_reader (int slot, void (*cb) (void *, int ), void *cb_arg)
{
reader_table_t slotp = reader_table + slot;
return ccid_set_prompt_cb (slotp->ccid.handle, cb, cb_arg);
}
static int
get_status_ccid (int slot, unsigned int *status, int on_wire)
{
int rc;
int bits;
rc = ccid_slot_status (reader_table[slot].ccid.handle, &bits, on_wire);
if (rc)
return rc;
if (bits == 0)
*status = (APDU_CARD_USABLE|APDU_CARD_PRESENT|APDU_CARD_ACTIVE);
else if (bits == 1)
*status = APDU_CARD_PRESENT;
else
*status = 0;
return 0;
}
/* Actually send the APDU of length APDULEN to SLOT and return a
maximum of *BUFLEN data in BUFFER, the actual returned size will be
set to BUFLEN. Returns: Internal CCID driver error code. */
static int
send_apdu_ccid (int slot, unsigned char *apdu, size_t apdulen,
unsigned char *buffer, size_t *buflen,
pininfo_t *pininfo)
{
long err;
size_t maxbuflen;
/* If we don't have an ATR, we need to reset the reader first. */
if (!reader_table[slot].atrlen
&& (err = reset_ccid_reader (slot)))
return err;
if (DBG_CARD_IO)
log_printhex (apdu, apdulen, " raw apdu:");
maxbuflen = *buflen;
if (pininfo)
err = ccid_transceive_secure (reader_table[slot].ccid.handle,
apdu, apdulen, pininfo,
buffer, maxbuflen, buflen);
else
err = ccid_transceive (reader_table[slot].ccid.handle,
apdu, apdulen,
buffer, maxbuflen, buflen);
if (err)
log_error ("ccid_transceive failed: (0x%lx)\n",
err);
return err;
}
/* Check whether the CCID reader supports the ISO command code COMMAND
on the pinpad. Return 0 on success. For a description of the pin
parameters, see ccid-driver.c */
static int
check_ccid_pinpad (int slot, int command, pininfo_t *pininfo)
{
unsigned char apdu[] = { 0, 0, 0, 0x81 };
apdu[1] = command;
return ccid_transceive_secure (reader_table[slot].ccid.handle, apdu,
sizeof apdu, pininfo, NULL, 0, NULL);
}
static int
ccid_pinpad_operation (int slot, int class, int ins, int p0, int p1,
pininfo_t *pininfo)
{
unsigned char apdu[4];
int err, sw;
unsigned char result[2];
size_t resultlen = 2;
apdu[0] = class;
apdu[1] = ins;
apdu[2] = p0;
apdu[3] = p1;
err = ccid_transceive_secure (reader_table[slot].ccid.handle,
apdu, sizeof apdu, pininfo,
result, 2, &resultlen);
if (err)
return err;
if (resultlen < 2)
return SW_HOST_INCOMPLETE_CARD_RESPONSE;
sw = (result[resultlen-2] << 8) | result[resultlen-1];
return sw;
}
/* Open the reader and try to read an ATR. */
static int
open_ccid_reader (struct dev_list *dl)
{
int err;
int slot;
int require_get_status;
reader_table_t slotp;
slot = new_reader_slot ();
if (slot == -1)
return -1;
slotp = reader_table + slot;
err = ccid_open_reader (dl->portstr, dl->idx, dl->ccid_table,
&slotp->ccid.handle, &slotp->rdrname);
if (!err)
{
err = ccid_get_atr (slotp->ccid.handle,
slotp->atr, sizeof slotp->atr, &slotp->atrlen);
if (err)
ccid_close_reader (slotp->ccid.handle);
}
if (err)
{
slotp->used = 0;
unlock_slot (slot);
return -1;
}
require_get_status = ccid_require_get_status (slotp->ccid.handle);
reader_table[slot].close_reader = close_ccid_reader;
reader_table[slot].reset_reader = reset_ccid_reader;
reader_table[slot].get_status_reader = get_status_ccid;
reader_table[slot].send_apdu_reader = send_apdu_ccid;
reader_table[slot].check_pinpad = check_ccid_pinpad;
reader_table[slot].dump_status_reader = dump_ccid_reader_status;
reader_table[slot].set_progress_cb = set_progress_cb_ccid_reader;
reader_table[slot].set_prompt_cb = set_prompt_cb_ccid_reader;
reader_table[slot].pinpad_verify = ccid_pinpad_operation;
reader_table[slot].pinpad_modify = ccid_pinpad_operation;
/* Our CCID reader code does not support T=0 at all, thus reset the
flag. */
reader_table[slot].is_t0 = 0;
reader_table[slot].require_get_status = require_get_status;
dump_reader_status (slot);
unlock_slot (slot);
return slot;
}
#endif /* HAVE_LIBUSB */
#ifdef USE_G10CODE_RAPDU
/*
The Remote APDU Interface.
This uses the Remote APDU protocol to contact a reader.
The port number is actually an index into the list of ports as
returned via the protocol.
*/
static int
rapdu_status_to_sw (int status)
{
int rc;
switch (status)
{
case RAPDU_STATUS_SUCCESS: rc = 0; break;
case RAPDU_STATUS_INVCMD:
case RAPDU_STATUS_INVPROT:
case RAPDU_STATUS_INVSEQ:
case RAPDU_STATUS_INVCOOKIE:
case RAPDU_STATUS_INVREADER: rc = SW_HOST_INV_VALUE; break;
case RAPDU_STATUS_TIMEOUT: rc = SW_HOST_CARD_IO_ERROR; break;
case RAPDU_STATUS_CARDIO: rc = SW_HOST_CARD_IO_ERROR; break;
case RAPDU_STATUS_NOCARD: rc = SW_HOST_NO_CARD; break;
case RAPDU_STATUS_CARDCHG: rc = SW_HOST_NO_CARD; break;
case RAPDU_STATUS_BUSY: rc = SW_HOST_BUSY; break;
case RAPDU_STATUS_NEEDRESET: rc = SW_HOST_CARD_INACTIVE; break;
default: rc = SW_HOST_GENERAL_ERROR; break;
}
return rc;
}
static int
close_rapdu_reader (int slot)
{
rapdu_release (reader_table[slot].rapdu.handle);
return 0;
}
static int
reset_rapdu_reader (int slot)
{
int err;
reader_table_t slotp;
rapdu_msg_t msg = NULL;
slotp = reader_table + slot;
err = rapdu_send_cmd (slotp->rapdu.handle, RAPDU_CMD_RESET);
if (err)
{
log_error ("sending rapdu command RESET failed: %s\n",
err < 0 ? strerror (errno): rapdu_strerror (err));
rapdu_msg_release (msg);
return rapdu_status_to_sw (err);
}
err = rapdu_read_msg (slotp->rapdu.handle, &msg);
if (err)
{
log_error ("receiving rapdu message failed: %s\n",
err < 0 ? strerror (errno): rapdu_strerror (err));
rapdu_msg_release (msg);
return rapdu_status_to_sw (err);
}
if (msg->cmd != RAPDU_STATUS_SUCCESS || !msg->datalen)
{
int sw = rapdu_status_to_sw (msg->cmd);
log_error ("rapdu command RESET failed: %s\n",
rapdu_strerror (msg->cmd));
rapdu_msg_release (msg);
return sw;
}
if (msg->datalen > DIM (slotp->atr))
{
log_error ("ATR returned by the RAPDU layer is too large\n");
rapdu_msg_release (msg);
return SW_HOST_INV_VALUE;
}
slotp->atrlen = msg->datalen;
memcpy (slotp->atr, msg->data, msg->datalen);
rapdu_msg_release (msg);
return 0;
}
static int
my_rapdu_get_status (int slot, unsigned int *status, int on_wire)
{
int err;
reader_table_t slotp;
rapdu_msg_t msg = NULL;
int oldslot;
(void)on_wire;
slotp = reader_table + slot;
oldslot = rapdu_set_reader (slotp->rapdu.handle, slot);
err = rapdu_send_cmd (slotp->rapdu.handle, RAPDU_CMD_GET_STATUS);
rapdu_set_reader (slotp->rapdu.handle, oldslot);
if (err)
{
log_error ("sending rapdu command GET_STATUS failed: %s\n",
err < 0 ? strerror (errno): rapdu_strerror (err));
return rapdu_status_to_sw (err);
}
err = rapdu_read_msg (slotp->rapdu.handle, &msg);
if (err)
{
log_error ("receiving rapdu message failed: %s\n",
err < 0 ? strerror (errno): rapdu_strerror (err));
rapdu_msg_release (msg);
return rapdu_status_to_sw (err);
}
if (msg->cmd != RAPDU_STATUS_SUCCESS || !msg->datalen)
{
int sw = rapdu_status_to_sw (msg->cmd);
log_error ("rapdu command GET_STATUS failed: %s\n",
rapdu_strerror (msg->cmd));
rapdu_msg_release (msg);
return sw;
}
*status = msg->data[0];
rapdu_msg_release (msg);
return 0;
}
/* Actually send the APDU of length APDULEN to SLOT and return a
maximum of *BUFLEN data in BUFFER, the actual returned size will be
set to BUFLEN. Returns: APDU error code. */
static int
my_rapdu_send_apdu (int slot, unsigned char *apdu, size_t apdulen,
unsigned char *buffer, size_t *buflen,
pininfo_t *pininfo)
{
int err;
reader_table_t slotp;
rapdu_msg_t msg = NULL;
size_t maxlen = *buflen;
slotp = reader_table + slot;
*buflen = 0;
if (DBG_CARD_IO)
log_printhex (apdu, apdulen, " APDU_data:");
if (apdulen < 4)
{
log_error ("rapdu_send_apdu: APDU is too short\n");
return SW_HOST_INV_VALUE;
}
err = rapdu_send_apdu (slotp->rapdu.handle, apdu, apdulen);
if (err)
{
log_error ("sending rapdu command APDU failed: %s\n",
err < 0 ? strerror (errno): rapdu_strerror (err));
rapdu_msg_release (msg);
return rapdu_status_to_sw (err);
}
err = rapdu_read_msg (slotp->rapdu.handle, &msg);
if (err)
{
log_error ("receiving rapdu message failed: %s\n",
err < 0 ? strerror (errno): rapdu_strerror (err));
rapdu_msg_release (msg);
return rapdu_status_to_sw (err);
}
if (msg->cmd != RAPDU_STATUS_SUCCESS || !msg->datalen)
{
int sw = rapdu_status_to_sw (msg->cmd);
log_error ("rapdu command APDU failed: %s\n",
rapdu_strerror (msg->cmd));
rapdu_msg_release (msg);
return sw;
}
if (msg->datalen > maxlen)
{
log_error ("rapdu response apdu too large\n");
rapdu_msg_release (msg);
return SW_HOST_INV_VALUE;
}
*buflen = msg->datalen;
memcpy (buffer, msg->data, msg->datalen);
rapdu_msg_release (msg);
return 0;
}
static int
open_rapdu_reader (int portno,
const unsigned char *cookie, size_t length,
int (*readfnc) (void *opaque,
void *buffer, size_t size),
void *readfnc_value,
int (*writefnc) (void *opaque,
const void *buffer, size_t size),
void *writefnc_value,
void (*closefnc) (void *opaque),
void *closefnc_value)
{
int err;
int slot;
reader_table_t slotp;
rapdu_msg_t msg = NULL;
slot = new_reader_slot ();
if (slot == -1)
return -1;
slotp = reader_table + slot;
slotp->rapdu.handle = rapdu_new ();
if (!slotp->rapdu.handle)
{
slotp->used = 0;
unlock_slot (slot);
return -1;
}
rapdu_set_reader (slotp->rapdu.handle, portno);
rapdu_set_iofunc (slotp->rapdu.handle,
readfnc, readfnc_value,
writefnc, writefnc_value,
closefnc, closefnc_value);
rapdu_set_cookie (slotp->rapdu.handle, cookie, length);
/* First try to get the current ATR, but if the card is inactive
issue a reset instead. */
err = rapdu_send_cmd (slotp->rapdu.handle, RAPDU_CMD_GET_ATR);
if (err == RAPDU_STATUS_NEEDRESET)
err = rapdu_send_cmd (slotp->rapdu.handle, RAPDU_CMD_RESET);
if (err)
{
log_info ("sending rapdu command GET_ATR/RESET failed: %s\n",
err < 0 ? strerror (errno): rapdu_strerror (err));
goto failure;
}
err = rapdu_read_msg (slotp->rapdu.handle, &msg);
if (err)
{
log_info ("receiving rapdu message failed: %s\n",
err < 0 ? strerror (errno): rapdu_strerror (err));
goto failure;
}
if (msg->cmd != RAPDU_STATUS_SUCCESS || !msg->datalen)
{
log_info ("rapdu command GET ATR failed: %s\n",
rapdu_strerror (msg->cmd));
goto failure;
}
if (msg->datalen > DIM (slotp->atr))
{
log_error ("ATR returned by the RAPDU layer is too large\n");
goto failure;
}
slotp->atrlen = msg->datalen;
memcpy (slotp->atr, msg->data, msg->datalen);
reader_table[slot].close_reader = close_rapdu_reader;
reader_table[slot].reset_reader = reset_rapdu_reader;
reader_table[slot].get_status_reader = my_rapdu_get_status;
reader_table[slot].send_apdu_reader = my_rapdu_send_apdu;
reader_table[slot].check_pinpad = NULL;
reader_table[slot].dump_status_reader = NULL;
reader_table[slot].pinpad_verify = NULL;
reader_table[slot].pinpad_modify = NULL;
dump_reader_status (slot);
rapdu_msg_release (msg);
unlock_slot (slot);
return slot;
failure:
rapdu_msg_release (msg);
rapdu_release (slotp->rapdu.handle);
slotp->used = 0;
unlock_slot (slot);
return -1;
}
#endif /*USE_G10CODE_RAPDU*/
/*
Driver Access
*/
gpg_error_t
apdu_dev_list_start (const char *portstr, struct dev_list **l_p)
{
struct dev_list *dl = xtrymalloc (sizeof (struct dev_list));
*l_p = NULL;
if (!dl)
return gpg_error_from_syserror ();
dl->portstr = portstr;
dl->idx = 0;
npth_mutex_lock (&reader_table_lock);
#ifdef HAVE_LIBUSB
if (opt.disable_ccid)
{
dl->ccid_table = NULL;
dl->idx_max = 1;
}
else
{
gpg_error_t err;
err = ccid_dev_scan (&dl->idx_max, &dl->ccid_table);
if (err)
return err;
if (dl->idx_max == 0)
{
/* If a CCID reader specification has been given, the user does
not want a fallback to other drivers. */
if (portstr && strlen (portstr) > 5 && portstr[4] == ':')
{
if (DBG_READER)
log_debug ("leave: apdu_open_reader => slot=-1 (no ccid)\n");
xfree (dl);
npth_mutex_unlock (&reader_table_lock);
return gpg_error (GPG_ERR_ENODEV);
}
else
dl->idx_max = 1;
}
}
#else
dl->ccid_table = NULL;
dl->idx_max = 1;
#endif /* HAVE_LIBUSB */
*l_p = dl;
return 0;
}
void
apdu_dev_list_finish (struct dev_list *dl)
{
#ifdef HAVE_LIBUSB
if (dl->ccid_table)
ccid_dev_scan_finish (dl->ccid_table, dl->idx_max);
#endif
xfree (dl);
npth_mutex_unlock (&reader_table_lock);
}
/* Open the reader and return an internal slot number or -1 on
error. If PORTSTR is NULL we default to a suitable port (for ctAPI:
the first USB reader. For PC/SC the first listed reader). */
static int
apdu_open_one_reader (const char *portstr)
{
static int pcsc_api_loaded;
int slot;
if (DBG_READER)
log_debug ("enter: apdu_open_reader: portstr=%s\n", portstr);
/* Lets try the PC/SC API */
if (!pcsc_api_loaded)
{
void *handle;
handle = dlopen (opt.pcsc_driver, RTLD_LAZY);
if (!handle)
{
log_error ("apdu_open_reader: failed to open driver '%s': %s\n",
opt.pcsc_driver, dlerror ());
return -1;
}
pcsc_establish_context = dlsym (handle, "SCardEstablishContext");
pcsc_release_context = dlsym (handle, "SCardReleaseContext");
pcsc_list_readers = dlsym (handle, "SCardListReaders");
#if defined(_WIN32) || defined(__CYGWIN__)
if (!pcsc_list_readers)
pcsc_list_readers = dlsym (handle, "SCardListReadersA");
#endif
pcsc_get_status_change = dlsym (handle, "SCardGetStatusChange");
#if defined(_WIN32) || defined(__CYGWIN__)
if (!pcsc_get_status_change)
pcsc_get_status_change = dlsym (handle, "SCardGetStatusChangeA");
#endif
pcsc_connect = dlsym (handle, "SCardConnect");
#if defined(_WIN32) || defined(__CYGWIN__)
if (!pcsc_connect)
pcsc_connect = dlsym (handle, "SCardConnectA");
#endif
pcsc_reconnect = dlsym (handle, "SCardReconnect");
#if defined(_WIN32) || defined(__CYGWIN__)
if (!pcsc_reconnect)
pcsc_reconnect = dlsym (handle, "SCardReconnectA");
#endif
pcsc_disconnect = dlsym (handle, "SCardDisconnect");
pcsc_status = dlsym (handle, "SCardStatus");
#if defined(_WIN32) || defined(__CYGWIN__)
if (!pcsc_status)
pcsc_status = dlsym (handle, "SCardStatusA");
#endif
pcsc_begin_transaction = dlsym (handle, "SCardBeginTransaction");
pcsc_end_transaction = dlsym (handle, "SCardEndTransaction");
pcsc_transmit = dlsym (handle, "SCardTransmit");
pcsc_set_timeout = dlsym (handle, "SCardSetTimeout");
pcsc_control = dlsym (handle, "SCardControl");
if (!pcsc_establish_context
|| !pcsc_release_context
|| !pcsc_list_readers
|| !pcsc_get_status_change
|| !pcsc_connect
|| !pcsc_reconnect
|| !pcsc_disconnect
|| !pcsc_status
|| !pcsc_begin_transaction
|| !pcsc_end_transaction
|| !pcsc_transmit
|| !pcsc_control
/* || !pcsc_set_timeout */)
{
/* Note that set_timeout is currently not used and also not
available under Windows. */
log_error ("apdu_open_reader: invalid PC/SC driver "
"(%d%d%d%d%d%d%d%d%d%d%d%d%d)\n",
!!pcsc_establish_context,
!!pcsc_release_context,
!!pcsc_list_readers,
!!pcsc_get_status_change,
!!pcsc_connect,
!!pcsc_reconnect,
!!pcsc_disconnect,
!!pcsc_status,
!!pcsc_begin_transaction,
!!pcsc_end_transaction,
!!pcsc_transmit,
!!pcsc_set_timeout,
!!pcsc_control );
dlclose (handle);
return -1;
}
pcsc_api_loaded = 1;
}
slot = open_pcsc_reader (portstr);
if (DBG_READER)
log_debug ("leave: apdu_open_reader => slot=%d [pc/sc]\n", slot);
return slot;
}
int
apdu_open_reader (struct dev_list *dl, int app_empty)
{
int slot;
#ifdef HAVE_LIBUSB
if (dl->ccid_table)
{ /* CCID readers. */
int readerno;
/* See whether we want to use the reader ID string or a reader
number. A readerno of -1 indicates that the reader ID string is
to be used. */
if (dl->portstr && strchr (dl->portstr, ':'))
readerno = -1; /* We want to use the readerid. */
else if (dl->portstr)
{
readerno = atoi (dl->portstr);
if (readerno < 0)
{
return -1;
}
}
else
readerno = 0; /* Default. */
if (readerno > 0)
{ /* Use single, the specific reader. */
if (readerno >= dl->idx_max)
return -1;
dl->idx = readerno;
dl->portstr = NULL;
slot = open_ccid_reader (dl);
dl->idx = dl->idx_max;
if (slot >= 0)
return slot;
else
return -1;
}
while (dl->idx < dl->idx_max)
{
unsigned int bai = ccid_get_BAI (dl->idx, dl->ccid_table);
if (DBG_READER)
log_debug ("apdu_open_reader: BAI=%x\n", bai);
/* Check identity by BAI against already opened HANDLEs. */
for (slot = 0; slot < MAX_READER; slot++)
if (reader_table[slot].used
&& reader_table[slot].ccid.handle
&& ccid_compare_BAI (reader_table[slot].ccid.handle, bai))
break;
if (slot == MAX_READER)
{ /* Found a new device. */
if (DBG_READER)
log_debug ("apdu_open_reader: new device=%x\n", bai);
slot = open_ccid_reader (dl);
dl->idx++;
if (slot >= 0)
return slot;
else
{
/* Skip this reader. */
log_error ("ccid open error: skip\n");
continue;
}
}
else
dl->idx++;
}
/* Not found. Try one for PC/SC, only when it's the initial scan. */
if (app_empty && dl->idx == dl->idx_max)
{
dl->idx++;
slot = apdu_open_one_reader (dl->portstr);
}
else
slot = -1;
}
else
#endif
{ /* PC/SC readers. */
if (app_empty && dl->idx == 0)
{
dl->idx++;
slot = apdu_open_one_reader (dl->portstr);
}
else
slot = -1;
}
return slot;
}
/* Open an remote reader and return an internal slot number or -1 on
error. This function is an alternative to apdu_open_reader and used
with remote readers only. Note that the supplied CLOSEFNC will
only be called once and the slot will not be valid afther this.
If PORTSTR is NULL we default to the first available port.
*/
int
apdu_open_remote_reader (const char *portstr,
const unsigned char *cookie, size_t length,
int (*readfnc) (void *opaque,
void *buffer, size_t size),
void *readfnc_value,
int (*writefnc) (void *opaque,
const void *buffer, size_t size),
void *writefnc_value,
void (*closefnc) (void *opaque),
void *closefnc_value)
{
#ifdef USE_G10CODE_RAPDU
return open_rapdu_reader (portstr? atoi (portstr) : 0,
cookie, length,
readfnc, readfnc_value,
writefnc, writefnc_value,
closefnc, closefnc_value);
#else
(void)portstr;
(void)cookie;
(void)length;
(void)readfnc;
(void)readfnc_value;
(void)writefnc;
(void)writefnc_value;
(void)closefnc;
(void)closefnc_value;
#ifdef _WIN32
errno = ENOENT;
#else
errno = ENOSYS;
#endif
return -1;
#endif
}
int
apdu_close_reader (int slot)
{
int sw;
if (DBG_READER)
log_debug ("enter: apdu_close_reader: slot=%d\n", slot);
if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used )
{
if (DBG_READER)
log_debug ("leave: apdu_close_reader => SW_HOST_NO_DRIVER\n");
return SW_HOST_NO_DRIVER;
}
sw = apdu_disconnect (slot);
if (sw)
{
/*
* When the reader/token was removed it might come here.
* It should go through to call CLOSE_READER even if we got an error.
*/
if (DBG_READER)
log_debug ("apdu_close_reader => 0x%x (apdu_disconnect)\n", sw);
}
if (reader_table[slot].close_reader)
{
sw = reader_table[slot].close_reader (slot);
reader_table[slot].used = 0;
if (DBG_READER)
log_debug ("leave: apdu_close_reader => 0x%x (close_reader)\n", sw);
return sw;
}
xfree (reader_table[slot].rdrname);
reader_table[slot].rdrname = NULL;
reader_table[slot].used = 0;
if (DBG_READER)
log_debug ("leave: apdu_close_reader => SW_HOST_NOT_SUPPORTED\n");
return SW_HOST_NOT_SUPPORTED;
}
/* Function suitable for a cleanup function to close all reader. It
should not be used if the reader will be opened again. The reason
for implementing this to properly close USB devices so that they
will startup the next time without error. */
void
apdu_prepare_exit (void)
{
static int sentinel;
int slot;
if (!sentinel)
{
sentinel = 1;
npth_mutex_lock (&reader_table_lock);
for (slot = 0; slot < MAX_READER; slot++)
if (reader_table[slot].used)
{
apdu_disconnect (slot);
if (reader_table[slot].close_reader)
reader_table[slot].close_reader (slot);
xfree (reader_table[slot].rdrname);
reader_table[slot].rdrname = NULL;
reader_table[slot].used = 0;
}
npth_mutex_unlock (&reader_table_lock);
sentinel = 0;
}
}
/* Enumerate all readers and return information on whether this reader
is in use. The caller should start with SLOT set to 0 and
increment it with each call until an error is returned. */
int
apdu_enum_reader (int slot, int *used)
{
if (slot < 0 || slot >= MAX_READER)
return SW_HOST_NO_DRIVER;
*used = reader_table[slot].used;
return 0;
}
/* Connect a card. This is used to power up the card and make sure
that an ATR is available. Depending on the reader backend it may
return an error for an inactive card or if no card is available.
Return -1 on error. Return 1 if reader requires get_status to
watch card removal. Return 0 if it's a token (always with a card),
or it supports INTERRUPT endpoint to watch card removal.
*/
int
apdu_connect (int slot)
{
int sw = 0;
unsigned int status = 0;
if (DBG_READER)
log_debug ("enter: apdu_connect: slot=%d\n", slot);
if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used )
{
if (DBG_READER)
log_debug ("leave: apdu_connect => SW_HOST_NO_DRIVER\n");
return -1;
}
/* Only if the access method provides a connect function we use it.
If not, we expect that the card has been implicitly connected by
apdu_open_reader. */
if (reader_table[slot].connect_card)
{
sw = lock_slot (slot);
if (!sw)
{
sw = reader_table[slot].connect_card (slot);
unlock_slot (slot);
}
}
/* We need to call apdu_get_status_internal, so that the last-status
machinery gets setup properly even if a card is inserted while
scdaemon is fired up and apdu_get_status has not yet been called.
Without that we would force a reset of the card with the next
call to apdu_get_status. */
if (!sw)
sw = apdu_get_status_internal (slot, 1, &status, 1);
if (sw)
;
else if (!(status & APDU_CARD_PRESENT))
sw = SW_HOST_NO_CARD;
else if ((status & APDU_CARD_PRESENT) && !(status & APDU_CARD_ACTIVE))
sw = SW_HOST_CARD_INACTIVE;
if (sw == SW_HOST_CARD_INACTIVE)
{
/* Try power it up again. */
sw = apdu_reset (slot);
}
if (DBG_READER)
log_debug ("leave: apdu_connect => sw=0x%x\n", sw);
if (sw)
return -1;
return reader_table[slot].require_get_status;
}
int
apdu_disconnect (int slot)
{
int sw;
if (DBG_READER)
log_debug ("enter: apdu_disconnect: slot=%d\n", slot);
if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used )
{
if (DBG_READER)
log_debug ("leave: apdu_disconnect => SW_HOST_NO_DRIVER\n");
return SW_HOST_NO_DRIVER;
}
if (reader_table[slot].disconnect_card)
{
sw = lock_slot (slot);
if (!sw)
{
sw = reader_table[slot].disconnect_card (slot);
unlock_slot (slot);
}
}
else
sw = 0;
if (DBG_READER)
log_debug ("leave: apdu_disconnect => sw=0x%x\n", sw);
return sw;
}
/* Set the progress callback of SLOT to CB and its args to CB_ARG. If
CB is NULL the progress callback is removed. */
int
apdu_set_progress_cb (int slot, gcry_handler_progress_t cb, void *cb_arg)
{
int sw;
if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used )
return SW_HOST_NO_DRIVER;
if (reader_table[slot].set_progress_cb)
{
sw = lock_slot (slot);
if (!sw)
{
sw = reader_table[slot].set_progress_cb (slot, cb, cb_arg);
unlock_slot (slot);
}
}
else
sw = 0;
return sw;
}
int
apdu_set_prompt_cb (int slot, void (*cb) (void *, int), void *cb_arg)
{
int sw;
if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used )
return SW_HOST_NO_DRIVER;
if (reader_table[slot].set_prompt_cb)
{
sw = lock_slot (slot);
if (!sw)
{
sw = reader_table[slot].set_prompt_cb (slot, cb, cb_arg);
unlock_slot (slot);
}
}
else
sw = 0;
return sw;
}
/* Do a reset for the card in reader at SLOT. */
int
apdu_reset (int slot)
{
int sw;
if (DBG_READER)
log_debug ("enter: apdu_reset: slot=%d\n", slot);
if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used )
{
if (DBG_READER)
log_debug ("leave: apdu_reset => SW_HOST_NO_DRIVER\n");
return SW_HOST_NO_DRIVER;
}
if ((sw = lock_slot (slot)))
{
if (DBG_READER)
log_debug ("leave: apdu_reset => sw=0x%x (lock_slot)\n", sw);
return sw;
}
if (reader_table[slot].reset_reader)
sw = reader_table[slot].reset_reader (slot);
unlock_slot (slot);
if (DBG_READER)
log_debug ("leave: apdu_reset => sw=0x%x\n", sw);
return sw;
}
/* Return the ATR or NULL if none is available. On success the length
of the ATR is stored at ATRLEN. The caller must free the returned
value. */
unsigned char *
apdu_get_atr (int slot, size_t *atrlen)
{
unsigned char *buf;
if (DBG_READER)
log_debug ("enter: apdu_get_atr: slot=%d\n", slot);
if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used )
{
if (DBG_READER)
log_debug ("leave: apdu_get_atr => NULL (bad slot)\n");
return NULL;
}
if (!reader_table[slot].atrlen)
{
if (DBG_READER)
log_debug ("leave: apdu_get_atr => NULL (no ATR)\n");
return NULL;
}
buf = xtrymalloc (reader_table[slot].atrlen);
if (!buf)
{
if (DBG_READER)
log_debug ("leave: apdu_get_atr => NULL (out of core)\n");
return NULL;
}
memcpy (buf, reader_table[slot].atr, reader_table[slot].atrlen);
*atrlen = reader_table[slot].atrlen;
if (DBG_READER)
log_debug ("leave: apdu_get_atr => atrlen=%zu\n", *atrlen);
return buf;
}
/* Retrieve the status for SLOT. The function does only wait for the
card to become available if HANG is set to true. On success the
bits in STATUS will be set to
APDU_CARD_USABLE (bit 0) = card present and usable
APDU_CARD_PRESENT (bit 1) = card present
APDU_CARD_ACTIVE (bit 2) = card active
(bit 3) = card access locked [not yet implemented]
For most applications, testing bit 0 is sufficient.
*/
static int
apdu_get_status_internal (int slot, int hang, unsigned int *status, int on_wire)
{
int sw;
unsigned int s = 0;
if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used )
return SW_HOST_NO_DRIVER;
if ((sw = hang? lock_slot (slot) : trylock_slot (slot)))
return sw;
if (reader_table[slot].get_status_reader)
sw = reader_table[slot].get_status_reader (slot, &s, on_wire);
unlock_slot (slot);
if (sw)
{
if (on_wire)
reader_table[slot].atrlen = 0;
s = 0;
}
if (status)
*status = s;
return sw;
}
/* See above for a description. */
int
apdu_get_status (int slot, int hang, unsigned int *status)
{
int sw;
if (DBG_READER)
log_debug ("enter: apdu_get_status: slot=%d hang=%d\n", slot, hang);
sw = apdu_get_status_internal (slot, hang, status, 0);
if (DBG_READER)
{
if (status)
log_debug ("leave: apdu_get_status => sw=0x%x status=%u\n",
sw, *status);
else
log_debug ("leave: apdu_get_status => sw=0x%x\n", sw);
}
return sw;
}
/* Check whether the reader supports the ISO command code COMMAND on
the pinpad. Return 0 on success. For a description of the pin
parameters, see ccid-driver.c */
int
apdu_check_pinpad (int slot, int command, pininfo_t *pininfo)
{
if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used )
return SW_HOST_NO_DRIVER;
if (opt.enable_pinpad_varlen)
pininfo->fixedlen = 0;
if (reader_table[slot].check_pinpad)
{
int sw;
if ((sw = lock_slot (slot)))
return sw;
sw = reader_table[slot].check_pinpad (slot, command, pininfo);
unlock_slot (slot);
return sw;
}
else
return SW_HOST_NOT_SUPPORTED;
}
int
apdu_pinpad_verify (int slot, int class, int ins, int p0, int p1,
pininfo_t *pininfo)
{
if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used )
return SW_HOST_NO_DRIVER;
if (reader_table[slot].pinpad_verify)
{
int sw;
if ((sw = lock_slot (slot)))
return sw;
sw = reader_table[slot].pinpad_verify (slot, class, ins, p0, p1,
pininfo);
unlock_slot (slot);
return sw;
}
else
return SW_HOST_NOT_SUPPORTED;
}
int
apdu_pinpad_modify (int slot, int class, int ins, int p0, int p1,
pininfo_t *pininfo)
{
if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used )
return SW_HOST_NO_DRIVER;
if (reader_table[slot].pinpad_modify)
{
int sw;
if ((sw = lock_slot (slot)))
return sw;
sw = reader_table[slot].pinpad_modify (slot, class, ins, p0, p1,
pininfo);
unlock_slot (slot);
return sw;
}
else
return SW_HOST_NOT_SUPPORTED;
}
/* Dispatcher for the actual send_apdu function. Note, that this
function should be called in locked state. */
static int
send_apdu (int slot, unsigned char *apdu, size_t apdulen,
unsigned char *buffer, size_t *buflen, pininfo_t *pininfo)
{
if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used )
return SW_HOST_NO_DRIVER;
if (reader_table[slot].send_apdu_reader)
return reader_table[slot].send_apdu_reader (slot,
apdu, apdulen,
buffer, buflen,
pininfo);
else
return SW_HOST_NOT_SUPPORTED;
}
/* Core APDU transceiver function. Parameters are described at
apdu_send_le with the exception of PININFO which indicates pinpad
related operations if not NULL. If EXTENDED_MODE is not 0
command chaining or extended length will be used according to these
values:
n < 0 := Use command chaining with the data part limited to -n
in each chunk. If -1 is used a default value is used.
n == 0 := No extended mode or command chaining.
n == 1 := Use extended length for input and output without a
length limit.
n > 1 := Use extended length with up to N bytes.
*/
static int
send_le (int slot, int class, int ins, int p0, int p1,
int lc, const char *data, int le,
unsigned char **retbuf, size_t *retbuflen,
pininfo_t *pininfo, int extended_mode)
{
#define SHORT_RESULT_BUFFER_SIZE 258
/* We allocate 8 extra bytes as a safety margin towards a driver bug. */
unsigned char short_result_buffer[SHORT_RESULT_BUFFER_SIZE+10];
unsigned char *result_buffer = NULL;
size_t result_buffer_size;
unsigned char *result;
size_t resultlen;
unsigned char short_apdu_buffer[5+256+1];
unsigned char *apdu_buffer = NULL;
size_t apdu_buffer_size;
unsigned char *apdu;
size_t apdulen;
int sw;
long rc; /* We need a long here due to PC/SC. */
int did_exact_length_hack = 0;
int use_chaining = 0;
int use_extended_length = 0;
int lc_chunk;
if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used )
return SW_HOST_NO_DRIVER;
if (DBG_CARD_IO)
log_debug ("send apdu: c=%02X i=%02X p1=%02X p2=%02X lc=%d le=%d em=%d\n",
class, ins, p0, p1, lc, le, extended_mode);
if (lc != -1 && (lc > 255 || lc < 0))
{
/* Data does not fit into an APDU. What we do now depends on
the EXTENDED_MODE parameter. */
if (!extended_mode)
return SW_WRONG_LENGTH; /* No way to send such an APDU. */
else if (extended_mode > 0)
use_extended_length = 1;
else if (extended_mode < 0)
{
/* Send APDU using chaining mode. */
if (lc > 16384)
return SW_WRONG_LENGTH; /* Sanity check. */
if ((class&0xf0) != 0)
return SW_HOST_INV_VALUE; /* Upper 4 bits need to be 0. */
use_chaining = extended_mode == -1? 255 : -extended_mode;
use_chaining &= 0xff;
}
else
return SW_HOST_INV_VALUE;
}
else if (lc == -1 && extended_mode > 0)
use_extended_length = 1;
if (le != -1 && (le > (extended_mode > 0? 255:256) || le < 0))
{
/* Expected Data does not fit into an APDU. What we do now
depends on the EXTENDED_MODE parameter. Note that a check
for command chaining does not make sense because we are
looking at Le. */
if (!extended_mode)
return SW_WRONG_LENGTH; /* No way to send such an APDU. */
else if (use_extended_length)
; /* We are already using extended length. */
else if (extended_mode > 0)
use_extended_length = 1;
else
return SW_HOST_INV_VALUE;
}
if ((!data && lc != -1) || (data && lc == -1))
return SW_HOST_INV_VALUE;
if (use_extended_length)
{
if (reader_table[slot].is_t0)
return SW_HOST_NOT_SUPPORTED;
/* Space for: cls/ins/p1/p2+Z+2_byte_Lc+Lc+2_byte_Le. */
apdu_buffer_size = 4 + 1 + (lc >= 0? (2+lc):0) + 2;
apdu_buffer = xtrymalloc (apdu_buffer_size + 10);
if (!apdu_buffer)
return SW_HOST_OUT_OF_CORE;
apdu = apdu_buffer;
}
else
{
apdu_buffer_size = sizeof short_apdu_buffer;
apdu = short_apdu_buffer;
}
if (use_extended_length && (le > 256 || le < 0))
{
/* Two more bytes are needed for status bytes. */
result_buffer_size = le < 0? 4096 : (le + 2);
result_buffer = xtrymalloc (result_buffer_size);
if (!result_buffer)
{
xfree (apdu_buffer);
return SW_HOST_OUT_OF_CORE;
}
result = result_buffer;
}
else
{
result_buffer_size = SHORT_RESULT_BUFFER_SIZE;
result = short_result_buffer;
}
#undef SHORT_RESULT_BUFFER_SIZE
if ((sw = lock_slot (slot)))
{
xfree (apdu_buffer);
xfree (result_buffer);
return sw;
}
do
{
if (use_extended_length)
{
use_chaining = 0;
apdulen = 0;
apdu[apdulen++] = class;
apdu[apdulen++] = ins;
apdu[apdulen++] = p0;
apdu[apdulen++] = p1;
if (lc > 0)
{
apdu[apdulen++] = 0; /* Z byte: Extended length marker. */
apdu[apdulen++] = ((lc >> 8) & 0xff);
apdu[apdulen++] = (lc & 0xff);
memcpy (apdu+apdulen, data, lc);
data += lc;
apdulen += lc;
}
if (le != -1)
{
if (lc <= 0)
apdu[apdulen++] = 0; /* Z byte: Extended length marker. */
apdu[apdulen++] = ((le >> 8) & 0xff);
apdu[apdulen++] = (le & 0xff);
}
}
else
{
apdulen = 0;
apdu[apdulen] = class;
if (use_chaining && lc > 255)
{
apdu[apdulen] |= 0x10;
assert (use_chaining < 256);
lc_chunk = use_chaining;
lc -= use_chaining;
}
else
{
use_chaining = 0;
lc_chunk = lc;
}
apdulen++;
apdu[apdulen++] = ins;
apdu[apdulen++] = p0;
apdu[apdulen++] = p1;
if (lc_chunk != -1)
{
apdu[apdulen++] = lc_chunk;
memcpy (apdu+apdulen, data, lc_chunk);
data += lc_chunk;
apdulen += lc_chunk;
/* T=0 does not allow the use of Lc together with Le;
thus disable Le in this case. */
if (reader_table[slot].is_t0)
le = -1;
}
if (le != -1 && !use_chaining)
apdu[apdulen++] = le; /* Truncation is okay (0 means 256). */
}
exact_length_hack:
/* As a safeguard don't pass any garbage to the driver. */
assert (apdulen <= apdu_buffer_size);
memset (apdu+apdulen, 0, apdu_buffer_size - apdulen);
resultlen = result_buffer_size;
rc = send_apdu (slot, apdu, apdulen, result, &resultlen, pininfo);
if (rc || resultlen < 2)
{
log_info ("apdu_send_simple(%d) failed: %s\n",
slot, apdu_strerror (rc));
unlock_slot (slot);
xfree (apdu_buffer);
xfree (result_buffer);
return rc? rc : SW_HOST_INCOMPLETE_CARD_RESPONSE;
}
sw = (result[resultlen-2] << 8) | result[resultlen-1];
if (!use_extended_length
&& !did_exact_length_hack && SW_EXACT_LENGTH_P (sw))
{
apdu[apdulen-1] = (sw & 0x00ff);
did_exact_length_hack = 1;
goto exact_length_hack;
}
}
while (use_chaining && sw == SW_SUCCESS);
if (apdu_buffer)
{
xfree (apdu_buffer);
apdu_buffer = NULL;
}
/* Store away the returned data but strip the statusword. */
resultlen -= 2;
if (DBG_CARD_IO)
{
log_debug (" response: sw=%04X datalen=%d\n",
sw, (unsigned int)resultlen);
if ( !retbuf && (sw == SW_SUCCESS || (sw & 0xff00) == SW_MORE_DATA))
log_printhex (result, resultlen, " dump: ");
}
if (sw == SW_SUCCESS || sw == SW_EOF_REACHED)
{
if (retbuf)
{
*retbuf = xtrymalloc (resultlen? resultlen : 1);
if (!*retbuf)
{
unlock_slot (slot);
xfree (result_buffer);
return SW_HOST_OUT_OF_CORE;
}
*retbuflen = resultlen;
memcpy (*retbuf, result, resultlen);
}
}
else if ((sw & 0xff00) == SW_MORE_DATA)
{
unsigned char *p = NULL, *tmp;
size_t bufsize = 4096;
/* It is likely that we need to return much more data, so we
start off with a large buffer. */
if (retbuf)
{
*retbuf = p = xtrymalloc (bufsize);
if (!*retbuf)
{
unlock_slot (slot);
xfree (result_buffer);
return SW_HOST_OUT_OF_CORE;
}
assert (resultlen < bufsize);
memcpy (p, result, resultlen);
p += resultlen;
}
do
{
int len = (sw & 0x00ff);
if (DBG_CARD_IO)
log_debug ("apdu_send_simple(%d): %d more bytes available\n",
slot, len);
apdu_buffer_size = sizeof short_apdu_buffer;
apdu = short_apdu_buffer;
apdulen = 0;
apdu[apdulen++] = class;
apdu[apdulen++] = 0xC0;
apdu[apdulen++] = 0;
apdu[apdulen++] = 0;
apdu[apdulen++] = len;
assert (apdulen <= apdu_buffer_size);
memset (apdu+apdulen, 0, apdu_buffer_size - apdulen);
resultlen = result_buffer_size;
rc = send_apdu (slot, apdu, apdulen, result, &resultlen, NULL);
if (rc || resultlen < 2)
{
log_error ("apdu_send_simple(%d) for get response failed: %s\n",
slot, apdu_strerror (rc));
unlock_slot (slot);
xfree (result_buffer);
return rc? rc : SW_HOST_INCOMPLETE_CARD_RESPONSE;
}
sw = (result[resultlen-2] << 8) | result[resultlen-1];
resultlen -= 2;
if (DBG_CARD_IO)
{
log_debug (" more: sw=%04X datalen=%d\n",
sw, (unsigned int)resultlen);
if (!retbuf && (sw==SW_SUCCESS || (sw&0xff00)==SW_MORE_DATA))
log_printhex (result, resultlen, " dump: ");
}
if ((sw & 0xff00) == SW_MORE_DATA
|| sw == SW_SUCCESS
|| sw == SW_EOF_REACHED )
{
if (retbuf && resultlen)
{
if (p - *retbuf + resultlen > bufsize)
{
bufsize += resultlen > 4096? resultlen: 4096;
tmp = xtryrealloc (*retbuf, bufsize);
if (!tmp)
{
unlock_slot (slot);
xfree (result_buffer);
return SW_HOST_OUT_OF_CORE;
}
p = tmp + (p - *retbuf);
*retbuf = tmp;
}
memcpy (p, result, resultlen);
p += resultlen;
}
}
else
log_info ("apdu_send_simple(%d) "
"got unexpected status %04X from get response\n",
slot, sw);
}
while ((sw & 0xff00) == SW_MORE_DATA);
if (retbuf)
{
*retbuflen = p - *retbuf;
tmp = xtryrealloc (*retbuf, *retbuflen);
if (tmp)
*retbuf = tmp;
}
}
unlock_slot (slot);
xfree (result_buffer);
if (DBG_CARD_IO && retbuf && sw == SW_SUCCESS)
log_printhex (*retbuf, *retbuflen, " dump: ");
return sw;
}
/* Send an APDU to the card in SLOT. The APDU is created from all
given parameters: CLASS, INS, P0, P1, LC, DATA, LE. A value of -1
for LC won't sent this field and the data field; in this case DATA
must also be passed as NULL. If EXTENDED_MODE is not 0 command
chaining or extended length will be used; see send_le for details.
The return value is the status word or -1 for an invalid SLOT or
other non card related error. If RETBUF is not NULL, it will
receive an allocated buffer with the returned data. The length of
that data will be put into *RETBUFLEN. The caller is responsible
for releasing the buffer even in case of errors. */
int
apdu_send_le(int slot, int extended_mode,
int class, int ins, int p0, int p1,
int lc, const char *data, int le,
unsigned char **retbuf, size_t *retbuflen)
{
return send_le (slot, class, ins, p0, p1,
lc, data, le,
retbuf, retbuflen,
NULL, extended_mode);
}
/* Send an APDU to the card in SLOT. The APDU is created from all
given parameters: CLASS, INS, P0, P1, LC, DATA. A value of -1 for
LC won't sent this field and the data field; in this case DATA must
also be passed as NULL. If EXTENDED_MODE is not 0 command chaining
or extended length will be used; see send_le for details. The
return value is the status word or -1 for an invalid SLOT or other
non card related error. If RETBUF is not NULL, it will receive an
allocated buffer with the returned data. The length of that data
will be put into *RETBUFLEN. The caller is responsible for
releasing the buffer even in case of errors. */
int
apdu_send (int slot, int extended_mode,
int class, int ins, int p0, int p1,
int lc, const char *data, unsigned char **retbuf, size_t *retbuflen)
{
return send_le (slot, class, ins, p0, p1, lc, data, 256,
retbuf, retbuflen, NULL, extended_mode);
}
/* Send an APDU to the card in SLOT. The APDU is created from all
given parameters: CLASS, INS, P0, P1, LC, DATA. A value of -1 for
LC won't sent this field and the data field; in this case DATA must
also be passed as NULL. If EXTENDED_MODE is not 0 command chaining
or extended length will be used; see send_le for details. The
return value is the status word or -1 for an invalid SLOT or other
non card related error. No data will be returned. */
int
apdu_send_simple (int slot, int extended_mode,
int class, int ins, int p0, int p1,
int lc, const char *data)
{
return send_le (slot, class, ins, p0, p1, lc, data, -1, NULL, NULL, NULL,
extended_mode);
}
/* This is a more generic version of the apdu sending routine. It
* takes an already formatted APDU in APDUDATA or length APDUDATALEN
* and returns with an APDU including the status word. With
* HANDLE_MORE set to true this function will handle the MORE DATA
* status and return all APDUs concatenated with one status word at
* the end. If EXTENDED_LENGTH is != 0 extended lengths are allowed
* with a max. result data length of EXTENDED_LENGTH bytes. The
* function does not return a regular status word but 0 on success.
* If the slot is locked, the function returns immediately with an
* error.
*
* Out of historical reasons the function returns 0 on success and
* outs the status word at the end of the result to be able to get the
* status word in the case of a not provided RETBUF, R_SW can be used
* to store the SW. But note that R_SW qill only be set if the
* function returns 0. */
int
apdu_send_direct (int slot, size_t extended_length,
const unsigned char *apdudata, size_t apdudatalen,
int handle_more, unsigned int *r_sw,
unsigned char **retbuf, size_t *retbuflen)
{
#define SHORT_RESULT_BUFFER_SIZE 258
unsigned char short_result_buffer[SHORT_RESULT_BUFFER_SIZE+10];
unsigned char *result_buffer = NULL;
size_t result_buffer_size;
unsigned char *result;
size_t resultlen;
unsigned char short_apdu_buffer[5+256+10];
unsigned char *apdu_buffer = NULL;
unsigned char *apdu;
size_t apdulen;
int sw;
long rc; /* we need a long here due to PC/SC. */
int class;
if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used )
return SW_HOST_NO_DRIVER;
if (apdudatalen > 65535)
return SW_HOST_INV_VALUE;
if (apdudatalen > sizeof short_apdu_buffer - 5)
{
apdu_buffer = xtrymalloc (apdudatalen + 5);
if (!apdu_buffer)
return SW_HOST_OUT_OF_CORE;
apdu = apdu_buffer;
}
else
{
apdu = short_apdu_buffer;
}
apdulen = apdudatalen;
memcpy (apdu, apdudata, apdudatalen);
class = apdulen? *apdu : 0;
if (extended_length >= 256 && extended_length <= 65536)
{
result_buffer_size = extended_length;
result_buffer = xtrymalloc (result_buffer_size + 10);
if (!result_buffer)
{
xfree (apdu_buffer);
return SW_HOST_OUT_OF_CORE;
}
result = result_buffer;
}
else
{
result_buffer_size = SHORT_RESULT_BUFFER_SIZE;
result = short_result_buffer;
}
#undef SHORT_RESULT_BUFFER_SIZE
if ((sw = trylock_slot (slot)))
{
xfree (apdu_buffer);
xfree (result_buffer);
return sw;
}
resultlen = result_buffer_size;
rc = send_apdu (slot, apdu, apdulen, result, &resultlen, NULL);
xfree (apdu_buffer);
apdu_buffer = NULL;
if (rc || resultlen < 2)
{
log_error ("apdu_send_direct(%d) failed: %s\n",
slot, apdu_strerror (rc));
unlock_slot (slot);
xfree (result_buffer);
return rc? rc : SW_HOST_INCOMPLETE_CARD_RESPONSE;
}
sw = (result[resultlen-2] << 8) | result[resultlen-1];
/* Store away the returned data but strip the statusword. */
resultlen -= 2;
if (DBG_CARD_IO)
{
log_debug (" response: sw=%04X datalen=%d\n",
sw, (unsigned int)resultlen);
if ( !retbuf && (sw == SW_SUCCESS || (sw & 0xff00) == SW_MORE_DATA))
log_printhex (result, resultlen, " dump: ");
}
if (handle_more && (sw & 0xff00) == SW_MORE_DATA)
{
unsigned char *p = NULL, *tmp;
size_t bufsize = 4096;
/* It is likely that we need to return much more data, so we
start off with a large buffer. */
if (retbuf)
{
*retbuf = p = xtrymalloc (bufsize + 2);
if (!*retbuf)
{
unlock_slot (slot);
xfree (result_buffer);
return SW_HOST_OUT_OF_CORE;
}
assert (resultlen < bufsize);
memcpy (p, result, resultlen);
p += resultlen;
}
do
{
int len = (sw & 0x00ff);
if (DBG_CARD_IO)
log_debug ("apdu_send_direct(%d): %d more bytes available\n",
slot, len);
apdu = short_apdu_buffer;
apdulen = 0;
apdu[apdulen++] = class;
apdu[apdulen++] = 0xC0;
apdu[apdulen++] = 0;
apdu[apdulen++] = 0;
apdu[apdulen++] = len;
memset (apdu+apdulen, 0, sizeof (short_apdu_buffer) - apdulen);
resultlen = result_buffer_size;
rc = send_apdu (slot, apdu, apdulen, result, &resultlen, NULL);
if (rc || resultlen < 2)
{
log_error ("apdu_send_direct(%d) for get response failed: %s\n",
slot, apdu_strerror (rc));
unlock_slot (slot);
xfree (result_buffer);
return rc ? rc : SW_HOST_INCOMPLETE_CARD_RESPONSE;
}
sw = (result[resultlen-2] << 8) | result[resultlen-1];
resultlen -= 2;
if (DBG_CARD_IO)
{
log_debug (" more: sw=%04X datalen=%d\n",
sw, (unsigned int)resultlen);
if (!retbuf && (sw==SW_SUCCESS || (sw&0xff00)==SW_MORE_DATA))
log_printhex (result, resultlen, " dump: ");
}
if ((sw & 0xff00) == SW_MORE_DATA
|| sw == SW_SUCCESS
|| sw == SW_EOF_REACHED )
{
if (retbuf && resultlen)
{
if (p - *retbuf + resultlen > bufsize)
{
bufsize += resultlen > 4096? resultlen: 4096;
tmp = xtryrealloc (*retbuf, bufsize + 2);
if (!tmp)
{
unlock_slot (slot);
xfree (result_buffer);
return SW_HOST_OUT_OF_CORE;
}
p = tmp + (p - *retbuf);
*retbuf = tmp;
}
memcpy (p, result, resultlen);
p += resultlen;
}
}
else
log_info ("apdu_send_direct(%d) "
"got unexpected status %04X from get response\n",
slot, sw);
}
while ((sw & 0xff00) == SW_MORE_DATA);
if (retbuf)
{
*retbuflen = p - *retbuf;
tmp = xtryrealloc (*retbuf, *retbuflen + 2);
if (tmp)
*retbuf = tmp;
}
}
else
{
if (retbuf)
{
*retbuf = xtrymalloc ((resultlen? resultlen : 1)+2);
if (!*retbuf)
{
unlock_slot (slot);
xfree (result_buffer);
return SW_HOST_OUT_OF_CORE;
}
*retbuflen = resultlen;
memcpy (*retbuf, result, resultlen);
}
}
unlock_slot (slot);
xfree (result_buffer);
/* Append the status word. Note that we reserved the two extra
bytes while allocating the buffer. */
if (retbuf)
{
(*retbuf)[(*retbuflen)++] = (sw >> 8);
(*retbuf)[(*retbuflen)++] = sw;
}
if (r_sw)
*r_sw = sw;
if (DBG_CARD_IO && retbuf)
log_printhex (*retbuf, *retbuflen, " dump: ");
return 0;
}
const char *
apdu_get_reader_name (int slot)
{
return reader_table[slot].rdrname;
}
gpg_error_t
apdu_init (void)
{
#ifdef USE_NPTH
gpg_error_t err;
int i;
if (npth_mutex_init (&reader_table_lock, NULL))
goto leave;
for (i = 0; i < MAX_READER; i++)
if (npth_mutex_init (&reader_table[i].lock, NULL))
goto leave;
/* All done well. */
return 0;
leave:
err = gpg_error_from_syserror ();
log_error ("apdu: error initializing mutex: %s\n", gpg_strerror (err));
return err;
#endif /*USE_NPTH*/
return 0;
}
diff --git a/scd/app-common.h b/scd/app-common.h
index 3df896228..5866c9b32 100644
--- a/scd/app-common.h
+++ b/scd/app-common.h
@@ -1,224 +1,325 @@
/* app-common.h - Common declarations for all card applications
* Copyright (C) 2003, 2005, 2008 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*
* $Id$
*/
#ifndef GNUPG_SCD_APP_COMMON_H
#define GNUPG_SCD_APP_COMMON_H
#include <npth.h>
#include <ksba.h>
/* Flags used with app_change_pin. */
#define APP_CHANGE_FLAG_RESET 1 /* PIN Reset mode. */
#define APP_CHANGE_FLAG_NULLPIN 2 /* NULL PIN mode. */
#define APP_CHANGE_FLAG_CLEAR 4 /* Clear the given PIN. */
/* Flags used with app_genkey. */
#define APP_GENKEY_FLAG_FORCE 1 /* Force overwriting existing key. */
/* Flags used with app_writekey. */
#define APP_WRITEKEY_FLAG_FORCE 1 /* Force overwriting existing key. */
+/* Flags used with app_readkey. */
+#define APP_READKEY_FLAG_INFO 1 /* Send also a KEYPAIRINFO line. */
+
/* Bit flags set by the decipher function into R_INFO. */
#define APP_DECIPHER_INFO_NOPAD 1 /* Padding has been removed. */
+/* List of supported card types. Generic is the usual ISO7817-4
+ * compliant card. More specific card or token versions can be given
+ * here. Use strcardtype() to map them to a string. */
+typedef enum
+ {
+ CARDTYPE_GENERIC = 0,
+ CARDTYPE_YUBIKEY
+
+ } cardtype_t;
+
+/* List of supported card applications. The source code for each
+ * application can usually be found in an app-NAME.c file. Use
+ * strapptype() to map them to a string. */
+typedef enum
+ {
+ APPTYPE_NONE = 0,
+ APPTYPE_UNDEFINED,
+ APPTYPE_OPENPGP,
+ APPTYPE_PIV,
+ APPTYPE_NKS,
+ APPTYPE_P15,
+ APPTYPE_GELDKARTE,
+ APPTYPE_DINSIG,
+ APPTYPE_SC_HSM
+ } apptype_t;
+
+
+/* Forward declarations. */
+struct card_ctx_s;
+struct app_ctx_s;
struct app_local_s; /* Defined by all app-*.c. */
-struct app_ctx_s {
- struct app_ctx_s *next;
+
+typedef struct card_ctx_s *card_t;
+typedef struct app_ctx_s *app_t;
+
+/* The object describing a card. */
+struct card_ctx_s {
+ card_t next;
npth_mutex_t lock;
- /* Number of connections currently using this application context.
- If this is not 0 the application has been initialized and the
- function pointers may be used. Note that for unsupported
- operations the particular function pointer is set to NULL */
+ /* Number of connections currently using this application context. */
unsigned int ref_count;
/* Used reader slot. */
int slot;
- unsigned char *serialno; /* Serialnumber in raw form, allocated. */
- size_t serialnolen; /* Length in octets of serialnumber. */
- const char *cardtype; /* NULL or string with the token's type. */
- const char *apptype;
+ cardtype_t cardtype; /* The token's type. */
unsigned int cardversion;/* Firmware version of the token or 0. */
- unsigned int appversion; /* Version of the application or 0. */
+
unsigned int card_status;
+
+ /* The serial number is associated with the card and not with a
+ * specific app. If a card uses different serial numbers for its
+ * applications, our code picks the serial number of a specific
+ * application and uses that. */
+ unsigned char *serialno; /* Serialnumber in raw form, allocated. */
+ size_t serialnolen; /* Length in octets of serialnumber. */
+
+ /* A linked list of applications used on this card. The app at the
+ * head of the list is the currently active app; To work with the
+ * other apps, switching to that app might be needed. Switching will
+ * put the active app at the head of the list. */
+ app_t app;
+
+ /* Various flags. */
unsigned int reset_requested:1;
unsigned int periodical_check_needed:1;
+};
+
+
+/* The object describing a card's applications. A card may have
+ * several applications and it is usuallay required to explicity
+ * switch between applications. */
+struct app_ctx_s {
+ app_t next;
+
+ card_t card; /* Link back to the card. */
+
+ apptype_t apptype; /* The type of the application. */
+ unsigned int appversion; /* Version of the application or 0. */
unsigned int did_chv1:1;
unsigned int force_chv1:1; /* True if the card does not cache CHV1. */
unsigned int did_chv2:1;
unsigned int did_chv3:1;
struct app_local_s *app_local; /* Local to the application. */
struct {
void (*deinit) (app_t app);
+ gpg_error_t (*reselect) (app_t app, ctrl_t ctrl);
gpg_error_t (*learn_status) (app_t app, ctrl_t ctrl, unsigned int flags);
gpg_error_t (*readcert) (app_t app, const char *certid,
unsigned char **cert, size_t *certlen);
- gpg_error_t (*readkey) (app_t app, const char *certid,
- unsigned char **pk, size_t *pklen);
+ gpg_error_t (*readkey) (app_t app, ctrl_t ctrl,
+ const char *certid, unsigned int flags,
+ unsigned char **pk, size_t *pklen);
gpg_error_t (*getattr) (app_t app, ctrl_t ctrl, const char *name);
gpg_error_t (*setattr) (app_t app, const char *name,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const unsigned char *value, size_t valuelen);
gpg_error_t (*sign) (app_t app,
const char *keyidstr, int hashalgo,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const void *indata, size_t indatalen,
unsigned char **outdata, size_t *outdatalen );
gpg_error_t (*auth) (app_t app, const char *keyidstr,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const void *indata, size_t indatalen,
unsigned char **outdata, size_t *outdatalen);
gpg_error_t (*decipher) (app_t app, const char *keyidstr,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const void *indata, size_t indatalen,
unsigned char **outdata, size_t *outdatalen,
unsigned int *r_info);
gpg_error_t (*writecert) (app_t app, ctrl_t ctrl,
const char *certid,
gpg_error_t (*pincb)(void*,const char *,char **),
void *pincb_arg,
const unsigned char *data, size_t datalen);
gpg_error_t (*writekey) (app_t app, ctrl_t ctrl,
const char *keyid, unsigned int flags,
gpg_error_t (*pincb)(void*,const char *,char **),
void *pincb_arg,
const unsigned char *pk, size_t pklen);
gpg_error_t (*genkey) (app_t app, ctrl_t ctrl,
const char *keyref, const char *keytype,
unsigned int flags, time_t createtime,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg);
gpg_error_t (*change_pin) (app_t app, ctrl_t ctrl,
const char *chvnostr, unsigned int flags,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg);
gpg_error_t (*check_pin) (app_t app, const char *keyidstr,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg);
+ gpg_error_t (*with_keygrip) (app_t app, ctrl_t ctrl, int action,
+ const char *keygrip_str);
} fnc;
};
+
+/* Action values for app_do_with_keygrip. */
+enum
+ {
+ KEYGRIP_ACTION_SEND_DATA,
+ KEYGRIP_ACTION_WRITE_STATUS,
+ KEYGRIP_ACTION_LOOKUP
+ };
+
+
+/* Helper to get the slot from an APP object. */
+static inline int
+app_get_slot (app_t app)
+{
+ if (app && app->card)
+ return app->card->slot;
+ return -1;
+}
+
+
/*-- app-help.c --*/
unsigned int app_help_count_bits (const unsigned char *a, size_t len);
+gpg_error_t app_help_get_keygrip_string_pk (const void *pk, size_t pklen,
+ char *hexkeygrip);
gpg_error_t app_help_get_keygrip_string (ksba_cert_t cert, char *hexkeygrip);
gpg_error_t app_help_pubkey_from_cert (const void *cert, size_t certlen,
unsigned char **r_pk, size_t *r_pklen);
size_t app_help_read_length_of_cert (int slot, int fid, size_t *r_certoff);
/*-- app.c --*/
-void app_send_card_list (ctrl_t ctrl);
+const char *strcardtype (cardtype_t t);
+const char *strapptype (apptype_t t);
+
+void app_update_priority_list (const char *arg);
+gpg_error_t app_send_card_list (ctrl_t ctrl);
+char *card_get_serialno (card_t card);
char *app_get_serialno (app_t app);
void app_dump_state (void);
void application_notify_card_reset (int slot);
-gpg_error_t check_application_conflict (const char *name, app_t app);
-gpg_error_t app_reset (app_t app, ctrl_t ctrl, int send_reset);
-gpg_error_t select_application (ctrl_t ctrl, const char *name, app_t *r_app,
+gpg_error_t check_application_conflict (card_t card, const char *name,
+ const unsigned char *serialno_bin,
+ size_t serialno_bin_len);
+gpg_error_t card_reset (card_t card, ctrl_t ctrl, int send_reset);
+gpg_error_t select_application (ctrl_t ctrl, const char *name, card_t *r_app,
int scan, const unsigned char *serialno_bin,
size_t serialno_bin_len);
+gpg_error_t select_additional_application (ctrl_t ctrl, const char *name);
char *get_supported_applications (void);
-void release_application (app_t app, int locked_already);
-gpg_error_t app_munge_serialno (app_t app);
-gpg_error_t app_write_learn_status (app_t app, ctrl_t ctrl,
+
+card_t card_ref (card_t card);
+void card_unref (card_t card);
+void card_unref_locked (card_t card);
+
+gpg_error_t app_munge_serialno (card_t card);
+gpg_error_t app_write_learn_status (card_t card, ctrl_t ctrl,
unsigned int flags);
-gpg_error_t app_readcert (app_t app, ctrl_t ctrl, const char *certid,
+gpg_error_t app_readcert (card_t card, ctrl_t ctrl, const char *certid,
unsigned char **cert, size_t *certlen);
-gpg_error_t app_readkey (app_t app, ctrl_t ctrl,
- const char *keyid, unsigned char **pk, size_t *pklen);
-gpg_error_t app_getattr (app_t app, ctrl_t ctrl, const char *name);
-gpg_error_t app_setattr (app_t app, ctrl_t ctrl, const char *name,
- gpg_error_t (*pincb)(void*, const char *, char **),
- void *pincb_arg,
- const unsigned char *value, size_t valuelen);
-gpg_error_t app_sign (app_t app, ctrl_t ctrl, const char *keyidstr, int hashalgo,
- gpg_error_t (*pincb)(void*, const char *, char **),
- void *pincb_arg,
- const void *indata, size_t indatalen,
- unsigned char **outdata, size_t *outdatalen );
-gpg_error_t app_auth (app_t app, ctrl_t ctrl, const char *keyidstr,
+gpg_error_t app_readkey (card_t card, ctrl_t ctrl,
+ const char *keyid, unsigned int flags,
+ unsigned char **pk, size_t *pklen);
+gpg_error_t app_getattr (card_t card, ctrl_t ctrl, const char *name);
+gpg_error_t app_setattr (card_t card, ctrl_t ctrl, const char *name,
+ gpg_error_t (*pincb)(void*, const char *, char **),
+ void *pincb_arg,
+ const unsigned char *value, size_t valuelen);
+gpg_error_t app_sign (card_t card, ctrl_t ctrl,
+ const char *keyidstr, int hashalgo,
+ gpg_error_t (*pincb)(void*, const char *, char **),
+ void *pincb_arg,
+ const void *indata, size_t indatalen,
+ unsigned char **outdata, size_t *outdatalen);
+gpg_error_t app_auth (card_t card, ctrl_t ctrl, const char *keyidstr,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const void *indata, size_t indatalen,
unsigned char **outdata, size_t *outdatalen);
-gpg_error_t app_decipher (app_t app, ctrl_t ctrl, const char *keyidstr,
+gpg_error_t app_decipher (card_t card, ctrl_t ctrl, const char *keyidstr,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const void *indata, size_t indatalen,
unsigned char **outdata, size_t *outdatalen,
unsigned int *r_info);
-gpg_error_t app_writecert (app_t app, ctrl_t ctrl,
+gpg_error_t app_writecert (card_t card, ctrl_t ctrl,
const char *certidstr,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const unsigned char *keydata, size_t keydatalen);
-gpg_error_t app_writekey (app_t app, ctrl_t ctrl,
+gpg_error_t app_writekey (card_t card, ctrl_t ctrl,
const char *keyidstr, unsigned int flags,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const unsigned char *keydata, size_t keydatalen);
-gpg_error_t app_genkey (app_t app, ctrl_t ctrl,
+gpg_error_t app_genkey (card_t card, ctrl_t ctrl,
const char *keynostr, const char *keytype,
unsigned int flags, time_t createtime,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg);
-gpg_error_t app_get_challenge (app_t app, ctrl_t ctrl, size_t nbytes,
+gpg_error_t app_get_challenge (card_t card, ctrl_t ctrl, size_t nbytes,
unsigned char *buffer);
-gpg_error_t app_change_pin (app_t app, ctrl_t ctrl,
+gpg_error_t app_change_pin (card_t card, ctrl_t ctrl,
const char *chvnostr, unsigned int flags,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg);
-gpg_error_t app_check_pin (app_t app, ctrl_t ctrl, const char *keyidstr,
- gpg_error_t (*pincb)(void*, const char *, char **),
- void *pincb_arg);
+gpg_error_t app_check_pin (card_t card, ctrl_t ctrl, const char *keyidstr,
+ gpg_error_t (*pincb)(void*, const char *, char **),
+ void *pincb_arg);
+card_t app_do_with_keygrip (ctrl_t ctrl, int action, const char *keygrip_str);
/*-- app-openpgp.c --*/
gpg_error_t app_select_openpgp (app_t app);
/*-- app-nks.c --*/
gpg_error_t app_select_nks (app_t app);
/*-- app-dinsig.c --*/
gpg_error_t app_select_dinsig (app_t app);
/*-- app-p15.c --*/
gpg_error_t app_select_p15 (app_t app);
/*-- app-geldkarte.c --*/
gpg_error_t app_select_geldkarte (app_t app);
/*-- app-sc-hsm.c --*/
gpg_error_t app_select_sc_hsm (app_t app);
/*-- app-piv.c --*/
gpg_error_t app_select_piv (app_t app);
#endif /*GNUPG_SCD_APP_COMMON_H*/
diff --git a/scd/app-dinsig.c b/scd/app-dinsig.c
index 983bed6e1..9993b68ff 100644
--- a/scd/app-dinsig.c
+++ b/scd/app-dinsig.c
@@ -1,573 +1,576 @@
/* app-dinsig.c - The DINSIG (DIN V 66291-1) card application.
* Copyright (C) 2002, 2004, 2005, 2007, 2008 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
/* The German signature law and its bylaw (SigG and SigV) is currently
used with an interface specification described in DIN V 66291-1.
The AID to be used is: 'D27600006601'.
The file IDs for certificates utilize the generic format:
Cxyz
C being the hex digit 'C' (12).
x being the service indicator:
'0' := SigG conform digital signature.
'1' := entity authentication.
'2' := key encipherment.
'3' := data encipherment.
'4' := key agreement.
other values are reserved for future use.
y being the security environment number using '0' for cards
not supporting a SE number.
z being the certificate type:
'0' := C.CH (base certificate of card holder) or C.ICC.
'1' .. '7' := C.CH (business or professional certificate
of card holder.
'8' .. 'D' := C.CA (certificate of a CA issue by the Root-CA).
'E' := C.RCA (self certified certificate of the Root-CA).
'F' := reserved.
The file IDs used by default are:
'1F00' EF.SSD (security service descriptor). [o,o]
'2F02' EF.GDO (global data objects) [m,m]
'A000' EF.PROT (signature log). Cyclic file with 20 records of 53 byte.
Read and update after user authentication. [o,o]
'B000' EF.PK.RCA.DS (public keys of Root-CA). Size is 512b or size
of keys. [m (unless a 'C00E' is present),m]
'B001' EF.PK.CA.DS (public keys of CAs). Size is 512b or size
of keys. [o,o]
'C00n' EF.C.CH.DS (digital signature certificate of card holder)
with n := 0 .. 7. Size is 2k or size of cert. Read and
update allowed after user authentication. [m,m]
'C00m' EF.C.CA.DS (digital signature certificate of CA)
with m := 8 .. E. Size is 1k or size of cert. Read always
allowed, update after user authentication. [o,o]
'C100' EF.C.ICC.AUT (AUT certificate of ICC) [o,m]
'C108' EF.C.CA.AUT (AUT certificate of CA) [o,m]
'D000' EF.DM (display message) [-,m]
The letters in brackets indicate optional or mandatory files: The
first for card terminals under full control and the second for
"business" card terminals.
*/
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <time.h>
#include "scdaemon.h"
#include "../common/i18n.h"
#include "iso7816.h"
-#include "app-common.h"
#include "../common/tlv.h"
static gpg_error_t
do_learn_status (app_t app, ctrl_t ctrl, unsigned int flags)
{
gpg_error_t err;
char ct_buf[100], id_buf[100];
char hexkeygrip[41];
size_t len, certoff;
unsigned char *der;
size_t derlen;
ksba_cert_t cert;
int fid;
(void)flags;
/* Return the certificate of the card holder. */
fid = 0xC000;
- len = app_help_read_length_of_cert (app->slot, fid, &certoff);
+ len = app_help_read_length_of_cert (app_get_slot (app), fid, &certoff);
if (!len)
return 0; /* Card has not been personalized. */
sprintf (ct_buf, "%d", 101);
sprintf (id_buf, "DINSIG.%04X", fid);
send_status_info (ctrl, "CERTINFO",
ct_buf, strlen (ct_buf),
id_buf, strlen (id_buf),
NULL, (size_t)0);
/* Now we need to read the certificate, so that we can get the
public key out of it. */
- err = iso7816_read_binary (app->slot, certoff, len-certoff, &der, &derlen);
+ err = iso7816_read_binary (app_get_slot (app), certoff, len-certoff,
+ &der, &derlen);
if (err)
{
log_info ("error reading entire certificate from FID 0x%04X: %s\n",
fid, gpg_strerror (err));
return 0;
}
err = ksba_cert_new (&cert);
if (err)
{
xfree (der);
return err;
}
err = ksba_cert_init_from_mem (cert, der, derlen);
xfree (der); der = NULL;
if (err)
{
log_error ("failed to parse the certificate at FID 0x%04X: %s\n",
fid, gpg_strerror (err));
ksba_cert_release (cert);
return err;
}
err = app_help_get_keygrip_string (cert, hexkeygrip);
if (err)
{
log_error ("failed to calculate the keygrip for FID 0x%04X\n", fid);
ksba_cert_release (cert);
return gpg_error (GPG_ERR_CARD);
}
ksba_cert_release (cert);
sprintf (id_buf, "DINSIG.%04X", fid);
send_status_info (ctrl, "KEYPAIRINFO",
hexkeygrip, 40,
id_buf, strlen (id_buf),
NULL, (size_t)0);
return 0;
}
/* Read the certificate with id CERTID (as returned by learn_status in
the CERTINFO status lines) and return it in the freshly allocated
buffer put into CERT and the length of the certificate put into
CERTLEN.
FIXME: This needs some cleanups and caching with do_learn_status.
*/
static gpg_error_t
do_readcert (app_t app, const char *certid,
unsigned char **cert, size_t *certlen)
{
int fid;
gpg_error_t err;
unsigned char *buffer;
const unsigned char *p;
size_t buflen, n;
int class, tag, constructed, ndef;
size_t totobjlen, objlen, hdrlen;
int rootca = 0;
*cert = NULL;
*certlen = 0;
if (strncmp (certid, "DINSIG.", 7) )
return gpg_error (GPG_ERR_INV_ID);
certid += 7;
if (!hexdigitp (certid) || !hexdigitp (certid+1)
|| !hexdigitp (certid+2) || !hexdigitp (certid+3)
|| certid[4])
return gpg_error (GPG_ERR_INV_ID);
fid = xtoi_4 (certid);
if (fid != 0xC000 )
return gpg_error (GPG_ERR_NOT_FOUND);
/* Read the entire file. fixme: This could be optimized by first
reading the header to figure out how long the certificate
actually is. */
- err = iso7816_select_file (app->slot, fid, 0);
+ err = iso7816_select_file (app_get_slot (app), fid, 0);
if (err)
{
log_error ("error selecting FID 0x%04X: %s\n", fid, gpg_strerror (err));
return err;
}
- err = iso7816_read_binary (app->slot, 0, 0, &buffer, &buflen);
+ err = iso7816_read_binary (app_get_slot (app), 0, 0, &buffer, &buflen);
if (err)
{
log_error ("error reading certificate from FID 0x%04X: %s\n",
fid, gpg_strerror (err));
return err;
}
if (!buflen || *buffer == 0xff)
{
log_info ("no certificate contained in FID 0x%04X\n", fid);
err = gpg_error (GPG_ERR_NOT_FOUND);
goto leave;
}
/* Now figure something out about the object. */
p = buffer;
n = buflen;
err = parse_ber_header (&p, &n, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (err)
goto leave;
if ( class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE && constructed )
;
else if ( class == CLASS_UNIVERSAL && tag == TAG_SET && constructed )
rootca = 1;
else
return gpg_error (GPG_ERR_INV_OBJ);
totobjlen = objlen + hdrlen;
assert (totobjlen <= buflen);
err = parse_ber_header (&p, &n, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (err)
goto leave;
if (rootca)
;
else if (class == CLASS_UNIVERSAL && tag == TAG_OBJECT_ID && !constructed)
{
const unsigned char *save_p;
/* The certificate seems to be contained in a userCertificate
container. Skip this and assume the following sequence is
the certificate. */
if (n < objlen)
{
err = gpg_error (GPG_ERR_INV_OBJ);
goto leave;
}
p += objlen;
n -= objlen;
save_p = p;
err = parse_ber_header (&p, &n, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (err)
goto leave;
if ( !(class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE && constructed) )
return gpg_error (GPG_ERR_INV_OBJ);
totobjlen = objlen + hdrlen;
assert (save_p + totobjlen <= buffer + buflen);
memmove (buffer, save_p, totobjlen);
}
*cert = buffer;
buffer = NULL;
*certlen = totobjlen;
leave:
xfree (buffer);
return err;
}
/* Verify the PIN if required. */
static gpg_error_t
verify_pin (app_t app,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg)
{
const char *s;
int rc;
pininfo_t pininfo;
if ( app->did_chv1 && !app->force_chv1 )
return 0; /* No need to verify it again. */
memset (&pininfo, 0, sizeof pininfo);
pininfo.fixedlen = -1;
pininfo.minlen = 6;
pininfo.maxlen = 8;
if (!opt.disable_pinpad
- && !iso7816_check_pinpad (app->slot, ISO7816_VERIFY, &pininfo) )
+ && !iso7816_check_pinpad (app_get_slot (app), ISO7816_VERIFY, &pininfo) )
{
rc = pincb (pincb_arg,
_("||Please enter your PIN at the reader's pinpad"),
NULL);
if (rc)
{
log_info (_("PIN callback returned error: %s\n"),
gpg_strerror (rc));
return rc;
}
- rc = iso7816_verify_kp (app->slot, 0x81, &pininfo);
+ rc = iso7816_verify_kp (app_get_slot (app), 0x81, &pininfo);
/* Dismiss the prompt. */
pincb (pincb_arg, NULL, NULL);
}
else /* No Pinpad. */
{
char *pinvalue;
rc = pincb (pincb_arg, "PIN", &pinvalue);
if (rc)
{
log_info ("PIN callback returned error: %s\n", gpg_strerror (rc));
return rc;
}
/* We require the PIN to be at least 6 and at max 8 bytes.
According to the specs, this should all be ASCII. */
for (s=pinvalue; digitp (s); s++)
;
if (*s)
{
log_error ("Non-numeric digits found in PIN\n");
xfree (pinvalue);
return gpg_error (GPG_ERR_BAD_PIN);
}
if (strlen (pinvalue) < pininfo.minlen)
{
log_error ("PIN is too short; minimum length is %d\n",
pininfo.minlen);
xfree (pinvalue);
return gpg_error (GPG_ERR_BAD_PIN);
}
else if (strlen (pinvalue) > pininfo.maxlen)
{
log_error ("PIN is too large; maximum length is %d\n",
pininfo.maxlen);
xfree (pinvalue);
return gpg_error (GPG_ERR_BAD_PIN);
}
- rc = iso7816_verify (app->slot, 0x81, pinvalue, strlen (pinvalue));
+ rc = iso7816_verify (app_get_slot (app), 0x81,
+ pinvalue, strlen (pinvalue));
if (gpg_err_code (rc) == GPG_ERR_INV_VALUE)
{
/* We assume that ISO 9564-1 encoding is used and we failed
because the first nibble we passed was 3 and not 2. DIN
says something about looking up such an encoding in the
SSD but I was not able to find any tag relevant to
this. */
char paddedpin[8];
int i, ndigits;
for (ndigits=0, s=pinvalue; *s; ndigits++, s++)
;
i = 0;
paddedpin[i++] = 0x20 | (ndigits & 0x0f);
for (s=pinvalue; i < sizeof paddedpin && *s && s[1]; s = s+2 )
paddedpin[i++] = (((*s - '0') << 4) | ((s[1] - '0') & 0x0f));
if (i < sizeof paddedpin && *s)
paddedpin[i++] = (((*s - '0') << 4) | 0x0f);
while (i < sizeof paddedpin)
paddedpin[i++] = 0xff;
- rc = iso7816_verify (app->slot, 0x81, paddedpin, sizeof paddedpin);
+ rc = iso7816_verify (app_get_slot (app), 0x81,
+ paddedpin, sizeof paddedpin);
}
xfree (pinvalue);
}
if (rc)
{
log_error ("verify PIN failed\n");
return rc;
}
app->did_chv1 = 1;
return 0;
}
/* Create the signature and return the allocated result in OUTDATA.
If a PIN is required the PINCB will be used to ask for the PIN;
that callback should return the PIN in an allocated buffer and
store that in the 3rd argument. */
static gpg_error_t
do_sign (app_t app, const char *keyidstr, int hashalgo,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const void *indata, size_t indatalen,
unsigned char **outdata, size_t *outdatalen )
{
static unsigned char sha1_prefix[15] = /* Object ID is 1.3.14.3.2.26 */
{ 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e, 0x03,
0x02, 0x1a, 0x05, 0x00, 0x04, 0x14 };
static unsigned char rmd160_prefix[15] = /* Object ID is 1.3.36.3.2.1 */
{ 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x24, 0x03,
0x02, 0x01, 0x05, 0x00, 0x04, 0x14 };
static unsigned char sha256_prefix[19] = /* OID is 2.16.840.1.101.3.4.2.1 */
{ 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05,
0x00, 0x04, 0x20 };
int rc;
int fid;
unsigned char data[19+32]; /* Must be large enough for a SHA-256 digest
+ the largest OID _prefix above. */
int datalen;
if (!keyidstr || !*keyidstr)
return gpg_error (GPG_ERR_INV_VALUE);
if (indatalen != 20 && indatalen != 16 && indatalen != 32
&& indatalen != (15+20) && indatalen != (19+32))
return gpg_error (GPG_ERR_INV_VALUE);
/* Check that the provided ID is valid. This is not really needed
but we do it to enforce correct usage by the caller. */
if (strncmp (keyidstr, "DINSIG.", 7) )
return gpg_error (GPG_ERR_INV_ID);
keyidstr += 7;
if (!hexdigitp (keyidstr) || !hexdigitp (keyidstr+1)
|| !hexdigitp (keyidstr+2) || !hexdigitp (keyidstr+3)
|| keyidstr[4])
return gpg_error (GPG_ERR_INV_ID);
fid = xtoi_4 (keyidstr);
if (fid != 0xC000)
return gpg_error (GPG_ERR_NOT_FOUND);
/* Prepare the DER object from INDATA. */
datalen = 35;
if (indatalen == 15+20)
{
/* Alright, the caller was so kind to send us an already
prepared DER object. Check that it is what we want and that
it matches the hash algorithm. */
if (hashalgo == GCRY_MD_SHA1 && !memcmp (indata, sha1_prefix, 15))
;
else if (hashalgo == GCRY_MD_RMD160 && !memcmp (indata, rmd160_prefix,15))
;
else
return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM);
memcpy (data, indata, indatalen);
}
else if (indatalen == 19+32)
{
/* Alright, the caller was so kind to send us an already
prepared DER object. Check that it is what we want and that
it matches the hash algorithm. */
datalen = indatalen;
if (hashalgo == GCRY_MD_SHA256 && !memcmp (indata, sha256_prefix, 19))
;
else if (hashalgo == GCRY_MD_SHA1 && !memcmp (indata, sha256_prefix, 19))
{
/* Fixme: This is a kludge. A better solution is not to use
SHA1 as default but use an autodetection. However this
needs changes in all app-*.c */
datalen = indatalen;
}
else
return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM);
memcpy (data, indata, indatalen);
}
else
{
int len = 15;
if (hashalgo == GCRY_MD_SHA1)
memcpy (data, sha1_prefix, len);
else if (hashalgo == GCRY_MD_RMD160)
memcpy (data, rmd160_prefix, len);
else if (hashalgo == GCRY_MD_SHA256)
{
len = 19;
datalen = len + indatalen;
memcpy (data, sha256_prefix, len);
}
else
return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM);
memcpy (data+len, indata, indatalen);
}
rc = verify_pin (app, pincb, pincb_arg);
if (!rc)
- rc = iso7816_compute_ds (app->slot, 0, data, datalen, 0,
+ rc = iso7816_compute_ds (app_get_slot (app), 0, data, datalen, 0,
outdata, outdatalen);
return rc;
}
#if 0
#warning test function - works but may brick your card
/* Handle the PASSWD command. CHVNOSTR is currently ignored; we
always use VHV0. RESET_MODE is not yet implemented. */
static gpg_error_t
do_change_pin (app_t app, ctrl_t ctrl, const char *chvnostr,
unsigned int flags,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg)
{
gpg_error_t err;
char *pinvalue;
const char *oldpin;
size_t oldpinlen;
if ((flags & APP_CHANGE_FLAG_RESET))
return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
if ((flags & APP_CHANGE_FLAG_NULLPIN))
{
/* With the nullpin flag, we do not verify the PIN - it would fail
if the Nullpin is still set. */
oldpin = "\0\0\0\0\0";
oldpinlen = 6;
}
else
{
err = verify_pin (app, pincb, pincb_arg);
if (err)
return err;
oldpin = NULL;
oldpinlen = 0;
}
/* TRANSLATORS: Do not translate the "|*|" prefixes but
keep it at the start of the string. We need this elsewhere
to get some infos on the string. */
err = pincb (pincb_arg, _("|N|Initial New PIN"), &pinvalue);
if (err)
{
log_error (_("error getting new PIN: %s\n"), gpg_strerror (err));
return err;
}
- err = iso7816_change_reference_data (app->slot, 0x81,
+ err = iso7816_change_reference_data (app_get_slot (app), 0x81,
oldpin, oldpinlen,
pinvalue, strlen (pinvalue));
xfree (pinvalue);
return err;
}
#endif /*0*/
/* Select the DINSIG application on the card in SLOT. This function
must be used before any other DINSIG application functions. */
gpg_error_t
app_select_dinsig (app_t app)
{
static char const aid[] = { 0xD2, 0x76, 0x00, 0x00, 0x66, 0x01 };
- int slot = app->slot;
+ int slot = app_get_slot (app);
int rc;
rc = iso7816_select_application (slot, aid, sizeof aid, 0);
if (!rc)
{
- app->apptype = "DINSIG";
+ app->apptype = APPTYPE_DINSIG;
+ app->fnc.reselect = NULL;
app->fnc.learn_status = do_learn_status;
app->fnc.readcert = do_readcert;
app->fnc.getattr = NULL;
app->fnc.setattr = NULL;
app->fnc.genkey = NULL;
app->fnc.sign = do_sign;
app->fnc.auth = NULL;
app->fnc.decipher = NULL;
app->fnc.change_pin = NULL /*do_change_pin*/;
app->fnc.check_pin = NULL;
app->force_chv1 = 1;
}
return rc;
}
diff --git a/scd/app-geldkarte.c b/scd/app-geldkarte.c
index 85bcedc4f..141985932 100644
--- a/scd/app-geldkarte.c
+++ b/scd/app-geldkarte.c
@@ -1,409 +1,409 @@
/* app-geldkarte.c - The German Geldkarte application
* Copyright (C) 2004 g10 Code GmbH
* Copyright (C) 2009 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
/* This is a read-only application to quickly dump information of a
German Geldkarte (debit card for small amounts). We only support
newer Geldkarte (with the AID DF_BOERSE_NEU) issued since 2000 or
even earlier.
*/
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <time.h>
#include <ctype.h>
#include "scdaemon.h"
#include "../common/i18n.h"
#include "iso7816.h"
-#include "app-common.h"
#include "../common/tlv.h"
/* Object with application (i.e. Geldkarte) specific data. */
struct app_local_s
{
char kblz[2+1+4+1];
const char *banktype;
char *cardno;
char expires[7+1];
char validfrom[10+1];
char *country;
char currency[3+1];
unsigned int currency_mult100;
unsigned char chipid;
unsigned char osvers;
int balance;
int maxamount;
int maxamount1;
};
/* Deconstructor. */
static void
do_deinit (app_t app)
{
if (app && app->app_local)
{
xfree (app->app_local->cardno);
xfree (app->app_local->country);
xfree (app->app_local);
app->app_local = NULL;
}
}
static gpg_error_t
send_one_string (ctrl_t ctrl, const char *name, const char *string)
{
if (!name || !string)
return 0;
send_status_info (ctrl, name, string, strlen (string), NULL, 0);
return 0;
}
/* Implement the GETATTR command. This is similar to the LEARN
command but returns just one value via the status interface. */
static gpg_error_t
do_getattr (app_t app, ctrl_t ctrl, const char *name)
{
gpg_error_t err;
struct app_local_s *ld = app->app_local;
char numbuf[100];
if (!strcmp (name, "X-KBLZ"))
err = send_one_string (ctrl, name, ld->kblz);
else if (!strcmp (name, "X-BANKINFO"))
err = send_one_string (ctrl, name, ld->banktype);
else if (!strcmp (name, "X-CARDNO"))
err = send_one_string (ctrl, name, ld->cardno);
else if (!strcmp (name, "X-EXPIRES"))
err = send_one_string (ctrl, name, ld->expires);
else if (!strcmp (name, "X-VALIDFROM"))
err = send_one_string (ctrl, name, ld->validfrom);
else if (!strcmp (name, "X-COUNTRY"))
err = send_one_string (ctrl, name, ld->country);
else if (!strcmp (name, "X-CURRENCY"))
err = send_one_string (ctrl, name, ld->currency);
else if (!strcmp (name, "X-ZKACHIPID"))
{
snprintf (numbuf, sizeof numbuf, "0x%02X", ld->chipid);
err = send_one_string (ctrl, name, numbuf);
}
else if (!strcmp (name, "X-OSVERSION"))
{
snprintf (numbuf, sizeof numbuf, "0x%02X", ld->osvers);
err = send_one_string (ctrl, name, numbuf);
}
else if (!strcmp (name, "X-BALANCE"))
{
snprintf (numbuf, sizeof numbuf, "%.2f",
(double)ld->balance / 100 * ld->currency_mult100);
err = send_one_string (ctrl, name, numbuf);
}
else if (!strcmp (name, "X-MAXAMOUNT"))
{
snprintf (numbuf, sizeof numbuf, "%.2f",
(double)ld->maxamount / 100 * ld->currency_mult100);
err = send_one_string (ctrl, name, numbuf);
}
else if (!strcmp (name, "X-MAXAMOUNT1"))
{
snprintf (numbuf, sizeof numbuf, "%.2f",
(double)ld->maxamount1 / 100 * ld->currency_mult100);
err = send_one_string (ctrl, name, numbuf);
}
else
err = gpg_error (GPG_ERR_INV_NAME);
return err;
}
static gpg_error_t
do_learn_status (app_t app, ctrl_t ctrl, unsigned int flags)
{
static const char *names[] = {
"X-KBLZ",
"X-BANKINFO",
"X-CARDNO",
"X-EXPIRES",
"X-VALIDFROM",
"X-COUNTRY",
"X-CURRENCY",
"X-ZKACHIPID",
"X-OSVERSION",
"X-BALANCE",
"X-MAXAMOUNT",
"X-MAXAMOUNT1",
NULL
};
gpg_error_t err = 0;
int idx;
(void)flags;
for (idx=0; names[idx] && !err; idx++)
err = do_getattr (app, ctrl, names[idx]);
return err;
}
static char *
copy_bcd (const unsigned char *string, size_t length)
{
const unsigned char *s;
size_t n;
size_t needed;
char *buffer, *dst;
if (!length)
return xtrystrdup ("");
/* Skip leading zeroes. */
for (; length && !*string; length--, string++)
;
s = string;
n = length;
needed = 0;
for (; n ; n--, s++)
{
if (!needed && !(*s & 0xf0))
; /* Skip the leading zero in the first nibble. */
else
{
if ( ((*s >> 4) & 0x0f) > 9 )
{
errno = EINVAL;
return NULL;
}
needed++;
}
if ( n == 1 && (*s & 0x0f) > 9 )
; /* Ignore the last digit if it has the sign. */
else
{
needed++;
if ( (*s & 0x0f) > 9 )
{
errno = EINVAL;
return NULL;
}
}
}
if (!needed) /* If it is all zero, print a "0". */
needed++;
buffer = dst = xtrymalloc (needed+1);
if (!buffer)
return NULL;
s = string;
n = length;
needed = 0;
for (; n ; n--, s++)
{
if (!needed && !(*s & 0xf0))
; /* Skip the leading zero in the first nibble. */
else
{
*dst++ = '0' + ((*s >> 4) & 0x0f);
needed++;
}
if ( n == 1 && (*s & 0x0f) > 9 )
; /* Ignore the last digit if it has the sign. */
else
{
*dst++ = '0' + (*s & 0x0f);
needed++;
}
}
if (!needed)
*dst++ = '0';
*dst = 0;
return buffer;
}
/* Convert the BCD number at STRING of LENGTH into an integer and store
that at RESULT. Return 0 on success. */
static gpg_error_t
bcd_to_int (const unsigned char *string, size_t length, int *result)
{
char *tmp;
tmp = copy_bcd (string, length);
if (!tmp)
return gpg_error (GPG_ERR_BAD_DATA);
*result = strtol (tmp, NULL, 10);
xfree (tmp);
return 0;
}
/* Select the Geldkarte application. */
gpg_error_t
app_select_geldkarte (app_t app)
{
static char const aid[] =
{ 0xD2, 0x76, 0x00, 0x00, 0x25, 0x45, 0x50, 0x02, 0x00 };
gpg_error_t err;
- int slot = app->slot;
+ int slot = app_get_slot (app);
unsigned char *result = NULL;
size_t resultlen;
struct app_local_s *ld;
const char *banktype;
err = iso7816_select_application (slot, aid, sizeof aid, 0);
if (err)
goto leave;
/* Read the first record of EF_ID (SFI=0x17). We require this
record to be at least 24 bytes with the first byte 0x67 and a
correct filler byte. */
err = iso7816_read_record (slot, 1, 1, ((0x17 << 3)|4), &result, &resultlen);
if (err)
goto leave; /* Oops - not a Geldkarte. */
if (resultlen < 24 || *result != 0x67 || result[22])
{
err = gpg_error (GPG_ERR_NOT_FOUND);
goto leave;
}
/* The short Bankleitzahl consists of 3 bytes at offset 1. */
switch (result[1])
{
case 0x21: banktype = "Oeffentlich-rechtliche oder private Bank"; break;
case 0x22: banktype = "Privat- oder Geschaeftsbank"; break;
case 0x25: banktype = "Sparkasse"; break;
case 0x26:
case 0x29: banktype = "Genossenschaftsbank"; break;
default:
err = gpg_error (GPG_ERR_NOT_FOUND);
goto leave; /* Probably not a Geldkarte. */
}
- app->apptype = "GELDKARTE";
+ app->apptype = APPTYPE_GELDKARTE;
app->fnc.deinit = do_deinit;
+ app->fnc.reselect = NULL;
/* If we don't have a serialno yet construct it from the EF_ID. */
- if (!app->serialno)
+ if (!app->card->serialno)
{
- app->serialno = xtrymalloc (10);
- if (!app->serialno)
+ app->card->serialno = xtrymalloc (10);
+ if (!app->card->serialno)
{
err = gpg_error_from_syserror ();
goto leave;
}
- memcpy (app->serialno, result, 10);
- app->serialnolen = 10;
- err = app_munge_serialno (app);
+ memcpy (app->card->serialno, result, 10);
+ app->card->serialnolen = 10;
+ err = app_munge_serialno (app->card);
if (err)
goto leave;
}
app->app_local = ld = xtrycalloc (1, sizeof *app->app_local);
if (!app->app_local)
{
err = gpg_err_code_from_syserror ();
goto leave;
}
snprintf (ld->kblz, sizeof ld->kblz, "%02X-%02X%02X",
result[1], result[2], result[3]);
ld->banktype = banktype;
ld->cardno = copy_bcd (result+4, 5);
if (!ld->cardno)
{
err = gpg_err_code_from_syserror ();
goto leave;
}
snprintf (ld->expires, sizeof ld->expires, "20%02X-%02X",
result[10], result[11]);
snprintf (ld->validfrom, sizeof ld->validfrom, "20%02X-%02X-%02X",
result[12], result[13], result[14]);
ld->country = copy_bcd (result+15, 2);
if (!ld->country)
{
err = gpg_err_code_from_syserror ();
goto leave;
}
snprintf (ld->currency, sizeof ld->currency, "%c%c%c",
isascii (result[17])? result[17]:' ',
isascii (result[18])? result[18]:' ',
isascii (result[19])? result[19]:' ');
ld->currency_mult100 = (result[20] == 0x01? 1:
result[20] == 0x02? 10:
result[20] == 0x04? 100:
result[20] == 0x08? 1000:
result[20] == 0x10? 10000:
result[20] == 0x20? 100000:0);
ld->chipid = result[21];
ld->osvers = result[23];
/* Read the first record of EF_BETRAG (SFI=0x18). */
xfree (result);
err = iso7816_read_record (slot, 1, 1, ((0x18 << 3)|4), &result, &resultlen);
if (err)
goto leave; /* It does not make sense to continue. */
if (resultlen < 12)
{
err = gpg_error (GPG_ERR_NOT_FOUND);
goto leave;
}
err = bcd_to_int (result+0, 3, &ld->balance);
if (!err)
err = bcd_to_int (result+3, 3, &ld->maxamount);
if (!err)
err = bcd_to_int (result+6, 3, &ld->maxamount1);
/* The next 3 bytes are the maximum amount chargable without using a
MAC. This is usually 0. */
if (err)
goto leave;
/* Setup the rest of the methods. */
app->fnc.learn_status = do_learn_status;
app->fnc.getattr = do_getattr;
leave:
xfree (result);
if (err)
do_deinit (app);
return err;
}
diff --git a/scd/app-help.c b/scd/app-help.c
index f0f551c55..e3ad74434 100644
--- a/scd/app-help.c
+++ b/scd/app-help.c
@@ -1,219 +1,231 @@
/* app-help.c - Application helper functions
* Copyright (C) 2004, 2009 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "scdaemon.h"
-#include "app-common.h"
#include "iso7816.h"
#include "../common/tlv.h"
/* Count the number of bits, assuming that A represents an unsigned
* big integer of length LEN bytes. If A is NULL a length of 0 is
* returned. */
unsigned int
app_help_count_bits (const unsigned char *a, size_t len)
{
unsigned int n = len * 8;
int i;
if (!a)
return 0;
for (; len && !*a; len--, a++, n -=8)
;
if (len)
{
for (i=7; i && !(*a & (1<<i)); i--)
n--;
}
return n;
}
-/* Return the KEYGRIP for the certificate CERT as an hex encoded
- string in the user provided buffer HEXKEYGRIP which must be of at
- least 41 bytes. */
+/* Return the KEYGRIP for the canonical encoded public key (PK,PKLEN)
+ * as an hex encoded string in the user provided buffer HEXKEYGRIP
+ * which must be of at least 41 bytes. */
gpg_error_t
-app_help_get_keygrip_string (ksba_cert_t cert, char *hexkeygrip)
+app_help_get_keygrip_string_pk (const void *pk, size_t pklen, char *hexkeygrip)
{
gpg_error_t err;
gcry_sexp_t s_pkey;
- ksba_sexp_t p;
- size_t n;
- unsigned char array[20];
+ unsigned char array[KEYGRIP_LEN];
- p = ksba_cert_get_public_key (cert);
- if (!p)
- return gpg_error (GPG_ERR_BUG);
- n = gcry_sexp_canon_len (p, 0, NULL, NULL);
- if (!n)
- return gpg_error (GPG_ERR_INV_SEXP);
- err = gcry_sexp_sscan (&s_pkey, NULL, (char*)p, n);
- xfree (p);
+ err = gcry_sexp_sscan (&s_pkey, NULL, pk, pklen);
if (err)
return err; /* Can't parse that S-expression. */
if (!gcry_pk_get_keygrip (s_pkey, array))
{
gcry_sexp_release (s_pkey);
return gpg_error (GPG_ERR_GENERAL); /* Failed to calculate the keygrip.*/
}
gcry_sexp_release (s_pkey);
- bin2hex (array, 20, hexkeygrip);
+ bin2hex (array, KEYGRIP_LEN, hexkeygrip);
return 0;
}
+/* Return the KEYGRIP for the certificate CERT as an hex encoded
+ string in the user provided buffer HEXKEYGRIP which must be of at
+ least 41 bytes. */
+gpg_error_t
+app_help_get_keygrip_string (ksba_cert_t cert, char *hexkeygrip)
+{
+ gpg_error_t err;
+ ksba_sexp_t p;
+ size_t n;
+
+ p = ksba_cert_get_public_key (cert);
+ if (!p)
+ return gpg_error (GPG_ERR_BUG);
+ n = gcry_sexp_canon_len (p, 0, NULL, NULL);
+ if (!n)
+ return gpg_error (GPG_ERR_INV_SEXP);
+ err = app_help_get_keygrip_string_pk ((void*)p, n, hexkeygrip);
+ ksba_free (p);
+ return err;
+}
+
+
gpg_error_t
app_help_pubkey_from_cert (const void *cert, size_t certlen,
unsigned char **r_pk, size_t *r_pklen)
{
gpg_error_t err;
ksba_cert_t kc;
unsigned char *pk;
size_t pklen;
*r_pk = NULL;
*r_pklen = 0;
err = ksba_cert_new (&kc);
if (err)
return err;
err = ksba_cert_init_from_mem (kc, cert, certlen);
if (err)
goto leave;
pk = ksba_cert_get_public_key (kc);
if (!pk)
{
err = gpg_error (GPG_ERR_NO_PUBKEY);
goto leave;
}
pklen = gcry_sexp_canon_len (pk, 0, NULL, &err);
leave:
if (!err)
{
*r_pk = pk;
*r_pklen = pklen;
}
else
ksba_free (pk);
ksba_cert_release (kc);
return err;
}
/* Given the SLOT and the File ID FID, return the length of the
certificate contained in that file. Returns 0 if the file does not
exists or does not contain a certificate. If R_CERTOFF is not
NULL, the length the header will be stored at this address; thus to
parse the X.509 certificate a read should start at that offset.
On success the file is still selected.
*/
size_t
app_help_read_length_of_cert (int slot, int fid, size_t *r_certoff)
{
gpg_error_t err;
unsigned char *buffer;
const unsigned char *p;
size_t buflen, n;
int class, tag, constructed, ndef;
size_t resultlen, objlen, hdrlen;
err = iso7816_select_file (slot, fid, 0);
if (err)
{
log_info ("error selecting FID 0x%04X: %s\n", fid, gpg_strerror (err));
return 0;
}
err = iso7816_read_binary (slot, 0, 32, &buffer, &buflen);
if (err)
{
log_info ("error reading certificate from FID 0x%04X: %s\n",
fid, gpg_strerror (err));
return 0;
}
if (!buflen || *buffer == 0xff)
{
log_info ("no certificate contained in FID 0x%04X\n", fid);
xfree (buffer);
return 0;
}
p = buffer;
n = buflen;
err = parse_ber_header (&p, &n, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (err)
{
log_info ("error parsing certificate in FID 0x%04X: %s\n",
fid, gpg_strerror (err));
xfree (buffer);
return 0;
}
/* All certificates should commence with a SEQUENCE except for the
special ROOT CA which are enclosed in a SET. */
if ( !(class == CLASS_UNIVERSAL && constructed
&& (tag == TAG_SEQUENCE || tag == TAG_SET)))
{
log_info ("data at FID 0x%04X does not look like a certificate\n", fid);
return 0;
}
resultlen = objlen + hdrlen;
if (r_certoff)
{
/* The callers want the offset to the actual certificate. */
*r_certoff = hdrlen;
err = parse_ber_header (&p, &n, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (err)
return 0;
if (class == CLASS_UNIVERSAL && tag == TAG_OBJECT_ID && !constructed)
{
/* The certificate seems to be contained in a
userCertificate container. Assume the following sequence
is the certificate. */
*r_certoff += hdrlen + objlen;
if (*r_certoff > resultlen)
{
*r_certoff = 0;
return 0; /* That should never happen. */
}
}
else
*r_certoff = 0;
}
return resultlen;
}
diff --git a/scd/app-nks.c b/scd/app-nks.c
index 40c941616..d12720cf6 100644
--- a/scd/app-nks.c
+++ b/scd/app-nks.c
@@ -1,1428 +1,1446 @@
/* app-nks.c - The Telesec NKS card application.
* Copyright (C) 2004, 2007, 2008, 2009 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
/* Notes:
- We are now targeting TCOS 3 cards and it may happen that there is
a regression towards TCOS 2 cards. Please report.
- The TKS3 AUT key is not used. It seems that it is only useful for
the internal authentication command and not accessible by other
applications. The key itself is in the encryption class but the
corresponding certificate has only the digitalSignature
capability.
- If required, we automagically switch between the NKS application
and the SigG application. This avoids to use the DINSIG
application which is somewhat limited, has no support for Secure
Messaging as required by TCOS 3 and has no way to change the PIN
or even set the NullPIN.
- We use the prefix NKS-DF01 for TCOS 2 cards and NKS-NKS3 for newer
cards. This is because the NKS application has moved to DF02 with
TCOS 3 and thus we better use a DF independent tag.
- We use only the global PINs for the NKS application.
*/
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <time.h>
#include "scdaemon.h"
#include "../common/i18n.h"
#include "iso7816.h"
-#include "app-common.h"
#include "../common/tlv.h"
#include "apdu.h"
#include "../common/host2net.h"
static char const aid_nks[] = { 0xD2, 0x76, 0x00, 0x00, 0x03, 0x01, 0x02 };
static char const aid_sigg[] = { 0xD2, 0x76, 0x00, 0x00, 0x66, 0x01 };
static struct
{
int is_sigg; /* Valid for SigG application. */
int fid; /* File ID. */
int nks_ver; /* 0 for NKS version 2, 3 for version 3. */
int certtype; /* Type of certificate or 0 if it is not a certificate. */
int iskeypair; /* If true has the FID of the corresponding certificate. */
int issignkey; /* True if file is a key usable for signing. */
int isenckey; /* True if file is a key usable for decryption. */
unsigned char kid; /* Corresponding key references. */
} filelist[] = {
{ 0, 0x4531, 0, 0, 0xC000, 1, 0, 0x80 }, /* EF_PK.NKS.SIG */
{ 0, 0xC000, 0, 101 }, /* EF_C.NKS.SIG */
{ 0, 0x4331, 0, 100 },
{ 0, 0x4332, 0, 100 },
{ 0, 0xB000, 0, 110 }, /* EF_PK.RCA.NKS */
{ 0, 0x45B1, 0, 0, 0xC200, 0, 1, 0x81 }, /* EF_PK.NKS.ENC */
{ 0, 0xC200, 0, 101 }, /* EF_C.NKS.ENC */
{ 0, 0x43B1, 0, 100 },
{ 0, 0x43B2, 0, 100 },
/* The authentication key is not used. */
/* { 0, 0x4571, 3, 0, 0xC500, 0, 0, 0x82 }, /\* EF_PK.NKS.AUT *\/ */
/* { 0, 0xC500, 3, 101 }, /\* EF_C.NKS.AUT *\/ */
{ 0, 0x45B2, 3, 0, 0xC201, 0, 1, 0x83 }, /* EF_PK.NKS.ENC1024 */
{ 0, 0xC201, 3, 101 }, /* EF_C.NKS.ENC1024 */
{ 1, 0x4531, 3, 0, 0xC000, 1, 1, 0x84 }, /* EF_PK.CH.SIG */
{ 1, 0xC000, 0, 101 }, /* EF_C.CH.SIG */
{ 1, 0xC008, 3, 101 }, /* EF_C.CA.SIG */
{ 1, 0xC00E, 3, 111 }, /* EF_C.RCA.SIG */
{ 0, 0 }
};
/* Object with application (i.e. NKS) specific data. */
struct app_local_s {
int nks_version; /* NKS version. */
int sigg_active; /* True if switched to the SigG application. */
int sigg_msig_checked;/* True if we checked for a mass signature card. */
int sigg_is_msig; /* True if this is a mass signature card. */
int need_app_select; /* Need to re-select the application. */
};
static gpg_error_t switch_application (app_t app, int enable_sigg);
/* Release local data. */
static void
do_deinit (app_t app)
{
if (app && app->app_local)
{
xfree (app->app_local);
app->app_local = NULL;
}
}
static int
all_zero_p (void *buffer, size_t length)
{
char *p;
for (p=buffer; length; length--, p++)
if (*p)
return 0;
return 1;
}
/* Read the file with FID, assume it contains a public key and return
its keygrip in the caller provided 41 byte buffer R_GRIPSTR. */
static gpg_error_t
keygripstr_from_pk_file (app_t app, int fid, char *r_gripstr)
{
gpg_error_t err;
unsigned char grip[20];
unsigned char *buffer[2];
size_t buflen[2];
gcry_sexp_t sexp;
int i;
int offset[2] = { 0, 0 };
- err = iso7816_select_file (app->slot, fid, 0);
+ err = iso7816_select_file (app_get_slot (app), fid, 0);
if (err)
return err;
- err = iso7816_read_record (app->slot, 1, 1, 0, &buffer[0], &buflen[0]);
+ err = iso7816_read_record (app_get_slot (app), 1, 1, 0,
+ &buffer[0], &buflen[0]);
if (err)
return err;
- err = iso7816_read_record (app->slot, 2, 1, 0, &buffer[1], &buflen[1]);
+ err = iso7816_read_record (app_get_slot (app), 2, 1, 0,
+ &buffer[1], &buflen[1]);
if (err)
{
xfree (buffer[0]);
return err;
}
if (app->app_local->nks_version < 3)
{
/* Old versions of NKS store the values in a TLV encoded format.
We need to do some checks. */
for (i=0; i < 2; i++)
{
/* Check that the value appears like an integer encoded as
Simple-TLV. We don't check the tag because the tests cards I
have use 1 for both, the modulus and the exponent - the
example in the documentation gives 2 for the exponent. */
if (buflen[i] < 3)
err = gpg_error (GPG_ERR_TOO_SHORT);
else if (buffer[i][1] != buflen[i]-2 )
err = gpg_error (GPG_ERR_INV_OBJ);
else
offset[i] = 2;
}
}
else
{
/* Remove leading zeroes to get a correct keygrip. Take care of
negative numbers. We should also fix it the same way in
libgcrypt but we can't yet rely on it yet. */
for (i=0; i < 2; i++)
{
while (buflen[i]-offset[i] > 1
&& !buffer[i][offset[i]]
&& !(buffer[i][offset[i]+1] & 0x80))
offset[i]++;
}
}
/* Check whether negative values are not prefixed with a zero and
fix that. */
for (i=0; i < 2; i++)
{
if ((buflen[i]-offset[i]) && (buffer[i][offset[i]] & 0x80))
{
unsigned char *newbuf;
size_t newlen;
newlen = 1 + buflen[i] - offset[i];
newbuf = xtrymalloc (newlen);
if (!newlen)
{
xfree (buffer[0]);
xfree (buffer[1]);
return gpg_error_from_syserror ();
}
newbuf[0] = 0;
memcpy (newbuf+1, buffer[i]+offset[i], buflen[i] - offset[i]);
xfree (buffer[i]);
buffer[i] = newbuf;
buflen[i] = newlen;
offset[i] = 0;
}
}
if (!err)
err = gcry_sexp_build (&sexp, NULL,
"(public-key (rsa (n %b) (e %b)))",
(int)buflen[0]-offset[0], buffer[0]+offset[0],
(int)buflen[1]-offset[1], buffer[1]+offset[1]);
xfree (buffer[0]);
xfree (buffer[1]);
if (err)
return err;
if (!gcry_pk_get_keygrip (sexp, grip))
{
err = gpg_error (GPG_ERR_INTERNAL); /* i.e. RSA not supported by
libgcrypt. */
}
else
{
bin2hex (grip, 20, r_gripstr);
}
gcry_sexp_release (sexp);
return err;
}
/* TCOS responds to a verify with empty data (i.e. without the Lc
byte) with the status of the PIN. PWID is the PIN ID, If SIGG is
true, the application is switched into SigG mode.
Returns:
-1 = Error retrieving the data,
-2 = No such PIN,
-3 = PIN blocked,
-4 = NullPIN activ,
n >= 0 = Number of verification attempts left. */
static int
get_chv_status (app_t app, int sigg, int pwid)
{
unsigned char *result = NULL;
size_t resultlen;
char command[4];
int rc;
if (switch_application (app, sigg))
return sigg? -2 : -1; /* No such PIN / General error. */
command[0] = 0x00;
command[1] = 0x20;
command[2] = 0x00;
command[3] = pwid;
- if (apdu_send_direct (app->slot, 0, (unsigned char *)command,
+ if (apdu_send_direct (app_get_slot (app), 0, (unsigned char *)command,
4, 0, NULL, &result, &resultlen))
rc = -1; /* Error. */
else if (resultlen < 2)
rc = -1; /* Error. */
else
{
unsigned int sw = buf16_to_uint (result+resultlen-2);
if (sw == 0x6a88)
rc = -2; /* No such PIN. */
else if (sw == 0x6983)
rc = -3; /* PIN is blocked. */
else if (sw == 0x6985)
rc = -4; /* NullPIN is activ. */
else if ((sw & 0xfff0) == 0x63C0)
rc = (sw & 0x000f); /* PIN has N tries left. */
else
rc = -1; /* Other error. */
}
xfree (result);
return rc;
}
/* Implement the GETATTR command. This is similar to the LEARN
command but returns just one value via the status interface. */
static gpg_error_t
do_getattr (app_t app, ctrl_t ctrl, const char *name)
{
static struct {
const char *name;
int special;
} table[] = {
{ "$AUTHKEYID", 1 },
{ "NKS-VERSION", 2 },
{ "CHV-STATUS", 3 },
{ NULL, 0 }
};
gpg_error_t err = 0;
int idx;
char buffer[100];
err = switch_application (app, 0);
if (err)
return err;
for (idx=0; table[idx].name && strcmp (table[idx].name, name); idx++)
;
if (!table[idx].name)
return gpg_error (GPG_ERR_INV_NAME);
switch (table[idx].special)
{
case 1: /* $AUTHKEYID */
{
/* NetKey 3.0 cards define an authentication key but according
to the specs this key is only usable for encryption and not
signing. it might work anyway but it has not yet been
tested - fixme. Thus for now we use the NKS signature key
for authentication. */
char const tmp[] = "NKS-NKS3.4531";
send_status_info (ctrl, table[idx].name, tmp, strlen (tmp), NULL, 0);
}
break;
case 2: /* NKS-VERSION */
snprintf (buffer, sizeof buffer, "%d", app->app_local->nks_version);
send_status_info (ctrl, table[idx].name,
buffer, strlen (buffer), NULL, 0);
break;
case 3: /* CHV-STATUS */
{
/* Returns: PW1.CH PW2.CH PW1.CH.SIG PW2.CH.SIG That are the
two global passwords followed by the two SigG passwords.
For the values, see the function get_chv_status. */
int tmp[4];
/* We use a helper array so that we can control that there is
no superfluous application switch. Note that PW2.CH.SIG
really has the identifier 0x83 and not 0x82 as one would
expect. */
tmp[0] = get_chv_status (app, 0, 0x00);
tmp[1] = get_chv_status (app, 0, 0x01);
tmp[2] = get_chv_status (app, 1, 0x81);
tmp[3] = get_chv_status (app, 1, 0x83);
snprintf (buffer, sizeof buffer,
"%d %d %d %d", tmp[0], tmp[1], tmp[2], tmp[3]);
send_status_info (ctrl, table[idx].name,
buffer, strlen (buffer), NULL, 0);
}
break;
default:
err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
break;
}
return err;
}
static void
do_learn_status_core (app_t app, ctrl_t ctrl, unsigned int flags, int is_sigg)
{
gpg_error_t err;
char ct_buf[100], id_buf[100];
int i;
const char *tag;
if (is_sigg)
tag = "SIGG";
else if (app->app_local->nks_version < 3)
tag = "DF01";
else
tag = "NKS3";
/* Output information about all useful objects in the NKS application. */
for (i=0; filelist[i].fid; i++)
{
if (filelist[i].nks_ver > app->app_local->nks_version)
continue;
if (!!filelist[i].is_sigg != !!is_sigg)
continue;
if (filelist[i].certtype && !(flags &1))
{
size_t len;
- len = app_help_read_length_of_cert (app->slot,
+ len = app_help_read_length_of_cert (app_get_slot (app),
filelist[i].fid, NULL);
if (len)
{
/* FIXME: We should store the length in the application's
context so that a following readcert does only need to
read that many bytes. */
snprintf (ct_buf, sizeof ct_buf, "%d", filelist[i].certtype);
snprintf (id_buf, sizeof id_buf, "NKS-%s.%04X",
tag, filelist[i].fid);
send_status_info (ctrl, "CERTINFO",
ct_buf, strlen (ct_buf),
id_buf, strlen (id_buf),
NULL, (size_t)0);
}
}
else if (filelist[i].iskeypair)
{
char gripstr[40+1];
err = keygripstr_from_pk_file (app, filelist[i].fid, gripstr);
if (err)
log_error ("can't get keygrip from FID 0x%04X: %s\n",
filelist[i].fid, gpg_strerror (err));
else
{
snprintf (id_buf, sizeof id_buf, "NKS-%s.%04X",
tag, filelist[i].fid);
send_status_info (ctrl, "KEYPAIRINFO",
gripstr, 40,
id_buf, strlen (id_buf),
NULL, (size_t)0);
}
}
}
}
static gpg_error_t
do_learn_status (app_t app, ctrl_t ctrl, unsigned int flags)
{
gpg_error_t err;
err = switch_application (app, 0);
if (err)
return err;
do_learn_status_core (app, ctrl, flags, 0);
err = switch_application (app, 1);
if (err)
return 0; /* Silently ignore if we can't switch to SigG. */
do_learn_status_core (app, ctrl, flags, 1);
return 0;
}
/* Read the certificate with id CERTID (as returned by learn_status in
the CERTINFO status lines) and return it in the freshly allocated
buffer put into CERT and the length of the certificate put into
CERTLEN. */
static gpg_error_t
do_readcert (app_t app, const char *certid,
unsigned char **cert, size_t *certlen)
{
int i, fid;
gpg_error_t err;
unsigned char *buffer;
const unsigned char *p;
size_t buflen, n;
int class, tag, constructed, ndef;
size_t totobjlen, objlen, hdrlen;
int rootca = 0;
int is_sigg = 0;
*cert = NULL;
*certlen = 0;
if (!strncmp (certid, "NKS-NKS3.", 9))
;
else if (!strncmp (certid, "NKS-DF01.", 9))
;
else if (!strncmp (certid, "NKS-SIGG.", 9))
is_sigg = 1;
else
return gpg_error (GPG_ERR_INV_ID);
err = switch_application (app, is_sigg);
if (err)
return err;
certid += 9;
if (!hexdigitp (certid) || !hexdigitp (certid+1)
|| !hexdigitp (certid+2) || !hexdigitp (certid+3)
|| certid[4])
return gpg_error (GPG_ERR_INV_ID);
fid = xtoi_4 (certid);
for (i=0; filelist[i].fid; i++)
if ((filelist[i].certtype || filelist[i].iskeypair)
&& filelist[i].fid == fid)
break;
if (!filelist[i].fid)
return gpg_error (GPG_ERR_NOT_FOUND);
/* If the requested objects is a plain public key, redirect it to
the corresponding certificate. The whole system is a bit messy
because we sometime use the key directly or let the caller
retrieve the key from the certificate. The rationale for
that is to support not-yet stored certificates. */
if (filelist[i].iskeypair)
fid = filelist[i].iskeypair;
/* Read the entire file. fixme: This could be optimized by first
reading the header to figure out how long the certificate
actually is. */
- err = iso7816_select_file (app->slot, fid, 0);
+ err = iso7816_select_file (app_get_slot (app), fid, 0);
if (err)
{
log_error ("error selecting FID 0x%04X: %s\n", fid, gpg_strerror (err));
return err;
}
- err = iso7816_read_binary (app->slot, 0, 0, &buffer, &buflen);
+ err = iso7816_read_binary (app_get_slot (app), 0, 0, &buffer, &buflen);
if (err)
{
log_error ("error reading certificate from FID 0x%04X: %s\n",
fid, gpg_strerror (err));
return err;
}
if (!buflen || *buffer == 0xff)
{
log_info ("no certificate contained in FID 0x%04X\n", fid);
err = gpg_error (GPG_ERR_NOT_FOUND);
goto leave;
}
/* Now figure something out about the object. */
p = buffer;
n = buflen;
err = parse_ber_header (&p, &n, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (err)
goto leave;
if ( class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE && constructed )
;
else if ( class == CLASS_UNIVERSAL && tag == TAG_SET && constructed )
rootca = 1;
else
return gpg_error (GPG_ERR_INV_OBJ);
totobjlen = objlen + hdrlen;
assert (totobjlen <= buflen);
err = parse_ber_header (&p, &n, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (err)
goto leave;
if (rootca)
;
else if (class == CLASS_UNIVERSAL && tag == TAG_OBJECT_ID && !constructed)
{
const unsigned char *save_p;
/* The certificate seems to be contained in a userCertificate
container. Skip this and assume the following sequence is
the certificate. */
if (n < objlen)
{
err = gpg_error (GPG_ERR_INV_OBJ);
goto leave;
}
p += objlen;
n -= objlen;
save_p = p;
err = parse_ber_header (&p, &n, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (err)
goto leave;
if ( !(class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE && constructed) )
return gpg_error (GPG_ERR_INV_OBJ);
totobjlen = objlen + hdrlen;
assert (save_p + totobjlen <= buffer + buflen);
memmove (buffer, save_p, totobjlen);
}
*cert = buffer;
buffer = NULL;
*certlen = totobjlen;
leave:
xfree (buffer);
return err;
}
/* Handle the READKEY command. On success a canonical encoded
S-expression with the public key will get stored at PK and its
length at PKLEN; the caller must release that buffer. On error PK
and PKLEN are not changed and an error code is returned. As of now
this function is only useful for the internal authentication key.
Other keys are automagically retrieved via by means of the
certificate parsing code in commands.c:cmd_readkey. For internal
use PK and PKLEN may be NULL to just check for an existing key. */
static gpg_error_t
-do_readkey (app_t app, const char *keyid, unsigned char **pk, size_t *pklen)
+do_readkey (app_t app, ctrl_t ctrl, const char *keyid, unsigned int flags,
+ unsigned char **pk, size_t *pklen)
{
gpg_error_t err;
unsigned char *buffer[2];
size_t buflen[2];
unsigned short path[1] = { 0x4500 };
/* We use a generic name to retrieve PK.AUT.IFD-SPK. */
if (!strcmp (keyid, "$IFDAUTHKEY") && app->app_local->nks_version >= 3)
;
else /* Return the error code expected by cmd_readkey. */
return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
/* Access the KEYD file which is always in the master directory. */
- err = iso7816_select_path (app->slot, path, DIM (path));
+ err = iso7816_select_path (app_get_slot (app), path, DIM (path));
if (err)
return err;
/* Due to the above select we need to re-select our application. */
app->app_local->need_app_select = 1;
/* Get the two records. */
- err = iso7816_read_record (app->slot, 5, 1, 0, &buffer[0], &buflen[0]);
+ err = iso7816_read_record (app_get_slot (app), 5, 1, 0,
+ &buffer[0], &buflen[0]);
if (err)
return err;
if (all_zero_p (buffer[0], buflen[0]))
{
xfree (buffer[0]);
return gpg_error (GPG_ERR_NOT_FOUND);
}
- err = iso7816_read_record (app->slot, 6, 1, 0, &buffer[1], &buflen[1]);
+ err = iso7816_read_record (app_get_slot (app), 6, 1, 0,
+ &buffer[1], &buflen[1]);
if (err)
{
xfree (buffer[0]);
return err;
}
+ if ((flags & APP_READKEY_FLAG_INFO))
+ {
+ /* Not yet implemented but we won't get here for any regular
+ * keyrefs anyway, thus the top layer will provide the
+ * keypairinfo from the certificate. */
+ (void)ctrl;
+ }
+
if (pk && pklen)
{
*pk = make_canon_sexp_from_rsa_pk (buffer[0], buflen[0],
buffer[1], buflen[1],
pklen);
if (!*pk)
err = gpg_error_from_syserror ();
}
xfree (buffer[0]);
xfree (buffer[1]);
return err;
}
/* Handle the WRITEKEY command for NKS. This function expects a
canonical encoded S-expression with the public key in KEYDATA and
its length in KEYDATALEN. The only supported KEYID is
"$IFDAUTHKEY" to store the terminal key on the card. Bit 0 of
FLAGS indicates whether an existing key shall get overwritten.
PINCB and PINCB_ARG are the usual arguments for the pinentry
callback. */
static gpg_error_t
do_writekey (app_t app, ctrl_t ctrl,
const char *keyid, unsigned int flags,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const unsigned char *keydata, size_t keydatalen)
{
gpg_error_t err;
int force = (flags & 1);
const unsigned char *rsa_n = NULL;
const unsigned char *rsa_e = NULL;
size_t rsa_n_len, rsa_e_len;
unsigned int nbits;
(void)ctrl;
(void)pincb;
(void)pincb_arg;
if (!strcmp (keyid, "$IFDAUTHKEY") && app->app_local->nks_version >= 3)
;
else
return gpg_error (GPG_ERR_INV_ID);
- if (!force && !do_readkey (app, keyid, NULL, NULL))
+ if (!force && !do_readkey (app, ctrl, keyid, 0, NULL, NULL))
return gpg_error (GPG_ERR_EEXIST);
/* Parse the S-expression. */
err = get_rsa_pk_from_canon_sexp (keydata, keydatalen,
&rsa_n, &rsa_n_len, &rsa_e, &rsa_e_len);
if (err)
goto leave;
/* Check that the parameters match the requirements. */
nbits = app_help_count_bits (rsa_n, rsa_n_len);
if (nbits != 1024)
{
log_error (_("RSA modulus missing or not of size %d bits\n"), 1024);
err = gpg_error (GPG_ERR_BAD_PUBKEY);
goto leave;
}
nbits = app_help_count_bits (rsa_e, rsa_e_len);
if (nbits < 2 || nbits > 32)
{
log_error (_("RSA public exponent missing or larger than %d bits\n"),
32);
err = gpg_error (GPG_ERR_BAD_PUBKEY);
goto leave;
}
/* /\* Store them. *\/ */
/* err = verify_pin (app, 0, NULL, pincb, pincb_arg); */
/* if (err) */
/* goto leave; */
/* Send the MSE:Store_Public_Key. */
err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
/* mse = xtrymalloc (1000); */
/* mse[0] = 0x80; /\* Algorithm reference. *\/ */
/* mse[1] = 1; */
/* mse[2] = 0x17; */
/* mse[3] = 0x84; /\* Private key reference. *\/ */
/* mse[4] = 1; */
/* mse[5] = 0x77; */
/* mse[6] = 0x7F; /\* Public key parameter. *\/ */
/* mse[7] = 0x49; */
/* mse[8] = 0x81; */
/* mse[9] = 3 + 0x80 + 2 + rsa_e_len; */
/* mse[10] = 0x81; /\* RSA modulus of 128 byte. *\/ */
/* mse[11] = 0x81; */
/* mse[12] = rsa_n_len; */
/* memcpy (mse+12, rsa_n, rsa_n_len); */
/* mse[10] = 0x82; /\* RSA public exponent of up to 4 bytes. *\/ */
/* mse[12] = rsa_e_len; */
/* memcpy (mse+12, rsa_e, rsa_e_len); */
-/* err = iso7816_manage_security_env (app->slot, 0x81, 0xB6, */
+/* err = iso7816_manage_security_env (app_get_slot (app), 0x81, 0xB6, */
/* mse, sizeof mse); */
leave:
return err;
}
static gpg_error_t
basic_pin_checks (const char *pinvalue, int minlen, int maxlen)
{
if (strlen (pinvalue) < minlen)
{
log_error ("PIN is too short; minimum length is %d\n", minlen);
return gpg_error (GPG_ERR_BAD_PIN);
}
if (strlen (pinvalue) > maxlen)
{
log_error ("PIN is too large; maximum length is %d\n", maxlen);
return gpg_error (GPG_ERR_BAD_PIN);
}
return 0;
}
/* Verify the PIN if required. */
static gpg_error_t
verify_pin (app_t app, int pwid, const char *desc,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg)
{
pininfo_t pininfo;
int rc;
if (!desc)
desc = "PIN";
memset (&pininfo, 0, sizeof pininfo);
pininfo.fixedlen = -1;
pininfo.minlen = 6;
pininfo.maxlen = 16;
if (!opt.disable_pinpad
- && !iso7816_check_pinpad (app->slot, ISO7816_VERIFY, &pininfo) )
+ && !iso7816_check_pinpad (app_get_slot (app), ISO7816_VERIFY, &pininfo) )
{
rc = pincb (pincb_arg, desc, NULL);
if (rc)
{
log_info (_("PIN callback returned error: %s\n"),
gpg_strerror (rc));
return rc;
}
- rc = iso7816_verify_kp (app->slot, pwid, &pininfo);
+ rc = iso7816_verify_kp (app_get_slot (app), pwid, &pininfo);
pincb (pincb_arg, NULL, NULL); /* Dismiss the prompt. */
}
else
{
char *pinvalue;
rc = pincb (pincb_arg, desc, &pinvalue);
if (rc)
{
log_info ("PIN callback returned error: %s\n", gpg_strerror (rc));
return rc;
}
rc = basic_pin_checks (pinvalue, pininfo.minlen, pininfo.maxlen);
if (rc)
{
xfree (pinvalue);
return rc;
}
- rc = iso7816_verify (app->slot, pwid, pinvalue, strlen (pinvalue));
+ rc = iso7816_verify (app_get_slot (app), pwid,
+ pinvalue, strlen (pinvalue));
xfree (pinvalue);
}
if (rc)
{
if ( gpg_err_code (rc) == GPG_ERR_USE_CONDITIONS )
log_error (_("the NullPIN has not yet been changed\n"));
else
log_error ("verify PIN failed\n");
return rc;
}
return 0;
}
/* Create the signature and return the allocated result in OUTDATA.
If a PIN is required the PINCB will be used to ask for the PIN;
that callback should return the PIN in an allocated buffer and
store that in the 3rd argument. */
static gpg_error_t
do_sign (app_t app, const char *keyidstr, int hashalgo,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const void *indata, size_t indatalen,
unsigned char **outdata, size_t *outdatalen )
{
static unsigned char sha1_prefix[15] = /* Object ID is 1.3.14.3.2.26 */
{ 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e, 0x03,
0x02, 0x1a, 0x05, 0x00, 0x04, 0x14 };
static unsigned char rmd160_prefix[15] = /* Object ID is 1.3.36.3.2.1 */
{ 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x24, 0x03,
0x02, 0x01, 0x05, 0x00, 0x04, 0x14 };
int rc, i;
int is_sigg = 0;
int fid;
unsigned char kid;
unsigned char data[83]; /* Must be large enough for a SHA-1 digest
+ the largest OID prefix. */
size_t datalen;
if (!keyidstr || !*keyidstr)
return gpg_error (GPG_ERR_INV_VALUE);
switch (indatalen)
{
case 16: case 20: case 35: case 47: case 51: case 67: case 83: break;
default: return gpg_error (GPG_ERR_INV_VALUE);
}
/* Check that the provided ID is valid. This is not really needed
but we do it to enforce correct usage by the caller. */
if (!strncmp (keyidstr, "NKS-NKS3.", 9) )
;
else if (!strncmp (keyidstr, "NKS-DF01.", 9) )
;
else if (!strncmp (keyidstr, "NKS-SIGG.", 9) )
is_sigg = 1;
else
return gpg_error (GPG_ERR_INV_ID);
keyidstr += 9;
rc = switch_application (app, is_sigg);
if (rc)
return rc;
if (is_sigg && app->app_local->sigg_is_msig)
{
log_info ("mass signature cards are not allowed\n");
return gpg_error (GPG_ERR_NOT_SUPPORTED);
}
if (!hexdigitp (keyidstr) || !hexdigitp (keyidstr+1)
|| !hexdigitp (keyidstr+2) || !hexdigitp (keyidstr+3)
|| keyidstr[4])
return gpg_error (GPG_ERR_INV_ID);
fid = xtoi_4 (keyidstr);
for (i=0; filelist[i].fid; i++)
if (filelist[i].iskeypair && filelist[i].fid == fid)
break;
if (!filelist[i].fid)
return gpg_error (GPG_ERR_NOT_FOUND);
if (!filelist[i].issignkey)
return gpg_error (GPG_ERR_INV_ID);
kid = filelist[i].kid;
/* Prepare the DER object from INDATA. */
if (app->app_local->nks_version > 2 && (indatalen == 35
|| indatalen == 47
|| indatalen == 51
|| indatalen == 67
|| indatalen == 83))
{
/* The caller send data matching the length of the ASN.1 encoded
hash for SHA-{1,224,256,384,512}. Assume that is okay. */
assert (indatalen <= sizeof data);
memcpy (data, indata, indatalen);
datalen = indatalen;
}
else if (indatalen == 35)
{
/* Alright, the caller was so kind to send us an already
prepared DER object. This is for TCOS 2. */
if (hashalgo == GCRY_MD_SHA1 && !memcmp (indata, sha1_prefix, 15))
;
else if (hashalgo == GCRY_MD_RMD160 && !memcmp (indata,rmd160_prefix,15))
;
else
return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM);
memcpy (data, indata, indatalen);
datalen = 35;
}
else if (indatalen == 20)
{
if (hashalgo == GCRY_MD_SHA1)
memcpy (data, sha1_prefix, 15);
else if (hashalgo == GCRY_MD_RMD160)
memcpy (data, rmd160_prefix, 15);
else
return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM);
memcpy (data+15, indata, indatalen);
datalen = 35;
}
else
return gpg_error (GPG_ERR_INV_VALUE);
/* Send an MSE for PSO:Computer_Signature. */
if (app->app_local->nks_version > 2)
{
unsigned char mse[6];
mse[0] = 0x80; /* Algorithm reference. */
mse[1] = 1;
mse[2] = 2; /* RSA, card does pkcs#1 v1.5 padding, no ASN.1 check. */
mse[3] = 0x84; /* Private key reference. */
mse[4] = 1;
mse[5] = kid;
- rc = iso7816_manage_security_env (app->slot, 0x41, 0xB6,
+ rc = iso7816_manage_security_env (app_get_slot (app), 0x41, 0xB6,
mse, sizeof mse);
}
/* Verify using PW1.CH. */
if (!rc)
rc = verify_pin (app, 0, NULL, pincb, pincb_arg);
/* Compute the signature. */
if (!rc)
- rc = iso7816_compute_ds (app->slot, 0, data, datalen, 0,
+ rc = iso7816_compute_ds (app_get_slot (app), 0, data, datalen, 0,
outdata, outdatalen);
return rc;
}
/* Decrypt the data in INDATA and return the allocated result in OUTDATA.
If a PIN is required the PINCB will be used to ask for the PIN; it
should return the PIN in an allocated buffer and put it into PIN. */
static gpg_error_t
do_decipher (app_t app, const char *keyidstr,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const void *indata, size_t indatalen,
unsigned char **outdata, size_t *outdatalen,
unsigned int *r_info)
{
int rc, i;
int is_sigg = 0;
int fid;
int kid;
(void)r_info;
if (!keyidstr || !*keyidstr || !indatalen)
return gpg_error (GPG_ERR_INV_VALUE);
/* Check that the provided ID is valid. This is not really needed
but we do it to enforce correct usage by the caller. */
if (!strncmp (keyidstr, "NKS-NKS3.", 9) )
;
else if (!strncmp (keyidstr, "NKS-DF01.", 9) )
;
else if (!strncmp (keyidstr, "NKS-SIGG.", 9) )
is_sigg = 1;
else
return gpg_error (GPG_ERR_INV_ID);
keyidstr += 9;
rc = switch_application (app, is_sigg);
if (rc)
return rc;
if (!hexdigitp (keyidstr) || !hexdigitp (keyidstr+1)
|| !hexdigitp (keyidstr+2) || !hexdigitp (keyidstr+3)
|| keyidstr[4])
return gpg_error (GPG_ERR_INV_ID);
fid = xtoi_4 (keyidstr);
for (i=0; filelist[i].fid; i++)
if (filelist[i].iskeypair && filelist[i].fid == fid)
break;
if (!filelist[i].fid)
return gpg_error (GPG_ERR_NOT_FOUND);
if (!filelist[i].isenckey)
return gpg_error (GPG_ERR_INV_ID);
kid = filelist[i].kid;
if (app->app_local->nks_version > 2)
{
unsigned char mse[6];
mse[0] = 0x80; /* Algorithm reference. */
mse[1] = 1;
mse[2] = 0x0a; /* RSA no padding. (0x1A is pkcs#1.5 padding.) */
mse[3] = 0x84; /* Private key reference. */
mse[4] = 1;
mse[5] = kid;
- rc = iso7816_manage_security_env (app->slot, 0x41, 0xB8,
+ rc = iso7816_manage_security_env (app_get_slot (app), 0x41, 0xB8,
mse, sizeof mse);
}
else
{
static const unsigned char mse[] =
{
0x80, 1, 0x10, /* Select algorithm RSA. */
0x84, 1, 0x81 /* Select local secret key 1 for decryption. */
};
- rc = iso7816_manage_security_env (app->slot, 0xC1, 0xB8,
+ rc = iso7816_manage_security_env (app_get_slot (app), 0xC1, 0xB8,
mse, sizeof mse);
}
if (!rc)
rc = verify_pin (app, 0, NULL, pincb, pincb_arg);
/* Note that we need to use extended length APDUs for TCOS 3 cards.
Command chaining does not work. */
if (!rc)
- rc = iso7816_decipher (app->slot, app->app_local->nks_version > 2? 1:0,
+ rc = iso7816_decipher (app_get_slot (app),
+ app->app_local->nks_version > 2? 1:0,
indata, indatalen, 0, 0x81,
outdata, outdatalen);
return rc;
}
/* Parse a password ID string. Returns NULL on error or a string
suitable as passphrase prompt on success. On success stores the
reference value for the password at R_PWID and a flag indicating
that the SigG application is to be used at R_SIGG. If NEW_MODE is
true, the returned description is suitable for a new Password.
Supported values for PWIDSTR are:
PW1.CH - Global password 1
PW2.CH - Global password 2
PW1.CH.SIG - SigG password 1
PW2.CH.SIG - SigG password 2
*/
static const char *
parse_pwidstr (const char *pwidstr, int new_mode, int *r_sigg, int *r_pwid)
{
const char *desc;
if (!pwidstr)
desc = NULL;
else if (!strcmp (pwidstr, "PW1.CH"))
{
*r_sigg = 0;
*r_pwid = 0x00;
/* TRANSLATORS: Do not translate the "|*|" prefixes but keep
them verbatim at the start of the string. */
desc = (new_mode
? _("|N|Please enter a new PIN for the standard keys.")
: _("||Please enter the PIN for the standard keys."));
}
else if (!strcmp (pwidstr, "PW2.CH"))
{
*r_pwid = 0x01;
desc = (new_mode
? _("|NP|Please enter a new PIN Unblocking Code (PUK) "
"for the standard keys.")
: _("|P|Please enter the PIN Unblocking Code (PUK) "
"for the standard keys."));
}
else if (!strcmp (pwidstr, "PW1.CH.SIG"))
{
*r_pwid = 0x81;
*r_sigg = 1;
desc = (new_mode
? _("|N|Please enter a new PIN for the key to create "
"qualified signatures.")
: _("||Please enter the PIN for the key to create "
"qualified signatures."));
}
else if (!strcmp (pwidstr, "PW2.CH.SIG"))
{
*r_pwid = 0x83; /* Yes, that is 83 and not 82. */
*r_sigg = 1;
desc = (new_mode
? _("|NP|Please enter a new PIN Unblocking Code (PUK) "
"for the key to create qualified signatures.")
: _("|P|Please enter the PIN Unblocking Code (PUK) "
"for the key to create qualified signatures."));
}
else
{
*r_pwid = 0; /* Only to avoid gcc warning in calling function. */
desc = NULL; /* Error. */
}
return desc;
}
/* Handle the PASSWD command. See parse_pwidstr() for allowed values
for CHVNOSTR. */
static gpg_error_t
do_change_pin (app_t app, ctrl_t ctrl, const char *pwidstr,
unsigned int flags,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg)
{
gpg_error_t err;
char *newpin = NULL;
char *oldpin = NULL;
size_t newpinlen;
size_t oldpinlen;
int is_sigg;
const char *newdesc;
int pwid;
pininfo_t pininfo;
(void)ctrl;
/* The minimum length is enforced by TCOS, the maximum length is
just a reasonable value. */
memset (&pininfo, 0, sizeof pininfo);
pininfo.minlen = 6;
pininfo.maxlen = 16;
newdesc = parse_pwidstr (pwidstr, 1, &is_sigg, &pwid);
if (!newdesc)
return gpg_error (GPG_ERR_INV_ID);
if ((flags & APP_CHANGE_FLAG_CLEAR))
return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
err = switch_application (app, is_sigg);
if (err)
return err;
if ((flags & APP_CHANGE_FLAG_NULLPIN))
{
/* With the nullpin flag, we do not verify the PIN - it would
fail if the Nullpin is still set. */
oldpin = xtrycalloc (1, 6);
if (!oldpin)
{
err = gpg_error_from_syserror ();
goto leave;
}
oldpinlen = 6;
}
else
{
const char *desc;
int dummy1, dummy2;
if ((flags & APP_CHANGE_FLAG_RESET))
{
/* Reset mode: Ask for the alternate PIN. */
const char *altpwidstr;
if (!strcmp (pwidstr, "PW1.CH"))
altpwidstr = "PW2.CH";
else if (!strcmp (pwidstr, "PW2.CH"))
altpwidstr = "PW1.CH";
else if (!strcmp (pwidstr, "PW1.CH.SIG"))
altpwidstr = "PW2.CH.SIG";
else if (!strcmp (pwidstr, "PW2.CH.SIG"))
altpwidstr = "PW1.CH.SIG";
else
{
err = gpg_error (GPG_ERR_BUG);
goto leave;
}
desc = parse_pwidstr (altpwidstr, 0, &dummy1, &dummy2);
}
else
{
/* Regular change mode: Ask for the old PIN. */
desc = parse_pwidstr (pwidstr, 0, &dummy1, &dummy2);
}
err = pincb (pincb_arg, desc, &oldpin);
if (err)
{
log_error ("error getting old PIN: %s\n", gpg_strerror (err));
goto leave;
}
oldpinlen = strlen (oldpin);
err = basic_pin_checks (oldpin, pininfo.minlen, pininfo.maxlen);
if (err)
goto leave;
}
err = pincb (pincb_arg, newdesc, &newpin);
if (err)
{
log_error (_("error getting new PIN: %s\n"), gpg_strerror (err));
goto leave;
}
newpinlen = strlen (newpin);
err = basic_pin_checks (newpin, pininfo.minlen, pininfo.maxlen);
if (err)
goto leave;
if ((flags & APP_CHANGE_FLAG_RESET))
{
char *data;
size_t datalen = oldpinlen + newpinlen;
data = xtrymalloc (datalen);
if (!data)
{
err = gpg_error_from_syserror ();
goto leave;
}
memcpy (data, oldpin, oldpinlen);
memcpy (data+oldpinlen, newpin, newpinlen);
- err = iso7816_reset_retry_counter_with_rc (app->slot, pwid,
+ err = iso7816_reset_retry_counter_with_rc (app_get_slot (app), pwid,
data, datalen);
wipememory (data, datalen);
xfree (data);
}
else
- err = iso7816_change_reference_data (app->slot, pwid,
+ err = iso7816_change_reference_data (app_get_slot (app), pwid,
oldpin, oldpinlen,
newpin, newpinlen);
leave:
xfree (oldpin);
xfree (newpin);
return err;
}
/* Perform a simple verify operation. KEYIDSTR should be NULL or empty. */
static gpg_error_t
do_check_pin (app_t app, const char *pwidstr,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg)
{
gpg_error_t err;
int pwid;
int is_sigg;
const char *desc;
desc = parse_pwidstr (pwidstr, 0, &is_sigg, &pwid);
if (!desc)
return gpg_error (GPG_ERR_INV_ID);
err = switch_application (app, is_sigg);
if (err)
return err;
return verify_pin (app, pwid, desc, pincb, pincb_arg);
}
/* Return the version of the NKS application. */
static int
get_nks_version (int slot)
{
unsigned char *result = NULL;
size_t resultlen;
int type;
if (iso7816_apdu_direct (slot, "\x80\xaa\x06\x00\x00", 5, 0,
NULL, &result, &resultlen))
return 2; /* NKS 2 does not support this command. */
/* Example value: 04 11 19 22 21 6A 20 80 03 03 01 01 01 00 00 00
vv tt ccccccccccccccccc aa bb cc vvvvvvvvvvv xx
vendor (Philips) -+ | | | | | | |
chip type -----------+ | | | | | |
chip id ----------------+ | | | | |
card type (3 - tcos 3) -------------------+ | | | |
OS version of card type ---------------------+ | | |
OS release of card type ------------------------+ | |
OS vendor internal version ------------------------+ |
RFU -----------------------------------------------------------+
*/
if (resultlen < 16)
type = 0; /* Invalid data returned. */
else
type = result[8];
xfree (result);
return type;
}
/* If ENABLE_SIGG is true switch to the SigG application if not yet
active. If false switch to the NKS application if not yet active.
Returns 0 on success. */
static gpg_error_t
switch_application (app_t app, int enable_sigg)
{
gpg_error_t err;
if (((app->app_local->sigg_active && enable_sigg)
|| (!app->app_local->sigg_active && !enable_sigg))
&& !app->app_local->need_app_select)
return 0; /* Already switched. */
log_info ("app-nks: switching to %s\n", enable_sigg? "SigG":"NKS");
if (enable_sigg)
- err = iso7816_select_application (app->slot, aid_sigg, sizeof aid_sigg, 0);
+ err = iso7816_select_application (app_get_slot (app),
+ aid_sigg, sizeof aid_sigg, 0);
else
- err = iso7816_select_application (app->slot, aid_nks, sizeof aid_nks, 0);
+ err = iso7816_select_application (app_get_slot (app),
+ aid_nks, sizeof aid_nks, 0);
if (!err && enable_sigg && app->app_local->nks_version >= 3
&& !app->app_local->sigg_msig_checked)
{
/* Check whether this card is a mass signature card. */
unsigned char *buffer;
size_t buflen;
const unsigned char *tmpl;
size_t tmpllen;
app->app_local->sigg_msig_checked = 1;
app->app_local->sigg_is_msig = 1;
- err = iso7816_select_file (app->slot, 0x5349, 0);
+ err = iso7816_select_file (app_get_slot (app), 0x5349, 0);
if (!err)
- err = iso7816_read_record (app->slot, 1, 1, 0, &buffer, &buflen);
+ err = iso7816_read_record (app_get_slot (app), 1, 1, 0,
+ &buffer, &buflen);
if (!err)
{
tmpl = find_tlv (buffer, buflen, 0x7a, &tmpllen);
if (tmpl && tmpllen == 12
&& !memcmp (tmpl,
"\x93\x02\x00\x01\xA4\x06\x83\x01\x81\x83\x01\x83",
12))
app->app_local->sigg_is_msig = 0;
xfree (buffer);
}
if (app->app_local->sigg_is_msig)
log_info ("This is a mass signature card\n");
}
if (!err)
{
app->app_local->need_app_select = 0;
app->app_local->sigg_active = enable_sigg;
}
else
log_error ("app-nks: error switching to %s: %s\n",
enable_sigg? "SigG":"NKS", gpg_strerror (err));
return err;
}
/* Select the NKS application. */
gpg_error_t
app_select_nks (app_t app)
{
- int slot = app->slot;
+ int slot = app_get_slot (app);
int rc;
rc = iso7816_select_application (slot, aid_nks, sizeof aid_nks, 0);
if (!rc)
{
- app->apptype = "NKS";
+ app->apptype = APPTYPE_NKS;
app->app_local = xtrycalloc (1, sizeof *app->app_local);
if (!app->app_local)
{
rc = gpg_error (gpg_err_code_from_errno (errno));
goto leave;
}
app->app_local->nks_version = get_nks_version (slot);
if (opt.verbose)
log_info ("Detected NKS version: %d\n", app->app_local->nks_version);
app->fnc.deinit = do_deinit;
+ app->fnc.reselect = NULL;
app->fnc.learn_status = do_learn_status;
app->fnc.readcert = do_readcert;
app->fnc.readkey = do_readkey;
app->fnc.getattr = do_getattr;
app->fnc.setattr = NULL;
app->fnc.writekey = do_writekey;
app->fnc.genkey = NULL;
app->fnc.sign = do_sign;
app->fnc.auth = NULL;
app->fnc.decipher = do_decipher;
app->fnc.change_pin = do_change_pin;
app->fnc.check_pin = do_check_pin;
}
leave:
if (rc)
do_deinit (app);
return rc;
}
diff --git a/scd/app-openpgp.c b/scd/app-openpgp.c
index 1e904b578..767f29d26 100644
--- a/scd/app-openpgp.c
+++ b/scd/app-openpgp.c
@@ -1,5341 +1,5406 @@
/* app-openpgp.c - The OpenPGP card application.
* Copyright (C) 2003, 2004, 2005, 2007, 2008,
* 2009, 2013, 2014, 2015 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
/* Some notes:
CHV means Card Holder Verification and is nothing else than a PIN
or password. That term seems to have been used originally with GSM
cards. Version v2 of the specs changes the term to the clearer
term PW for password. We use the terms here interchangeable
because we do not want to change existing strings i18n wise.
Version 2 of the specs also drops the separate PW2 which was
required in v1 due to ISO requirements. It is now possible to have
one physical PW but two reference to it so that they can be
individually be verified (e.g. to implement a forced verification
for one key). Thus you will noticed the use of PW2 with the verify
command but not with change_reference_data because the latter
operates directly on the physical PW.
The Reset Code (RC) as implemented by v2 cards uses the same error
counter as the PW2 of v1 cards. By default no RC is set and thus
that error counter is set to 0. After setting the RC the error
counter will be initialized to 3.
*/
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <assert.h>
#include <time.h>
-#if GNUPG_MAJOR_VERSION == 1
-/* This is used with GnuPG version < 1.9. The code has been source
- copied from the current GnuPG >= 1.9 and is maintained over
- there. */
-#include "options.h"
-#include "errors.h"
-#include "memory.h"
-#include "cardglue.h"
-#else /* GNUPG_MAJOR_VERSION != 1 */
#include "scdaemon.h"
-#endif /* GNUPG_MAJOR_VERSION != 1 */
-
#include "../common/util.h"
#include "../common/i18n.h"
#include "iso7816.h"
-#include "app-common.h"
#include "../common/tlv.h"
#include "../common/host2net.h"
#include "../common/openpgpdefs.h"
+
+/* The AID of this application. */
+static char const openpgp_aid[] = { 0xD2, 0x76, 0x00, 0x01, 0x24, 0x01 };
+
+
/* A table describing the DOs of the card. */
static struct {
int tag;
int constructed;
int get_from; /* Constructed DO with this DO or 0 for direct access. */
unsigned int binary:1;
unsigned int dont_cache:1;
unsigned int flush_on_error:1;
unsigned int get_immediate_in_v11:1; /* Enable a hack to bypass the cache of
this data object if it is used in 1.1
and later versions of the card. This
does not work with composite DO and
is currently only useful for the CHV
status bytes. */
unsigned int try_extlen:2; /* Large object; try to use an extended
length APDU when !=0. The size is
determined by extcap.max_certlen_3
when == 1, and by extcap.max_special_do
when == 2. */
char *desc;
} data_objects[] = {
{ 0x005E, 0, 0, 1, 0, 0, 0, 2, "Login Data" },
{ 0x5F50, 0, 0, 0, 0, 0, 0, 2, "URL" },
{ 0x5F52, 0, 0, 1, 0, 0, 0, 0, "Historical Bytes" },
{ 0x0065, 1, 0, 1, 0, 0, 0, 0, "Cardholder Related Data"},
{ 0x005B, 0, 0x65, 0, 0, 0, 0, 0, "Name" },
{ 0x5F2D, 0, 0x65, 0, 0, 0, 0, 0, "Language preferences" },
{ 0x5F35, 0, 0x65, 0, 0, 0, 0, 0, "Salutation" },
{ 0x006E, 1, 0, 1, 0, 0, 0, 0, "Application Related Data" },
{ 0x004F, 0, 0x6E, 1, 0, 0, 0, 0, "AID" },
{ 0x0073, 1, 0, 1, 0, 0, 0, 0, "Discretionary Data Objects" },
{ 0x0047, 0, 0x6E, 1, 1, 0, 0, 0, "Card Capabilities" },
{ 0x00C0, 0, 0x6E, 1, 1, 0, 0, 0, "Extended Card Capabilities" },
{ 0x00C1, 0, 0x6E, 1, 1, 0, 0, 0, "Algorithm Attributes Signature" },
{ 0x00C2, 0, 0x6E, 1, 1, 0, 0, 0, "Algorithm Attributes Decryption" },
{ 0x00C3, 0, 0x6E, 1, 1, 0, 0, 0, "Algorithm Attributes Authentication" },
{ 0x00C4, 0, 0x6E, 1, 0, 1, 1, 0, "CHV Status Bytes" },
{ 0x00C5, 0, 0x6E, 1, 0, 0, 0, 0, "Fingerprints" },
{ 0x00C6, 0, 0x6E, 1, 0, 0, 0, 0, "CA Fingerprints" },
{ 0x00CD, 0, 0x6E, 1, 0, 0, 0, 0, "Generation time" },
{ 0x007A, 1, 0, 1, 0, 0, 0, 0, "Security Support Template" },
{ 0x0093, 0, 0x7A, 1, 1, 0, 0, 0, "Digital Signature Counter" },
{ 0x0101, 0, 0, 0, 0, 0, 0, 2, "Private DO 1"},
{ 0x0102, 0, 0, 0, 0, 0, 0, 2, "Private DO 2"},
{ 0x0103, 0, 0, 0, 0, 0, 0, 2, "Private DO 3"},
{ 0x0104, 0, 0, 0, 0, 0, 0, 2, "Private DO 4"},
{ 0x7F21, 1, 0, 1, 0, 0, 0, 1, "Cardholder certificate"},
/* V3.0 */
{ 0x7F74, 0, 0x6E, 1, 0, 0, 0, 0, "General Feature Management"},
{ 0x00D5, 0, 0, 1, 0, 0, 0, 0, "AES key data"},
{ 0x00D6, 0, 0x6E, 1, 0, 0, 0, 0, "UIF for Signature"},
{ 0x00D7, 0, 0x6E, 1, 0, 0, 0, 0, "UIF for Decryption"},
{ 0x00D8, 0, 0x6E, 1, 0, 0, 0, 0, "UIF for Authentication"},
{ 0x00F9, 0, 0, 1, 0, 0, 0, 0, "KDF data object"},
{ 0 }
};
/* Type of keys. */
typedef enum
{
KEY_TYPE_ECC,
KEY_TYPE_RSA,
}
key_type_t;
/* The format of RSA private keys. */
typedef enum
{
RSA_UNKNOWN_FMT,
RSA_STD,
RSA_STD_N,
RSA_CRT,
RSA_CRT_N
}
rsa_key_format_t;
/* One cache item for DOs. */
struct cache_s {
struct cache_s *next;
int tag;
size_t length;
unsigned char data[1];
};
/* Object with application (i.e. OpenPGP card) specific data. */
struct app_local_s {
/* A linked list with cached DOs. */
struct cache_s *cache;
/* Keep track of the public keys. */
struct
{
int read_done; /* True if we have at least tried to read them. */
unsigned char *key; /* This is a malloced buffer with a canonical
encoded S-expression encoding a public
key. Might be NULL if key is not
available. */
size_t keylen; /* The length of the above S-expression. This
is usually only required for cross checks
because the length of an S-expression is
implicitly available. */
+ unsigned char keygrip_str[41]; /* The keygrip, null terminated */
} pk[3];
unsigned char status_indicator; /* The card status indicator. */
unsigned int manufacturer:16; /* Manufacturer ID from the s/n. */
/* Keep track of the ISO card capabilities. */
struct
{
unsigned int cmd_chaining:1; /* Command chaining is supported. */
unsigned int ext_lc_le:1; /* Extended Lc and Le are supported. */
} cardcap;
/* Keep track of extended card capabilities. */
struct
{
unsigned int is_v2:1; /* Compatible to v2 or later. */
unsigned int extcap_v3:1; /* Extcap is in v3 format. */
unsigned int has_button:1; /* Has confirmation button or not. */
unsigned int sm_supported:1; /* Secure Messaging is supported. */
unsigned int get_challenge:1;
unsigned int key_import:1;
unsigned int change_force_chv:1;
unsigned int private_dos:1;
unsigned int algo_attr_change:1; /* Algorithm attributes changeable. */
unsigned int has_decrypt:1; /* Support symmetric decryption. */
unsigned int kdf_do:1; /* Support KDF DO. */
unsigned int sm_algo:2; /* Symmetric crypto algo for SM. */
unsigned int pin_blk2:1; /* PIN block 2 format supported. */
unsigned int mse:1; /* MSE command supported. */
unsigned int max_certlen_3:16;
unsigned int max_get_challenge:16; /* Maximum size for get_challenge. */
unsigned int max_special_do:16; /* Maximum size for special DOs. */
} extcap;
/* Flags used to control the application. */
struct
{
unsigned int no_sync:1; /* Do not sync CHV1 and CHV2 */
unsigned int def_chv2:1; /* Use 123456 for CHV2. */
} flags;
/* Pinpad request specified on card. */
struct
{
unsigned int specified:1;
int fixedlen_user;
int fixedlen_admin;
} pinpad;
struct
{
key_type_t key_type;
union {
struct {
unsigned int n_bits; /* Size of the modulus in bits. The rest
of this strucuire is only valid if
this is not 0. */
unsigned int e_bits; /* Size of the public exponent in bits. */
rsa_key_format_t format;
} rsa;
struct {
const char *curve;
int flags;
} ecc;
};
} keyattr[3];
};
#define ECC_FLAG_DJB_TWEAK (1 << 0)
#define ECC_FLAG_PUBKEY (1 << 1)
/***** Local prototypes *****/
static unsigned long convert_sig_counter_value (const unsigned char *value,
size_t valuelen);
static unsigned long get_sig_counter (app_t app);
static gpg_error_t do_auth (app_t app, const char *keyidstr,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const void *indata, size_t indatalen,
unsigned char **outdata, size_t *outdatalen);
static void parse_algorithm_attribute (app_t app, int keyno);
static gpg_error_t change_keyattr_from_string
(app_t app,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const void *value, size_t valuelen);
/* Deconstructor. */
static void
do_deinit (app_t app)
{
if (app && app->app_local)
{
struct cache_s *c, *c2;
int i;
for (c = app->app_local->cache; c; c = c2)
{
c2 = c->next;
xfree (c);
}
for (i=0; i < DIM (app->app_local->pk); i++)
{
xfree (app->app_local->pk[i].key);
app->app_local->pk[i].read_done = 0;
}
xfree (app->app_local);
app->app_local = NULL;
}
}
/* Wrapper around iso7816_get_data which first tries to get the data
from the cache. With GET_IMMEDIATE passed as true, the cache is
bypassed. With TRY_EXTLEN extended lengths APDUs are use if
supported by the card. */
static gpg_error_t
get_cached_data (app_t app, int tag,
unsigned char **result, size_t *resultlen,
int get_immediate, int try_extlen)
{
gpg_error_t err;
int i;
unsigned char *p;
size_t len;
struct cache_s *c;
int exmode;
*result = NULL;
*resultlen = 0;
if (!get_immediate)
{
for (c=app->app_local->cache; c; c = c->next)
if (c->tag == tag)
{
if(c->length)
{
p = xtrymalloc (c->length);
if (!p)
return gpg_error (gpg_err_code_from_errno (errno));
memcpy (p, c->data, c->length);
*result = p;
}
*resultlen = c->length;
return 0;
}
}
if (try_extlen && app->app_local->cardcap.ext_lc_le)
{
if (try_extlen == 1)
exmode = app->app_local->extcap.max_certlen_3;
else if (try_extlen == 2 && app->app_local->extcap.extcap_v3)
exmode = app->app_local->extcap.max_special_do;
else
exmode = 0;
}
else
exmode = 0;
- err = iso7816_get_data (app->slot, exmode, tag, &p, &len);
+ err = iso7816_get_data (app_get_slot (app), exmode, tag, &p, &len);
if (err)
return err;
if (len)
*result = p;
*resultlen = len;
/* Check whether we should cache this object. */
if (get_immediate)
return 0;
for (i=0; data_objects[i].tag; i++)
if (data_objects[i].tag == tag)
{
if (data_objects[i].dont_cache)
return 0;
break;
}
/* Okay, cache it. */
for (c=app->app_local->cache; c; c = c->next)
assert (c->tag != tag);
c = xtrymalloc (sizeof *c + len);
if (c)
{
if (len)
memcpy (c->data, p, len);
else
xfree (p);
c->length = len;
c->tag = tag;
c->next = app->app_local->cache;
app->app_local->cache = c;
}
return 0;
}
/* Remove DO at TAG from the cache. */
static void
flush_cache_item (app_t app, int tag)
{
struct cache_s *c, *cprev;
int i;
if (!app->app_local)
return;
for (c=app->app_local->cache, cprev=NULL; c ; cprev=c, c = c->next)
if (c->tag == tag)
{
if (cprev)
cprev->next = c->next;
else
app->app_local->cache = c->next;
xfree (c);
for (c=app->app_local->cache; c ; c = c->next)
{
assert (c->tag != tag); /* Oops: duplicated entry. */
}
return;
}
/* Try again if we have an outer tag. */
for (i=0; data_objects[i].tag; i++)
if (data_objects[i].tag == tag && data_objects[i].get_from
&& data_objects[i].get_from != tag)
flush_cache_item (app, data_objects[i].get_from);
}
/* Flush all entries from the cache which might be out of sync after
an error. */
static void
flush_cache_after_error (app_t app)
{
int i;
for (i=0; data_objects[i].tag; i++)
if (data_objects[i].flush_on_error)
flush_cache_item (app, data_objects[i].tag);
}
/* Flush the entire cache. */
static void
flush_cache (app_t app)
{
if (app && app->app_local)
{
struct cache_s *c, *c2;
for (c = app->app_local->cache; c; c = c2)
{
c2 = c->next;
xfree (c);
}
app->app_local->cache = NULL;
}
}
/* Get the DO identified by TAG from the card in SLOT and return a
buffer with its content in RESULT and NBYTES. The return value is
NULL if not found or a pointer which must be used to release the
buffer holding value. */
static void *
get_one_do (app_t app, int tag, unsigned char **result, size_t *nbytes,
int *r_rc)
{
int rc, i;
unsigned char *buffer;
size_t buflen;
unsigned char *value;
size_t valuelen;
int dummyrc;
int exmode;
if (!r_rc)
r_rc = &dummyrc;
*result = NULL;
*nbytes = 0;
*r_rc = 0;
for (i=0; data_objects[i].tag && data_objects[i].tag != tag; i++)
;
if (app->appversion > 0x0100 && data_objects[i].get_immediate_in_v11)
{
exmode = 0;
- rc = iso7816_get_data (app->slot, exmode, tag, &buffer, &buflen);
+ rc = iso7816_get_data (app_get_slot (app), exmode, tag, &buffer, &buflen);
if (rc)
{
*r_rc = rc;
return NULL;
}
*result = buffer;
*nbytes = buflen;
return buffer;
}
value = NULL;
rc = -1;
if (data_objects[i].tag && data_objects[i].get_from)
{
rc = get_cached_data (app, data_objects[i].get_from,
&buffer, &buflen,
(data_objects[i].dont_cache
|| data_objects[i].get_immediate_in_v11),
data_objects[i].try_extlen);
if (!rc)
{
const unsigned char *s;
s = find_tlv_unchecked (buffer, buflen, tag, &valuelen);
if (!s)
value = NULL; /* not found */
else if (valuelen > buflen - (s - buffer))
{
log_error ("warning: constructed DO too short\n");
value = NULL;
xfree (buffer); buffer = NULL;
}
else
value = buffer + (s - buffer);
}
}
if (!value) /* Not in a constructed DO, try simple. */
{
rc = get_cached_data (app, tag, &buffer, &buflen,
(data_objects[i].dont_cache
|| data_objects[i].get_immediate_in_v11),
data_objects[i].try_extlen);
if (!rc)
{
value = buffer;
valuelen = buflen;
}
}
if (!rc)
{
*nbytes = valuelen;
*result = value;
return buffer;
}
*r_rc = rc;
return NULL;
}
static void
dump_all_do (int slot)
{
int rc, i, j;
unsigned char *buffer;
size_t buflen;
for (i=0; data_objects[i].tag; i++)
{
if (data_objects[i].get_from)
continue;
/* We don't try extended length APDU because such large DO would
be pretty useless in a log file. */
rc = iso7816_get_data (slot, 0, data_objects[i].tag, &buffer, &buflen);
if (gpg_err_code (rc) == GPG_ERR_NO_OBJ)
;
else if (rc)
log_info ("DO '%s' not available: %s\n",
data_objects[i].desc, gpg_strerror (rc));
else
{
if (data_objects[i].binary)
{
log_info ("DO '%s': ", data_objects[i].desc);
log_printhex (buffer, buflen, "");
}
else
log_info ("DO '%s': '%.*s'\n",
data_objects[i].desc,
(int)buflen, buffer); /* FIXME: sanitize */
if (data_objects[i].constructed)
{
for (j=0; data_objects[j].tag; j++)
{
const unsigned char *value;
size_t valuelen;
if (j==i || data_objects[i].tag != data_objects[j].get_from)
continue;
value = find_tlv_unchecked (buffer, buflen,
data_objects[j].tag, &valuelen);
if (!value)
; /* not found */
else if (valuelen > buflen - (value - buffer))
log_error ("warning: constructed DO too short\n");
else
{
if (data_objects[j].binary)
{
log_info ("DO '%s': ", data_objects[j].desc);
if (valuelen > 200)
log_info ("[%u]\n", (unsigned int)valuelen);
else
log_printhex (value, valuelen, "");
}
else
log_info ("DO '%s': '%.*s'\n",
data_objects[j].desc,
(int)valuelen, value); /* FIXME: sanitize */
}
}
}
}
xfree (buffer); buffer = NULL;
}
}
/* Count the number of bits, assuming the A represents an unsigned big
integer of length LEN bytes. */
static unsigned int
count_bits (const unsigned char *a, size_t len)
{
unsigned int n = len * 8;
int i;
for (; len && !*a; len--, a++, n -=8)
;
if (len)
{
for (i=7; i && !(*a & (1<<i)); i--)
n--;
}
return n;
}
/* GnuPG makes special use of the login-data DO, this function parses
the login data to store the flags for later use. It may be called
at any time and should be called after changing the login-data DO.
Everything up to a LF is considered a mailbox or account name. If
the first LF is followed by DC4 (0x14) control sequence are
expected up to the next LF. Control sequences are separated by FS
(0x18) and consist of key=value pairs. There are two keys defined:
F=<flags>
Where FLAGS is a plain hexadecimal number representing flag values.
The lsb is here the rightmost bit. Defined flags bits are:
Bit 0 = CHV1 and CHV2 are not synchronized
Bit 1 = CHV2 has been set to the default PIN of "123456"
(this implies that bit 0 is also set).
P=<pinpad-request>
Where PINPAD_REQUEST is in the format of: <n> or <n>,<m>.
N for user PIN, M for admin PIN. If M is missing it means M=N.
0 means to force not to use pinpad.
*/
static void
parse_login_data (app_t app)
{
unsigned char *buffer, *p;
size_t buflen, len;
void *relptr;
/* Set defaults. */
app->app_local->flags.no_sync = 0;
app->app_local->flags.def_chv2 = 0;
app->app_local->pinpad.specified = 0;
app->app_local->pinpad.fixedlen_user = -1;
app->app_local->pinpad.fixedlen_admin = -1;
/* Read the DO. */
relptr = get_one_do (app, 0x005E, &buffer, &buflen, NULL);
if (!relptr)
return; /* Ooops. */
for (; buflen; buflen--, buffer++)
if (*buffer == '\n')
break;
if (buflen < 2 || buffer[1] != '\x14')
{
xfree (relptr);
return; /* No control sequences. */
}
buflen--;
buffer++;
do
{
buflen--;
buffer++;
if (buflen > 1 && *buffer == 'F' && buffer[1] == '=')
{
/* Flags control sequence found. */
int lastdig = 0;
/* For now we are only interested in the last digit, so skip
any leading digits but bail out on invalid characters. */
for (p=buffer+2, len = buflen-2; len && hexdigitp (p); p++, len--)
lastdig = xtoi_1 (p);
buffer = p;
buflen = len;
if (len && !(*p == '\n' || *p == '\x18'))
goto next; /* Invalid characters in field. */
app->app_local->flags.no_sync = !!(lastdig & 1);
app->app_local->flags.def_chv2 = (lastdig & 3) == 3;
}
else if (buflen > 1 && *buffer == 'P' && buffer[1] == '=')
{
/* Pinpad request control sequence found. */
buffer += 2;
buflen -= 2;
if (buflen)
{
if (digitp (buffer))
{
char *q;
int n, m;
n = strtol (buffer, &q, 10);
if (q >= (char *)buffer + buflen
|| *q == '\x18' || *q == '\n')
m = n;
else
{
if (*q++ != ',' || !digitp (q))
goto next;
m = strtol (q, &q, 10);
}
if (buflen < ((unsigned char *)q - buffer))
break;
buflen -= ((unsigned char *)q - buffer);
buffer = q;
if (buflen && !(*buffer == '\n' || *buffer == '\x18'))
goto next;
app->app_local->pinpad.specified = 1;
app->app_local->pinpad.fixedlen_user = n;
app->app_local->pinpad.fixedlen_admin = m;
}
}
}
next:
/* Skip to FS (0x18) or LF (\n). */
for (; buflen && *buffer != '\x18' && *buffer != '\n'; buflen--)
buffer++;
}
while (buflen && *buffer != '\n');
xfree (relptr);
}
#define MAX_ARGS_STORE_FPR 3
/* Note, that FPR must be at least 20 bytes. */
static gpg_error_t
store_fpr (app_t app, int keynumber, u32 timestamp, unsigned char *fpr,
int algo, ...)
{
unsigned int n, nbits;
unsigned char *buffer, *p;
int tag, tag2;
int rc;
const unsigned char *m[MAX_ARGS_STORE_FPR];
size_t mlen[MAX_ARGS_STORE_FPR];
va_list ap;
int argc;
int i;
n = 6; /* key packet version, 4-byte timestamps, and algorithm */
if (algo == PUBKEY_ALGO_ECDH)
argc = 3;
else
argc = 2;
va_start (ap, algo);
for (i = 0; i < argc; i++)
{
m[i] = va_arg (ap, const unsigned char *);
mlen[i] = va_arg (ap, size_t);
if (algo == PUBKEY_ALGO_RSA || i == 1)
n += 2;
n += mlen[i];
}
va_end (ap);
p = buffer = xtrymalloc (3 + n);
if (!buffer)
return gpg_error_from_syserror ();
*p++ = 0x99; /* ctb */
*p++ = n >> 8; /* 2 byte length header */
*p++ = n;
*p++ = 4; /* key packet version */
*p++ = timestamp >> 24;
*p++ = timestamp >> 16;
*p++ = timestamp >> 8;
*p++ = timestamp;
*p++ = algo;
for (i = 0; i < argc; i++)
{
if (algo == PUBKEY_ALGO_RSA || i == 1)
{
nbits = count_bits (m[i], mlen[i]);
*p++ = nbits >> 8;
*p++ = nbits;
}
memcpy (p, m[i], mlen[i]);
p += mlen[i];
}
gcry_md_hash_buffer (GCRY_MD_SHA1, fpr, buffer, n+3);
xfree (buffer);
tag = (app->appversion > 0x0007? 0xC7 : 0xC6) + keynumber;
flush_cache_item (app, 0xC5);
tag2 = 0xCE + keynumber;
flush_cache_item (app, 0xCD);
- rc = iso7816_put_data (app->slot, 0, tag, fpr, 20);
+ rc = iso7816_put_data (app_get_slot (app), 0, tag, fpr, 20);
if (rc)
log_error (_("failed to store the fingerprint: %s\n"),gpg_strerror (rc));
if (!rc && app->appversion > 0x0100)
{
unsigned char buf[4];
buf[0] = timestamp >> 24;
buf[1] = timestamp >> 16;
buf[2] = timestamp >> 8;
buf[3] = timestamp;
- rc = iso7816_put_data (app->slot, 0, tag2, buf, 4);
+ rc = iso7816_put_data (app_get_slot (app), 0, tag2, buf, 4);
if (rc)
log_error (_("failed to store the creation date: %s\n"),
gpg_strerror (rc));
}
return rc;
}
static void
send_fpr_if_not_null (ctrl_t ctrl, const char *keyword,
int number, const unsigned char *fpr)
{
int i;
char buf[41];
char numbuf[25];
for (i=0; i < 20 && !fpr[i]; i++)
;
if (i==20)
return; /* All zero. */
bin2hex (fpr, 20, buf);
if (number == -1)
*numbuf = 0; /* Don't print the key number */
else
sprintf (numbuf, "%d", number);
send_status_info (ctrl, keyword,
numbuf, (size_t)strlen(numbuf),
buf, (size_t)strlen (buf), NULL, 0);
}
static void
send_fprtime_if_not_null (ctrl_t ctrl, const char *keyword,
int number, const unsigned char *stamp)
{
char numbuf1[50], numbuf2[50];
unsigned long value;
value = buf32_to_ulong (stamp);
if (!value)
return;
sprintf (numbuf1, "%d", number);
sprintf (numbuf2, "%lu", value);
send_status_info (ctrl, keyword,
numbuf1, (size_t)strlen(numbuf1),
numbuf2, (size_t)strlen(numbuf2), NULL, 0);
}
static void
send_key_data (ctrl_t ctrl, const char *name,
const unsigned char *a, size_t alen)
{
char *buffer, *buf;
size_t buflen;
buffer = buf = bin2hex (a, alen, NULL);
if (!buffer)
{
log_error ("memory allocation error in send_key_data\n");
return;
}
buflen = strlen (buffer);
/* 768 is the hexified size for the modulus of an 3072 bit key. We
use extra chunks to transmit larger data (i.e for 4096 bit). */
for ( ;buflen > 768; buflen -= 768, buf += 768)
send_status_info (ctrl, "KEY-DATA",
"-", 1,
buf, 768,
NULL, 0);
send_status_info (ctrl, "KEY-DATA",
name, (size_t)strlen(name),
buf, buflen,
NULL, 0);
xfree (buffer);
}
static void
send_key_attr (ctrl_t ctrl, app_t app, const char *keyword, int keyno)
{
char buffer[200];
assert (keyno >=0 && keyno < DIM(app->app_local->keyattr));
if (app->app_local->keyattr[keyno].key_type == KEY_TYPE_RSA)
snprintf (buffer, sizeof buffer, "%d 1 rsa%u %u %d",
keyno+1,
app->app_local->keyattr[keyno].rsa.n_bits,
app->app_local->keyattr[keyno].rsa.e_bits,
app->app_local->keyattr[keyno].rsa.format);
else if (app->app_local->keyattr[keyno].key_type == KEY_TYPE_ECC)
{
snprintf (buffer, sizeof buffer, "%d %d %s",
keyno+1,
keyno==1? PUBKEY_ALGO_ECDH :
(app->app_local->keyattr[keyno].ecc.flags & ECC_FLAG_DJB_TWEAK)?
PUBKEY_ALGO_EDDSA : PUBKEY_ALGO_ECDSA,
app->app_local->keyattr[keyno].ecc.curve);
}
else
snprintf (buffer, sizeof buffer, "%d 0 0 UNKNOWN", keyno+1);
send_status_direct (ctrl, keyword, buffer);
}
#define RSA_SMALL_SIZE_KEY 1952
#define RSA_SMALL_SIZE_OP 2048
static int
determine_rsa_response (app_t app, int keyno)
{
int size;
size = 2 + 3 /* header */
+ 4 /* tag+len */ + (app->app_local->keyattr[keyno].rsa.n_bits+7)/8
+ 2 /* tag+len */ + (app->app_local->keyattr[keyno].rsa.e_bits+7)/8;
return size;
}
/* Implement the GETATTR command. This is similar to the LEARN
command but returns just one value via the status interface. */
static gpg_error_t
do_getattr (app_t app, ctrl_t ctrl, const char *name)
{
static struct {
const char *name;
int tag;
int special;
} table[] = {
{ "DISP-NAME", 0x005B },
{ "LOGIN-DATA", 0x005E },
{ "DISP-LANG", 0x5F2D },
{ "DISP-SEX", 0x5F35 },
{ "PUBKEY-URL", 0x5F50 },
{ "KEY-FPR", 0x00C5, 3 },
{ "KEY-TIME", 0x00CD, 4 },
{ "KEY-ATTR", 0x0000, -5 },
{ "CA-FPR", 0x00C6, 3 },
{ "CHV-STATUS", 0x00C4, 1 },
{ "SIG-COUNTER", 0x0093, 2 },
{ "SERIALNO", 0x004F, -1 },
{ "AID", 0x004F },
{ "EXTCAP", 0x0000, -2 },
{ "PRIVATE-DO-1", 0x0101 },
{ "PRIVATE-DO-2", 0x0102 },
{ "PRIVATE-DO-3", 0x0103 },
{ "PRIVATE-DO-4", 0x0104 },
{ "$AUTHKEYID", 0x0000, -3 },
+ { "$ENCRKEYID", 0x0000, -6 },
+ { "$SIGNKEYID", 0x0000, -7 },
{ "$DISPSERIALNO",0x0000, -4 },
{ "UIF-1", 0x00D6, 0 },
{ "UIF-2", 0x00D7, 0 },
{ "UIF-3", 0x00D8, 0 },
{ "KDF", 0x00F9 },
{ NULL, 0 }
};
int idx, i, rc;
void *relptr;
unsigned char *value;
size_t valuelen;
for (idx=0; table[idx].name && strcmp (table[idx].name, name); idx++)
;
if (!table[idx].name)
return gpg_error (GPG_ERR_INV_NAME);
if (table[idx].special == -1)
{
/* The serial number is very special. We could have used the
AID DO to retrieve it. The AID DO is available anyway but
not hex formatted. */
char *serial = app_get_serialno (app);
if (serial)
{
send_status_direct (ctrl, "SERIALNO", serial);
xfree (serial);
}
return 0;
}
if (table[idx].special == -2)
{
char tmp[110];
snprintf (tmp, sizeof tmp,
"gc=%d ki=%d fc=%d pd=%d mcl3=%u aac=%d "
"sm=%d si=%u dec=%d bt=%d kdf=%d",
app->app_local->extcap.get_challenge,
app->app_local->extcap.key_import,
app->app_local->extcap.change_force_chv,
app->app_local->extcap.private_dos,
app->app_local->extcap.max_certlen_3,
app->app_local->extcap.algo_attr_change,
(app->app_local->extcap.sm_supported
? (app->app_local->extcap.sm_algo == 0? CIPHER_ALGO_3DES :
(app->app_local->extcap.sm_algo == 1?
CIPHER_ALGO_AES : CIPHER_ALGO_AES256))
: 0),
app->app_local->status_indicator,
app->app_local->extcap.has_decrypt,
app->app_local->extcap.has_button,
app->app_local->extcap.kdf_do);
send_status_info (ctrl, table[idx].name, tmp, strlen (tmp), NULL, 0);
return 0;
}
if (table[idx].special == -3)
{
char const tmp[] = "OPENPGP.3";
send_status_info (ctrl, table[idx].name, tmp, strlen (tmp), NULL, 0);
return 0;
}
if (table[idx].special == -4)
{
char *serial = app_get_serialno (app);
if (serial)
{
if (strlen (serial) > 16+12)
{
send_status_info (ctrl, table[idx].name, serial+16, 12, NULL, 0);
xfree (serial);
return 0;
}
xfree (serial);
}
return gpg_error (GPG_ERR_INV_NAME);
}
if (table[idx].special == -5)
{
for (i=0; i < 3; i++)
send_key_attr (ctrl, app, table[idx].name, i);
return 0;
}
+ if (table[idx].special == -6)
+ {
+ char const tmp[] = "OPENPGP.2";
+ send_status_info (ctrl, table[idx].name, tmp, strlen (tmp), NULL, 0);
+ return 0;
+ }
+ if (table[idx].special == -7)
+ {
+ char const tmp[] = "OPENPGP.1";
+ send_status_info (ctrl, table[idx].name, tmp, strlen (tmp), NULL, 0);
+ return 0;
+ }
relptr = get_one_do (app, table[idx].tag, &value, &valuelen, &rc);
if (relptr)
{
if (table[idx].special == 1)
{
char numbuf[7*23];
for (i=0,*numbuf=0; i < valuelen && i < 7; i++)
sprintf (numbuf+strlen (numbuf), " %d", value[i]);
send_status_info (ctrl, table[idx].name,
numbuf, strlen (numbuf), NULL, 0);
}
else if (table[idx].special == 2)
{
char numbuf[50];
sprintf (numbuf, "%lu", convert_sig_counter_value (value, valuelen));
send_status_info (ctrl, table[idx].name,
numbuf, strlen (numbuf), NULL, 0);
}
else if (table[idx].special == 3)
{
if (valuelen >= 60)
for (i=0; i < 3; i++)
send_fpr_if_not_null (ctrl, table[idx].name, i+1, value+i*20);
}
else if (table[idx].special == 4)
{
if (valuelen >= 12)
for (i=0; i < 3; i++)
send_fprtime_if_not_null (ctrl, table[idx].name, i+1, value+i*4);
}
else
send_status_info (ctrl, table[idx].name, value, valuelen, NULL, 0);
xfree (relptr);
}
return rc;
}
/* Return the DISP-NAME without any padding characters. Caller must
* free the result. If not found or empty NULL is returned. */
static char *
get_disp_name (app_t app)
{
int rc;
void *relptr;
unsigned char *value;
size_t valuelen;
char *string;
char *p, *given;
char *result;
relptr = get_one_do (app, 0x005B, &value, &valuelen, &rc);
if (!relptr)
return NULL;
string = xtrymalloc (valuelen + 1);
if (!string)
{
xfree (relptr);
return NULL;
}
memcpy (string, value, valuelen);
string[valuelen] = 0;
xfree (relptr);
/* Swap surname and given name. */
given = strstr (string, "<<");
for (p = string; *p; p++)
if (*p == '<')
*p = ' ';
if (given && given[2])
{
*given = 0;
given += 2;
result = strconcat (given, " ", string, NULL);
}
else
{
result = string;
string = NULL;
}
xfree (string);
return result;
}
/* Return the pretty formatted serialnumber. On error NULL is
* returned. */
static char *
get_disp_serialno (app_t app)
{
char *serial = app_get_serialno (app);
/* For our OpenPGP cards we do not want to show the entire serial
* number but a nicely reformatted actual serial number. */
if (serial && strlen (serial) > 16+12)
{
memmove (serial, serial+16, 4);
serial[4] = ' ';
/* memmove (serial+5, serial+20, 4); */
/* serial[9] = ' '; */
/* memmove (serial+10, serial+24, 4); */
/* serial[14] = 0; */
memmove (serial+5, serial+20, 8);
serial[13] = 0;
}
return serial;
}
/* Return the number of remaining tries for the standard or the admin
* pw. Returns -1 on card error. */
static int
get_remaining_tries (app_t app, int adminpw)
{
void *relptr;
unsigned char *value;
size_t valuelen;
int remaining;
relptr = get_one_do (app, 0x00C4, &value, &valuelen, NULL);
if (!relptr || valuelen < 7)
{
log_error (_("error retrieving CHV status from card\n"));
xfree (relptr);
return -1;
}
remaining = value[adminpw? 6 : 4];
xfree (relptr);
return remaining;
}
/* Retrieve the fingerprint from the card inserted in SLOT and write
the according hex representation to FPR. Caller must have provide
a buffer at FPR of least 41 bytes. Returns 0 on success or an
error code. */
-#if GNUPG_MAJOR_VERSION > 1
static gpg_error_t
retrieve_fpr_from_card (app_t app, int keyno, char *fpr)
{
gpg_error_t err = 0;
void *relptr;
unsigned char *value;
size_t valuelen;
assert (keyno >=0 && keyno <= 2);
relptr = get_one_do (app, 0x00C5, &value, &valuelen, NULL);
if (relptr && valuelen >= 60)
bin2hex (value+keyno*20, 20, fpr);
else
err = gpg_error (GPG_ERR_NOT_FOUND);
xfree (relptr);
return err;
}
-#endif /*GNUPG_MAJOR_VERSION > 1*/
/* Retrieve the public key material for the RSA key, whose fingerprint
is FPR, from gpg output, which can be read through the stream FP.
The RSA modulus will be stored at the address of M and MLEN, the
public exponent at E and ELEN. Returns zero on success, an error
code on failure. Caller must release the allocated buffers at M
and E if the function returns success. */
-#if GNUPG_MAJOR_VERSION > 1
static gpg_error_t
retrieve_key_material (FILE *fp, const char *hexkeyid,
const unsigned char **m, size_t *mlen,
const unsigned char **e, size_t *elen)
{
gcry_error_t err = 0;
char *line = NULL; /* read_line() buffer. */
size_t line_size = 0; /* Helper for for read_line. */
int found_key = 0; /* Helper to find a matching key. */
unsigned char *m_new = NULL;
unsigned char *e_new = NULL;
size_t m_new_n = 0;
size_t e_new_n = 0;
/* Loop over all records until we have found the subkey
corresponding to the fingerprint. Inm general the first record
should be the pub record, but we don't rely on that. Given that
we only need to look at one key, it is sufficient to compare the
keyid so that we don't need to look at "fpr" records. */
for (;;)
{
char *p;
char *fields[6] = { NULL, NULL, NULL, NULL, NULL, NULL };
int nfields;
size_t max_length;
gcry_mpi_t mpi;
int i;
max_length = 4096;
i = read_line (fp, &line, &line_size, &max_length);
if (!i)
break; /* EOF. */
if (i < 0)
{
err = gpg_error_from_syserror ();
goto leave; /* Error. */
}
if (!max_length)
{
err = gpg_error (GPG_ERR_TRUNCATED);
goto leave; /* Line truncated - we better stop processing. */
}
/* Parse the line into fields. */
for (nfields=0, p=line; p && nfields < DIM (fields); nfields++)
{
fields[nfields] = p;
p = strchr (p, ':');
if (p)
*(p++) = 0;
}
if (!nfields)
continue; /* No fields at all - skip line. */
if (!found_key)
{
if ( (!strcmp (fields[0], "sub") || !strcmp (fields[0], "pub") )
&& nfields > 4 && !strcmp (fields[4], hexkeyid))
found_key = 1;
continue;
}
if ( !strcmp (fields[0], "sub") || !strcmp (fields[0], "pub") )
break; /* Next key - stop. */
if ( strcmp (fields[0], "pkd") )
continue; /* Not a key data record. */
if ( nfields < 4 || (i = atoi (fields[1])) < 0 || i > 1
|| (!i && m_new) || (i && e_new))
{
err = gpg_error (GPG_ERR_GENERAL);
goto leave; /* Error: Invalid key data record or not an RSA key. */
}
err = gcry_mpi_scan (&mpi, GCRYMPI_FMT_HEX, fields[3], 0, NULL);
if (err)
mpi = NULL;
else if (!i)
err = gcry_mpi_aprint (GCRYMPI_FMT_STD, &m_new, &m_new_n, mpi);
else
err = gcry_mpi_aprint (GCRYMPI_FMT_STD, &e_new, &e_new_n, mpi);
gcry_mpi_release (mpi);
if (err)
goto leave;
}
if (m_new && e_new)
{
*m = m_new;
*mlen = m_new_n;
m_new = NULL;
*e = e_new;
*elen = e_new_n;
e_new = NULL;
}
else
err = gpg_error (GPG_ERR_GENERAL);
leave:
xfree (m_new);
xfree (e_new);
xfree (line);
return err;
}
-#endif /*GNUPG_MAJOR_VERSION > 1*/
static gpg_error_t
rsa_read_pubkey (app_t app, ctrl_t ctrl, u32 created_at, int keyno,
const unsigned char *data, size_t datalen, gcry_sexp_t *r_sexp)
{
gpg_error_t err;
const unsigned char *m, *e;
size_t mlen, elen;
unsigned char *mbuf = NULL, *ebuf = NULL;
m = find_tlv (data, datalen, 0x0081, &mlen);
if (!m)
{
log_error (_("response does not contain the RSA modulus\n"));
return gpg_error (GPG_ERR_CARD);
}
e = find_tlv (data, datalen, 0x0082, &elen);
if (!e)
{
log_error (_("response does not contain the RSA public exponent\n"));
return gpg_error (GPG_ERR_CARD);
}
if (ctrl)
{
send_key_data (ctrl, "n", m, mlen);
send_key_data (ctrl, "e", e, elen);
}
for (; mlen && !*m; mlen--, m++) /* strip leading zeroes */
;
for (; elen && !*e; elen--, e++) /* strip leading zeroes */
;
if (ctrl)
{
unsigned char fprbuf[20];
err = store_fpr (app, keyno, created_at, fprbuf, PUBKEY_ALGO_RSA,
m, mlen, e, elen);
if (err)
return err;
send_fpr_if_not_null (ctrl, "KEY-FPR", -1, fprbuf);
}
mbuf = xtrymalloc (mlen + 1);
if (!mbuf)
{
err = gpg_error_from_syserror ();
goto leave;
}
/* Prepend numbers with a 0 if needed. */
if (mlen && (*m & 0x80))
{
*mbuf = 0;
memcpy (mbuf+1, m, mlen);
mlen++;
}
else
memcpy (mbuf, m, mlen);
ebuf = xtrymalloc (elen + 1);
if (!ebuf)
{
err = gpg_error_from_syserror ();
goto leave;
}
/* Prepend numbers with a 0 if needed. */
if (elen && (*e & 0x80))
{
*ebuf = 0;
memcpy (ebuf+1, e, elen);
elen++;
}
else
memcpy (ebuf, e, elen);
err = gcry_sexp_build (r_sexp, NULL, "(public-key(rsa(n%b)(e%b)))",
(int)mlen, mbuf, (int)elen, ebuf);
leave:
xfree (mbuf);
xfree (ebuf);
return err;
}
/* Determine KDF hash algorithm and KEK encryption algorithm by CURVE. */
static const unsigned char*
ecdh_params (const char *curve)
{
unsigned int nbits;
openpgp_curve_to_oid (curve, &nbits);
/* See RFC-6637 for those constants.
0x03: Number of bytes
0x01: Version for this parameter format
KEK digest algorithm
KEK cipher algorithm
*/
if (nbits <= 256)
return (const unsigned char*)"\x03\x01\x08\x07";
else if (nbits <= 384)
return (const unsigned char*)"\x03\x01\x09\x09";
else
return (const unsigned char*)"\x03\x01\x0a\x09";
}
static gpg_error_t
ecc_read_pubkey (app_t app, ctrl_t ctrl, u32 created_at, int keyno,
const unsigned char *data, size_t datalen, gcry_sexp_t *r_sexp)
{
gpg_error_t err;
unsigned char *qbuf = NULL;
const unsigned char *ecc_q;
size_t ecc_q_len;
gcry_mpi_t oid = NULL;
int n;
const char *curve;
const char *oidstr;
const unsigned char *oidbuf;
size_t oid_len;
int algo;
const char *format;
ecc_q = find_tlv (data, datalen, 0x0086, &ecc_q_len);
if (!ecc_q)
{
log_error (_("response does not contain the EC public key\n"));
return gpg_error (GPG_ERR_CARD);
}
curve = app->app_local->keyattr[keyno].ecc.curve;
oidstr = openpgp_curve_to_oid (curve, NULL);
err = openpgp_oid_from_str (oidstr, &oid);
if (err)
return err;
oidbuf = gcry_mpi_get_opaque (oid, &n);
if (!oidbuf)
{
err = gpg_error_from_syserror ();
goto leave;
}
oid_len = (n+7)/8;
qbuf = xtrymalloc (ecc_q_len + 1);
if (!qbuf)
{
err = gpg_error_from_syserror ();
goto leave;
}
if ((app->app_local->keyattr[keyno].ecc.flags & ECC_FLAG_DJB_TWEAK))
{ /* Prepend 0x40 prefix. */
*qbuf = 0x40;
memcpy (qbuf+1, ecc_q, ecc_q_len);
ecc_q_len++;
}
else
memcpy (qbuf, ecc_q, ecc_q_len);
if (ctrl)
{
send_key_data (ctrl, "q", qbuf, ecc_q_len);
send_key_data (ctrl, "curve", oidbuf, oid_len);
}
if (keyno == 1)
{
if (ctrl)
send_key_data (ctrl, "kdf/kek", ecdh_params (curve), (size_t)4);
algo = PUBKEY_ALGO_ECDH;
}
else
{
if ((app->app_local->keyattr[keyno].ecc.flags & ECC_FLAG_DJB_TWEAK))
algo = PUBKEY_ALGO_EDDSA;
else
algo = PUBKEY_ALGO_ECDSA;
}
if (ctrl)
{
unsigned char fprbuf[20];
err = store_fpr (app, keyno, created_at, fprbuf, algo, oidbuf, oid_len,
qbuf, ecc_q_len, ecdh_params (curve), (size_t)4);
if (err)
goto leave;
send_fpr_if_not_null (ctrl, "KEY-FPR", -1, fprbuf);
}
if (!(app->app_local->keyattr[keyno].ecc.flags & ECC_FLAG_DJB_TWEAK))
format = "(public-key(ecc(curve%s)(q%b)))";
else if (keyno == 1)
format = "(public-key(ecc(curve%s)(flags djb-tweak)(q%b)))";
else
format = "(public-key(ecc(curve%s)(flags eddsa)(q%b)))";
err = gcry_sexp_build (r_sexp, NULL, format,
app->app_local->keyattr[keyno].ecc.curve,
(int)ecc_q_len, qbuf);
leave:
gcry_mpi_release (oid);
xfree (qbuf);
return err;
}
+static gpg_error_t
+store_keygrip (app_t app, int keyno)
+{
+ gpg_error_t err;
+ unsigned char grip[20];
+
+ err = keygrip_from_canon_sexp (app->app_local->pk[keyno].key,
+ app->app_local->pk[keyno].keylen,
+ grip);
+ if (err)
+ return err;
+
+ bin2hex (grip, 20, app->app_local->pk[keyno].keygrip_str);
+ return 0;
+}
+
+
/* Parse tag-length-value data for public key in BUFFER of BUFLEN
length. Key of KEYNO in APP is updated with an S-expression of
public key. When CTRL is not NULL, fingerprint is computed with
CREATED_AT, and fingerprint is written to the card, and key data
and fingerprint are send back to the client side.
*/
static gpg_error_t
read_public_key (app_t app, ctrl_t ctrl, u32 created_at, int keyno,
const unsigned char *buffer, size_t buflen)
{
gpg_error_t err;
const unsigned char *data;
size_t datalen;
gcry_sexp_t s_pkey = NULL;
data = find_tlv (buffer, buflen, 0x7F49, &datalen);
if (!data)
{
log_error (_("response does not contain the public key data\n"));
return gpg_error (GPG_ERR_CARD);
}
if (app->app_local->keyattr[keyno].key_type == KEY_TYPE_RSA)
err = rsa_read_pubkey (app, ctrl, created_at, keyno,
data, datalen, &s_pkey);
else if (app->app_local->keyattr[keyno].key_type == KEY_TYPE_ECC)
err = ecc_read_pubkey (app, ctrl, created_at, keyno,
data, datalen, &s_pkey);
else
err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
if (!err)
{
unsigned char *keybuf;
size_t len;
len = gcry_sexp_sprint (s_pkey, GCRYSEXP_FMT_CANON, NULL, 0);
keybuf = xtrymalloc (len);
if (!data)
{
err = gpg_error_from_syserror ();
gcry_sexp_release (s_pkey);
return err;
}
gcry_sexp_sprint (s_pkey, GCRYSEXP_FMT_CANON, keybuf, len);
gcry_sexp_release (s_pkey);
app->app_local->pk[keyno].key = keybuf;
/* Decrement for trailing '\0' */
app->app_local->pk[keyno].keylen = len - 1;
+
+ err = store_keygrip (app, keyno);
}
return err;
}
/* Get the public key for KEYNO and store it as an S-expression with
the APP handle. On error that field gets cleared. If we already
know about the public key we will just return. Note that this does
not mean a key is available; this is solely indicated by the
presence of the app->app_local->pk[KEYNO].key field.
Note that GnuPG 1.x does not need this and it would be too time
consuming to send it just for the fun of it. However, given that we
use the same code in gpg 1.4, we can't use the gcry S-expression
here but need to open encode it. */
-#if GNUPG_MAJOR_VERSION > 1
static gpg_error_t
get_public_key (app_t app, int keyno)
{
gpg_error_t err = 0;
unsigned char *buffer;
const unsigned char *m, *e;
size_t buflen;
size_t mlen = 0;
size_t elen = 0;
char *keybuf = NULL;
gcry_sexp_t s_pkey;
size_t len;
if (keyno < 0 || keyno > 2)
return gpg_error (GPG_ERR_INV_ID);
/* Already cached? */
if (app->app_local->pk[keyno].read_done)
return 0;
xfree (app->app_local->pk[keyno].key);
app->app_local->pk[keyno].key = NULL;
app->app_local->pk[keyno].keylen = 0;
m = e = NULL; /* (avoid cc warning) */
if (app->appversion > 0x0100)
{
int exmode, le_value;
/* We may simply read the public key out of these cards. */
if (app->app_local->cardcap.ext_lc_le
&& app->app_local->keyattr[keyno].key_type == KEY_TYPE_RSA
&& app->app_local->keyattr[keyno].rsa.n_bits > RSA_SMALL_SIZE_KEY)
{
exmode = 1; /* Use extended length. */
le_value = determine_rsa_response (app, keyno);
}
else
{
exmode = 0;
le_value = 256; /* Use legacy value. */
}
- err = iso7816_read_public_key (app->slot, exmode,
+ err = iso7816_read_public_key (app_get_slot (app), exmode,
(keyno == 0? "\xB6" :
keyno == 1? "\xB8" : "\xA4"),
2, le_value, &buffer, &buflen);
if (err)
{
log_error (_("reading public key failed: %s\n"), gpg_strerror (err));
goto leave;
}
err = read_public_key (app, NULL, 0U, keyno, buffer, buflen);
}
else
{
/* Due to a design problem in v1.0 cards we can't get the public
key out of these cards without doing a verify on CHV3.
Clearly that is not an option and thus we try to locate the
key using an external helper.
The helper we use here is gpg itself, which should know about
the key in any case. */
char fpr[41];
char *hexkeyid;
char *command = NULL;
FILE *fp;
int ret;
buffer = NULL; /* We don't need buffer. */
err = retrieve_fpr_from_card (app, keyno, fpr);
if (err)
{
log_error ("error while retrieving fpr from card: %s\n",
gpg_strerror (err));
goto leave;
}
hexkeyid = fpr + 24;
ret = gpgrt_asprintf
- (&command, "gpg --list-keys --with-colons --with-key-data '%s'", fpr);
+ (&command, "%s --list-keys --with-colons --with-key-data '%s'",
+ gnupg_module_name (GNUPG_MODULE_NAME_GPG), fpr);
if (ret < 0)
{
err = gpg_error_from_syserror ();
goto leave;
}
fp = popen (command, "r");
xfree (command);
if (!fp)
{
err = gpg_error_from_syserror ();
log_error ("running gpg failed: %s\n", gpg_strerror (err));
goto leave;
}
err = retrieve_key_material (fp, hexkeyid, &m, &mlen, &e, &elen);
pclose (fp);
if (err)
{
log_error ("error while retrieving key material through pipe: %s\n",
gpg_strerror (err));
goto leave;
}
err = gcry_sexp_build (&s_pkey, NULL, "(public-key(rsa(n%b)(e%b)))",
(int)mlen, m, (int)elen, e);
if (err)
goto leave;
len = gcry_sexp_sprint (s_pkey, GCRYSEXP_FMT_CANON, NULL, 0);
keybuf = xtrymalloc (len);
if (!keybuf)
{
err = gpg_error_from_syserror ();
gcry_sexp_release (s_pkey);
goto leave;
}
gcry_sexp_sprint (s_pkey, GCRYSEXP_FMT_CANON, keybuf, len);
gcry_sexp_release (s_pkey);
app->app_local->pk[keyno].key = (unsigned char*)keybuf;
/* Decrement for trailing '\0' */
app->app_local->pk[keyno].keylen = len - 1;
+ err = store_keygrip (app, keyno);
}
leave:
/* Set a flag to indicate that we tried to read the key. */
- app->app_local->pk[keyno].read_done = 1;
+ if (!err)
+ app->app_local->pk[keyno].read_done = 1;
xfree (buffer);
return err;
}
-#endif /* GNUPG_MAJOR_VERSION > 1 */
-
/* Send the KEYPAIRINFO back. KEY needs to be in the range [1,3].
This is used by the LEARN command. */
static gpg_error_t
send_keypair_info (app_t app, ctrl_t ctrl, int key)
{
int keyno = key - 1;
gpg_error_t err = 0;
- /* Note that GnuPG 1.x does not need this and it would be too time
- consuming to send it just for the fun of it. */
-#if GNUPG_MAJOR_VERSION > 1
- unsigned char grip[20];
- char gripstr[41];
char idbuf[50];
const char *usage;
err = get_public_key (app, keyno);
if (err)
goto leave;
assert (keyno >= 0 && keyno <= 2);
if (!app->app_local->pk[keyno].key)
goto leave; /* No such key - ignore. */
- err = keygrip_from_canon_sexp (app->app_local->pk[keyno].key,
- app->app_local->pk[keyno].keylen,
- grip);
- if (err)
- goto leave;
-
- bin2hex (grip, 20, gripstr);
-
switch (keyno)
{
case 0: usage = "sc"; break;
case 1: usage = "e"; break;
case 2: usage = "sa"; break;
default: usage = ""; break;
}
sprintf (idbuf, "OPENPGP.%d", keyno+1);
send_status_info (ctrl, "KEYPAIRINFO",
- gripstr, 40,
+ app->app_local->pk[keyno].keygrip_str, 40,
idbuf, strlen (idbuf),
usage, strlen (usage),
NULL, (size_t)0);
leave:
-#endif /* GNUPG_MAJOR_VERSION > 1 */
-
return err;
}
/* Handle the LEARN command for OpenPGP. */
static gpg_error_t
do_learn_status (app_t app, ctrl_t ctrl, unsigned int flags)
{
(void)flags;
do_getattr (app, ctrl, "EXTCAP");
do_getattr (app, ctrl, "DISP-NAME");
do_getattr (app, ctrl, "DISP-LANG");
do_getattr (app, ctrl, "DISP-SEX");
do_getattr (app, ctrl, "PUBKEY-URL");
do_getattr (app, ctrl, "LOGIN-DATA");
do_getattr (app, ctrl, "KEY-FPR");
if (app->appversion > 0x0100)
do_getattr (app, ctrl, "KEY-TIME");
do_getattr (app, ctrl, "CA-FPR");
do_getattr (app, ctrl, "CHV-STATUS");
do_getattr (app, ctrl, "SIG-COUNTER");
if (app->app_local->extcap.kdf_do)
do_getattr (app, ctrl, "KDF");
if (app->app_local->extcap.has_button)
{
do_getattr (app, ctrl, "UIF-1");
do_getattr (app, ctrl, "UIF-2");
do_getattr (app, ctrl, "UIF-3");
}
if (app->app_local->extcap.private_dos)
{
do_getattr (app, ctrl, "PRIVATE-DO-1");
do_getattr (app, ctrl, "PRIVATE-DO-2");
if (app->did_chv2)
do_getattr (app, ctrl, "PRIVATE-DO-3");
if (app->did_chv3)
do_getattr (app, ctrl, "PRIVATE-DO-4");
}
send_keypair_info (app, ctrl, 1);
send_keypair_info (app, ctrl, 2);
send_keypair_info (app, ctrl, 3);
/* Note: We do not send the Cardholder Certificate, because that is
relatively long and for OpenPGP applications not really needed. */
return 0;
}
/* Handle the READKEY command for OpenPGP. On success a canonical
encoded S-expression with the public key will get stored at PK and
its length (for assertions) at PKLEN; the caller must release that
buffer. On error PK and PKLEN are not changed and an error code is
returned. */
static gpg_error_t
-do_readkey (app_t app, const char *keyid, unsigned char **pk, size_t *pklen)
+do_readkey (app_t app, ctrl_t ctrl, const char *keyid, unsigned int flags,
+ unsigned char **pk, size_t *pklen)
{
gpg_error_t err;
int keyno;
unsigned char *buf;
if (!strcmp (keyid, "OPENPGP.1"))
keyno = 0;
else if (!strcmp (keyid, "OPENPGP.2"))
keyno = 1;
else if (!strcmp (keyid, "OPENPGP.3"))
keyno = 2;
else
return gpg_error (GPG_ERR_INV_ID);
err = get_public_key (app, keyno);
if (err)
return err;
buf = app->app_local->pk[keyno].key;
if (!buf)
return gpg_error (GPG_ERR_NO_PUBKEY);
- *pklen = app->app_local->pk[keyno].keylen;
- *pk = xtrymalloc (*pklen);
- if (!*pk)
+ if ((flags & APP_READKEY_FLAG_INFO))
{
- err = gpg_error_from_syserror ();
- *pklen = 0;
- return err;
+ err = send_keypair_info (app, ctrl, keyno+1);
+ if (err)
+ return err;
+ }
+
+ if (pk && pklen)
+ {
+ *pklen = app->app_local->pk[keyno].keylen;
+ *pk = xtrymalloc (*pklen);
+ if (!*pk)
+ {
+ err = gpg_error_from_syserror ();
+ *pklen = 0;
+ return err;
+ }
+ memcpy (*pk, buf, *pklen);
}
- memcpy (*pk, buf, *pklen);
return 0;
}
/* Read the standard certificate of an OpenPGP v2 card. It is
returned in a freshly allocated buffer with that address stored at
CERT and the length of the certificate stored at CERTLEN. CERTID
needs to be set to "OPENPGP.3". */
static gpg_error_t
do_readcert (app_t app, const char *certid,
unsigned char **cert, size_t *certlen)
{
-#if GNUPG_MAJOR_VERSION > 1
gpg_error_t err;
unsigned char *buffer;
size_t buflen;
void *relptr;
*cert = NULL;
*certlen = 0;
if (strcmp (certid, "OPENPGP.3"))
return gpg_error (GPG_ERR_INV_ID);
if (!app->app_local->extcap.is_v2)
return gpg_error (GPG_ERR_NOT_FOUND);
relptr = get_one_do (app, 0x7F21, &buffer, &buflen, NULL);
if (!relptr)
return gpg_error (GPG_ERR_NOT_FOUND);
if (!buflen)
err = gpg_error (GPG_ERR_NOT_FOUND);
else if (!(*cert = xtrymalloc (buflen)))
err = gpg_error_from_syserror ();
else
{
memcpy (*cert, buffer, buflen);
*certlen = buflen;
err = 0;
}
xfree (relptr);
return err;
-#else
- return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
-#endif
}
/* Decide if we use the pinpad of the reader for PIN input according
to the user preference on the card, and the capability of the
reader. This routine is only called when the reader has pinpad.
Returns 0 if we use pinpad, 1 otherwise. */
static int
check_pinpad_request (app_t app, pininfo_t *pininfo, int admin_pin)
{
if (app->app_local->pinpad.specified == 0) /* No preference on card. */
{
if (pininfo->fixedlen == 0) /* Reader has varlen capability. */
return 0; /* Then, use pinpad. */
else
/*
* Reader has limited capability, and it may not match PIN of
* the card.
*/
return 1;
}
if (admin_pin)
pininfo->fixedlen = app->app_local->pinpad.fixedlen_admin;
else
pininfo->fixedlen = app->app_local->pinpad.fixedlen_user;
if (pininfo->fixedlen == 0 /* User requests disable pinpad. */
|| pininfo->fixedlen < pininfo->minlen
|| pininfo->fixedlen > pininfo->maxlen
/* Reader doesn't have the capability to input a PIN which
* length is FIXEDLEN. */)
return 1;
return 0;
}
/* Return a string with information about the card for use in a
* prompt. Returns NULL on memory failure. */
static char *
get_prompt_info (app_t app, int chvno, unsigned long sigcount, int remaining)
{
char *serial, *disp_name, *rembuf, *tmpbuf, *result;
serial = get_disp_serialno (app);
if (!serial)
return NULL;
disp_name = get_disp_name (app);
if (chvno == 1)
{
/* TRANSLATORS: Put a \x1f right before a colon. This can be
* used by pinentry to nicely align the names and values. Keep
* the %s at the start and end of the string. */
result = xtryasprintf (_("%s"
"Number\x1f: %s%%0A"
"Holder\x1f: %s%%0A"
"Counter\x1f: %lu"
"%s"),
"\x1e",
serial,
disp_name? disp_name:"",
sigcount,
"");
}
else
{
result = xtryasprintf (_("%s"
"Number\x1f: %s%%0A"
"Holder\x1f: %s"
"%s"),
"\x1e",
serial,
disp_name? disp_name:"",
"");
}
xfree (disp_name);
xfree (serial);
if (remaining != -1)
{
/* TRANSLATORS: This is the number of remaining attempts to
* enter a PIN. Use %%0A (double-percent,0A) for a linefeed. */
rembuf = xtryasprintf (_("Remaining attempts: %d"), remaining);
if (!rembuf)
{
xfree (result);
return NULL;
}
tmpbuf = strconcat (result, "%0A%0A", rembuf, NULL);
xfree (rembuf);
if (!tmpbuf)
{
xfree (result);
return NULL;
}
xfree (result);
result = tmpbuf;
}
return result;
}
#define KDF_DATA_LENGTH_MIN 90
#define KDF_DATA_LENGTH_MAX 110
/* Compute hash if KDF-DO is available. CHVNO must be 0 for reset
code, 1 or 2 for user pin and 3 for admin pin.
*/
static gpg_error_t
pin2hash_if_kdf (app_t app, int chvno, char *pinvalue, int *r_pinlen)
{
gpg_error_t err = 0;
void *relptr = NULL;
unsigned char *buffer;
size_t buflen;
if (app->app_local->extcap.kdf_do
&& (relptr = get_one_do (app, 0x00F9, &buffer, &buflen, NULL))
&& buflen >= KDF_DATA_LENGTH_MIN && (buffer[2] == 0x03))
{
const char *salt;
unsigned long s2k_count;
char dek[32];
int salt_index;
s2k_count = (((unsigned int)buffer[8] << 24)
| (buffer[9] << 16) | (buffer[10] << 8) | buffer[11]);
if (buflen == KDF_DATA_LENGTH_MIN)
salt_index =14;
else if (buflen == KDF_DATA_LENGTH_MAX)
salt_index = (chvno==3 ? 34 : (chvno==0 ? 24 : 14));
else
{
err = gpg_error (GPG_ERR_INV_DATA);
goto leave;
}
salt = &buffer[salt_index];
err = gcry_kdf_derive (pinvalue, strlen (pinvalue),
GCRY_KDF_ITERSALTED_S2K,
DIGEST_ALGO_SHA256, salt, 8,
s2k_count, sizeof (dek), dek);
if (!err)
{
/* pinvalue has a buffer of MAXLEN_PIN+1, 32 is OK. */
*r_pinlen = 32;
memcpy (pinvalue, dek, *r_pinlen);
wipememory (dek, *r_pinlen);
}
}
else
*r_pinlen = strlen (pinvalue);
leave:
xfree (relptr);
return err;
}
/* Verify a CHV either using the pinentry or if possible by
using a pinpad. PINCB and PINCB_ARG describe the usual callback
for the pinentry. CHVNO must be either 1 or 2. SIGCOUNT is only
used with CHV1. PINVALUE is the address of a pointer which will
receive a newly allocated block with the actual PIN (this is useful
in case that PIN shall be used for another verify operation). The
caller needs to free this value. If the function returns with
success and NULL is stored at PINVALUE, the caller should take this
as an indication that the pinpad has been used.
*/
static gpg_error_t
verify_a_chv (app_t app,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg, int chvno, unsigned long sigcount,
char **pinvalue, int *pinlen)
{
int rc = 0;
char *prompt_buffer = NULL;
const char *prompt;
pininfo_t pininfo;
int minlen = 6;
int remaining;
log_assert (chvno == 1 || chvno == 2);
*pinvalue = NULL;
*pinlen = 0;
remaining = get_remaining_tries (app, 0);
if (remaining == -1)
return gpg_error (GPG_ERR_CARD);
if (chvno == 2 && app->app_local->flags.def_chv2)
{
/* Special case for def_chv2 mechanism. */
if (opt.verbose)
log_info (_("using default PIN as %s\n"), "CHV2");
- rc = iso7816_verify (app->slot, 0x82, "123456", 6);
+ rc = iso7816_verify (app_get_slot (app), 0x82, "123456", 6);
if (rc)
{
/* Verification of CHV2 with the default PIN failed,
although the card pretends to have the default PIN set as
CHV2. We better disable the def_chv2 flag now. */
log_info (_("failed to use default PIN as %s: %s"
" - disabling further default use\n"),
"CHV2", gpg_strerror (rc));
app->app_local->flags.def_chv2 = 0;
}
return rc;
}
memset (&pininfo, 0, sizeof pininfo);
pininfo.fixedlen = -1;
pininfo.minlen = minlen;
{
const char *firstline = _("||Please unlock the card");
char *infoblock = get_prompt_info (app, chvno, sigcount,
remaining < 3? remaining : -1);
prompt_buffer = strconcat (firstline, "%0A%0A", infoblock, NULL);
if (prompt_buffer)
prompt = prompt_buffer;
else
prompt = firstline; /* ENOMEM fallback. */
xfree (infoblock);
}
if (!opt.disable_pinpad
- && !iso7816_check_pinpad (app->slot, ISO7816_VERIFY, &pininfo)
+ && !iso7816_check_pinpad (app_get_slot (app), ISO7816_VERIFY, &pininfo)
&& !check_pinpad_request (app, &pininfo, 0))
{
/* The reader supports the verify command through the pinpad.
Note that the pincb appends a text to the prompt telling the
user to use the pinpad. */
rc = pincb (pincb_arg, prompt, NULL);
prompt = NULL;
xfree (prompt_buffer);
prompt_buffer = NULL;
if (rc)
{
log_info (_("PIN callback returned error: %s\n"),
gpg_strerror (rc));
return rc;
}
- rc = iso7816_verify_kp (app->slot, 0x80+chvno, &pininfo);
+ rc = iso7816_verify_kp (app_get_slot (app), 0x80+chvno, &pininfo);
/* Dismiss the prompt. */
pincb (pincb_arg, NULL, NULL);
log_assert (!*pinvalue);
}
else
{
/* The reader has no pinpad or we don't want to use it. */
rc = pincb (pincb_arg, prompt, pinvalue);
prompt = NULL;
xfree (prompt_buffer);
prompt_buffer = NULL;
if (rc)
{
log_info (_("PIN callback returned error: %s\n"),
gpg_strerror (rc));
return rc;
}
if (strlen (*pinvalue) < minlen)
{
log_error (_("PIN for CHV%d is too short;"
" minimum length is %d\n"), chvno, minlen);
xfree (*pinvalue);
*pinvalue = NULL;
return gpg_error (GPG_ERR_BAD_PIN);
}
rc = pin2hash_if_kdf (app, chvno, *pinvalue, pinlen);
if (!rc)
- rc = iso7816_verify (app->slot, 0x80+chvno, *pinvalue, *pinlen);
+ rc = iso7816_verify (app_get_slot (app),
+ 0x80 + chvno, *pinvalue, *pinlen);
}
if (rc)
{
log_error (_("verify CHV%d failed: %s\n"), chvno, gpg_strerror (rc));
xfree (*pinvalue);
*pinvalue = NULL;
flush_cache_after_error (app);
}
return rc;
}
/* Verify CHV2 if required. Depending on the configuration of the
card CHV1 will also be verified. */
static gpg_error_t
verify_chv2 (app_t app,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg)
{
int rc;
char *pinvalue;
int pinlen;
if (app->did_chv2)
return 0; /* We already verified CHV2. */
rc = verify_a_chv (app, pincb, pincb_arg, 2, 0, &pinvalue, &pinlen);
if (rc)
return rc;
app->did_chv2 = 1;
if (!app->did_chv1 && !app->force_chv1 && pinvalue)
{
/* For convenience we verify CHV1 here too. We do this only if
the card is not configured to require a verification before
each CHV1 controlled operation (force_chv1) and if we are not
using the pinpad (PINVALUE == NULL). */
- rc = iso7816_verify (app->slot, 0x81, pinvalue, pinlen);
+ rc = iso7816_verify (app_get_slot (app), 0x81, pinvalue, pinlen);
if (gpg_err_code (rc) == GPG_ERR_BAD_PIN)
rc = gpg_error (GPG_ERR_PIN_NOT_SYNCED);
if (rc)
{
log_error (_("verify CHV%d failed: %s\n"), 1, gpg_strerror (rc));
flush_cache_after_error (app);
}
else
app->did_chv1 = 1;
}
xfree (pinvalue);
return rc;
}
/* Build the prompt to enter the Admin PIN. The prompt depends on the
current sdtate of the card. */
static gpg_error_t
build_enter_admin_pin_prompt (app_t app, char **r_prompt)
{
int remaining;
char *prompt;
char *infoblock;
*r_prompt = NULL;
remaining = get_remaining_tries (app, 1);
if (remaining == -1)
return gpg_error (GPG_ERR_CARD);
if (!remaining)
{
log_info (_("card is permanently locked!\n"));
return gpg_error (GPG_ERR_BAD_PIN);
}
log_info (ngettext("%d Admin PIN attempt remaining before card"
" is permanently locked\n",
"%d Admin PIN attempts remaining before card"
" is permanently locked\n",
remaining), remaining);
infoblock = get_prompt_info (app, 3, 0, remaining < 3? remaining : -1);
/* TRANSLATORS: Do not translate the "|A|" prefix but keep it at
the start of the string. Use %0A (single percent) for a linefeed. */
prompt = strconcat (_("|A|Please enter the Admin PIN"),
"%0A%0A", infoblock, NULL);
xfree (infoblock);
if (!prompt)
return gpg_error_from_syserror ();
*r_prompt = prompt;
return 0;
}
/* Verify CHV3 if required. */
static gpg_error_t
verify_chv3 (app_t app,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg)
{
int rc = 0;
-#if GNUPG_MAJOR_VERSION != 1
if (!opt.allow_admin)
{
log_info (_("access to admin commands is not configured\n"));
return gpg_error (GPG_ERR_EACCES);
}
-#endif
if (!app->did_chv3)
{
pininfo_t pininfo;
int minlen = 8;
char *prompt;
memset (&pininfo, 0, sizeof pininfo);
pininfo.fixedlen = -1;
pininfo.minlen = minlen;
rc = build_enter_admin_pin_prompt (app, &prompt);
if (rc)
return rc;
if (!opt.disable_pinpad
- && !iso7816_check_pinpad (app->slot, ISO7816_VERIFY, &pininfo)
+ && !iso7816_check_pinpad (app_get_slot (app),
+ ISO7816_VERIFY, &pininfo)
&& !check_pinpad_request (app, &pininfo, 1))
{
/* The reader supports the verify command through the pinpad. */
rc = pincb (pincb_arg, prompt, NULL);
xfree (prompt);
prompt = NULL;
if (rc)
{
log_info (_("PIN callback returned error: %s\n"),
gpg_strerror (rc));
return rc;
}
- rc = iso7816_verify_kp (app->slot, 0x83, &pininfo);
+ rc = iso7816_verify_kp (app_get_slot (app), 0x83, &pininfo);
/* Dismiss the prompt. */
pincb (pincb_arg, NULL, NULL);
}
else
{
char *pinvalue;
int pinlen;
rc = pincb (pincb_arg, prompt, &pinvalue);
xfree (prompt);
prompt = NULL;
if (rc)
{
log_info (_("PIN callback returned error: %s\n"),
gpg_strerror (rc));
return rc;
}
if (strlen (pinvalue) < minlen)
{
log_error (_("PIN for CHV%d is too short;"
" minimum length is %d\n"), 3, minlen);
xfree (pinvalue);
return gpg_error (GPG_ERR_BAD_PIN);
}
rc = pin2hash_if_kdf (app, 3, pinvalue, &pinlen);
if (!rc)
- rc = iso7816_verify (app->slot, 0x83, pinvalue, pinlen);
+ rc = iso7816_verify (app_get_slot (app), 0x83, pinvalue, pinlen);
xfree (pinvalue);
}
if (rc)
{
log_error (_("verify CHV%d failed: %s\n"), 3, gpg_strerror (rc));
flush_cache_after_error (app);
return rc;
}
app->did_chv3 = 1;
}
return rc;
}
/* Handle the SETATTR operation. All arguments are already basically
checked. */
static gpg_error_t
do_setattr (app_t app, const char *name,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const unsigned char *value, size_t valuelen)
{
gpg_error_t rc;
int idx;
static struct {
const char *name;
int tag;
int flush_tag; /* The tag which needs to be flushed or 0. */
int need_chv;
int special;
unsigned int need_v2:1;
} table[] = {
{ "DISP-NAME", 0x005B, 0, 3 },
{ "LOGIN-DATA", 0x005E, 0, 3, 2 },
{ "DISP-LANG", 0x5F2D, 0, 3 },
{ "DISP-SEX", 0x5F35, 0, 3 },
{ "PUBKEY-URL", 0x5F50, 0, 3 },
{ "CHV-STATUS-1", 0x00C4, 0, 3, 1 },
{ "CA-FPR-1", 0x00CA, 0x00C6, 3 },
{ "CA-FPR-2", 0x00CB, 0x00C6, 3 },
{ "CA-FPR-3", 0x00CC, 0x00C6, 3 },
{ "PRIVATE-DO-1", 0x0101, 0, 2 },
{ "PRIVATE-DO-2", 0x0102, 0, 3 },
{ "PRIVATE-DO-3", 0x0103, 0, 2 },
{ "PRIVATE-DO-4", 0x0104, 0, 3 },
{ "CERT-3", 0x7F21, 0, 3, 0, 1 },
{ "SM-KEY-ENC", 0x00D1, 0, 3, 0, 1 },
{ "SM-KEY-MAC", 0x00D2, 0, 3, 0, 1 },
{ "KEY-ATTR", 0, 0, 0, 3, 1 },
{ "AESKEY", 0x00D5, 0, 3, 0, 1 },
{ "UIF-1", 0x00D6, 0, 3, 5, 1 },
{ "UIF-2", 0x00D7, 0, 3, 5, 1 },
{ "UIF-3", 0x00D8, 0, 3, 5, 1 },
{ "KDF", 0x00F9, 0, 3, 4, 1 },
{ NULL, 0 }
};
int exmode;
for (idx=0; table[idx].name && strcmp (table[idx].name, name); idx++)
;
if (!table[idx].name)
return gpg_error (GPG_ERR_INV_NAME);
if (table[idx].need_v2 && !app->app_local->extcap.is_v2)
return gpg_error (GPG_ERR_NOT_SUPPORTED); /* Not yet supported. */
if (table[idx].special == 5 && app->app_local->extcap.has_button == 0)
return gpg_error (GPG_ERR_INV_OBJ);
if (table[idx].special == 3)
return change_keyattr_from_string (app, pincb, pincb_arg, value, valuelen);
switch (table[idx].need_chv)
{
case 2:
rc = verify_chv2 (app, pincb, pincb_arg);
break;
case 3:
rc = verify_chv3 (app, pincb, pincb_arg);
break;
default:
rc = 0;
}
if (rc)
return rc;
/* Flush the cache before writing it, so that the next get operation
will reread the data from the card and thus get synced in case of
errors (e.g. data truncated by the card). */
flush_cache_item (app, table[idx].flush_tag? table[idx].flush_tag
/* */ : table[idx].tag);
if (app->app_local->cardcap.ext_lc_le && valuelen > 254)
exmode = 1; /* Use extended length w/o a limit. */
else if (app->app_local->cardcap.cmd_chaining && valuelen > 254)
exmode = -254; /* Command chaining with max. 254 bytes. */
else
exmode = 0;
- rc = iso7816_put_data (app->slot, exmode, table[idx].tag, value, valuelen);
+ rc = iso7816_put_data (app_get_slot (app),
+ exmode, table[idx].tag, value, valuelen);
if (rc)
log_error ("failed to set '%s': %s\n", table[idx].name, gpg_strerror (rc));
if (table[idx].special == 1)
app->force_chv1 = (valuelen && *value == 0);
else if (table[idx].special == 2)
parse_login_data (app);
else if (table[idx].special == 4)
{
app->did_chv1 = 0;
app->did_chv2 = 0;
app->did_chv3 = 0;
}
return rc;
}
/* Handle the WRITECERT command for OpenPGP. This writes the standard
* certificate to the card; CERTID needs to be set to "OPENPGP.3".
* PINCB and PINCB_ARG are the usual arguments for the pinentry
* callback. */
static gpg_error_t
do_writecert (app_t app, ctrl_t ctrl,
const char *certidstr,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const unsigned char *certdata, size_t certdatalen)
{
(void)ctrl;
-#if GNUPG_MAJOR_VERSION > 1
if (strcmp (certidstr, "OPENPGP.3"))
return gpg_error (GPG_ERR_INV_ID);
if (!certdata || !certdatalen)
return gpg_error (GPG_ERR_INV_ARG);
if (!app->app_local->extcap.is_v2)
return gpg_error (GPG_ERR_NOT_SUPPORTED);
if (certdatalen > app->app_local->extcap.max_certlen_3)
return gpg_error (GPG_ERR_TOO_LARGE);
return do_setattr (app, "CERT-3", pincb, pincb_arg, certdata, certdatalen);
-#else
- return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
-#endif
}
static gpg_error_t
clear_chv_status (app_t app, int chvno)
{
unsigned char apdu[4];
gpg_error_t err;
if (!app->app_local->extcap.is_v2)
return GPG_ERR_UNSUPPORTED_OPERATION;
apdu[0] = 0x00;
apdu[1] = ISO7816_VERIFY;
apdu[2] = 0xff;
apdu[3] = 0x80+chvno;
- err = iso7816_apdu_direct (app->slot, apdu, 4, 0, NULL, NULL, NULL);
+ err = iso7816_apdu_direct (app_get_slot (app), apdu, 4, 0, NULL, NULL, NULL);
if (err)
{
if (gpg_err_code (err) == GPG_ERR_INV_VALUE)
err = gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
return err;
}
if (chvno == 1)
{
apdu[3]++;
- err = iso7816_apdu_direct (app->slot, apdu, 4, 0, NULL, NULL, NULL);
+ err = iso7816_apdu_direct (app_get_slot (app),
+ apdu, 4, 0, NULL, NULL, NULL);
app->did_chv1 = app->did_chv2 = 0;
}
else if (chvno == 2)
app->did_chv2 = 0;
else if (chvno == 3)
app->did_chv3 = 0;
return err;
}
/* Handle the PASSWD command. The following combinations are
possible:
Flags CHVNO Vers. Description
RESET 1 1 Verify CHV3 and set a new CHV1 and CHV2
RESET 1 2 Verify PW3 and set a new PW1.
RESET 2 1 Verify CHV3 and set a new CHV1 and CHV2.
RESET 2 2 Verify PW3 and set a new Reset Code.
RESET 3 any Returns GPG_ERR_INV_ID.
- 1 1 Verify CHV2 and set a new CHV1 and CHV2.
- 1 2 Verify PW1 and set a new PW1.
- 2 1 Verify CHV2 and set a new CHV1 and CHV2.
- 2 2 Verify Reset Code and set a new PW1.
- 3 any Verify CHV3/PW3 and set a new CHV3/PW3.
The CHVNO can be prefixed with "OPENPGP.".
*/
static gpg_error_t
do_change_pin (app_t app, ctrl_t ctrl, const char *chvnostr,
unsigned int flags,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg)
{
int rc = 0;
int chvno;
char *resetcode = NULL;
char *oldpinvalue = NULL;
char *pinvalue = NULL;
int reset_mode = !!(flags & APP_CHANGE_FLAG_RESET);
int set_resetcode = 0;
pininfo_t pininfo;
int use_pinpad = 0;
int minlen = 6;
int pinlen0 = 0;
int pinlen = 0;
(void)ctrl;
if (digitp (chvnostr))
chvno = atoi (chvnostr);
else if (!ascii_strcasecmp (chvnostr, "OPENPGP.1"))
chvno = 1;
else if (!ascii_strcasecmp (chvnostr, "OPENPGP.2"))
chvno = 2;
else if (!ascii_strcasecmp (chvnostr, "OPENPGP.3"))
chvno = 3;
else
return gpg_error (GPG_ERR_INV_ID);
memset (&pininfo, 0, sizeof pininfo);
pininfo.fixedlen = -1;
pininfo.minlen = minlen;
if ((flags & APP_CHANGE_FLAG_CLEAR))
return clear_chv_status (app, chvno);
if (reset_mode && chvno == 3)
{
rc = gpg_error (GPG_ERR_INV_ID);
goto leave;
}
if (!app->app_local->extcap.is_v2)
{
/* Version 1 cards. */
if (reset_mode || chvno == 3)
{
/* We always require that the PIN is entered. */
app->did_chv3 = 0;
rc = verify_chv3 (app, pincb, pincb_arg);
if (rc)
goto leave;
}
else if (chvno == 1 || chvno == 2)
{
/* On a v1.x card CHV1 and CVH2 should always have the same
value, thus we enforce it here. */
int save_force = app->force_chv1;
app->force_chv1 = 0;
app->did_chv1 = 0;
app->did_chv2 = 0;
rc = verify_chv2 (app, pincb, pincb_arg);
app->force_chv1 = save_force;
if (rc)
goto leave;
}
else
{
rc = gpg_error (GPG_ERR_INV_ID);
goto leave;
}
}
else
{
/* Version 2 cards. */
if (!opt.disable_pinpad
- && !iso7816_check_pinpad (app->slot,
+ && !iso7816_check_pinpad (app_get_slot (app),
ISO7816_CHANGE_REFERENCE_DATA, &pininfo)
&& !check_pinpad_request (app, &pininfo, chvno == 3))
use_pinpad = 1;
if (reset_mode)
{
/* To reset a PIN the Admin PIN is required. */
use_pinpad = 0;
app->did_chv3 = 0;
rc = verify_chv3 (app, pincb, pincb_arg);
if (rc)
goto leave;
if (chvno == 2)
set_resetcode = 1;
}
else if (chvno == 1 || chvno == 3)
{
if (!use_pinpad)
{
char *promptbuf = NULL;
const char *prompt;
if (chvno == 3)
{
minlen = 8;
rc = build_enter_admin_pin_prompt (app, &promptbuf);
if (rc)
goto leave;
prompt = promptbuf;
}
else
prompt = _("||Please enter the PIN");
rc = pincb (pincb_arg, prompt, &oldpinvalue);
xfree (promptbuf);
promptbuf = NULL;
if (rc)
{
log_info (_("PIN callback returned error: %s\n"),
gpg_strerror (rc));
goto leave;
}
if (strlen (oldpinvalue) < minlen)
{
log_info (_("PIN for CHV%d is too short;"
" minimum length is %d\n"), chvno, minlen);
rc = gpg_error (GPG_ERR_BAD_PIN);
goto leave;
}
}
}
else if (chvno == 2)
{
/* There is no PW2 for v2 cards. We use this condition to
allow a PW reset using the Reset Code. */
void *relptr;
unsigned char *value;
size_t valuelen;
int remaining;
use_pinpad = 0;
minlen = 8;
relptr = get_one_do (app, 0x00C4, &value, &valuelen, NULL);
if (!relptr || valuelen < 7)
{
log_error (_("error retrieving CHV status from card\n"));
xfree (relptr);
rc = gpg_error (GPG_ERR_CARD);
goto leave;
}
remaining = value[5];
xfree (relptr);
if (!remaining)
{
log_error (_("Reset Code not or not anymore available\n"));
rc = gpg_error (GPG_ERR_BAD_PIN);
goto leave;
}
rc = pincb (pincb_arg,
_("||Please enter the Reset Code for the card"),
&resetcode);
if (rc)
{
log_info (_("PIN callback returned error: %s\n"),
gpg_strerror (rc));
goto leave;
}
if (strlen (resetcode) < minlen)
{
log_info (_("Reset Code is too short; minimum length is %d\n"),
minlen);
rc = gpg_error (GPG_ERR_BAD_PIN);
goto leave;
}
}
else
{
rc = gpg_error (GPG_ERR_INV_ID);
goto leave;
}
}
if (chvno == 3)
app->did_chv3 = 0;
else
app->did_chv1 = app->did_chv2 = 0;
if (!use_pinpad)
{
/* TRANSLATORS: Do not translate the "|*|" prefixes but
keep it at the start of the string. We need this elsewhere
to get some infos on the string. */
rc = pincb (pincb_arg, set_resetcode? _("|RN|New Reset Code") :
chvno == 3? _("|AN|New Admin PIN") : _("|N|New PIN"),
&pinvalue);
if (rc || pinvalue == NULL)
{
log_error (_("error getting new PIN: %s\n"), gpg_strerror (rc));
goto leave;
}
}
if (resetcode)
{
char *buffer;
buffer = xtrymalloc (strlen (resetcode) + strlen (pinvalue) + 1);
if (!buffer)
rc = gpg_error_from_syserror ();
else
{
strcpy (buffer, resetcode);
rc = pin2hash_if_kdf (app, 0, buffer, &pinlen0);
if (!rc)
{
strcpy (buffer+pinlen0, pinvalue);
rc = pin2hash_if_kdf (app, 0, buffer+pinlen0, &pinlen);
}
if (!rc)
- rc = iso7816_reset_retry_counter_with_rc (app->slot, 0x81,
+ rc = iso7816_reset_retry_counter_with_rc (app_get_slot (app), 0x81,
buffer, pinlen0+pinlen);
wipememory (buffer, pinlen0 + pinlen);
xfree (buffer);
}
}
else if (set_resetcode)
{
if (strlen (pinvalue) < 8)
{
log_error (_("Reset Code is too short; minimum length is %d\n"), 8);
rc = gpg_error (GPG_ERR_BAD_PIN);
}
else
{
rc = pin2hash_if_kdf (app, 0, pinvalue, &pinlen);
if (!rc)
- rc = iso7816_put_data (app->slot, 0, 0xD3, pinvalue, pinlen);
+ rc = iso7816_put_data (app_get_slot (app),
+ 0, 0xD3, pinvalue, pinlen);
}
}
else if (reset_mode)
{
rc = pin2hash_if_kdf (app, 1, pinvalue, &pinlen);
if (!rc)
- rc = iso7816_reset_retry_counter (app->slot, 0x81, pinvalue, pinlen);
+ rc = iso7816_reset_retry_counter (app_get_slot (app),
+ 0x81, pinvalue, pinlen);
if (!rc && !app->app_local->extcap.is_v2)
- rc = iso7816_reset_retry_counter (app->slot, 0x82, pinvalue, pinlen);
+ rc = iso7816_reset_retry_counter (app_get_slot (app),
+ 0x82, pinvalue, pinlen);
}
else if (!app->app_local->extcap.is_v2)
{
/* Version 1 cards. */
if (chvno == 1 || chvno == 2)
{
- rc = iso7816_change_reference_data (app->slot, 0x81, NULL, 0,
+ rc = iso7816_change_reference_data (app_get_slot (app),
+ 0x81, NULL, 0,
pinvalue, strlen (pinvalue));
if (!rc)
- rc = iso7816_change_reference_data (app->slot, 0x82, NULL, 0,
+ rc = iso7816_change_reference_data (app_get_slot (app),
+ 0x82, NULL, 0,
pinvalue, strlen (pinvalue));
}
else /* CHVNO == 3 */
{
- rc = iso7816_change_reference_data (app->slot, 0x80 + chvno, NULL, 0,
+ rc = iso7816_change_reference_data (app_get_slot (app),
+ 0x80 + chvno, NULL, 0,
pinvalue, strlen (pinvalue));
}
}
else
{
/* Version 2 cards. */
assert (chvno == 1 || chvno == 3);
if (use_pinpad)
{
rc = pincb (pincb_arg,
chvno == 3 ?
_("||Please enter the Admin PIN and New Admin PIN") :
_("||Please enter the PIN and New PIN"), NULL);
if (rc)
{
log_info (_("PIN callback returned error: %s\n"),
gpg_strerror (rc));
goto leave;
}
- rc = iso7816_change_reference_data_kp (app->slot, 0x80 + chvno, 0,
+ rc = iso7816_change_reference_data_kp (app_get_slot (app),
+ 0x80 + chvno, 0,
&pininfo);
pincb (pincb_arg, NULL, NULL); /* Dismiss the prompt. */
}
else
{
rc = pin2hash_if_kdf (app, chvno, oldpinvalue, &pinlen0);
if (!rc)
rc = pin2hash_if_kdf (app, chvno, pinvalue, &pinlen);
if (!rc)
- rc = iso7816_change_reference_data (app->slot, 0x80 + chvno,
+ rc = iso7816_change_reference_data (app_get_slot (app),
+ 0x80 + chvno,
oldpinvalue, pinlen0,
pinvalue, pinlen);
}
}
if (pinvalue)
{
wipememory (pinvalue, pinlen);
xfree (pinvalue);
}
if (rc)
flush_cache_after_error (app);
leave:
if (resetcode)
{
wipememory (resetcode, strlen (resetcode));
xfree (resetcode);
}
if (oldpinvalue)
{
wipememory (oldpinvalue, pinlen0);
xfree (oldpinvalue);
}
return rc;
}
/* Check whether a key already exists. KEYIDX is the index of the key
(0..2). If FORCE is TRUE a diagnositic will be printed but no
error returned if the key already exists. The flag GENERATING is
only used to print correct messages. */
static gpg_error_t
does_key_exist (app_t app, int keyidx, int generating, int force)
{
const unsigned char *fpr;
unsigned char *buffer;
size_t buflen, n;
int i;
assert (keyidx >=0 && keyidx <= 2);
- if (iso7816_get_data (app->slot, 0, 0x006E, &buffer, &buflen))
+ if (iso7816_get_data (app_get_slot (app), 0, 0x006E, &buffer, &buflen))
{
log_error (_("error reading application data\n"));
return gpg_error (GPG_ERR_GENERAL);
}
fpr = find_tlv (buffer, buflen, 0x00C5, &n);
if (!fpr || n < 60)
{
log_error (_("error reading fingerprint DO\n"));
xfree (buffer);
return gpg_error (GPG_ERR_GENERAL);
}
fpr += 20*keyidx;
for (i=0; i < 20 && !fpr[i]; i++)
;
xfree (buffer);
if (i!=20 && !force)
{
log_error (_("key already exists\n"));
return gpg_error (GPG_ERR_EEXIST);
}
else if (i!=20)
log_info (_("existing key will be replaced\n"));
else if (generating)
log_info (_("generating new key\n"));
else
log_info (_("writing new key\n"));
return 0;
}
/* Create a TLV tag and value and store it at BUFFER. Return the length
of tag and length. A LENGTH greater than 65535 is truncated. */
static size_t
add_tlv (unsigned char *buffer, unsigned int tag, size_t length)
{
unsigned char *p = buffer;
assert (tag <= 0xffff);
if ( tag > 0xff )
*p++ = tag >> 8;
*p++ = tag;
if (length < 128)
*p++ = length;
else if (length < 256)
{
*p++ = 0x81;
*p++ = length;
}
else
{
if (length > 0xffff)
length = 0xffff;
*p++ = 0x82;
*p++ = length >> 8;
*p++ = length;
}
return p - buffer;
}
static gpg_error_t
build_privkey_template (app_t app, int keyno,
const unsigned char *rsa_n, size_t rsa_n_len,
const unsigned char *rsa_e, size_t rsa_e_len,
const unsigned char *rsa_p, size_t rsa_p_len,
const unsigned char *rsa_q, size_t rsa_q_len,
const unsigned char *rsa_u, size_t rsa_u_len,
const unsigned char *rsa_dp, size_t rsa_dp_len,
const unsigned char *rsa_dq, size_t rsa_dq_len,
unsigned char **result, size_t *resultlen)
{
size_t rsa_e_reqlen;
unsigned char privkey[7*(1+3+3)];
size_t privkey_len;
unsigned char exthdr[2+2+3];
size_t exthdr_len;
unsigned char suffix[2+3];
size_t suffix_len;
unsigned char *tp;
size_t datalen;
unsigned char *template;
size_t template_size;
*result = NULL;
*resultlen = 0;
switch (app->app_local->keyattr[keyno].rsa.format)
{
case RSA_STD:
case RSA_STD_N:
case RSA_CRT:
case RSA_CRT_N:
break;
default:
return gpg_error (GPG_ERR_INV_VALUE);
}
/* Get the required length for E. Rounded up to the nearest byte */
rsa_e_reqlen = (app->app_local->keyattr[keyno].rsa.e_bits + 7) / 8;
assert (rsa_e_len <= rsa_e_reqlen);
/* Build the 7f48 cardholder private key template. */
datalen = 0;
tp = privkey;
tp += add_tlv (tp, 0x91, rsa_e_reqlen);
datalen += rsa_e_reqlen;
tp += add_tlv (tp, 0x92, rsa_p_len);
datalen += rsa_p_len;
tp += add_tlv (tp, 0x93, rsa_q_len);
datalen += rsa_q_len;
if (app->app_local->keyattr[keyno].rsa.format == RSA_CRT
|| app->app_local->keyattr[keyno].rsa.format == RSA_CRT_N)
{
tp += add_tlv (tp, 0x94, rsa_u_len);
datalen += rsa_u_len;
tp += add_tlv (tp, 0x95, rsa_dp_len);
datalen += rsa_dp_len;
tp += add_tlv (tp, 0x96, rsa_dq_len);
datalen += rsa_dq_len;
}
if (app->app_local->keyattr[keyno].rsa.format == RSA_STD_N
|| app->app_local->keyattr[keyno].rsa.format == RSA_CRT_N)
{
tp += add_tlv (tp, 0x97, rsa_n_len);
datalen += rsa_n_len;
}
privkey_len = tp - privkey;
/* Build the extended header list without the private key template. */
tp = exthdr;
*tp++ = keyno ==0 ? 0xb6 : keyno == 1? 0xb8 : 0xa4;
*tp++ = 0;
tp += add_tlv (tp, 0x7f48, privkey_len);
exthdr_len = tp - exthdr;
/* Build the 5f48 suffix of the data. */
tp = suffix;
tp += add_tlv (tp, 0x5f48, datalen);
suffix_len = tp - suffix;
/* Now concatenate everything. */
template_size = (1 + 3 /* 0x4d and len. */
+ exthdr_len
+ privkey_len
+ suffix_len
+ datalen);
tp = template = xtrymalloc_secure (template_size);
if (!template)
return gpg_error_from_syserror ();
tp += add_tlv (tp, 0x4d, exthdr_len + privkey_len + suffix_len + datalen);
memcpy (tp, exthdr, exthdr_len);
tp += exthdr_len;
memcpy (tp, privkey, privkey_len);
tp += privkey_len;
memcpy (tp, suffix, suffix_len);
tp += suffix_len;
memcpy (tp, rsa_e, rsa_e_len);
if (rsa_e_len < rsa_e_reqlen)
{
/* Right justify E. */
memmove (tp + rsa_e_reqlen - rsa_e_len, tp, rsa_e_len);
memset (tp, 0, rsa_e_reqlen - rsa_e_len);
}
tp += rsa_e_reqlen;
memcpy (tp, rsa_p, rsa_p_len);
tp += rsa_p_len;
memcpy (tp, rsa_q, rsa_q_len);
tp += rsa_q_len;
if (app->app_local->keyattr[keyno].rsa.format == RSA_CRT
|| app->app_local->keyattr[keyno].rsa.format == RSA_CRT_N)
{
memcpy (tp, rsa_u, rsa_u_len);
tp += rsa_u_len;
memcpy (tp, rsa_dp, rsa_dp_len);
tp += rsa_dp_len;
memcpy (tp, rsa_dq, rsa_dq_len);
tp += rsa_dq_len;
}
if (app->app_local->keyattr[keyno].rsa.format == RSA_STD_N
|| app->app_local->keyattr[keyno].rsa.format == RSA_CRT_N)
{
memcpy (tp, rsa_n, rsa_n_len);
tp += rsa_n_len;
}
/* Sanity check. We don't know the exact length because we
allocated 3 bytes for the first length header. */
assert (tp - template <= template_size);
*result = template;
*resultlen = tp - template;
return 0;
}
static gpg_error_t
build_ecc_privkey_template (app_t app, int keyno,
const unsigned char *ecc_d, size_t ecc_d_len,
const unsigned char *ecc_q, size_t ecc_q_len,
unsigned char **result, size_t *resultlen)
{
unsigned char privkey[2+2];
size_t privkey_len;
unsigned char exthdr[2+2+1];
size_t exthdr_len;
unsigned char suffix[2+1];
size_t suffix_len;
unsigned char *tp;
size_t datalen;
unsigned char *template;
size_t template_size;
int pubkey_required;
pubkey_required = !!(app->app_local->keyattr[keyno].ecc.flags
& ECC_FLAG_PUBKEY);
*result = NULL;
*resultlen = 0;
/* Build the 7f48 cardholder private key template. */
datalen = 0;
tp = privkey;
tp += add_tlv (tp, 0x92, ecc_d_len);
datalen += ecc_d_len;
if (pubkey_required)
{
tp += add_tlv (tp, 0x99, ecc_q_len);
datalen += ecc_q_len;
}
privkey_len = tp - privkey;
/* Build the extended header list without the private key template. */
tp = exthdr;
*tp++ = keyno ==0 ? 0xb6 : keyno == 1? 0xb8 : 0xa4;
*tp++ = 0;
tp += add_tlv (tp, 0x7f48, privkey_len);
exthdr_len = tp - exthdr;
/* Build the 5f48 suffix of the data. */
tp = suffix;
tp += add_tlv (tp, 0x5f48, datalen);
suffix_len = tp - suffix;
/* Now concatenate everything. */
template_size = (1 + 1 /* 0x4d and len. */
+ exthdr_len
+ privkey_len
+ suffix_len
+ datalen);
if (exthdr_len + privkey_len + suffix_len + datalen >= 128)
template_size++;
tp = template = xtrymalloc_secure (template_size);
if (!template)
return gpg_error_from_syserror ();
tp += add_tlv (tp, 0x4d, exthdr_len + privkey_len + suffix_len + datalen);
memcpy (tp, exthdr, exthdr_len);
tp += exthdr_len;
memcpy (tp, privkey, privkey_len);
tp += privkey_len;
memcpy (tp, suffix, suffix_len);
tp += suffix_len;
memcpy (tp, ecc_d, ecc_d_len);
tp += ecc_d_len;
if (pubkey_required)
{
memcpy (tp, ecc_q, ecc_q_len);
tp += ecc_q_len;
}
assert (tp - template == template_size);
*result = template;
*resultlen = tp - template;
return 0;
}
/* Helper for do_writekley to change the size of a key. Not ethat
this deletes the entire key without asking. */
static gpg_error_t
change_keyattr (app_t app, int keyno, const unsigned char *buf, size_t buflen,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg)
{
gpg_error_t err;
assert (keyno >=0 && keyno <= 2);
/* Prepare for storing the key. */
err = verify_chv3 (app, pincb, pincb_arg);
if (err)
return err;
/* Change the attribute. */
- err = iso7816_put_data (app->slot, 0, 0xC1+keyno, buf, buflen);
+ err = iso7816_put_data (app_get_slot (app), 0, 0xC1+keyno, buf, buflen);
if (err)
log_error ("error changing key attribute (key=%d)\n", keyno+1);
else
log_info ("key attribute changed (key=%d)\n", keyno+1);
flush_cache (app);
parse_algorithm_attribute (app, keyno);
app->did_chv1 = 0;
app->did_chv2 = 0;
app->did_chv3 = 0;
return err;
}
static gpg_error_t
change_rsa_keyattr (app_t app, int keyno, unsigned int nbits,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg)
{
gpg_error_t err = 0;
unsigned char *buf;
size_t buflen;
void *relptr;
/* Read the current attributes into a buffer. */
relptr = get_one_do (app, 0xC1+keyno, &buf, &buflen, NULL);
if (!relptr)
err = gpg_error (GPG_ERR_CARD);
else if (buflen < 6)
{
/* Attributes too short. */
xfree (relptr);
err = gpg_error (GPG_ERR_CARD);
}
else
{
/* If key attribute was RSA, we only change n_bits and don't
touch anything else. Before we do so, we round up NBITS to a
sensible way in the same way as gpg's key generation does it.
This may help to sort out problems with a few bits too short
keys. */
nbits = ((nbits + 31) / 32) * 32;
buf[1] = (nbits >> 8);
buf[2] = nbits;
/* If it was not RSA, we need to fill other parts. */
if (buf[0] != PUBKEY_ALGO_RSA)
{
buf[0] = PUBKEY_ALGO_RSA;
buf[3] = 0;
buf[4] = 32;
buf[5] = 0;
buflen = 6;
}
err = change_keyattr (app, keyno, buf, buflen, pincb, pincb_arg);
xfree (relptr);
}
return err;
}
/* Helper to process an setattr command for name KEY-ATTR.
In (VALUE,VALUELEN), it expects following string:
RSA: "--force <key> <algo> rsa<nbits>"
ECC: "--force <key> <algo> <curvename>"
*/
static gpg_error_t
change_keyattr_from_string (app_t app,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const void *value, size_t valuelen)
{
gpg_error_t err = 0;
char *string;
int key, keyno, algo;
int n = 0;
/* VALUE is expected to be a string but not guaranteed to be
terminated. Thus copy it to an allocated buffer first. */
string = xtrymalloc (valuelen+1);
if (!string)
return gpg_error_from_syserror ();
memcpy (string, value, valuelen);
string[valuelen] = 0;
/* Because this function deletes the key we require the string
"--force" in the data to make clear that something serious might
happen. */
sscanf (string, "--force %d %d %n", &key, &algo, &n);
if (n < 12)
{
err = gpg_error (GPG_ERR_INV_DATA);
goto leave;
}
keyno = key - 1;
if (keyno < 0 || keyno > 2)
err = gpg_error (GPG_ERR_INV_ID);
else if (algo == PUBKEY_ALGO_RSA)
{
unsigned int nbits;
errno = 0;
nbits = strtoul (string+n+3, NULL, 10);
if (errno)
err = gpg_error (GPG_ERR_INV_DATA);
else if (nbits < 1024)
err = gpg_error (GPG_ERR_TOO_SHORT);
else if (nbits > 4096)
err = gpg_error (GPG_ERR_TOO_LARGE);
else
err = change_rsa_keyattr (app, keyno, nbits, pincb, pincb_arg);
}
else if (algo == PUBKEY_ALGO_ECDH || algo == PUBKEY_ALGO_ECDSA
|| algo == PUBKEY_ALGO_EDDSA)
{
const char *oidstr;
gcry_mpi_t oid;
const unsigned char *oidbuf;
size_t oid_len;
oidstr = openpgp_curve_to_oid (string+n, NULL);
if (!oidstr)
{
err = gpg_error (GPG_ERR_INV_DATA);
goto leave;
}
err = openpgp_oid_from_str (oidstr, &oid);
if (err)
goto leave;
oidbuf = gcry_mpi_get_opaque (oid, &n);
oid_len = (n+7)/8;
/* We have enough room at STRING. */
string[0] = algo;
memcpy (string+1, oidbuf+1, oid_len-1);
err = change_keyattr (app, keyno, string, oid_len, pincb, pincb_arg);
gcry_mpi_release (oid);
}
else
err = gpg_error (GPG_ERR_PUBKEY_ALGO);
leave:
xfree (string);
return err;
}
static gpg_error_t
rsa_writekey (app_t app, gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg, int keyno,
const unsigned char *buf, size_t buflen, int depth)
{
gpg_error_t err;
const unsigned char *tok;
size_t toklen;
int last_depth1, last_depth2;
const unsigned char *rsa_n = NULL;
const unsigned char *rsa_e = NULL;
const unsigned char *rsa_p = NULL;
const unsigned char *rsa_q = NULL;
size_t rsa_n_len, rsa_e_len, rsa_p_len, rsa_q_len;
unsigned int nbits;
unsigned int maxbits;
unsigned char *template = NULL;
unsigned char *tp;
size_t template_len;
unsigned char fprbuf[20];
u32 created_at = 0;
if (app->app_local->keyattr[keyno].key_type != KEY_TYPE_RSA)
{
log_error (_("unsupported algorithm: %s"), "RSA");
err = gpg_error (GPG_ERR_INV_VALUE);
goto leave;
}
last_depth1 = depth;
while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))
&& depth && depth >= last_depth1)
{
if (tok)
{
err = gpg_error (GPG_ERR_UNKNOWN_SEXP);
goto leave;
}
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
goto leave;
if (tok && toklen == 1)
{
const unsigned char **mpi;
size_t *mpi_len;
switch (*tok)
{
case 'n': mpi = &rsa_n; mpi_len = &rsa_n_len; break;
case 'e': mpi = &rsa_e; mpi_len = &rsa_e_len; break;
case 'p': mpi = &rsa_p; mpi_len = &rsa_p_len; break;
case 'q': mpi = &rsa_q; mpi_len = &rsa_q_len;break;
default: mpi = NULL; mpi_len = NULL; break;
}
if (mpi && *mpi)
{
err = gpg_error (GPG_ERR_DUP_VALUE);
goto leave;
}
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
goto leave;
if (tok && mpi)
{
/* Strip off leading zero bytes and save. */
for (;toklen && !*tok; toklen--, tok++)
;
*mpi = tok;
*mpi_len = toklen;
}
}
/* Skip until end of list. */
last_depth2 = depth;
while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))
&& depth && depth >= last_depth2)
;
if (err)
goto leave;
}
/* Parse other attributes. */
last_depth1 = depth;
while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))
&& depth && depth >= last_depth1)
{
if (tok)
{
err = gpg_error (GPG_ERR_UNKNOWN_SEXP);
goto leave;
}
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
goto leave;
if (tok && toklen == 10 && !memcmp ("created-at", tok, toklen))
{
if ((err = parse_sexp (&buf,&buflen,&depth,&tok,&toklen)))
goto leave;
if (tok)
{
for (created_at=0; toklen && *tok && *tok >= '0' && *tok <= '9';
tok++, toklen--)
created_at = created_at*10 + (*tok - '0');
}
}
/* Skip until end of list. */
last_depth2 = depth;
while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))
&& depth && depth >= last_depth2)
;
if (err)
goto leave;
}
/* Check that we have all parameters and that they match the card
description. */
if (!created_at)
{
log_error (_("creation timestamp missing\n"));
err = gpg_error (GPG_ERR_INV_VALUE);
goto leave;
}
maxbits = app->app_local->keyattr[keyno].rsa.n_bits;
nbits = rsa_n? count_bits (rsa_n, rsa_n_len) : 0;
if (opt.verbose)
log_info ("RSA modulus size is %u bits\n", nbits);
if (nbits && nbits != maxbits
&& app->app_local->extcap.algo_attr_change)
{
/* Try to switch the key to a new length. */
err = change_rsa_keyattr (app, keyno, nbits, pincb, pincb_arg);
if (!err)
maxbits = app->app_local->keyattr[keyno].rsa.n_bits;
}
if (nbits != maxbits)
{
log_error (_("RSA modulus missing or not of size %d bits\n"),
(int)maxbits);
err = gpg_error (GPG_ERR_BAD_SECKEY);
goto leave;
}
maxbits = app->app_local->keyattr[keyno].rsa.e_bits;
if (maxbits > 32 && !app->app_local->extcap.is_v2)
maxbits = 32; /* Our code for v1 does only support 32 bits. */
nbits = rsa_e? count_bits (rsa_e, rsa_e_len) : 0;
if (nbits < 2 || nbits > maxbits)
{
log_error (_("RSA public exponent missing or larger than %d bits\n"),
(int)maxbits);
err = gpg_error (GPG_ERR_BAD_SECKEY);
goto leave;
}
maxbits = app->app_local->keyattr[keyno].rsa.n_bits/2;
nbits = rsa_p? count_bits (rsa_p, rsa_p_len) : 0;
if (nbits != maxbits)
{
log_error (_("RSA prime %s missing or not of size %d bits\n"),
"P", (int)maxbits);
err = gpg_error (GPG_ERR_BAD_SECKEY);
goto leave;
}
nbits = rsa_q? count_bits (rsa_q, rsa_q_len) : 0;
if (nbits != maxbits)
{
log_error (_("RSA prime %s missing or not of size %d bits\n"),
"Q", (int)maxbits);
err = gpg_error (GPG_ERR_BAD_SECKEY);
goto leave;
}
/* We need to remove the cached public key. */
xfree (app->app_local->pk[keyno].key);
app->app_local->pk[keyno].key = NULL;
app->app_local->pk[keyno].keylen = 0;
app->app_local->pk[keyno].read_done = 0;
if (app->app_local->extcap.is_v2)
{
unsigned char *rsa_u, *rsa_dp, *rsa_dq;
size_t rsa_u_len, rsa_dp_len, rsa_dq_len;
gcry_mpi_t mpi_e, mpi_p, mpi_q;
gcry_mpi_t mpi_u = gcry_mpi_snew (0);
gcry_mpi_t mpi_dp = gcry_mpi_snew (0);
gcry_mpi_t mpi_dq = gcry_mpi_snew (0);
gcry_mpi_t mpi_tmp = gcry_mpi_snew (0);
int exmode;
/* Calculate the u, dp and dq components needed by RSA_CRT cards */
gcry_mpi_scan (&mpi_e, GCRYMPI_FMT_USG, rsa_e, rsa_e_len, NULL);
gcry_mpi_scan (&mpi_p, GCRYMPI_FMT_USG, rsa_p, rsa_p_len, NULL);
gcry_mpi_scan (&mpi_q, GCRYMPI_FMT_USG, rsa_q, rsa_q_len, NULL);
gcry_mpi_invm (mpi_u, mpi_q, mpi_p);
gcry_mpi_sub_ui (mpi_tmp, mpi_p, 1);
gcry_mpi_invm (mpi_dp, mpi_e, mpi_tmp);
gcry_mpi_sub_ui (mpi_tmp, mpi_q, 1);
gcry_mpi_invm (mpi_dq, mpi_e, mpi_tmp);
gcry_mpi_aprint (GCRYMPI_FMT_USG, &rsa_u, &rsa_u_len, mpi_u);
gcry_mpi_aprint (GCRYMPI_FMT_USG, &rsa_dp, &rsa_dp_len, mpi_dp);
gcry_mpi_aprint (GCRYMPI_FMT_USG, &rsa_dq, &rsa_dq_len, mpi_dq);
gcry_mpi_release (mpi_e);
gcry_mpi_release (mpi_p);
gcry_mpi_release (mpi_q);
gcry_mpi_release (mpi_u);
gcry_mpi_release (mpi_dp);
gcry_mpi_release (mpi_dq);
gcry_mpi_release (mpi_tmp);
/* Build the private key template as described in section 4.3.3.7 of
the OpenPGP card specs version 2.0. */
err = build_privkey_template (app, keyno,
rsa_n, rsa_n_len,
rsa_e, rsa_e_len,
rsa_p, rsa_p_len,
rsa_q, rsa_q_len,
rsa_u, rsa_u_len,
rsa_dp, rsa_dp_len,
rsa_dq, rsa_dq_len,
&template, &template_len);
xfree(rsa_u);
xfree(rsa_dp);
xfree(rsa_dq);
if (err)
goto leave;
/* Prepare for storing the key. */
err = verify_chv3 (app, pincb, pincb_arg);
if (err)
goto leave;
/* Store the key. */
if (app->app_local->cardcap.ext_lc_le && template_len > 254)
exmode = 1; /* Use extended length w/o a limit. */
else if (app->app_local->cardcap.cmd_chaining && template_len > 254)
exmode = -254;
else
exmode = 0;
- err = iso7816_put_data_odd (app->slot, exmode, 0x3fff,
+ err = iso7816_put_data_odd (app_get_slot (app), exmode, 0x3fff,
template, template_len);
}
else
{
/* Build the private key template as described in section 4.3.3.6 of
the OpenPGP card specs version 1.1:
0xC0 <length> public exponent
0xC1 <length> prime p
0xC2 <length> prime q
*/
assert (rsa_e_len <= 4);
template_len = (1 + 1 + 4
+ 1 + 1 + rsa_p_len
+ 1 + 1 + rsa_q_len);
template = tp = xtrymalloc_secure (template_len);
if (!template)
{
err = gpg_error_from_syserror ();
goto leave;
}
*tp++ = 0xC0;
*tp++ = 4;
memcpy (tp, rsa_e, rsa_e_len);
if (rsa_e_len < 4)
{
/* Right justify E. */
memmove (tp+4-rsa_e_len, tp, rsa_e_len);
memset (tp, 0, 4-rsa_e_len);
}
tp += 4;
*tp++ = 0xC1;
*tp++ = rsa_p_len;
memcpy (tp, rsa_p, rsa_p_len);
tp += rsa_p_len;
*tp++ = 0xC2;
*tp++ = rsa_q_len;
memcpy (tp, rsa_q, rsa_q_len);
tp += rsa_q_len;
assert (tp - template == template_len);
/* Prepare for storing the key. */
err = verify_chv3 (app, pincb, pincb_arg);
if (err)
goto leave;
/* Store the key. */
- err = iso7816_put_data (app->slot, 0,
+ err = iso7816_put_data (app_get_slot (app), 0,
(app->appversion > 0x0007? 0xE0:0xE9)+keyno,
template, template_len);
}
if (err)
{
log_error (_("failed to store the key: %s\n"), gpg_strerror (err));
goto leave;
}
err = store_fpr (app, keyno, created_at, fprbuf, PUBKEY_ALGO_RSA,
rsa_n, rsa_n_len, rsa_e, rsa_e_len);
if (err)
goto leave;
leave:
xfree (template);
return err;
}
static gpg_error_t
ecc_writekey (app_t app, gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg, int keyno,
const unsigned char *buf, size_t buflen, int depth)
{
gpg_error_t err;
const unsigned char *tok;
size_t toklen;
int last_depth1, last_depth2;
const unsigned char *ecc_q = NULL;
const unsigned char *ecc_d = NULL;
size_t ecc_q_len, ecc_d_len;
const char *curve = NULL;
u32 created_at = 0;
const char *oidstr;
int flag_djb_tweak = 0;
int algo;
gcry_mpi_t oid = NULL;
const unsigned char *oidbuf;
unsigned int n;
size_t oid_len;
unsigned char fprbuf[20];
/* (private-key(ecc(curve%s)(q%m)(d%m))(created-at%d)):
curve = "NIST P-256" */
/* (private-key(ecc(curve%s)(q%m)(d%m))(created-at%d)):
curve = "secp256k1" */
/* (private-key(ecc(curve%s)(flags eddsa)(q%m)(d%m))(created-at%d)):
curve = "Ed25519" */
last_depth1 = depth;
while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))
&& depth && depth >= last_depth1)
{
if (tok)
{
err = gpg_error (GPG_ERR_UNKNOWN_SEXP);
goto leave;
}
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
goto leave;
if (tok && toklen == 5 && !memcmp (tok, "curve", 5))
{
char *curve_name;
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
goto leave;
curve_name = xtrymalloc (toklen+1);
if (!curve_name)
{
err = gpg_error_from_syserror ();
goto leave;
}
memcpy (curve_name, tok, toklen);
curve_name[toklen] = 0;
curve = openpgp_is_curve_supported (curve_name, NULL, NULL);
xfree (curve_name);
}
else if (tok && toklen == 5 && !memcmp (tok, "flags", 5))
{
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
goto leave;
if (tok)
{
if ((toklen == 5 && !memcmp (tok, "eddsa", 5))
|| (toklen == 9 && !memcmp (tok, "djb-tweak", 9)))
flag_djb_tweak = 1;
}
}
else if (tok && toklen == 1)
{
const unsigned char **buf2;
size_t *buf2len;
int native = flag_djb_tweak;
switch (*tok)
{
case 'q': buf2 = &ecc_q; buf2len = &ecc_q_len; break;
case 'd': buf2 = &ecc_d; buf2len = &ecc_d_len; native = 0; break;
default: buf2 = NULL; buf2len = NULL; break;
}
if (buf2 && *buf2)
{
err = gpg_error (GPG_ERR_DUP_VALUE);
goto leave;
}
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
goto leave;
if (tok && buf2)
{
if (!native)
/* Strip off leading zero bytes and save. */
for (;toklen && !*tok; toklen--, tok++)
;
*buf2 = tok;
*buf2len = toklen;
}
}
/* Skip until end of list. */
last_depth2 = depth;
while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))
&& depth && depth >= last_depth2)
;
if (err)
goto leave;
}
/* Parse other attributes. */
last_depth1 = depth;
while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))
&& depth && depth >= last_depth1)
{
if (tok)
{
err = gpg_error (GPG_ERR_UNKNOWN_SEXP);
goto leave;
}
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
goto leave;
if (tok && toklen == 10 && !memcmp ("created-at", tok, toklen))
{
if ((err = parse_sexp (&buf,&buflen,&depth,&tok,&toklen)))
goto leave;
if (tok)
{
for (created_at=0; toklen && *tok && *tok >= '0' && *tok <= '9';
tok++, toklen--)
created_at = created_at*10 + (*tok - '0');
}
}
/* Skip until end of list. */
last_depth2 = depth;
while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))
&& depth && depth >= last_depth2)
;
if (err)
goto leave;
}
/* Check that we have all parameters and that they match the card
description. */
if (!curve)
{
log_error (_("unsupported curve\n"));
err = gpg_error (GPG_ERR_INV_VALUE);
goto leave;
}
if (!created_at)
{
log_error (_("creation timestamp missing\n"));
err = gpg_error (GPG_ERR_INV_VALUE);
goto leave;
}
if (flag_djb_tweak && keyno != 1)
algo = PUBKEY_ALGO_EDDSA;
else if (keyno == 1)
algo = PUBKEY_ALGO_ECDH;
else
algo = PUBKEY_ALGO_ECDSA;
oidstr = openpgp_curve_to_oid (curve, NULL);
err = openpgp_oid_from_str (oidstr, &oid);
if (err)
goto leave;
oidbuf = gcry_mpi_get_opaque (oid, &n);
if (!oidbuf)
{
err = gpg_error_from_syserror ();
goto leave;
}
oid_len = (n+7)/8;
if (app->app_local->keyattr[keyno].key_type != KEY_TYPE_ECC
|| app->app_local->keyattr[keyno].ecc.curve != curve
|| (flag_djb_tweak !=
(app->app_local->keyattr[keyno].ecc.flags & ECC_FLAG_DJB_TWEAK)))
{
if (app->app_local->extcap.algo_attr_change)
{
unsigned char *keyattr;
if (!oid_len)
{
err = gpg_error (GPG_ERR_INTERNAL);
goto leave;
}
keyattr = xtrymalloc (oid_len);
if (!keyattr)
{
err = gpg_error_from_syserror ();
goto leave;
}
keyattr[0] = algo;
memcpy (keyattr+1, oidbuf+1, oid_len-1);
err = change_keyattr (app, keyno, keyattr, oid_len, pincb, pincb_arg);
xfree (keyattr);
if (err)
goto leave;
}
else
{
log_error ("key attribute on card doesn't match\n");
err = gpg_error (GPG_ERR_INV_VALUE);
goto leave;
}
}
if (opt.verbose)
log_info ("ECC private key size is %u bytes\n", (unsigned int)ecc_d_len);
/* We need to remove the cached public key. */
xfree (app->app_local->pk[keyno].key);
app->app_local->pk[keyno].key = NULL;
app->app_local->pk[keyno].keylen = 0;
app->app_local->pk[keyno].read_done = 0;
if (app->app_local->extcap.is_v2)
{
/* Build the private key template as described in section 4.3.3.7 of
the OpenPGP card specs version 2.0. */
unsigned char *template;
size_t template_len;
int exmode;
err = build_ecc_privkey_template (app, keyno,
ecc_d, ecc_d_len,
ecc_q, ecc_q_len,
&template, &template_len);
if (err)
goto leave;
/* Prepare for storing the key. */
err = verify_chv3 (app, pincb, pincb_arg);
if (err)
{
xfree (template);
goto leave;
}
/* Store the key. */
if (app->app_local->cardcap.ext_lc_le && template_len > 254)
exmode = 1; /* Use extended length w/o a limit. */
else if (app->app_local->cardcap.cmd_chaining && template_len > 254)
exmode = -254;
else
exmode = 0;
- err = iso7816_put_data_odd (app->slot, exmode, 0x3fff,
+ err = iso7816_put_data_odd (app_get_slot (app), exmode, 0x3fff,
template, template_len);
xfree (template);
}
else
err = gpg_error (GPG_ERR_NOT_SUPPORTED);
if (err)
{
log_error (_("failed to store the key: %s\n"), gpg_strerror (err));
goto leave;
}
err = store_fpr (app, keyno, created_at, fprbuf, algo, oidbuf, oid_len,
ecc_q, ecc_q_len, ecdh_params (curve), (size_t)4);
leave:
gcry_mpi_release (oid);
return err;
}
/* Handle the WRITEKEY command for OpenPGP. This function expects a
canonical encoded S-expression with the secret key in KEYDATA and
its length (for assertions) in KEYDATALEN. KEYID needs to be the
usual keyid which for OpenPGP is the string "OPENPGP.n" with
n=1,2,3. Bit 0 of FLAGS indicates whether an existing key shall
get overwritten. PINCB and PINCB_ARG are the usual arguments for
the pinentry callback. */
static gpg_error_t
do_writekey (app_t app, ctrl_t ctrl,
const char *keyid, unsigned int flags,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const unsigned char *keydata, size_t keydatalen)
{
gpg_error_t err;
int force = (flags & 1);
int keyno;
const unsigned char *buf, *tok;
size_t buflen, toklen;
int depth;
(void)ctrl;
if (!strcmp (keyid, "OPENPGP.1"))
keyno = 0;
else if (!strcmp (keyid, "OPENPGP.2"))
keyno = 1;
else if (!strcmp (keyid, "OPENPGP.3"))
keyno = 2;
else
return gpg_error (GPG_ERR_INV_ID);
err = does_key_exist (app, keyno, 0, force);
if (err)
return err;
/*
Parse the S-expression
*/
buf = keydata;
buflen = keydatalen;
depth = 0;
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
goto leave;
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
goto leave;
if (!tok || toklen != 11 || memcmp ("private-key", tok, toklen))
{
if (!tok)
;
else if (toklen == 21 && !memcmp ("protected-private-key", tok, toklen))
log_info ("protected-private-key passed to writekey\n");
else if (toklen == 20 && !memcmp ("shadowed-private-key", tok, toklen))
log_info ("shadowed-private-key passed to writekey\n");
err = gpg_error (GPG_ERR_BAD_SECKEY);
goto leave;
}
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
goto leave;
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
goto leave;
if (tok && toklen == 3 && memcmp ("rsa", tok, toklen) == 0)
err = rsa_writekey (app, pincb, pincb_arg, keyno, buf, buflen, depth);
else if (tok && toklen == 3 && memcmp ("ecc", tok, toklen) == 0)
err = ecc_writekey (app, pincb, pincb_arg, keyno, buf, buflen, depth);
else
{
err = gpg_error (GPG_ERR_WRONG_PUBKEY_ALGO);
goto leave;
}
leave:
return err;
}
/* Handle the GENKEY command. */
static gpg_error_t
do_genkey (app_t app, ctrl_t ctrl, const char *keynostr, const char *keytype,
unsigned int flags, time_t createtime,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg)
{
gpg_error_t err;
char numbuf[30];
unsigned char *buffer = NULL;
const unsigned char *keydata;
size_t buflen, keydatalen;
u32 created_at;
int keyno = atoi (keynostr) - 1;
int force = (flags & 1);
time_t start_at;
int exmode = 0;
int le_value = 256; /* Use legacy value. */
(void)keytype; /* Ignored for OpenPGP cards. */
if (keyno < 0 || keyno > 2)
return gpg_error (GPG_ERR_INV_ID);
/* We flush the cache to increase the traffic before a key
generation. This _might_ help a card to gather more entropy. */
flush_cache (app);
/* Obviously we need to remove the cached public key. */
xfree (app->app_local->pk[keyno].key);
app->app_local->pk[keyno].key = NULL;
app->app_local->pk[keyno].keylen = 0;
app->app_local->pk[keyno].read_done = 0;
/* Check whether a key already exists. */
err = does_key_exist (app, keyno, 1, force);
if (err)
return err;
if (app->app_local->keyattr[keyno].key_type == KEY_TYPE_RSA)
{
unsigned int keybits = app->app_local->keyattr[keyno].rsa.n_bits;
/* Because we send the key parameter back via status lines we need
to put a limit on the max. allowed keysize. 2048 bit will
already lead to a 527 byte long status line and thus a 4096 bit
key would exceed the Assuan line length limit. */
if (keybits > 4096)
return gpg_error (GPG_ERR_TOO_LARGE);
if (app->app_local->cardcap.ext_lc_le && keybits > RSA_SMALL_SIZE_KEY
&& app->app_local->keyattr[keyno].key_type == KEY_TYPE_RSA)
{
exmode = 1; /* Use extended length w/o a limit. */
le_value = determine_rsa_response (app, keyno);
/* No need to check le_value because it comes from a 16 bit
value and thus can't create an overflow on a 32 bit
system. */
}
}
/* Prepare for key generation by verifying the Admin PIN. */
err = verify_chv3 (app, pincb, pincb_arg);
if (err)
return err;
log_info (_("please wait while key is being generated ...\n"));
start_at = time (NULL);
- err = iso7816_generate_keypair (app->slot, exmode, 0x80, 0,
+ err = iso7816_generate_keypair (app_get_slot (app), exmode, 0x80, 0,
(keyno == 0? "\xB6" :
keyno == 1? "\xB8" : "\xA4"),
2, le_value, &buffer, &buflen);
if (err)
{
log_error (_("generating key failed\n"));
return gpg_error (GPG_ERR_CARD);
}
{
int nsecs = (int)(time (NULL) - start_at);
log_info (ngettext("key generation completed (%d second)\n",
"key generation completed (%d seconds)\n",
nsecs), nsecs);
}
keydata = find_tlv (buffer, buflen, 0x7F49, &keydatalen);
if (!keydata)
{
err = gpg_error (GPG_ERR_CARD);
log_error (_("response does not contain the public key data\n"));
goto leave;
}
created_at = (u32)(createtime? createtime : gnupg_get_time ());
sprintf (numbuf, "%u", created_at);
send_status_info (ctrl, "KEY-CREATED-AT",
numbuf, (size_t)strlen(numbuf), NULL, 0);
err = read_public_key (app, ctrl, created_at, keyno, buffer, buflen);
leave:
xfree (buffer);
return err;
}
static unsigned long
convert_sig_counter_value (const unsigned char *value, size_t valuelen)
{
unsigned long ul;
if (valuelen == 3 )
ul = (value[0] << 16) | (value[1] << 8) | value[2];
else
{
log_error (_("invalid structure of OpenPGP card (DO 0x93)\n"));
ul = 0;
}
return ul;
}
static unsigned long
get_sig_counter (app_t app)
{
void *relptr;
unsigned char *value;
size_t valuelen;
unsigned long ul;
relptr = get_one_do (app, 0x0093, &value, &valuelen, NULL);
if (!relptr)
return 0;
ul = convert_sig_counter_value (value, valuelen);
xfree (relptr);
return ul;
}
static gpg_error_t
compare_fingerprint (app_t app, int keyno, unsigned char *sha1fpr)
{
const unsigned char *fpr;
unsigned char *buffer;
size_t buflen, n;
int rc, i;
assert (keyno >= 0 && keyno <= 2);
rc = get_cached_data (app, 0x006E, &buffer, &buflen, 0, 0);
if (rc)
{
log_error (_("error reading application data\n"));
return gpg_error (GPG_ERR_GENERAL);
}
fpr = find_tlv (buffer, buflen, 0x00C5, &n);
if (!fpr || n != 60)
{
xfree (buffer);
log_error (_("error reading fingerprint DO\n"));
return gpg_error (GPG_ERR_GENERAL);
}
fpr += keyno*20;
for (i=0; i < 20; i++)
if (sha1fpr[i] != fpr[i])
{
xfree (buffer);
log_info (_("fingerprint on card does not match requested one\n"));
return gpg_error (GPG_ERR_WRONG_SECKEY);
}
xfree (buffer);
return 0;
}
/* If a fingerprint has been specified check it against the one on the
card. This allows for a meaningful error message in case the key
on the card has been replaced but the shadow information known to
gpg has not been updated. If there is no fingerprint we assume
that this is okay. */
static gpg_error_t
check_against_given_fingerprint (app_t app, const char *fpr, int key)
{
unsigned char tmp[20];
const char *s;
int n;
for (s=fpr, n=0; hexdigitp (s); s++, n++)
;
if (n != 40)
return gpg_error (GPG_ERR_INV_ID);
else if (!*s)
; /* okay */
else
return gpg_error (GPG_ERR_INV_ID);
for (s=fpr, n=0; n < 20; s += 2, n++)
tmp[n] = xtoi_2 (s);
return compare_fingerprint (app, key-1, tmp);
}
+/* Check KEYIDSTR, if it's valid.
+ When KEYNO is 0, it means it's for PIN check.
+ Otherwise, KEYNO corresponds to the slot (signing, decipher and auth).
+ KEYIDSTR is either:
+ (1) Serial number
+ (2) Serial number "/" fingerprint
+ (3) keygrip
+
+ When KEYNO is 0 and KEYIDSTR is for a keygrip, the keygrip should
+ be to be compared is the first one (keygrip for signing).
+ */
+static int
+check_keyidstr (app_t app, const char *keyidstr, int keyno)
+{
+ int rc;
+ const char *s;
+ int n;
+ const char *fpr = NULL;
+ unsigned char tmp_sn[20]; /* Actually 16 bytes but also for the fpr. */
+
+ if (strlen (keyidstr) < 32)
+ return gpg_error (GPG_ERR_INV_ID);
+ else
+ {
+ for (s=keyidstr, n=0; hexdigitp (s); s++, n++)
+ ;
+
+ /* Check if it's a keygrip */
+ if (n == 40)
+ {
+ const unsigned char *keygrip_str;
+
+ keygrip_str = app->app_local->pk[keyno?keyno-1:0].keygrip_str;
+ if (!strncmp (keygrip_str, keyidstr, 40))
+ return 0;
+ else
+ return gpg_error (GPG_ERR_INV_ID);
+ }
+
+ if (n != 32 || strncmp (keyidstr, "D27600012401", 12))
+ return gpg_error (GPG_ERR_INV_ID);
+ else if (!*s)
+ ; /* no fingerprint given: we allow this for now. */
+ else if (*s == '/')
+ fpr = s + 1;
+ else
+ return gpg_error (GPG_ERR_INV_ID);
+
+ for (s=keyidstr, n=0; n < 16; s += 2, n++)
+ tmp_sn[n] = xtoi_2 (s);
+
+ if (app->card->serialnolen != 16)
+ return gpg_error (GPG_ERR_INV_CARD);
+ if (memcmp (app->card->serialno, tmp_sn, 16))
+ return gpg_error (GPG_ERR_WRONG_CARD);
+ }
+
+ /* If a fingerprint has been specified check it against the one on
+ the card. This is allows for a meaningful error message in case
+ the key on the card has been replaced but the shadow information
+ known to gpg was not updated. If there is no fingerprint, gpg
+ will detect a bogus signature anyway due to the
+ verify-after-signing feature. */
+ rc = (fpr&&keyno)? check_against_given_fingerprint (app, fpr, keyno) : 0;
+
+ return rc;
+}
+
/* Compute a digital signature on INDATA which is expected to be the
raw message digest. For this application the KEYIDSTR consists of
the serialnumber and the fingerprint delimited by a slash.
Note that this function may return the error code
GPG_ERR_WRONG_CARD to indicate that the card currently present does
not match the one required for the requested action (e.g. the
serial number does not match).
As a special feature a KEYIDSTR of "OPENPGP.3" redirects the
operation to the auth command.
*/
static gpg_error_t
do_sign (app_t app, const char *keyidstr, int hashalgo,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const void *indata, size_t indatalen,
unsigned char **outdata, size_t *outdatalen )
{
static unsigned char rmd160_prefix[15] = /* Object ID is 1.3.36.3.2.1 */
{ 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x24, 0x03,
0x02, 0x01, 0x05, 0x00, 0x04, 0x14 };
static unsigned char sha1_prefix[15] = /* (1.3.14.3.2.26) */
{ 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e, 0x03,
0x02, 0x1a, 0x05, 0x00, 0x04, 0x14 };
static unsigned char sha224_prefix[19] = /* (2.16.840.1.101.3.4.2.4) */
{ 0x30, 0x2D, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48,
0x01, 0x65, 0x03, 0x04, 0x02, 0x04, 0x05, 0x00, 0x04,
0x1C };
static unsigned char sha256_prefix[19] = /* (2.16.840.1.101.3.4.2.1) */
{ 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05,
0x00, 0x04, 0x20 };
static unsigned char sha384_prefix[19] = /* (2.16.840.1.101.3.4.2.2) */
{ 0x30, 0x41, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, 0x05,
0x00, 0x04, 0x30 };
static unsigned char sha512_prefix[19] = /* (2.16.840.1.101.3.4.2.3) */
{ 0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05,
0x00, 0x04, 0x40 };
int rc;
unsigned char data[19+64];
size_t datalen;
- unsigned char tmp_sn[20]; /* Actually 16 bytes but also for the fpr. */
- const char *s;
- int n;
- const char *fpr = NULL;
unsigned long sigcount;
int use_auth = 0;
int exmode, le_value;
if (!keyidstr || !*keyidstr)
return gpg_error (GPG_ERR_INV_VALUE);
/* Strip off known prefixes. */
#define X(a,b,c,d) \
if (hashalgo == GCRY_MD_ ## a \
&& (d) \
&& indatalen == sizeof b ## _prefix + (c) \
&& !memcmp (indata, b ## _prefix, sizeof b ## _prefix)) \
{ \
indata = (const char*)indata + sizeof b ## _prefix; \
indatalen -= sizeof b ## _prefix; \
}
if (indatalen == 20)
; /* Assume a plain SHA-1 or RMD160 digest has been given. */
else X(SHA1, sha1, 20, 1)
else X(RMD160, rmd160, 20, 1)
else X(SHA224, sha224, 28, app->app_local->extcap.is_v2)
else X(SHA256, sha256, 32, app->app_local->extcap.is_v2)
else X(SHA384, sha384, 48, app->app_local->extcap.is_v2)
else X(SHA512, sha512, 64, app->app_local->extcap.is_v2)
else if ((indatalen == 28 || indatalen == 32
|| indatalen == 48 || indatalen ==64)
&& app->app_local->extcap.is_v2)
; /* Assume a plain SHA-3 digest has been given. */
else
{
log_error (_("card does not support digest algorithm %s\n"),
gcry_md_algo_name (hashalgo));
/* Or the supplied digest length does not match an algorithm. */
return gpg_error (GPG_ERR_INV_VALUE);
}
#undef X
/* Check whether an OpenPGP card of any version has been requested. */
if (!strcmp (keyidstr, "OPENPGP.1"))
;
else if (!strcmp (keyidstr, "OPENPGP.3"))
use_auth = 1;
- else if (strlen (keyidstr) < 32 || strncmp (keyidstr, "D27600012401", 12))
- return gpg_error (GPG_ERR_INV_ID);
else
{
- for (s=keyidstr, n=0; hexdigitp (s); s++, n++)
- ;
- if (n != 32)
- return gpg_error (GPG_ERR_INV_ID);
- else if (!*s)
- ; /* no fingerprint given: we allow this for now. */
- else if (*s == '/')
- fpr = s + 1;
- else
- return gpg_error (GPG_ERR_INV_ID);
-
- for (s=keyidstr, n=0; n < 16; s += 2, n++)
- tmp_sn[n] = xtoi_2 (s);
-
- if (app->serialnolen != 16)
- return gpg_error (GPG_ERR_INV_CARD);
- if (memcmp (app->serialno, tmp_sn, 16))
- return gpg_error (GPG_ERR_WRONG_CARD);
+ rc = check_keyidstr (app, keyidstr, 1);
+ if (rc)
+ return rc;
}
- /* If a fingerprint has been specified check it against the one on
- the card. This is allows for a meaningful error message in case
- the key on the card has been replaced but the shadow information
- known to gpg was not updated. If there is no fingerprint, gpg
- will detect a bogus signature anyway due to the
- verify-after-signing feature. */
- rc = fpr? check_against_given_fingerprint (app, fpr, 1) : 0;
- if (rc)
- return rc;
-
/* Concatenate prefix and digest. */
#define X(a,b,d) \
if (hashalgo == GCRY_MD_ ## a && (d) ) \
{ \
datalen = sizeof b ## _prefix + indatalen; \
assert (datalen <= sizeof data); \
memcpy (data, b ## _prefix, sizeof b ## _prefix); \
memcpy (data + sizeof b ## _prefix, indata, indatalen); \
}
if (use_auth
|| app->app_local->keyattr[use_auth? 2: 0].key_type == KEY_TYPE_RSA)
{
X(SHA1, sha1, 1)
else X(RMD160, rmd160, 1)
else X(SHA224, sha224, app->app_local->extcap.is_v2)
else X(SHA256, sha256, app->app_local->extcap.is_v2)
else X(SHA384, sha384, app->app_local->extcap.is_v2)
else X(SHA512, sha512, app->app_local->extcap.is_v2)
else
return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM);
}
else
{
datalen = indatalen;
memcpy (data, indata, indatalen);
}
#undef X
/* Redirect to the AUTH command if asked to. */
if (use_auth)
{
return do_auth (app, "OPENPGP.3", pincb, pincb_arg,
data, datalen,
outdata, outdatalen);
}
/* Show the number of signature done using this key. */
sigcount = get_sig_counter (app);
log_info (_("signatures created so far: %lu\n"), sigcount);
/* Check CHV if needed. */
if (!app->did_chv1 || app->force_chv1)
{
char *pinvalue;
int pinlen;
- rc = verify_a_chv (app, pincb, pincb_arg, 1, sigcount, &pinvalue, &pinlen);
+ rc = verify_a_chv (app, pincb, pincb_arg, 1, sigcount,
+ &pinvalue, &pinlen);
if (rc)
return rc;
app->did_chv1 = 1;
/* For cards with versions < 2 we want to keep CHV1 and CHV2 in
sync, thus we verify CHV2 here using the given PIN. Cards
with version2 to not have the need for a separate CHV2 and
internally use just one. Obviously we can't do that if the
pinpad has been used. */
if (!app->did_chv2 && pinvalue && !app->app_local->extcap.is_v2)
{
- rc = iso7816_verify (app->slot, 0x82, pinvalue, pinlen);
+ rc = iso7816_verify (app_get_slot (app), 0x82, pinvalue, pinlen);
if (gpg_err_code (rc) == GPG_ERR_BAD_PIN)
rc = gpg_error (GPG_ERR_PIN_NOT_SYNCED);
if (rc)
{
log_error (_("verify CHV%d failed: %s\n"), 2, gpg_strerror (rc));
xfree (pinvalue);
flush_cache_after_error (app);
return rc;
}
app->did_chv2 = 1;
}
xfree (pinvalue);
}
if (app->app_local->cardcap.ext_lc_le
&& app->app_local->keyattr[0].key_type == KEY_TYPE_RSA
&& app->app_local->keyattr[0].rsa.n_bits > RSA_SMALL_SIZE_OP)
{
exmode = 1; /* Use extended length. */
le_value = app->app_local->keyattr[0].rsa.n_bits / 8;
}
else
{
exmode = 0;
le_value = 0;
}
- rc = iso7816_compute_ds (app->slot, exmode, data, datalen, le_value,
+ rc = iso7816_compute_ds (app_get_slot (app), exmode, data, datalen, le_value,
outdata, outdatalen);
if (gpg_err_code (rc) == GPG_ERR_TIMEOUT)
clear_chv_status (app, 1);
else if (!rc && app->force_chv1)
app->did_chv1 = 0;
return rc;
}
/* Compute a digital signature using the INTERNAL AUTHENTICATE command
on INDATA which is expected to be the raw message digest. For this
application the KEYIDSTR consists of the serialnumber and the
fingerprint delimited by a slash. Optionally the id OPENPGP.3 may
be given.
Note that this function may return the error code
GPG_ERR_WRONG_CARD to indicate that the card currently present does
not match the one required for the requested action (e.g. the
serial number does not match). */
static gpg_error_t
do_auth (app_t app, const char *keyidstr,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const void *indata, size_t indatalen,
unsigned char **outdata, size_t *outdatalen )
{
int rc;
- unsigned char tmp_sn[20]; /* Actually 16 but we use it also for the fpr. */
- const char *s;
- int n;
- const char *fpr = NULL;
if (!keyidstr || !*keyidstr)
return gpg_error (GPG_ERR_INV_VALUE);
if (app->app_local->keyattr[2].key_type == KEY_TYPE_RSA
&& indatalen > 101) /* For a 2048 bit key. */
return gpg_error (GPG_ERR_INV_VALUE);
if (app->app_local->keyattr[2].key_type == KEY_TYPE_ECC)
{
if (!(app->app_local->keyattr[2].ecc.flags & ECC_FLAG_DJB_TWEAK)
&& (indatalen == 51 || indatalen == 67 || indatalen == 83))
{
const char *p = (const char *)indata + 19;
indata = p;
indatalen -= 19;
}
else
{
const char *p = (const char *)indata + 15;
indata = p;
indatalen -= 15;
}
}
/* Check whether an OpenPGP card of any version has been requested. */
if (!strcmp (keyidstr, "OPENPGP.3"))
;
- else if (strlen (keyidstr) < 32 || strncmp (keyidstr, "D27600012401", 12))
- return gpg_error (GPG_ERR_INV_ID);
else
{
- for (s=keyidstr, n=0; hexdigitp (s); s++, n++)
- ;
- if (n != 32)
- return gpg_error (GPG_ERR_INV_ID);
- else if (!*s)
- ; /* no fingerprint given: we allow this for now. */
- else if (*s == '/')
- fpr = s + 1;
- else
- return gpg_error (GPG_ERR_INV_ID);
-
- for (s=keyidstr, n=0; n < 16; s += 2, n++)
- tmp_sn[n] = xtoi_2 (s);
-
- if (app->serialnolen != 16)
- return gpg_error (GPG_ERR_INV_CARD);
- if (memcmp (app->serialno, tmp_sn, 16))
- return gpg_error (GPG_ERR_WRONG_CARD);
+ rc = check_keyidstr (app, keyidstr, 3);
+ if (rc)
+ return rc;
}
- /* If a fingerprint has been specified check it against the one on
- the card. This is allows for a meaningful error message in case
- the key on the card has been replaced but the shadow information
- known to gpg was not updated. If there is no fingerprint, gpg
- will detect a bogus signature anyway due to the
- verify-after-signing feature. */
- rc = fpr? check_against_given_fingerprint (app, fpr, 3) : 0;
- if (rc)
- return rc;
-
rc = verify_chv2 (app, pincb, pincb_arg);
if (!rc)
{
int exmode, le_value;
if (app->app_local->cardcap.ext_lc_le
&& app->app_local->keyattr[2].key_type == KEY_TYPE_RSA
&& app->app_local->keyattr[2].rsa.n_bits > RSA_SMALL_SIZE_OP)
{
exmode = 1; /* Use extended length. */
le_value = app->app_local->keyattr[2].rsa.n_bits / 8;
}
else
{
exmode = 0;
le_value = 0;
}
- rc = iso7816_internal_authenticate (app->slot, exmode,
+ rc = iso7816_internal_authenticate (app_get_slot (app), exmode,
indata, indatalen, le_value,
outdata, outdatalen);
if (gpg_err_code (rc) == GPG_ERR_TIMEOUT)
clear_chv_status (app, 1);
}
return rc;
}
static gpg_error_t
do_decipher (app_t app, const char *keyidstr,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const void *indata, size_t indatalen,
unsigned char **outdata, size_t *outdatalen,
unsigned int *r_info)
{
- int rc;
- unsigned char tmp_sn[20]; /* actually 16 but we use it also for the fpr. */
- const char *s;
int n;
- const char *fpr = NULL;
+ int rc;
int exmode, le_value;
unsigned char *fixbuf = NULL;
int padind = 0;
int fixuplen = 0;
if (!keyidstr || !*keyidstr || !indatalen)
return gpg_error (GPG_ERR_INV_VALUE);
/* Check whether an OpenPGP card of any version has been requested. */
if (!strcmp (keyidstr, "OPENPGP.2"))
;
- else if (strlen (keyidstr) < 32 || strncmp (keyidstr, "D27600012401", 12))
- return gpg_error (GPG_ERR_INV_ID);
else
{
- for (s=keyidstr, n=0; hexdigitp (s); s++, n++)
- ;
- if (n != 32)
- return gpg_error (GPG_ERR_INV_ID);
- else if (!*s)
- ; /* no fingerprint given: we allow this for now. */
- else if (*s == '/')
- fpr = s + 1;
- else
- return gpg_error (GPG_ERR_INV_ID);
-
- for (s=keyidstr, n=0; n < 16; s += 2, n++)
- tmp_sn[n] = xtoi_2 (s);
-
- if (app->serialnolen != 16)
- return gpg_error (GPG_ERR_INV_CARD);
- if (memcmp (app->serialno, tmp_sn, 16))
- return gpg_error (GPG_ERR_WRONG_CARD);
+ rc = check_keyidstr (app, keyidstr, 2);
+ if (rc)
+ return rc;
}
- /* If a fingerprint has been specified check it against the one on
- the card. This is allows for a meaningful error message in case
- the key on the card has been replaced but the shadow information
- known to gpg was not updated. If there is no fingerprint, the
- decryption won't produce the right plaintext anyway. */
- rc = fpr? check_against_given_fingerprint (app, fpr, 2) : 0;
- if (rc)
- return rc;
-
rc = verify_chv2 (app, pincb, pincb_arg);
if (rc)
return rc;
if ((indatalen == 16 + 1 || indatalen == 32 + 1)
&& ((char *)indata)[0] == 0x02)
{
/* PSO:DECIPHER with symmetric key. */
padind = -1;
}
else if (app->app_local->keyattr[1].key_type == KEY_TYPE_RSA)
{
/* We might encounter a couple of leading zeroes in the
cryptogram. Due to internal use of MPIs these leading zeroes
are stripped. However the OpenPGP card expects exactly 128
bytes for the cryptogram (for a 1k key). Thus we need to fix
it up. We do this for up to 16 leading zero bytes; a
cryptogram with more than this is with a very high
probability anyway broken. If a signed conversion was used
we may also encounter one leading zero followed by the correct
length. We fix that as well. */
if (indatalen >= (128-16) && indatalen < 128) /* 1024 bit key. */
fixuplen = 128 - indatalen;
else if (indatalen >= (192-16) && indatalen < 192) /* 1536 bit key. */
fixuplen = 192 - indatalen;
else if (indatalen >= (256-16) && indatalen < 256) /* 2048 bit key. */
fixuplen = 256 - indatalen;
else if (indatalen >= (384-16) && indatalen < 384) /* 3072 bit key. */
fixuplen = 384 - indatalen;
else if (indatalen >= (512-16) && indatalen < 512) /* 4096 bit key. */
fixuplen = 512 - indatalen;
else if (!*(const char *)indata && (indatalen == 129
|| indatalen == 193
|| indatalen == 257
|| indatalen == 385
|| indatalen == 513))
fixuplen = -1;
else
fixuplen = 0;
if (fixuplen > 0)
{
/* While we have to prepend stuff anyway, we can also
include the padding byte here so that iso1816_decipher
does not need to do another data mangling. */
fixuplen++;
fixbuf = xtrymalloc (fixuplen + indatalen);
if (!fixbuf)
return gpg_error_from_syserror ();
memset (fixbuf, 0, fixuplen);
memcpy (fixbuf+fixuplen, indata, indatalen);
indata = fixbuf;
indatalen = fixuplen + indatalen;
padind = -1; /* Already padded. */
}
else if (fixuplen < 0)
{
/* We use the extra leading zero as the padding byte. */
padind = -1;
}
}
else if (app->app_local->keyattr[1].key_type == KEY_TYPE_ECC)
{
int old_format_len = 0;
if ((app->app_local->keyattr[1].ecc.flags & ECC_FLAG_DJB_TWEAK))
{
if (indatalen > 32 && (indatalen % 2))
{ /*
* Skip the prefix. It may be 0x40 (in new format), or MPI
* head of 0x00 (in old format).
*/
indata = (const char *)indata + 1;
indatalen--;
}
else if (indatalen < 32)
{ /*
* Old format trancated by MPI handling.
*/
old_format_len = indatalen;
indatalen = 32;
}
}
n = 0;
if (indatalen < 128)
fixuplen = 7;
else
fixuplen = 10;
fixbuf = xtrymalloc (fixuplen + indatalen);
if (!fixbuf)
return gpg_error_from_syserror ();
/* Build 'Cipher DO' */
fixbuf[n++] = '\xa6';
if (indatalen < 128)
fixbuf[n++] = (char)(indatalen+5);
else
{
fixbuf[n++] = 0x81;
fixbuf[n++] = (char)(indatalen+7);
}
fixbuf[n++] = '\x7f';
fixbuf[n++] = '\x49';
if (indatalen < 128)
fixbuf[n++] = (char)(indatalen+2);
else
{
fixbuf[n++] = 0x81;
fixbuf[n++] = (char)(indatalen+3);
}
fixbuf[n++] = '\x86';
if (indatalen < 128)
fixbuf[n++] = (char)indatalen;
else
{
fixbuf[n++] = 0x81;
fixbuf[n++] = (char)indatalen;
}
if (old_format_len)
{
memset (fixbuf+fixuplen, 0, 32 - old_format_len);
memcpy (fixbuf+fixuplen + 32 - old_format_len,
indata, old_format_len);
}
else
{
memcpy (fixbuf+fixuplen, indata, indatalen);
}
indata = fixbuf;
indatalen = fixuplen + indatalen;
padind = -1;
}
else
return gpg_error (GPG_ERR_INV_VALUE);
if (app->app_local->cardcap.ext_lc_le
&& (indatalen > 254
|| (app->app_local->keyattr[1].key_type == KEY_TYPE_RSA
&& app->app_local->keyattr[1].rsa.n_bits > RSA_SMALL_SIZE_OP)))
{
exmode = 1; /* Extended length w/o a limit. */
le_value = app->app_local->keyattr[1].rsa.n_bits / 8;
}
else if (app->app_local->cardcap.cmd_chaining && indatalen > 254)
{
exmode = -254; /* Command chaining with max. 254 bytes. */
le_value = 0;
}
else
exmode = le_value = 0;
- rc = iso7816_decipher (app->slot, exmode,
+ rc = iso7816_decipher (app_get_slot (app), exmode,
indata, indatalen, le_value, padind,
outdata, outdatalen);
xfree (fixbuf);
if (!rc && app->app_local->keyattr[1].key_type == KEY_TYPE_ECC)
{
unsigned char prefix = 0;
if (app->app_local->keyattr[1].ecc.flags & ECC_FLAG_DJB_TWEAK)
prefix = 0x40;
else if ((*outdatalen % 2) == 0) /* No 0x04 -> x-coordinate only */
prefix = 0x41;
if (prefix)
{ /* Add the prefix */
fixbuf = xtrymalloc (*outdatalen + 1);
if (!fixbuf)
{
xfree (*outdata);
return gpg_error_from_syserror ();
}
fixbuf[0] = prefix;
memcpy (fixbuf+1, *outdata, *outdatalen);
xfree (*outdata);
*outdata = fixbuf;
*outdatalen = *outdatalen + 1;
}
}
if (gpg_err_code (rc) == GPG_ERR_TIMEOUT)
clear_chv_status (app, 1);
if (gpg_err_code (rc) == GPG_ERR_CARD /* actual SW is 0x640a */
&& app->app_local->manufacturer == 5
&& app->appversion == 0x0200)
log_info ("NOTE: Cards with manufacturer id 5 and s/n <= 346 (0x15a)"
" do not work with encryption keys > 2048 bits\n");
*r_info |= APP_DECIPHER_INFO_NOPAD;
return rc;
}
/* Perform a simple verify operation for CHV1 and CHV2, so that
further operations won't ask for CHV2 and it is possible to do a
cheap check on the PIN: If there is something wrong with the PIN
entry system, only the regular CHV will get blocked and not the
dangerous CHV3. KEYIDSTR is the usual card's serial number; an
optional fingerprint part will be ignored.
There is a special mode if the keyidstr is "<serialno>[CHV3]" with
the "[CHV3]" being a literal string: The Admin Pin is checked if
and only if the retry counter is still at 3. */
static gpg_error_t
do_check_pin (app_t app, const char *keyidstr,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg)
{
- unsigned char tmp_sn[20];
- const char *s;
- int n;
int admin_pin = 0;
+ int rc;
if (!keyidstr || !*keyidstr)
return gpg_error (GPG_ERR_INV_VALUE);
- /* Check whether an OpenPGP card of any version has been requested. */
- if (strlen (keyidstr) < 32 || strncmp (keyidstr, "D27600012401", 12))
- return gpg_error (GPG_ERR_INV_ID);
+ rc = check_keyidstr (app, keyidstr, 0);
+ if (rc)
+ return rc;
- for (s=keyidstr, n=0; hexdigitp (s); s++, n++)
- ;
- if (n != 32)
- return gpg_error (GPG_ERR_INV_ID);
- else if (!*s)
- ; /* No fingerprint given: we allow this for now. */
- else if (*s == '/')
- ; /* We ignore a fingerprint. */
- else if (!strcmp (s, "[CHV3]") )
+ if ((strlen (keyidstr) >= 32+6 && !strcmp (keyidstr+32, "[CHV3]"))
+ || (strlen (keyidstr) >= 40+6 && !strcmp (keyidstr+40, "[CHV3]")))
admin_pin = 1;
- else
- return gpg_error (GPG_ERR_INV_ID);
-
- for (s=keyidstr, n=0; n < 16; s += 2, n++)
- tmp_sn[n] = xtoi_2 (s);
-
- if (app->serialnolen != 16)
- return gpg_error (GPG_ERR_INV_CARD);
- if (memcmp (app->serialno, tmp_sn, 16))
- return gpg_error (GPG_ERR_WRONG_CARD);
/* Yes, there is a race conditions: The user might pull the card
right here and we won't notice that. However this is not a
problem and the check above is merely for a graceful failure
between operations. */
if (admin_pin)
{
void *relptr;
unsigned char *value;
size_t valuelen;
int count;
relptr = get_one_do (app, 0x00C4, &value, &valuelen, NULL);
if (!relptr || valuelen < 7)
{
log_error (_("error retrieving CHV status from card\n"));
xfree (relptr);
return gpg_error (GPG_ERR_CARD);
}
count = value[6];
xfree (relptr);
if (!count)
{
log_info (_("card is permanently locked!\n"));
return gpg_error (GPG_ERR_BAD_PIN);
}
else if (count < 3)
{
log_info (_("verification of Admin PIN is currently prohibited "
"through this command\n"));
return gpg_error (GPG_ERR_GENERAL);
}
app->did_chv3 = 0; /* Force verification. */
return verify_chv3 (app, pincb, pincb_arg);
}
else
return verify_chv2 (app, pincb, pincb_arg);
}
+static gpg_error_t
+do_with_keygrip (app_t app, ctrl_t ctrl, int action, const char *keygrip_str)
+{
+ int i;
+
+ /* Make sure we have load the public keys. */
+ for (i = 0; i < 3; i++)
+ get_public_key (app, i);
+
+ if (action == KEYGRIP_ACTION_LOOKUP)
+ {
+ if (keygrip_str == NULL)
+ return gpg_error (GPG_ERR_NOT_FOUND);
+
+ for (i = 0; i < 3; i++)
+ if (app->app_local->pk[i].read_done
+ && !strcmp (keygrip_str, app->app_local->pk[i].keygrip_str))
+ return 0; /* Found */
+ }
+ else
+ {
+ char idbuf[50];
+ char buf[65];
+ int data = (action == KEYGRIP_ACTION_SEND_DATA);
+
+ if (DIM (buf) < 2 * app->card->serialnolen + 1)
+ return gpg_error (GPG_ERR_BUFFER_TOO_SHORT);
+
+ bin2hex (app->card->serialno, app->card->serialnolen, buf);
+
+ if (keygrip_str == NULL)
+ {
+ for (i = 0; i < 3; i++)
+ if (app->app_local->pk[i].read_done)
+ {
+ sprintf (idbuf, "OPENPGP.%d", i+1);
+ send_keyinfo (ctrl, data,
+ app->app_local->pk[i].keygrip_str,buf, idbuf);
+ }
+ /* Return an error so that the dispatcher keeps on looping
+ * over the other applications. Only for clarity we use a
+ * different error code than for the not_found case. */
+ return gpg_error (GPG_ERR_TRUE);
+ }
+ else
+ {
+ for (i = 0; i < 3; i++)
+ if (app->app_local->pk[i].read_done
+ && !strcmp (keygrip_str, app->app_local->pk[i].keygrip_str))
+ {
+ sprintf (idbuf, "OPENPGP.%d", i+1);
+ send_keyinfo (ctrl, data, keygrip_str, buf, idbuf);
+ return 0;
+ }
+ }
+ }
+
+ return gpg_error (GPG_ERR_NOT_FOUND);
+}
/* Show information about card capabilities. */
static void
show_caps (struct app_local_s *s)
{
log_info ("Version-2+ .....: %s\n", s->extcap.is_v2? "yes":"no");
log_info ("Extcap-v3 ......: %s\n", s->extcap.extcap_v3? "yes":"no");
log_info ("Button .........: %s\n", s->extcap.has_button? "yes":"no");
log_info ("SM-Support .....: %s", s->extcap.sm_supported? "yes":"no");
if (s->extcap.sm_supported)
log_printf (" (%s)", s->extcap.sm_algo==2? "3DES":
(s->extcap.sm_algo==2? "AES-128" : "AES-256"));
log_info ("Get-Challenge ..: %s", s->extcap.get_challenge? "yes":"no");
if (s->extcap.get_challenge)
log_printf (" (%u bytes max)", s->extcap.max_get_challenge);
log_info ("Key-Import .....: %s\n", s->extcap.key_import? "yes":"no");
log_info ("Change-Force-PW1: %s\n", s->extcap.change_force_chv? "yes":"no");
log_info ("Private-DOs ....: %s\n", s->extcap.private_dos? "yes":"no");
log_info ("Algo-Attr-Change: %s\n", s->extcap.algo_attr_change? "yes":"no");
log_info ("Symmetric Crypto: %s\n", s->extcap.has_decrypt? "yes":"no");
log_info ("KDF-Support ....: %s\n", s->extcap.kdf_do? "yes":"no");
log_info ("Max-Cert3-Len ..: %u\n", s->extcap.max_certlen_3);
if (s->extcap.extcap_v3)
{
log_info ("PIN-Block-2 ....: %s\n", s->extcap.pin_blk2? "yes":"no");
log_info ("MSE-Support ....: %s\n", s->extcap.mse? "yes":"no");
log_info ("Max-Special-DOs : %u\n", s->extcap.max_special_do);
}
log_info ("Cmd-Chaining ...: %s\n", s->cardcap.cmd_chaining?"yes":"no");
log_info ("Ext-Lc-Le ......: %s\n", s->cardcap.ext_lc_le?"yes":"no");
log_info ("Status-Indicator: %02X\n", s->status_indicator);
log_info ("GnuPG-No-Sync ..: %s\n", s->flags.no_sync? "yes":"no");
log_info ("GnuPG-Def-PW2 ..: %s\n", s->flags.def_chv2? "yes":"no");
}
/* Parse the historical bytes in BUFFER of BUFLEN and store them in
APPLOC. */
static void
parse_historical (struct app_local_s *apploc,
const unsigned char * buffer, size_t buflen)
{
/* Example buffer: 00 31 C5 73 C0 01 80 00 90 00 */
if (buflen < 4)
{
log_error ("warning: historical bytes are too short\n");
return; /* Too short. */
}
if (*buffer)
{
log_error ("warning: bad category indicator in historical bytes\n");
return;
}
/* Skip category indicator. */
buffer++;
buflen--;
/* Get the status indicator. */
apploc->status_indicator = buffer[buflen-3];
buflen -= 3;
/* Parse the compact TLV. */
while (buflen)
{
unsigned int tag = (*buffer & 0xf0) >> 4;
unsigned int len = (*buffer & 0x0f);
if (len+1 > buflen)
{
log_error ("warning: bad Compact-TLV in historical bytes\n");
return; /* Error. */
}
buffer++;
buflen--;
if (tag == 7 && len == 3)
{
/* Card capabilities. */
apploc->cardcap.cmd_chaining = !!(buffer[2] & 0x80);
apploc->cardcap.ext_lc_le = !!(buffer[2] & 0x40);
}
buffer += len;
buflen -= len;
}
}
/*
* Check if the OID in an DER encoding is available by GnuPG/libgcrypt,
* and return the curve name. Return NULL if not available.
* The constant string is not allocated dynamically, never free it.
*/
static const char *
ecc_curve (unsigned char *buf, size_t buflen)
{
gcry_mpi_t oid;
char *oidstr;
const char *result;
unsigned char *oidbuf;
oidbuf = xtrymalloc (buflen + 1);
if (!oidbuf)
return NULL;
memcpy (oidbuf+1, buf, buflen);
oidbuf[0] = buflen;
oid = gcry_mpi_set_opaque (NULL, oidbuf, (buflen+1) * 8);
if (!oid)
{
xfree (oidbuf);
return NULL;
}
oidstr = openpgp_oid_to_str (oid);
gcry_mpi_release (oid);
if (!oidstr)
return NULL;
result = openpgp_oid_to_curve (oidstr, 1);
xfree (oidstr);
return result;
}
/* Parse and optionally show the algorithm attributes for KEYNO.
KEYNO must be in the range 0..2. */
static void
parse_algorithm_attribute (app_t app, int keyno)
{
unsigned char *buffer;
size_t buflen;
void *relptr;
const char desc[3][5] = {"sign", "encr", "auth"};
assert (keyno >=0 && keyno <= 2);
app->app_local->keyattr[keyno].key_type = KEY_TYPE_RSA;
app->app_local->keyattr[keyno].rsa.n_bits = 0;
relptr = get_one_do (app, 0xC1+keyno, &buffer, &buflen, NULL);
if (!relptr)
{
log_error ("error reading DO 0x%02X\n", 0xc1+keyno);
return;
}
if (buflen < 1)
{
log_error ("error reading DO 0x%02X\n", 0xc1+keyno);
xfree (relptr);
return;
}
if (opt.verbose)
log_info ("Key-Attr-%s ..: ", desc[keyno]);
if (*buffer == PUBKEY_ALGO_RSA && (buflen == 5 || buflen == 6))
{
app->app_local->keyattr[keyno].rsa.n_bits = (buffer[1]<<8 | buffer[2]);
app->app_local->keyattr[keyno].rsa.e_bits = (buffer[3]<<8 | buffer[4]);
app->app_local->keyattr[keyno].rsa.format = 0;
if (buflen < 6)
app->app_local->keyattr[keyno].rsa.format = RSA_STD;
else
app->app_local->keyattr[keyno].rsa.format = (buffer[5] == 0? RSA_STD :
buffer[5] == 1? RSA_STD_N :
buffer[5] == 2? RSA_CRT :
buffer[5] == 3? RSA_CRT_N :
RSA_UNKNOWN_FMT);
if (opt.verbose)
log_printf
("RSA, n=%u, e=%u, fmt=%s\n",
app->app_local->keyattr[keyno].rsa.n_bits,
app->app_local->keyattr[keyno].rsa.e_bits,
app->app_local->keyattr[keyno].rsa.format == RSA_STD? "std" :
app->app_local->keyattr[keyno].rsa.format == RSA_STD_N?"std+n":
app->app_local->keyattr[keyno].rsa.format == RSA_CRT? "crt" :
app->app_local->keyattr[keyno].rsa.format == RSA_CRT_N?"crt+n":"?");
}
else if (*buffer == PUBKEY_ALGO_ECDH || *buffer == PUBKEY_ALGO_ECDSA
|| *buffer == PUBKEY_ALGO_EDDSA)
{
const char *curve;
int oidlen = buflen - 1;
app->app_local->keyattr[keyno].ecc.flags = 0;
if (buffer[buflen-1] == 0x00 || buffer[buflen-1] == 0xff)
{ /* Found "pubkey required"-byte for private key template. */
oidlen--;
if (buffer[buflen-1] == 0xff)
app->app_local->keyattr[keyno].ecc.flags |= ECC_FLAG_PUBKEY;
}
curve = ecc_curve (buffer + 1, oidlen);
if (!curve)
log_printhex (buffer+1, buflen-1, "Curve with OID not supported: ");
else
{
app->app_local->keyattr[keyno].key_type = KEY_TYPE_ECC;
app->app_local->keyattr[keyno].ecc.curve = curve;
if (*buffer == PUBKEY_ALGO_EDDSA
|| (*buffer == PUBKEY_ALGO_ECDH
&& !strcmp (app->app_local->keyattr[keyno].ecc.curve,
"Curve25519")))
app->app_local->keyattr[keyno].ecc.flags |= ECC_FLAG_DJB_TWEAK;
if (opt.verbose)
log_printf
("ECC, curve=%s%s\n", app->app_local->keyattr[keyno].ecc.curve,
!(app->app_local->keyattr[keyno].ecc.flags & ECC_FLAG_DJB_TWEAK)?
"": keyno==1? " (djb-tweak)": " (eddsa)");
}
}
else if (opt.verbose)
log_printhex (buffer, buflen, "");
xfree (relptr);
}
+
+/* Reselect the application. This is used by cards which support
+ * on-the-fly switching between applications. */
+static gpg_error_t
+do_reselect (app_t app, ctrl_t ctrl)
+{
+ gpg_error_t err;
+
+ (void)ctrl;
+
+ /* An extra check which should not be necessary because the caller
+ * should have made sure that a re-select is only called for
+ * approriate cards. */
+ if (app->card->cardtype != CARDTYPE_YUBIKEY)
+ return gpg_error (GPG_ERR_NOT_SUPPORTED);
+
+ /* Note that the card can't cope with P2=0xCO, thus we need to pass
+ * a special flag value. */
+ err = iso7816_select_application (app_get_slot (app),
+ openpgp_aid, sizeof openpgp_aid, 0x0001);
+ return err;
+}
+
+
/* Select the OpenPGP application on the card in SLOT. This function
must be used before any other OpenPGP application functions. */
gpg_error_t
app_select_openpgp (app_t app)
{
- static char const aid[] = { 0xD2, 0x76, 0x00, 0x01, 0x24, 0x01 };
- int slot = app->slot;
+ int slot = app_get_slot (app);
int rc;
unsigned char *buffer;
size_t buflen;
void *relptr;
/* Note that the card can't cope with P2=0xCO, thus we need to pass a
special flag value. */
- rc = iso7816_select_application (slot, aid, sizeof aid, 0x0001);
+ rc = iso7816_select_application (slot,
+ openpgp_aid, sizeof openpgp_aid, 0x0001);
if (!rc)
{
unsigned int manufacturer;
- app->apptype = "OPENPGP";
+ app->apptype = APPTYPE_OPENPGP;
app->did_chv1 = 0;
app->did_chv2 = 0;
app->did_chv3 = 0;
app->app_local = NULL;
/* The OpenPGP card returns the serial number as part of the
AID; because we prefer to use OpenPGP serial numbers, we
replace a possibly already set one from a EF.GDO with this
one. Note, that for current OpenPGP cards, no EF.GDO exists
and thus it won't matter at all. */
rc = iso7816_get_data (slot, 0, 0x004F, &buffer, &buflen);
if (rc)
goto leave;
if (opt.verbose)
{
log_info ("AID: ");
log_printhex (buffer, buflen, "");
}
app->appversion = buffer[6] << 8;
app->appversion |= buffer[7];
manufacturer = (buffer[8]<<8 | buffer[9]);
- xfree (app->serialno);
- app->serialno = buffer;
- app->serialnolen = buflen;
+ xfree (app->card->serialno);
+ app->card->serialno = buffer;
+ app->card->serialnolen = buflen;
buffer = NULL;
app->app_local = xtrycalloc (1, sizeof *app->app_local);
if (!app->app_local)
{
rc = gpg_error (gpg_err_code_from_errno (errno));
goto leave;
}
app->app_local->manufacturer = manufacturer;
if (app->appversion >= 0x0200)
app->app_local->extcap.is_v2 = 1;
if (app->appversion >= 0x0300)
app->app_local->extcap.extcap_v3 = 1;
/* Read the historical bytes. */
relptr = get_one_do (app, 0x5f52, &buffer, &buflen, NULL);
if (relptr)
{
if (opt.verbose)
{
log_info ("Historical Bytes: ");
log_printhex (buffer, buflen, "");
}
parse_historical (app->app_local, buffer, buflen);
xfree (relptr);
}
/* Read the force-chv1 flag. */
relptr = get_one_do (app, 0x00C4, &buffer, &buflen, NULL);
if (!relptr)
{
log_error (_("can't access %s - invalid OpenPGP card?\n"),
"CHV Status Bytes");
goto leave;
}
app->force_chv1 = (buflen && *buffer == 0);
xfree (relptr);
/* Read the extended capabilities. */
relptr = get_one_do (app, 0x00C0, &buffer, &buflen, NULL);
if (!relptr)
{
log_error (_("can't access %s - invalid OpenPGP card?\n"),
"Extended Capability Flags" );
goto leave;
}
if (buflen)
{
app->app_local->extcap.sm_supported = !!(*buffer & 0x80);
app->app_local->extcap.get_challenge = !!(*buffer & 0x40);
app->app_local->extcap.key_import = !!(*buffer & 0x20);
app->app_local->extcap.change_force_chv = !!(*buffer & 0x10);
app->app_local->extcap.private_dos = !!(*buffer & 0x08);
app->app_local->extcap.algo_attr_change = !!(*buffer & 0x04);
app->app_local->extcap.has_decrypt = !!(*buffer & 0x02);
app->app_local->extcap.kdf_do = !!(*buffer & 0x01);
}
if (buflen >= 10)
{
/* Available with cards of v2 or later. */
app->app_local->extcap.sm_algo = buffer[1];
app->app_local->extcap.max_get_challenge
= (buffer[2] << 8 | buffer[3]);
app->app_local->extcap.max_certlen_3 = (buffer[4] << 8 | buffer[5]);
/* Interpretation is different between v2 and v3, unfortunately. */
if (app->app_local->extcap.extcap_v3)
{
app->app_local->extcap.max_special_do
= (buffer[6] << 8 | buffer[7]);
app->app_local->extcap.pin_blk2 = !!(buffer[8] & 0x01);
app->app_local->extcap.mse= !!(buffer[9] & 0x01);
}
}
xfree (relptr);
/* Some of the first cards accidentally don't set the
CHANGE_FORCE_CHV bit but allow it anyway. */
if (app->appversion <= 0x0100 && manufacturer == 1)
app->app_local->extcap.change_force_chv = 1;
/* Check optional DO of "General Feature Management" for button. */
relptr = get_one_do (app, 0x7f74, &buffer, &buflen, NULL);
if (relptr)
/* It must be: 03 81 01 20 */
app->app_local->extcap.has_button = 1;
parse_login_data (app);
if (opt.verbose)
show_caps (app->app_local);
parse_algorithm_attribute (app, 0);
parse_algorithm_attribute (app, 1);
parse_algorithm_attribute (app, 2);
if (opt.verbose > 1)
dump_all_do (slot);
app->fnc.deinit = do_deinit;
+ app->fnc.reselect = do_reselect;
app->fnc.learn_status = do_learn_status;
app->fnc.readcert = do_readcert;
app->fnc.readkey = do_readkey;
app->fnc.getattr = do_getattr;
app->fnc.setattr = do_setattr;
app->fnc.writecert = do_writecert;
app->fnc.writekey = do_writekey;
app->fnc.genkey = do_genkey;
app->fnc.sign = do_sign;
app->fnc.auth = do_auth;
app->fnc.decipher = do_decipher;
app->fnc.change_pin = do_change_pin;
app->fnc.check_pin = do_check_pin;
+ app->fnc.with_keygrip = do_with_keygrip;
}
leave:
if (rc)
do_deinit (app);
return rc;
}
diff --git a/scd/app-p15.c b/scd/app-p15.c
index 190292433..ce82b34a9 100644
--- a/scd/app-p15.c
+++ b/scd/app-p15.c
@@ -1,3424 +1,3436 @@
/* app-p15.c - The pkcs#15 card application.
* Copyright (C) 2005 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
/* Information pertaining to the BELPIC developer card samples:
Unblock PUK: "222222111111"
Reset PIN: "333333111111")
e.g. the APDUs 00:20:00:02:08:2C:33:33:33:11:11:11:FF
and 00:24:01:01:08:24:12:34:FF:FF:FF:FF:FF
should change the PIN into 1234.
*/
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <time.h>
#include "scdaemon.h"
#include "iso7816.h"
-#include "app-common.h"
#include "../common/tlv.h"
#include "apdu.h" /* fixme: we should move the card detection to a
separate file */
/* Types of cards we know and which needs special treatment. */
typedef enum
{
CARD_TYPE_UNKNOWN,
CARD_TYPE_TCOS,
CARD_TYPE_MICARDO,
CARD_TYPE_BELPIC /* Belgian eID card specs. */
}
card_type_t;
/* A list card types with ATRs noticed with these cards. */
#define X(a) ((unsigned char const *)(a))
static struct
{
size_t atrlen;
unsigned char const *atr;
card_type_t type;
} card_atr_list[] = {
{ 19, X("\x3B\xBA\x13\x00\x81\x31\x86\x5D\x00\x64\x05\x0A\x02\x01\x31\x80"
"\x90\x00\x8B"),
CARD_TYPE_TCOS }, /* SLE44 */
{ 19, X("\x3B\xBA\x14\x00\x81\x31\x86\x5D\x00\x64\x05\x14\x02\x02\x31\x80"
"\x90\x00\x91"),
CARD_TYPE_TCOS }, /* SLE66S */
{ 19, X("\x3B\xBA\x96\x00\x81\x31\x86\x5D\x00\x64\x05\x60\x02\x03\x31\x80"
"\x90\x00\x66"),
CARD_TYPE_TCOS }, /* SLE66P */
{ 27, X("\x3B\xFF\x94\x00\xFF\x80\xB1\xFE\x45\x1F\x03\x00\x68\xD2\x76\x00"
"\x00\x28\xFF\x05\x1E\x31\x80\x00\x90\x00\x23"),
CARD_TYPE_MICARDO }, /* German BMI card */
{ 19, X("\x3B\x6F\x00\xFF\x00\x68\xD2\x76\x00\x00\x28\xFF\x05\x1E\x31\x80"
"\x00\x90\x00"),
CARD_TYPE_MICARDO }, /* German BMI card (ATR due to reader problem) */
{ 26, X("\x3B\xFE\x94\x00\xFF\x80\xB1\xFA\x45\x1F\x03\x45\x73\x74\x45\x49"
"\x44\x20\x76\x65\x72\x20\x31\x2E\x30\x43"),
CARD_TYPE_MICARDO }, /* EstEID (Estonian Big Brother card) */
{ 0 }
};
#undef X
/* The AID of PKCS15. */
static char const pkcs15_aid[] = { 0xA0, 0, 0, 0, 0x63,
0x50, 0x4B, 0x43, 0x53, 0x2D, 0x31, 0x35 };
/* The Belgian eID variant - they didn't understood why a shared AID
is useful for a standard. Oh well. */
static char const pkcs15be_aid[] = { 0xA0, 0, 0, 0x01, 0x77,
0x50, 0x4B, 0x43, 0x53, 0x2D, 0x31, 0x35 };
/* The PIN types as defined in pkcs#15 v1.1 */
typedef enum
{
PIN_TYPE_BCD = 0,
PIN_TYPE_ASCII_NUMERIC = 1,
PIN_TYPE_UTF8 = 2,
PIN_TYPE_HALF_NIBBLE_BCD = 3,
PIN_TYPE_ISO9564_1 = 4
} pin_type_t;
/* A bit array with for the key usage flags from the
commonKeyAttributes. */
struct keyusage_flags_s
{
unsigned int encrypt: 1;
unsigned int decrypt: 1;
unsigned int sign: 1;
unsigned int sign_recover: 1;
unsigned int wrap: 1;
unsigned int unwrap: 1;
unsigned int verify: 1;
unsigned int verify_recover: 1;
unsigned int derive: 1;
unsigned int non_repudiation: 1;
};
typedef struct keyusage_flags_s keyusage_flags_t;
/* This is an object to store information about a Certificate
Directory File (CDF) in a format suitable for further processing by
us. To keep memory management, simple we use a linked list of
items; i.e. one such object represents one certificate and the list
the entire CDF. */
struct cdf_object_s
{
/* Link to next item when used in a linked list. */
struct cdf_object_s *next;
/* Length and allocated buffer with the Id of this object. */
size_t objidlen;
unsigned char *objid;
/* To avoid reading a certificate more than once, we cache it in an
allocated memory IMAGE of IMAGELEN. */
size_t imagelen;
unsigned char *image;
/* Set to true if a length and offset is available. */
int have_off;
/* The offset and length of the object. They are only valid if
HAVE_OFF is true and set to 0 if HAVE_OFF is false. */
unsigned long off, len;
/* The length of the path as given in the CDF and the path itself.
path[0] is the top DF (usually 0x3f00). The path will never be
empty. */
size_t pathlen;
unsigned short path[1];
};
typedef struct cdf_object_s *cdf_object_t;
/* This is an object to store information about a Private Key
Directory File (PrKDF) in a format suitable for further processing
by us. To keep memory management, simple we use a linked list of
items; i.e. one such object represents one certificate and the list
the entire PrKDF. */
struct prkdf_object_s
{
/* Link to next item when used in a linked list. */
struct prkdf_object_s *next;
/* Length and allocated buffer with the Id of this object. */
size_t objidlen;
unsigned char *objid;
/* Length and allocated buffer with the authId of this object or
NULL if no authID is known. */
size_t authidlen;
unsigned char *authid;
/* The key's usage flags. */
keyusage_flags_t usageflags;
/* The keyReference and a flag telling whether it is valid. */
unsigned long key_reference;
int key_reference_valid;
/* Set to true if a length and offset is available. */
int have_off;
/* The offset and length of the object. They are only valid if
HAVE_OFF is true and set to 0 if HAVE_OFF is false. */
unsigned long off, len;
/* The length of the path as given in the PrKDF and the path itself.
path[0] is the top DF (usually 0x3f00). */
size_t pathlen;
unsigned short path[1];
};
typedef struct prkdf_object_s *prkdf_object_t;
/* This is an object to store information about a Authentication
Object Directory File (AODF) in a format suitable for further
processing by us. To keep memory management, simple we use a linked
list of items; i.e. one such object represents one authentication
object and the list the entire AOKDF. */
struct aodf_object_s
{
/* Link to next item when used in a linked list. */
struct aodf_object_s *next;
/* Length and allocated buffer with the Id of this object. */
size_t objidlen;
unsigned char *objid;
/* Length and allocated buffer with the authId of this object or
NULL if no authID is known. */
size_t authidlen;
unsigned char *authid;
/* The PIN Flags. */
struct
{
unsigned int case_sensitive: 1;
unsigned int local: 1;
unsigned int change_disabled: 1;
unsigned int unblock_disabled: 1;
unsigned int initialized: 1;
unsigned int needs_padding: 1;
unsigned int unblocking_pin: 1;
unsigned int so_pin: 1;
unsigned int disable_allowed: 1;
unsigned int integrity_protected: 1;
unsigned int confidentiality_protected: 1;
unsigned int exchange_ref_data: 1;
} pinflags;
/* The PIN Type. */
pin_type_t pintype;
/* The minimum length of a PIN. */
unsigned long min_length;
/* The stored length of a PIN. */
unsigned long stored_length;
/* The maximum length of a PIN and a flag telling whether it is valid. */
unsigned long max_length;
int max_length_valid;
/* The pinReference and a flag telling whether it is valid. */
unsigned long pin_reference;
int pin_reference_valid;
/* The padChar and a flag telling whether it is valid. */
char pad_char;
int pad_char_valid;
/* Set to true if a length and offset is available. */
int have_off;
/* The offset and length of the object. They are only valid if
HAVE_OFF is true and set to 0 if HAVE_OFF is false. */
unsigned long off, len;
/* The length of the path as given in the Aodf and the path itself.
path[0] is the top DF (usually 0x3f00). PATH is optional and thus
may be NULL. Malloced.*/
size_t pathlen;
unsigned short *path;
};
typedef struct aodf_object_s *aodf_object_t;
/* Context local to this application. */
struct app_local_s
{
/* The home DF. Note, that we don't yet support a multilevel
hierarchy. Thus we assume this is directly below the MF. */
unsigned short home_df;
/* The type of the card. */
card_type_t card_type;
/* Flag indicating whether we may use direct path selection. */
int direct_path_selection;
/* Structure with the EFIDs of the objects described in the ODF
file. */
struct
{
unsigned short private_keys;
unsigned short public_keys;
unsigned short trusted_public_keys;
unsigned short secret_keys;
unsigned short certificates;
unsigned short trusted_certificates;
unsigned short useful_certificates;
unsigned short data_objects;
unsigned short auth_objects;
} odf;
/* The PKCS#15 serialnumber from EF(TokeiNFo) or NULL. Malloced. */
unsigned char *serialno;
size_t serialnolen;
/* Information on all certificates. */
cdf_object_t certificate_info;
/* Information on all trusted certificates. */
cdf_object_t trusted_certificate_info;
/* Information on all useful certificates. */
cdf_object_t useful_certificate_info;
/* Information on all private keys. */
prkdf_object_t private_key_info;
/* Information on all authentication objects. */
aodf_object_t auth_object_info;
};
/*** Local prototypes. ***/
static gpg_error_t readcert_by_cdf (app_t app, cdf_object_t cdf,
unsigned char **r_cert, size_t *r_certlen);
/* Release the CDF object A */
static void
release_cdflist (cdf_object_t a)
{
while (a)
{
cdf_object_t tmp = a->next;
xfree (a->image);
xfree (a->objid);
xfree (a);
a = tmp;
}
}
/* Release the PrKDF object A. */
static void
release_prkdflist (prkdf_object_t a)
{
while (a)
{
prkdf_object_t tmp = a->next;
xfree (a->objid);
xfree (a->authid);
xfree (a);
a = tmp;
}
}
/* Release just one aodf object. */
void
release_aodf_object (aodf_object_t a)
{
if (a)
{
xfree (a->objid);
xfree (a->authid);
xfree (a->path);
xfree (a);
}
}
/* Release the AODF list A. */
static void
release_aodflist (aodf_object_t a)
{
while (a)
{
aodf_object_t tmp = a->next;
release_aodf_object (a);
a = tmp;
}
}
/* Release all local resources. */
static void
do_deinit (app_t app)
{
if (app && app->app_local)
{
release_cdflist (app->app_local->certificate_info);
release_cdflist (app->app_local->trusted_certificate_info);
release_cdflist (app->app_local->useful_certificate_info);
release_prkdflist (app->app_local->private_key_info);
release_aodflist (app->app_local->auth_object_info);
xfree (app->app_local->serialno);
xfree (app->app_local);
app->app_local = NULL;
}
}
/* Do a select and a read for the file with EFID. EFID_DESC is a
desctription of the EF to be used with error messages. On success
BUFFER and BUFLEN contain the entire content of the EF. The caller
must free BUFFER only on success. */
static gpg_error_t
select_and_read_binary (int slot, unsigned short efid, const char *efid_desc,
unsigned char **buffer, size_t *buflen)
{
gpg_error_t err;
err = iso7816_select_file (slot, efid, 0);
if (err)
{
log_error ("error selecting %s (0x%04X): %s\n",
efid_desc, efid, gpg_strerror (err));
return err;
}
err = iso7816_read_binary (slot, 0, 0, buffer, buflen);
if (err)
{
log_error ("error reading %s (0x%04X): %s\n",
efid_desc, efid, gpg_strerror (err));
return err;
}
return 0;
}
/* This function calls select file to read a file using a complete
path which may or may not start at the master file (MF). */
static gpg_error_t
select_ef_by_path (app_t app, const unsigned short *path, size_t pathlen)
{
gpg_error_t err;
int i, j;
if (!pathlen)
return gpg_error (GPG_ERR_INV_VALUE);
if (pathlen && *path != 0x3f00 )
log_debug ("WARNING: relative path selection not yet implemented\n");
if (app->app_local->direct_path_selection)
{
- err = iso7816_select_path (app->slot, path+1, pathlen-1);
+ err = iso7816_select_path (app_get_slot (app), path+1, pathlen-1);
if (err)
{
log_error ("error selecting path ");
for (j=0; j < pathlen; j++)
log_printf ("%04hX", path[j]);
log_printf (": %s\n", gpg_strerror (err));
return err;
}
}
else
{
/* FIXME: Need code to remember the last PATH so that we can decide
what select commands to send in case the path does not start off
with 3F00. We might also want to use direct path selection if
supported by the card. */
for (i=0; i < pathlen; i++)
{
- err = iso7816_select_file (app->slot, path[i], !(i+1 == pathlen));
+ err = iso7816_select_file (app_get_slot (app),
+ path[i], !(i+1 == pathlen));
if (err)
{
log_error ("error selecting part %d from path ", i);
for (j=0; j < pathlen; j++)
log_printf ("%04hX", path[j]);
log_printf (": %s\n", gpg_strerror (err));
return err;
}
}
}
return 0;
}
/* Parse a cert Id string (or a key Id string) and return the binary
object Id string in a newly allocated buffer stored at R_OBJID and
R_OBJIDLEN. On Error NULL will be stored there and an error code
returned. On success caller needs to free the buffer at R_OBJID. */
static gpg_error_t
parse_certid (app_t app, const char *certid,
unsigned char **r_objid, size_t *r_objidlen)
{
char tmpbuf[10];
const char *s;
size_t objidlen;
unsigned char *objid;
int i;
*r_objid = NULL;
*r_objidlen = 0;
if (app->app_local->home_df)
snprintf (tmpbuf, sizeof tmpbuf,
"P15-%04X.", (unsigned int)(app->app_local->home_df & 0xffff));
else
strcpy (tmpbuf, "P15.");
if (strncmp (certid, tmpbuf, strlen (tmpbuf)) )
{
if (!strncmp (certid, "P15.", 4)
|| (!strncmp (certid, "P15-", 4)
&& hexdigitp (certid+4)
&& hexdigitp (certid+5)
&& hexdigitp (certid+6)
&& hexdigitp (certid+7)
&& certid[8] == '.'))
return gpg_error (GPG_ERR_NOT_FOUND);
return gpg_error (GPG_ERR_INV_ID);
}
certid += strlen (tmpbuf);
for (s=certid, objidlen=0; hexdigitp (s); s++, objidlen++)
;
if (*s || !objidlen || (objidlen%2))
return gpg_error (GPG_ERR_INV_ID);
objidlen /= 2;
objid = xtrymalloc (objidlen);
if (!objid)
return gpg_error_from_syserror ();
for (s=certid, i=0; i < objidlen; i++, s+=2)
objid[i] = xtoi_2 (s);
*r_objid = objid;
*r_objidlen = objidlen;
return 0;
}
/* Find a certificate object by the certificate ID CERTID and store a
pointer to it at R_CDF. */
static gpg_error_t
cdf_object_from_certid (app_t app, const char *certid, cdf_object_t *r_cdf)
{
gpg_error_t err;
size_t objidlen;
unsigned char *objid;
cdf_object_t cdf;
err = parse_certid (app, certid, &objid, &objidlen);
if (err)
return err;
for (cdf = app->app_local->certificate_info; cdf; cdf = cdf->next)
if (cdf->objidlen == objidlen && !memcmp (cdf->objid, objid, objidlen))
break;
if (!cdf)
for (cdf = app->app_local->trusted_certificate_info; cdf; cdf = cdf->next)
if (cdf->objidlen == objidlen && !memcmp (cdf->objid, objid, objidlen))
break;
if (!cdf)
for (cdf = app->app_local->useful_certificate_info; cdf; cdf = cdf->next)
if (cdf->objidlen == objidlen && !memcmp (cdf->objid, objid, objidlen))
break;
xfree (objid);
if (!cdf)
return gpg_error (GPG_ERR_NOT_FOUND);
*r_cdf = cdf;
return 0;
}
/* Find a private key object by the key Id string KEYIDSTR and store a
pointer to it at R_PRKDF. */
static gpg_error_t
prkdf_object_from_keyidstr (app_t app, const char *keyidstr,
prkdf_object_t *r_prkdf)
{
gpg_error_t err;
size_t objidlen;
unsigned char *objid;
prkdf_object_t prkdf;
err = parse_certid (app, keyidstr, &objid, &objidlen);
if (err)
return err;
for (prkdf = app->app_local->private_key_info; prkdf; prkdf = prkdf->next)
if (prkdf->objidlen == objidlen && !memcmp (prkdf->objid, objid, objidlen))
break;
xfree (objid);
if (!prkdf)
return gpg_error (GPG_ERR_NOT_FOUND);
*r_prkdf = prkdf;
return 0;
}
/* Read and parse the Object Directory File and store away the
pointers. ODF_FID shall contain the FID of the ODF.
Example of such a file:
A0 06 30 04 04 02 60 34 = Private Keys
A4 06 30 04 04 02 60 35 = Certificates
A5 06 30 04 04 02 60 36 = TrustedCertificates
A7 06 30 04 04 02 60 37 = DataObjects
A8 06 30 04 04 02 60 38 = AuthObjects
These are all PathOrObjects using the path CHOICE element. The
paths are octet strings of length 2. Using this Path CHOICE
element is recommended, so we only implement that for now.
*/
static gpg_error_t
read_ef_odf (app_t app, unsigned short odf_fid)
{
gpg_error_t err;
unsigned char *buffer, *p;
size_t buflen;
unsigned short value;
size_t offset;
- err = select_and_read_binary (app->slot, odf_fid, "ODF", &buffer, &buflen);
+ err = select_and_read_binary (app_get_slot (app), odf_fid, "ODF",
+ &buffer, &buflen);
if (err)
return err;
if (buflen < 8)
{
log_error ("error: ODF too short\n");
xfree (buffer);
return gpg_error (GPG_ERR_INV_OBJ);
}
p = buffer;
while (buflen && *p && *p != 0xff)
{
if ( buflen >= 8
&& (p[0] & 0xf0) == 0xA0
&& !memcmp (p+1, "\x06\x30\x04\x04\x02", 5) )
{
offset = 6;
}
else if ( buflen >= 12
&& (p[0] & 0xf0) == 0xA0
&& !memcmp (p+1, "\x0a\x30\x08\x04\x06\x3F\x00", 7)
&& app->app_local->home_df == ((p[8]<<8)|p[9]) )
{
/* We only allow a full path if all files are at the same
level and below the home directory. The extend this we
would need to make use of new data type capable of
keeping a full path. */
offset = 10;
}
else
{
log_error ("ODF format is not supported by us\n");
xfree (buffer);
return gpg_error (GPG_ERR_INV_OBJ);
}
switch ((p[0] & 0x0f))
{
case 0: value = app->app_local->odf.private_keys; break;
case 1: value = app->app_local->odf.public_keys; break;
case 2: value = app->app_local->odf.trusted_public_keys; break;
case 3: value = app->app_local->odf.secret_keys; break;
case 4: value = app->app_local->odf.certificates; break;
case 5: value = app->app_local->odf.trusted_certificates; break;
case 6: value = app->app_local->odf.useful_certificates; break;
case 7: value = app->app_local->odf.data_objects; break;
case 8: value = app->app_local->odf.auth_objects; break;
default: value = 0; break;
}
if (value)
{
log_error ("duplicate object type %d in ODF ignored\n",(p[0]&0x0f));
continue;
}
value = ((p[offset] << 8) | p[offset+1]);
switch ((p[0] & 0x0f))
{
case 0: app->app_local->odf.private_keys = value; break;
case 1: app->app_local->odf.public_keys = value; break;
case 2: app->app_local->odf.trusted_public_keys = value; break;
case 3: app->app_local->odf.secret_keys = value; break;
case 4: app->app_local->odf.certificates = value; break;
case 5: app->app_local->odf.trusted_certificates = value; break;
case 6: app->app_local->odf.useful_certificates = value; break;
case 7: app->app_local->odf.data_objects = value; break;
case 8: app->app_local->odf.auth_objects = value; break;
default:
log_error ("unknown object type %d in ODF ignored\n", (p[0]&0x0f));
}
offset += 2;
if (buflen < offset)
break;
p += offset;
buflen -= offset;
}
if (buflen)
log_info ("warning: %u bytes of garbage detected at end of ODF\n",
(unsigned int)buflen);
xfree (buffer);
return 0;
}
/* Parse the BIT STRING with the keyUsageFlags from the
CommonKeyAttributes. */
static gpg_error_t
parse_keyusage_flags (const unsigned char *der, size_t derlen,
keyusage_flags_t *usageflags)
{
unsigned int bits, mask;
int i, unused, full;
memset (usageflags, 0, sizeof *usageflags);
if (!derlen)
return gpg_error (GPG_ERR_INV_OBJ);
unused = *der++; derlen--;
if ((!derlen && unused) || unused/8 > derlen)
return gpg_error (GPG_ERR_ENCODING_PROBLEM);
full = derlen - (unused+7)/8;
unused %= 8;
mask = 0;
for (i=1; unused; i <<= 1, unused--)
mask |= i;
/* First octet */
if (derlen)
{
bits = *der++; derlen--;
if (full)
full--;
else
{
bits &= ~mask;
mask = 0;
}
}
else
bits = 0;
if ((bits & 0x80)) usageflags->encrypt = 1;
if ((bits & 0x40)) usageflags->decrypt = 1;
if ((bits & 0x20)) usageflags->sign = 1;
if ((bits & 0x10)) usageflags->sign_recover = 1;
if ((bits & 0x08)) usageflags->wrap = 1;
if ((bits & 0x04)) usageflags->unwrap = 1;
if ((bits & 0x02)) usageflags->verify = 1;
if ((bits & 0x01)) usageflags->verify_recover = 1;
/* Second octet. */
if (derlen)
{
bits = *der++; derlen--;
if (full)
full--;
else
{
bits &= ~mask;
}
}
else
bits = 0;
if ((bits & 0x80)) usageflags->derive = 1;
if ((bits & 0x40)) usageflags->non_repudiation = 1;
return 0;
}
/* Read and parse the Private Key Directory Files. */
/*
6034 (privatekeys)
30 33 30 11 0C 08 53 4B 2E 43 48 2E 44 53 03 02 030...SK.CH.DS..
06 80 04 01 07 30 0C 04 01 01 03 03 06 00 40 02 .....0........@.
02 00 50 A1 10 30 0E 30 08 04 06 3F 00 40 16 00 ..P..0.0...?.@..
50 02 02 04 00 30 33 30 11 0C 08 53 4B 2E 43 48 P....030...SK.CH
2E 4B 45 03 02 06 80 04 01 0A 30 0C 04 01 0C 03 .KE.......0.....
03 06 44 00 02 02 00 52 A1 10 30 0E 30 08 04 06 ..D....R..0.0...
3F 00 40 16 00 52 02 02 04 00 30 34 30 12 0C 09 ?.@..R....040...
53 4B 2E 43 48 2E 41 55 54 03 02 06 80 04 01 0A SK.CH.AUT.......
30 0C 04 01 0D 03 03 06 20 00 02 02 00 51 A1 10 0....... ....Q..
30 0E 30 08 04 06 3F 00 40 16 00 51 02 02 04 00 0.0...?.@..Q....
30 37 30 15 0C 0C 53 4B 2E 43 48 2E 44 53 2D 53 070...SK.CH.DS-S
50 58 03 02 06 80 04 01 0A 30 0C 04 01 02 03 03 PX.......0......
06 20 00 02 02 00 53 A1 10 30 0E 30 08 04 06 3F . ....S..0.0...?
00 40 16 00 53 02 02 04 00 00 00 00 00 00 00 00 .@..S...........
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0 30 51: SEQUENCE {
2 30 17: SEQUENCE { -- commonObjectAttributes
4 0C 8: UTF8String 'SK.CH.DS'
14 03 2: BIT STRING 6 unused bits
: '01'B (bit 0)
18 04 1: OCTET STRING --authid
: 07
: }
21 30 12: SEQUENCE { -- commonKeyAttributes
23 04 1: OCTET STRING
: 01
26 03 3: BIT STRING 6 unused bits
: '1000000000'B (bit 9)
31 02 2: INTEGER 80 -- keyReference (optional)
: }
35 A1 16: [1] { -- keyAttributes
37 30 14: SEQUENCE { -- privateRSAKeyAttributes
39 30 8: SEQUENCE { -- objectValue
41 04 6: OCTET STRING --path
: 3F 00 40 16 00 50
: }
49 02 2: INTEGER 1024 -- modulus
: }
: }
: }
*/
static gpg_error_t
read_ef_prkdf (app_t app, unsigned short fid, prkdf_object_t *result)
{
gpg_error_t err;
unsigned char *buffer = NULL;
size_t buflen;
const unsigned char *p;
size_t n, objlen, hdrlen;
int class, tag, constructed, ndef;
prkdf_object_t prkdflist = NULL;
int i;
if (!fid)
return gpg_error (GPG_ERR_NO_DATA); /* No private keys. */
- err = select_and_read_binary (app->slot, fid, "PrKDF", &buffer, &buflen);
+ err = select_and_read_binary (app_get_slot (app), fid, "PrKDF",
+ &buffer, &buflen);
if (err)
return err;
p = buffer;
n = buflen;
/* FIXME: This shares a LOT of code with read_ef_cdf! */
/* Loop over the records. We stop as soon as we detect a new record
starting with 0x00 or 0xff as these values are commonly used to
pad data blocks and are no valid ASN.1 encoding. */
while (n && *p && *p != 0xff)
{
const unsigned char *pp;
size_t nn;
int where;
const char *errstr = NULL;
prkdf_object_t prkdf = NULL;
unsigned long ul;
const unsigned char *objid;
size_t objidlen;
const unsigned char *authid = NULL;
size_t authidlen = 0;
keyusage_flags_t usageflags;
unsigned long key_reference = 0;
int key_reference_valid = 0;
const char *s;
err = parse_ber_header (&p, &n, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > n || tag != TAG_SEQUENCE))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
{
log_error ("error parsing PrKDF record: %s\n", gpg_strerror (err));
goto leave;
}
pp = p;
nn = objlen;
p += objlen;
n -= objlen;
/* Parse the commonObjectAttributes. */
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nn || tag != TAG_SEQUENCE))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
{
const unsigned char *ppp = pp;
size_t nnn = objlen;
pp += objlen;
nn -= objlen;
/* Search the optional AuthId. We need to skip the optional
Label (UTF8STRING) and the optional CommonObjectFlags
(BITSTRING). */
where = __LINE__;
err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nnn || class != CLASS_UNIVERSAL))
err = gpg_error (GPG_ERR_INV_OBJ);
if (gpg_err_code (err) == GPG_ERR_EOF)
goto no_authid;
if (err)
goto parse_error;
if (tag == TAG_UTF8_STRING)
{
ppp += objlen; /* Skip the Label. */
nnn -= objlen;
where = __LINE__;
err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nnn || class != CLASS_UNIVERSAL))
err = gpg_error (GPG_ERR_INV_OBJ);
if (gpg_err_code (err) == GPG_ERR_EOF)
goto no_authid;
if (err)
goto parse_error;
}
if (tag == TAG_BIT_STRING)
{
ppp += objlen; /* Skip the CommonObjectFlags. */
nnn -= objlen;
where = __LINE__;
err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nnn || class != CLASS_UNIVERSAL))
err = gpg_error (GPG_ERR_INV_OBJ);
if (gpg_err_code (err) == GPG_ERR_EOF)
goto no_authid;
if (err)
goto parse_error;
}
if (tag == TAG_OCTET_STRING && objlen)
{
authid = ppp;
authidlen = objlen;
}
no_authid:
;
}
/* Parse the commonKeyAttributes. */
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nn || tag != TAG_SEQUENCE))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
{
const unsigned char *ppp = pp;
size_t nnn = objlen;
pp += objlen;
nn -= objlen;
/* Get the Id. */
where = __LINE__;
err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nnn
|| class != CLASS_UNIVERSAL || tag != TAG_OCTET_STRING))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
objid = ppp;
objidlen = objlen;
ppp += objlen;
nnn -= objlen;
/* Get the KeyUsageFlags. */
where = __LINE__;
err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nnn
|| class != CLASS_UNIVERSAL || tag != TAG_BIT_STRING))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
err = parse_keyusage_flags (ppp, objlen, &usageflags);
if (err)
goto parse_error;
ppp += objlen;
nnn -= objlen;
/* Find the keyReference */
where = __LINE__;
err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (gpg_err_code (err) == GPG_ERR_EOF)
goto leave_cki;
if (!err && objlen > nnn)
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
if (class == CLASS_UNIVERSAL && tag == TAG_BOOLEAN)
{
/* Skip the native element. */
ppp += objlen;
nnn -= objlen;
err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (gpg_err_code (err) == GPG_ERR_EOF)
goto leave_cki;
if (!err && objlen > nnn)
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
}
if (class == CLASS_UNIVERSAL && tag == TAG_BIT_STRING)
{
/* Skip the accessFlags. */
ppp += objlen;
nnn -= objlen;
err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (gpg_err_code (err) == GPG_ERR_EOF)
goto leave_cki;
if (!err && objlen > nnn)
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
}
if (class == CLASS_UNIVERSAL && tag == TAG_INTEGER)
{
/* Yep, this is the keyReference. */
for (ul=0; objlen; objlen--)
{
ul <<= 8;
ul |= (*ppp++) & 0xff;
nnn--;
}
key_reference = ul;
key_reference_valid = 1;
}
leave_cki:
;
}
/* Skip subClassAttributes. */
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && objlen > nn)
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
if (class == CLASS_CONTEXT && tag == 0)
{
pp += objlen;
nn -= objlen;
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
}
/* Parse the keyAttributes. */
if (!err && (objlen > nn || class != CLASS_CONTEXT || tag != 1))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
nn = objlen;
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && objlen > nn)
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
if (class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE)
; /* RSA */
else if (class == CLASS_CONTEXT)
{
switch (tag)
{
case 0: errstr = "EC key objects are not supported"; break;
case 1: errstr = "DH key objects are not supported"; break;
case 2: errstr = "DSA key objects are not supported"; break;
case 3: errstr = "KEA key objects are not supported"; break;
default: errstr = "unknown privateKeyObject"; break;
}
goto parse_error;
}
else
{
err = gpg_error (GPG_ERR_INV_OBJ);
goto parse_error;
}
nn = objlen;
/* Check that the reference is a Path object. */
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && objlen > nn)
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
if (class != CLASS_UNIVERSAL || tag != TAG_SEQUENCE)
{
errstr = "unsupported reference type";
goto parse_error;
}
nn = objlen;
/* Parse the Path object. */
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && objlen > nn)
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
/* Make sure that the next element is a non zero path and of
even length (FID are two bytes each). */
if (class != CLASS_UNIVERSAL || tag != TAG_OCTET_STRING
|| !objlen || (objlen & 1) )
{
errstr = "invalid path reference";
goto parse_error;
}
/* Create a new PrKDF list item. */
prkdf = xtrycalloc (1, (sizeof *prkdf
- sizeof(unsigned short)
+ objlen/2 * sizeof(unsigned short)));
if (!prkdf)
{
err = gpg_error_from_syserror ();
goto leave;
}
prkdf->objidlen = objidlen;
prkdf->objid = xtrymalloc (objidlen);
if (!prkdf->objid)
{
err = gpg_error_from_syserror ();
xfree (prkdf);
goto leave;
}
memcpy (prkdf->objid, objid, objidlen);
if (authid)
{
prkdf->authidlen = authidlen;
prkdf->authid = xtrymalloc (authidlen);
if (!prkdf->authid)
{
err = gpg_error_from_syserror ();
xfree (prkdf->objid);
xfree (prkdf);
goto leave;
}
memcpy (prkdf->authid, authid, authidlen);
}
prkdf->pathlen = objlen/2;
for (i=0; i < prkdf->pathlen; i++, pp += 2, nn -= 2)
prkdf->path[i] = ((pp[0] << 8) | pp[1]);
prkdf->usageflags = usageflags;
prkdf->key_reference = key_reference;
prkdf->key_reference_valid = key_reference_valid;
if (nn)
{
/* An index and length follows. */
prkdf->have_off = 1;
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nn
|| class != CLASS_UNIVERSAL || tag != TAG_INTEGER))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
for (ul=0; objlen; objlen--)
{
ul <<= 8;
ul |= (*pp++) & 0xff;
nn--;
}
prkdf->off = ul;
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nn
|| class != CLASS_CONTEXT || tag != 0))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
for (ul=0; objlen; objlen--)
{
ul <<= 8;
ul |= (*pp++) & 0xff;
nn--;
}
prkdf->len = ul;
}
log_debug ("PrKDF %04hX: id=", fid);
for (i=0; i < prkdf->objidlen; i++)
log_printf ("%02X", prkdf->objid[i]);
log_printf (" path=");
for (i=0; i < prkdf->pathlen; i++)
log_printf ("%04hX", prkdf->path[i]);
if (prkdf->have_off)
log_printf ("[%lu/%lu]", prkdf->off, prkdf->len);
if (prkdf->authid)
{
log_printf (" authid=");
for (i=0; i < prkdf->authidlen; i++)
log_printf ("%02X", prkdf->authid[i]);
}
if (prkdf->key_reference_valid)
log_printf (" keyref=0x%02lX", prkdf->key_reference);
log_printf (" usage=");
s = "";
if (prkdf->usageflags.encrypt) log_printf ("%sencrypt", s), s = ",";
if (prkdf->usageflags.decrypt) log_printf ("%sdecrypt", s), s = ",";
if (prkdf->usageflags.sign ) log_printf ("%ssign", s), s = ",";
if (prkdf->usageflags.sign_recover)
log_printf ("%ssign_recover", s), s = ",";
if (prkdf->usageflags.wrap ) log_printf ("%swrap", s), s = ",";
if (prkdf->usageflags.unwrap ) log_printf ("%sunwrap", s), s = ",";
if (prkdf->usageflags.verify ) log_printf ("%sverify", s), s = ",";
if (prkdf->usageflags.verify_recover)
log_printf ("%sverify_recover", s), s = ",";
if (prkdf->usageflags.derive ) log_printf ("%sderive", s), s = ",";
if (prkdf->usageflags.non_repudiation)
log_printf ("%snon_repudiation", s), s = ",";
log_printf ("\n");
/* Put it into the list. */
prkdf->next = prkdflist;
prkdflist = prkdf;
prkdf = NULL;
continue; /* Ready. */
parse_error:
log_error ("error parsing PrKDF record (%d): %s - skipped\n",
where, errstr? errstr : gpg_strerror (err));
if (prkdf)
{
xfree (prkdf->objid);
xfree (prkdf->authid);
xfree (prkdf);
}
err = 0;
} /* End looping over all records. */
leave:
xfree (buffer);
if (err)
release_prkdflist (prkdflist);
else
*result = prkdflist;
return err;
}
/* Read and parse the Certificate Directory Files identified by FID.
On success a newlist of CDF object gets stored at RESULT and the
caller is then responsible of releasing this list. On error a
error code is returned and RESULT won't get changed. */
static gpg_error_t
read_ef_cdf (app_t app, unsigned short fid, cdf_object_t *result)
{
gpg_error_t err;
unsigned char *buffer = NULL;
size_t buflen;
const unsigned char *p;
size_t n, objlen, hdrlen;
int class, tag, constructed, ndef;
cdf_object_t cdflist = NULL;
int i;
if (!fid)
return gpg_error (GPG_ERR_NO_DATA); /* No certificates. */
- err = select_and_read_binary (app->slot, fid, "CDF", &buffer, &buflen);
+ err = select_and_read_binary (app_get_slot (app), fid, "CDF",
+ &buffer, &buflen);
if (err)
return err;
p = buffer;
n = buflen;
/* Loop over the records. We stop as soon as we detect a new record
starting with 0x00 or 0xff as these values are commonly used to
pad data blocks and are no valid ASN.1 encoding. */
while (n && *p && *p != 0xff)
{
const unsigned char *pp;
size_t nn;
int where;
const char *errstr = NULL;
cdf_object_t cdf = NULL;
unsigned long ul;
const unsigned char *objid;
size_t objidlen;
err = parse_ber_header (&p, &n, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > n || tag != TAG_SEQUENCE))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
{
log_error ("error parsing CDF record: %s\n", gpg_strerror (err));
goto leave;
}
pp = p;
nn = objlen;
p += objlen;
n -= objlen;
/* Skip the commonObjectAttributes. */
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nn || tag != TAG_SEQUENCE))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
pp += objlen;
nn -= objlen;
/* Parse the commonCertificateAttributes. */
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nn || tag != TAG_SEQUENCE))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
{
const unsigned char *ppp = pp;
size_t nnn = objlen;
pp += objlen;
nn -= objlen;
/* Get the Id. */
where = __LINE__;
err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nnn
|| class != CLASS_UNIVERSAL || tag != TAG_OCTET_STRING))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
objid = ppp;
objidlen = objlen;
}
/* Parse the certAttribute. */
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nn || class != CLASS_CONTEXT || tag != 1))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
nn = objlen;
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nn
|| class != CLASS_UNIVERSAL || tag != TAG_SEQUENCE))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
nn = objlen;
/* Check that the reference is a Path object. */
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && objlen > nn)
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
if (class != CLASS_UNIVERSAL || tag != TAG_SEQUENCE)
{
errstr = "unsupported reference type";
goto parse_error;
}
nn = objlen;
/* Parse the Path object. */
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && objlen > nn)
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
/* Make sure that the next element is a non zero path and of
even length (FID are two bytes each). */
if (class != CLASS_UNIVERSAL || tag != TAG_OCTET_STRING
|| !objlen || (objlen & 1) )
{
errstr = "invalid path reference";
goto parse_error;
}
/* Create a new CDF list item. */
cdf = xtrycalloc (1, (sizeof *cdf
- sizeof(unsigned short)
+ objlen/2 * sizeof(unsigned short)));
if (!cdf)
{
err = gpg_error_from_syserror ();
goto leave;
}
cdf->objidlen = objidlen;
cdf->objid = xtrymalloc (objidlen);
if (!cdf->objid)
{
err = gpg_error_from_syserror ();
xfree (cdf);
goto leave;
}
memcpy (cdf->objid, objid, objidlen);
cdf->pathlen = objlen/2;
for (i=0; i < cdf->pathlen; i++, pp += 2, nn -= 2)
cdf->path[i] = ((pp[0] << 8) | pp[1]);
if (nn)
{
/* An index and length follows. */
cdf->have_off = 1;
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nn
|| class != CLASS_UNIVERSAL || tag != TAG_INTEGER))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
for (ul=0; objlen; objlen--)
{
ul <<= 8;
ul |= (*pp++) & 0xff;
nn--;
}
cdf->off = ul;
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nn
|| class != CLASS_CONTEXT || tag != 0))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
for (ul=0; objlen; objlen--)
{
ul <<= 8;
ul |= (*pp++) & 0xff;
nn--;
}
cdf->len = ul;
}
log_debug ("CDF %04hX: id=", fid);
for (i=0; i < cdf->objidlen; i++)
log_printf ("%02X", cdf->objid[i]);
log_printf (" path=");
for (i=0; i < cdf->pathlen; i++)
log_printf ("%04hX", cdf->path[i]);
if (cdf->have_off)
log_printf ("[%lu/%lu]", cdf->off, cdf->len);
log_printf ("\n");
/* Put it into the list. */
cdf->next = cdflist;
cdflist = cdf;
cdf = NULL;
continue; /* Ready. */
parse_error:
log_error ("error parsing CDF record (%d): %s - skipped\n",
where, errstr? errstr : gpg_strerror (err));
xfree (cdf);
err = 0;
} /* End looping over all records. */
leave:
xfree (buffer);
if (err)
release_cdflist (cdflist);
else
*result = cdflist;
return err;
}
/*
SEQUENCE {
SEQUENCE { -- CommonObjectAttributes
UTF8String 'specific PIN for DS'
BIT STRING 0 unused bits
'00000011'B
}
SEQUENCE { -- CommonAuthenticationObjectAttributes
OCTET STRING
07 -- iD
}
[1] { -- typeAttributes
SEQUENCE { -- PinAttributes
BIT STRING 0 unused bits
'0000100000110010'B -- local,initialized,needs-padding
-- exchangeRefData
ENUMERATED 1 -- ascii-numeric
INTEGER 6 -- minLength
INTEGER 6 -- storedLength
INTEGER 8 -- maxLength
[0]
02 -- pinReference
GeneralizedTime 19/04/2002 12:12 GMT -- lastPinChange
SEQUENCE {
OCTET STRING
3F 00 40 16 -- path to DF of PIN
}
}
}
}
*/
/* Read and parse an Authentication Object Directory File identified
by FID. On success a newlist of AODF objects gets stored at RESULT
and the caller is responsible of releasing this list. On error a
error code is returned and RESULT won't get changed. */
static gpg_error_t
read_ef_aodf (app_t app, unsigned short fid, aodf_object_t *result)
{
gpg_error_t err;
unsigned char *buffer = NULL;
size_t buflen;
const unsigned char *p;
size_t n, objlen, hdrlen;
int class, tag, constructed, ndef;
aodf_object_t aodflist = NULL;
int i;
if (!fid)
return gpg_error (GPG_ERR_NO_DATA); /* No authentication objects. */
- err = select_and_read_binary (app->slot, fid, "AODF", &buffer, &buflen);
+ err = select_and_read_binary (app_get_slot (app), fid, "AODF",
+ &buffer, &buflen);
if (err)
return err;
p = buffer;
n = buflen;
/* FIXME: This shares a LOT of code with read_ef_prkdf! */
/* Loop over the records. We stop as soon as we detect a new record
starting with 0x00 or 0xff as these values are commonly used to
pad data blocks and are no valid ASN.1 encoding. */
while (n && *p && *p != 0xff)
{
const unsigned char *pp;
size_t nn;
int where;
const char *errstr = NULL;
aodf_object_t aodf = NULL;
unsigned long ul;
const char *s;
err = parse_ber_header (&p, &n, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > n || tag != TAG_SEQUENCE))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
{
log_error ("error parsing AODF record: %s\n", gpg_strerror (err));
goto leave;
}
pp = p;
nn = objlen;
p += objlen;
n -= objlen;
/* Allocate memory for a new AODF list item. */
aodf = xtrycalloc (1, sizeof *aodf);
if (!aodf)
goto no_core;
/* Parse the commonObjectAttributes. */
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nn || tag != TAG_SEQUENCE))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
{
const unsigned char *ppp = pp;
size_t nnn = objlen;
pp += objlen;
nn -= objlen;
/* Search the optional AuthId. We need to skip the optional
Label (UTF8STRING) and the optional CommonObjectFlags
(BITSTRING). */
where = __LINE__;
err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nnn || class != CLASS_UNIVERSAL))
err = gpg_error (GPG_ERR_INV_OBJ);
if (gpg_err_code (err) == GPG_ERR_EOF)
goto no_authid;
if (err)
goto parse_error;
if (tag == TAG_UTF8_STRING)
{
ppp += objlen; /* Skip the Label. */
nnn -= objlen;
where = __LINE__;
err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nnn || class != CLASS_UNIVERSAL))
err = gpg_error (GPG_ERR_INV_OBJ);
if (gpg_err_code (err) == GPG_ERR_EOF)
goto no_authid;
if (err)
goto parse_error;
}
if (tag == TAG_BIT_STRING)
{
ppp += objlen; /* Skip the CommonObjectFlags. */
nnn -= objlen;
where = __LINE__;
err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nnn || class != CLASS_UNIVERSAL))
err = gpg_error (GPG_ERR_INV_OBJ);
if (gpg_err_code (err) == GPG_ERR_EOF)
goto no_authid;
if (err)
goto parse_error;
}
if (tag == TAG_OCTET_STRING && objlen)
{
aodf->authidlen = objlen;
aodf->authid = xtrymalloc (objlen);
if (!aodf->authid)
goto no_core;
memcpy (aodf->authid, ppp, objlen);
}
no_authid:
;
}
/* Parse the CommonAuthenticationObjectAttributes. */
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nn || tag != TAG_SEQUENCE))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
{
const unsigned char *ppp = pp;
size_t nnn = objlen;
pp += objlen;
nn -= objlen;
/* Get the Id. */
where = __LINE__;
err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nnn
|| class != CLASS_UNIVERSAL || tag != TAG_OCTET_STRING))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
aodf->objidlen = objlen;
aodf->objid = xtrymalloc (objlen);
if (!aodf->objid)
goto no_core;
memcpy (aodf->objid, ppp, objlen);
}
/* Parse the typeAttributes. */
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nn || class != CLASS_CONTEXT || tag != 1))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
nn = objlen;
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && objlen > nn)
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
if (class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE)
; /* PinAttributes */
else if (class == CLASS_CONTEXT)
{
switch (tag)
{
case 0: errstr = "biometric auth types are not supported"; break;
case 1: errstr = "authKey auth types are not supported"; break;
case 2: errstr = "external auth type are not supported"; break;
default: errstr = "unknown privateKeyObject"; break;
}
goto parse_error;
}
else
{
err = gpg_error (GPG_ERR_INV_OBJ);
goto parse_error;
}
nn = objlen;
/* PinFlags */
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nn || !objlen
|| class != CLASS_UNIVERSAL || tag != TAG_BIT_STRING))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
{
unsigned int bits, mask;
int unused, full;
unused = *pp++; nn--; objlen--;
if ((!objlen && unused) || unused/8 > objlen)
{
err = gpg_error (GPG_ERR_ENCODING_PROBLEM);
goto parse_error;
}
full = objlen - (unused+7)/8;
unused %= 8;
mask = 0;
for (i=1; unused; i <<= 1, unused--)
mask |= i;
/* The first octet */
bits = 0;
if (objlen)
{
bits = *pp++; nn--; objlen--;
if (full)
full--;
else
{
bits &= ~mask;
mask = 0;
}
}
if ((bits & 0x80)) /* ASN.1 bit 0. */
aodf->pinflags.case_sensitive = 1;
if ((bits & 0x40)) /* ASN.1 bit 1. */
aodf->pinflags.local = 1;
if ((bits & 0x20))
aodf->pinflags.change_disabled = 1;
if ((bits & 0x10))
aodf->pinflags.unblock_disabled = 1;
if ((bits & 0x08))
aodf->pinflags.initialized = 1;
if ((bits & 0x04))
aodf->pinflags.needs_padding = 1;
if ((bits & 0x02))
aodf->pinflags.unblocking_pin = 1;
if ((bits & 0x01))
aodf->pinflags.so_pin = 1;
/* The second octet. */
bits = 0;
if (objlen)
{
bits = *pp++; nn--; objlen--;
if (full)
full--;
else
{
bits &= ~mask;
}
}
if ((bits & 0x80))
aodf->pinflags.disable_allowed = 1;
if ((bits & 0x40))
aodf->pinflags.integrity_protected = 1;
if ((bits & 0x20))
aodf->pinflags.confidentiality_protected = 1;
if ((bits & 0x10))
aodf->pinflags.exchange_ref_data = 1;
/* Skip remaining bits. */
pp += objlen;
nn -= objlen;
}
/* PinType */
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nn
|| class != CLASS_UNIVERSAL || tag != TAG_ENUMERATED))
err = gpg_error (GPG_ERR_INV_OBJ);
if (!err && objlen > sizeof (ul))
err = gpg_error (GPG_ERR_UNSUPPORTED_ENCODING);
if (err)
goto parse_error;
for (ul=0; objlen; objlen--)
{
ul <<= 8;
ul |= (*pp++) & 0xff;
nn--;
}
aodf->pintype = ul;
/* minLength */
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nn
|| class != CLASS_UNIVERSAL || tag != TAG_INTEGER))
err = gpg_error (GPG_ERR_INV_OBJ);
if (!err && objlen > sizeof (ul))
err = gpg_error (GPG_ERR_UNSUPPORTED_ENCODING);
if (err)
goto parse_error;
for (ul=0; objlen; objlen--)
{
ul <<= 8;
ul |= (*pp++) & 0xff;
nn--;
}
aodf->min_length = ul;
/* storedLength */
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nn
|| class != CLASS_UNIVERSAL || tag != TAG_INTEGER))
err = gpg_error (GPG_ERR_INV_OBJ);
if (!err && objlen > sizeof (ul))
err = gpg_error (GPG_ERR_UNSUPPORTED_ENCODING);
if (err)
goto parse_error;
for (ul=0; objlen; objlen--)
{
ul <<= 8;
ul |= (*pp++) & 0xff;
nn--;
}
aodf->stored_length = ul;
/* optional maxLength */
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (gpg_err_code (err) == GPG_ERR_EOF)
goto ready;
if (!err && objlen > nn)
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
if (class == CLASS_UNIVERSAL && tag == TAG_INTEGER)
{
if (objlen > sizeof (ul))
{
err = gpg_error (GPG_ERR_UNSUPPORTED_ENCODING);
goto parse_error;
}
for (ul=0; objlen; objlen--)
{
ul <<= 8;
ul |= (*pp++) & 0xff;
nn--;
}
aodf->max_length = ul;
aodf->max_length_valid = 1;
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (gpg_err_code (err) == GPG_ERR_EOF)
goto ready;
if (!err && objlen > nn)
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
}
/* Optional pinReference. */
if (class == CLASS_CONTEXT && tag == 0)
{
if (objlen > sizeof (ul))
{
err = gpg_error (GPG_ERR_UNSUPPORTED_ENCODING);
goto parse_error;
}
for (ul=0; objlen; objlen--)
{
ul <<= 8;
ul |= (*pp++) & 0xff;
nn--;
}
aodf->pin_reference = ul;
aodf->pin_reference_valid = 1;
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (gpg_err_code (err) == GPG_ERR_EOF)
goto ready;
if (!err && objlen > nn)
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
}
/* Optional padChar. */
if (class == CLASS_UNIVERSAL && tag == TAG_OCTET_STRING)
{
if (objlen != 1)
{
errstr = "padChar is not of size(1)";
goto parse_error;
}
aodf->pad_char = *pp++; nn--;
aodf->pad_char_valid = 1;
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (gpg_err_code (err) == GPG_ERR_EOF)
goto ready;
if (!err && objlen > nn)
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
}
/* Skip optional lastPinChange. */
if (class == CLASS_UNIVERSAL && tag == TAG_GENERALIZED_TIME)
{
pp += objlen;
nn -= objlen;
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (gpg_err_code (err) == GPG_ERR_EOF)
goto ready;
if (!err && objlen > nn)
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
}
/* Optional Path object. */
if (class == CLASS_UNIVERSAL || tag == TAG_SEQUENCE)
{
const unsigned char *ppp = pp;
size_t nnn = objlen;
pp += objlen;
nn -= objlen;
where = __LINE__;
err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && objlen > nnn)
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
/* Make sure that the next element is a non zero FID and of
even length (FID are two bytes each). */
if (class != CLASS_UNIVERSAL || tag != TAG_OCTET_STRING
|| !objlen || (objlen & 1) )
{
errstr = "invalid path reference";
goto parse_error;
}
aodf->pathlen = objlen/2;
aodf->path = xtrymalloc (aodf->pathlen);
if (!aodf->path)
goto no_core;
for (i=0; i < aodf->pathlen; i++, ppp += 2, nnn -= 2)
aodf->path[i] = ((ppp[0] << 8) | ppp[1]);
if (nnn)
{
/* An index and length follows. */
aodf->have_off = 1;
where = __LINE__;
err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nnn
|| class != CLASS_UNIVERSAL || tag != TAG_INTEGER))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
for (ul=0; objlen; objlen--)
{
ul <<= 8;
ul |= (*ppp++) & 0xff;
nnn--;
}
aodf->off = ul;
where = __LINE__;
err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nnn
|| class != CLASS_CONTEXT || tag != 0))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
for (ul=0; objlen; objlen--)
{
ul <<= 8;
ul |= (*ppp++) & 0xff;
nnn--;
}
aodf->len = ul;
}
}
/* Igonore further objects which might be there due to future
extensions of pkcs#15. */
ready:
log_debug ("AODF %04hX: id=", fid);
for (i=0; i < aodf->objidlen; i++)
log_printf ("%02X", aodf->objid[i]);
if (aodf->authid)
{
log_printf (" authid=");
for (i=0; i < aodf->authidlen; i++)
log_printf ("%02X", aodf->authid[i]);
}
log_printf (" flags=");
s = "";
if (aodf->pinflags.case_sensitive)
log_printf ("%scase_sensitive", s), s = ",";
if (aodf->pinflags.local)
log_printf ("%slocal", s), s = ",";
if (aodf->pinflags.change_disabled)
log_printf ("%schange_disabled", s), s = ",";
if (aodf->pinflags.unblock_disabled)
log_printf ("%sunblock_disabled", s), s = ",";
if (aodf->pinflags.initialized)
log_printf ("%sinitialized", s), s = ",";
if (aodf->pinflags.needs_padding)
log_printf ("%sneeds_padding", s), s = ",";
if (aodf->pinflags.unblocking_pin)
log_printf ("%sunblocking_pin", s), s = ",";
if (aodf->pinflags.so_pin)
log_printf ("%sso_pin", s), s = ",";
if (aodf->pinflags.disable_allowed)
log_printf ("%sdisable_allowed", s), s = ",";
if (aodf->pinflags.integrity_protected)
log_printf ("%sintegrity_protected", s), s = ",";
if (aodf->pinflags.confidentiality_protected)
log_printf ("%sconfidentiality_protected", s), s = ",";
if (aodf->pinflags.exchange_ref_data)
log_printf ("%sexchange_ref_data", s), s = ",";
{
char numbuf[50];
switch (aodf->pintype)
{
case PIN_TYPE_BCD: s = "bcd"; break;
case PIN_TYPE_ASCII_NUMERIC: s = "ascii-numeric"; break;
case PIN_TYPE_UTF8: s = "utf8"; break;
case PIN_TYPE_HALF_NIBBLE_BCD: s = "half-nibble-bcd"; break;
case PIN_TYPE_ISO9564_1: s = "iso9564-1"; break;
default:
sprintf (numbuf, "%lu", (unsigned long)aodf->pintype);
s = numbuf;
}
log_printf (" type=%s", s);
}
log_printf (" min=%lu", aodf->min_length);
log_printf (" stored=%lu", aodf->stored_length);
if (aodf->max_length_valid)
log_printf (" max=%lu", aodf->max_length);
if (aodf->pad_char_valid)
log_printf (" pad=0x%02x", aodf->pad_char);
if (aodf->pin_reference_valid)
log_printf (" pinref=0x%02lX", aodf->pin_reference);
if (aodf->pathlen)
{
log_printf (" path=");
for (i=0; i < aodf->pathlen; i++)
log_printf ("%04hX", aodf->path[i]);
if (aodf->have_off)
log_printf ("[%lu/%lu]", aodf->off, aodf->len);
}
log_printf ("\n");
/* Put it into the list. */
aodf->next = aodflist;
aodflist = aodf;
aodf = NULL;
continue; /* Ready. */
no_core:
err = gpg_error_from_syserror ();
release_aodf_object (aodf);
goto leave;
parse_error:
log_error ("error parsing AODF record (%d): %s - skipped\n",
where, errstr? errstr : gpg_strerror (err));
err = 0;
release_aodf_object (aodf);
} /* End looping over all records. */
leave:
xfree (buffer);
if (err)
release_aodflist (aodflist);
else
*result = aodflist;
return err;
}
/* Read and parse the EF(TokenInfo).
TokenInfo ::= SEQUENCE {
version INTEGER {v1(0)} (v1,...),
serialNumber OCTET STRING,
manufacturerID Label OPTIONAL,
label [0] Label OPTIONAL,
tokenflags TokenFlags,
seInfo SEQUENCE OF SecurityEnvironmentInfo OPTIONAL,
recordInfo [1] RecordInfo OPTIONAL,
supportedAlgorithms [2] SEQUENCE OF AlgorithmInfo OPTIONAL,
...,
issuerId [3] Label OPTIONAL,
holderId [4] Label OPTIONAL,
lastUpdate [5] LastUpdate OPTIONAL,
preferredLanguage PrintableString OPTIONAL -- In accordance with
-- IETF RFC 1766
} (CONSTRAINED BY { -- Each AlgorithmInfo.reference value must be unique --})
TokenFlags ::= BIT STRING {
readOnly (0),
loginRequired (1),
prnGeneration (2),
eidCompliant (3)
}
5032:
30 31 02 01 00 04 04 05 45 36 9F 0C 0C 44 2D 54 01......E6...D-T
72 75 73 74 20 47 6D 62 48 80 14 4F 66 66 69 63 rust GmbH..Offic
65 20 69 64 65 6E 74 69 74 79 20 63 61 72 64 03 e identity card.
02 00 40 20 63 61 72 64 03 02 00 40 00 00 00 00 ..@ card...@....
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0 49: SEQUENCE {
2 1: INTEGER 0
5 4: OCTET STRING 05 45 36 9F
11 12: UTF8String 'D-Trust GmbH'
25 20: [0] 'Office identity card'
47 2: BIT STRING
: '00000010'B (bit 1)
: Error: Spurious zero bits in bitstring.
: }
*/
static gpg_error_t
read_ef_tokeninfo (app_t app)
{
gpg_error_t err;
unsigned char *buffer = NULL;
size_t buflen;
const unsigned char *p;
size_t n, objlen, hdrlen;
int class, tag, constructed, ndef;
unsigned long ul;
- err = select_and_read_binary (app->slot, 0x5032, "TokenInfo",
+ err = select_and_read_binary (app_get_slot (app), 0x5032, "TokenInfo",
&buffer, &buflen);
if (err)
return err;
p = buffer;
n = buflen;
err = parse_ber_header (&p, &n, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > n || tag != TAG_SEQUENCE))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
{
log_error ("error parsing TokenInfo: %s\n", gpg_strerror (err));
goto leave;
}
n = objlen;
/* Version. */
err = parse_ber_header (&p, &n, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > n || tag != TAG_INTEGER))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto leave;
for (ul=0; objlen; objlen--)
{
ul <<= 8;
ul |= (*p++) & 0xff;
n--;
}
if (ul)
{
log_error ("invalid version %lu in TokenInfo\n", ul);
err = gpg_error (GPG_ERR_INV_OBJ);
goto leave;
}
/* serialNumber. */
err = parse_ber_header (&p, &n, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > n || tag != TAG_OCTET_STRING || !objlen))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto leave;
xfree (app->app_local->serialno);
app->app_local->serialno = xtrymalloc (objlen);
if (!app->app_local->serialno)
{
err = gpg_error_from_syserror ();
goto leave;
}
memcpy (app->app_local->serialno, p, objlen);
app->app_local->serialnolen = objlen;
log_printhex (p, objlen, "Serialnumber from EF(TokenInfo) is:");
leave:
xfree (buffer);
return err;
}
/* Get all the basic information from the pkcs#15 card, check the
structure and initialize our local context. This is used once at
application initialization. */
static gpg_error_t
read_p15_info (app_t app)
{
gpg_error_t err;
if (!read_ef_tokeninfo (app))
{
/* If we don't have a serial number yet but the TokenInfo provides
one, use that. */
- if (!app->serialno && app->app_local->serialno)
+ if (!app->card->serialno && app->app_local->serialno)
{
- app->serialno = app->app_local->serialno;
- app->serialnolen = app->app_local->serialnolen;
+ app->card->serialno = app->app_local->serialno;
+ app->card->serialnolen = app->app_local->serialnolen;
app->app_local->serialno = NULL;
app->app_local->serialnolen = 0;
- err = app_munge_serialno (app);
+ err = app_munge_serialno (app->card);
if (err)
return err;
}
}
/* Read the ODF so that we know the location of all directory
files. */
/* Fixme: We might need to get a non-standard ODF FID from TokenInfo. */
err = read_ef_odf (app, 0x5031);
if (err)
return err;
/* Read certificate information. */
assert (!app->app_local->certificate_info);
assert (!app->app_local->trusted_certificate_info);
assert (!app->app_local->useful_certificate_info);
err = read_ef_cdf (app, app->app_local->odf.certificates,
&app->app_local->certificate_info);
if (!err || gpg_err_code (err) == GPG_ERR_NO_DATA)
err = read_ef_cdf (app, app->app_local->odf.trusted_certificates,
&app->app_local->trusted_certificate_info);
if (!err || gpg_err_code (err) == GPG_ERR_NO_DATA)
err = read_ef_cdf (app, app->app_local->odf.useful_certificates,
&app->app_local->useful_certificate_info);
if (gpg_err_code (err) == GPG_ERR_NO_DATA)
err = 0;
if (err)
return err;
/* Read information about private keys. */
assert (!app->app_local->private_key_info);
err = read_ef_prkdf (app, app->app_local->odf.private_keys,
&app->app_local->private_key_info);
if (gpg_err_code (err) == GPG_ERR_NO_DATA)
err = 0;
if (err)
return err;
/* Read information about authentication objects. */
assert (!app->app_local->auth_object_info);
err = read_ef_aodf (app, app->app_local->odf.auth_objects,
&app->app_local->auth_object_info);
if (gpg_err_code (err) == GPG_ERR_NO_DATA)
err = 0;
return err;
}
/* Helper to do_learn_status: Send information about all certificates
listed in CERTINFO back. Use CERTTYPE as type of the
certificate. */
static gpg_error_t
send_certinfo (app_t app, ctrl_t ctrl, const char *certtype,
cdf_object_t certinfo)
{
for (; certinfo; certinfo = certinfo->next)
{
char *buf, *p;
buf = xtrymalloc (9 + certinfo->objidlen*2 + 1);
if (!buf)
return gpg_error_from_syserror ();
p = stpcpy (buf, "P15");
if (app->app_local->home_df)
{
snprintf (p, 6, "-%04X",
(unsigned int)(app->app_local->home_df & 0xffff));
p += 5;
}
p = stpcpy (p, ".");
bin2hex (certinfo->objid, certinfo->objidlen, p);
send_status_info (ctrl, "CERTINFO",
certtype, strlen (certtype),
buf, strlen (buf),
NULL, (size_t)0);
xfree (buf);
}
return 0;
}
/* Get the keygrip of the private key object PRKDF. On success the
keygrip gets returned in the caller provided 41 byte buffer
R_GRIPSTR. */
static gpg_error_t
keygripstr_from_prkdf (app_t app, prkdf_object_t prkdf, char *r_gripstr)
{
gpg_error_t err;
cdf_object_t cdf;
unsigned char *der;
size_t derlen;
ksba_cert_t cert;
/* FIXME: We should check whether a public key directory file and a
matching public key for PRKDF is available. This should make
extraction of the key much easier. My current test card doesn't
have one, so we can only use the fallback solution bu looking for
a matching certificate and extract the key from there. */
/* Look for a matching certificate. A certificate matches if the Id
matches the one of the private key info. */
for (cdf = app->app_local->certificate_info; cdf; cdf = cdf->next)
if (cdf->objidlen == prkdf->objidlen
&& !memcmp (cdf->objid, prkdf->objid, prkdf->objidlen))
break;
if (!cdf)
for (cdf = app->app_local->trusted_certificate_info; cdf; cdf = cdf->next)
if (cdf->objidlen == prkdf->objidlen
&& !memcmp (cdf->objid, prkdf->objid, prkdf->objidlen))
break;
if (!cdf)
for (cdf = app->app_local->useful_certificate_info; cdf; cdf = cdf->next)
if (cdf->objidlen == prkdf->objidlen
&& !memcmp (cdf->objid, prkdf->objid, prkdf->objidlen))
break;
if (!cdf)
return gpg_error (GPG_ERR_NOT_FOUND);
err = readcert_by_cdf (app, cdf, &der, &derlen);
if (err)
return err;
err = ksba_cert_new (&cert);
if (!err)
err = ksba_cert_init_from_mem (cert, der, derlen);
xfree (der);
if (!err)
err = app_help_get_keygrip_string (cert, r_gripstr);
ksba_cert_release (cert);
return err;
}
/* Helper to do_learn_status: Send information about all known
keypairs back. FIXME: much code duplication from
send_certinfo(). */
static gpg_error_t
send_keypairinfo (app_t app, ctrl_t ctrl, prkdf_object_t keyinfo)
{
gpg_error_t err;
for (; keyinfo; keyinfo = keyinfo->next)
{
char gripstr[40+1];
char *buf, *p;
int j;
buf = xtrymalloc (9 + keyinfo->objidlen*2 + 1);
if (!buf)
return gpg_error_from_syserror ();
p = stpcpy (buf, "P15");
if (app->app_local->home_df)
{
snprintf (p, 6, "-%04X",
(unsigned int)(app->app_local->home_df & 0xffff));
p += 5;
}
p = stpcpy (p, ".");
bin2hex (keyinfo->objid, keyinfo->objidlen, p);
err = keygripstr_from_prkdf (app, keyinfo, gripstr);
if (err)
{
log_error ("can't get keygrip from ");
for (j=0; j < keyinfo->pathlen; j++)
log_printf ("%04hX", keyinfo->path[j]);
log_printf (": %s\n", gpg_strerror (err));
}
else
{
assert (strlen (gripstr) == 40);
send_status_info (ctrl, "KEYPAIRINFO",
gripstr, 40,
buf, strlen (buf),
NULL, (size_t)0);
}
xfree (buf);
}
return 0;
}
/* This is the handler for the LEARN command. */
static gpg_error_t
do_learn_status (app_t app, ctrl_t ctrl, unsigned int flags)
{
gpg_error_t err;
if ((flags & 1))
err = 0;
else
{
err = send_certinfo (app, ctrl, "100", app->app_local->certificate_info);
if (!err)
err = send_certinfo (app, ctrl, "101",
app->app_local->trusted_certificate_info);
if (!err)
err = send_certinfo (app, ctrl, "102",
app->app_local->useful_certificate_info);
}
if (!err)
err = send_keypairinfo (app, ctrl, app->app_local->private_key_info);
return err;
}
/* Read a certifciate using the information in CDF and return the
certificate in a newly llocated buffer R_CERT and its length
R_CERTLEN. */
static gpg_error_t
readcert_by_cdf (app_t app, cdf_object_t cdf,
unsigned char **r_cert, size_t *r_certlen)
{
gpg_error_t err;
unsigned char *buffer = NULL;
const unsigned char *p, *save_p;
size_t buflen, n;
int class, tag, constructed, ndef;
size_t totobjlen, objlen, hdrlen;
int rootca;
int i;
*r_cert = NULL;
*r_certlen = 0;
/* First check whether it has been cached. */
if (cdf->image)
{
*r_cert = xtrymalloc (cdf->imagelen);
if (!*r_cert)
return gpg_error_from_syserror ();
memcpy (*r_cert, cdf->image, cdf->imagelen);
*r_certlen = cdf->imagelen;
return 0;
}
/* Read the entire file. fixme: This could be optimized by first
reading the header to figure out how long the certificate
actually is. */
err = select_ef_by_path (app, cdf->path, cdf->pathlen);
if (err)
goto leave;
- err = iso7816_read_binary (app->slot, cdf->off, cdf->len, &buffer, &buflen);
+ err = iso7816_read_binary (app_get_slot (app), cdf->off, cdf->len,
+ &buffer, &buflen);
if (!err && (!buflen || *buffer == 0xff))
err = gpg_error (GPG_ERR_NOT_FOUND);
if (err)
{
log_error ("error reading certificate with Id ");
for (i=0; i < cdf->objidlen; i++)
log_printf ("%02X", cdf->objid[i]);
log_printf (": %s\n", gpg_strerror (err));
goto leave;
}
/* Check whether this is really a certificate. */
p = buffer;
n = buflen;
err = parse_ber_header (&p, &n, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (err)
goto leave;
if (class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE && constructed)
rootca = 0;
else if ( class == CLASS_UNIVERSAL && tag == TAG_SET && constructed )
rootca = 1;
else
{
err = gpg_error (GPG_ERR_INV_OBJ);
goto leave;
}
totobjlen = objlen + hdrlen;
assert (totobjlen <= buflen);
err = parse_ber_header (&p, &n, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (err)
goto leave;
if (!rootca
&& class == CLASS_UNIVERSAL && tag == TAG_OBJECT_ID && !constructed)
{
/* The certificate seems to be contained in a userCertificate
container. Skip this and assume the following sequence is
the certificate. */
if (n < objlen)
{
err = gpg_error (GPG_ERR_INV_OBJ);
goto leave;
}
p += objlen;
n -= objlen;
save_p = p;
err = parse_ber_header (&p, &n, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (err)
goto leave;
if ( !(class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE && constructed) )
{
err = gpg_error (GPG_ERR_INV_OBJ);
goto leave;
}
totobjlen = objlen + hdrlen;
assert (save_p + totobjlen <= buffer + buflen);
memmove (buffer, save_p, totobjlen);
}
*r_cert = buffer;
buffer = NULL;
*r_certlen = totobjlen;
/* Try to cache it. */
if (!cdf->image && (cdf->image = xtrymalloc (*r_certlen)))
{
memcpy (cdf->image, *r_cert, *r_certlen);
cdf->imagelen = *r_certlen;
}
leave:
xfree (buffer);
return err;
}
/* Handler for the READCERT command.
Read the certificate with id CERTID (as returned by learn_status in
the CERTINFO status lines) and return it in the freshly allocated
buffer to be stored at R_CERT and its length at R_CERTLEN. A error
code will be returned on failure and R_CERT and R_CERTLEN will be
set to (NULL,0). */
static gpg_error_t
do_readcert (app_t app, const char *certid,
unsigned char **r_cert, size_t *r_certlen)
{
gpg_error_t err;
cdf_object_t cdf;
*r_cert = NULL;
*r_certlen = 0;
err = cdf_object_from_certid (app, certid, &cdf);
if (!err)
err = readcert_by_cdf (app, cdf, r_cert, r_certlen);
return err;
}
/* Implement the GETATTR command. This is similar to the LEARN
command but returns just one value via the status interface. */
static gpg_error_t
do_getattr (app_t app, ctrl_t ctrl, const char *name)
{
gpg_error_t err;
if (!strcmp (name, "$AUTHKEYID"))
{
char *buf, *p;
prkdf_object_t prkdf;
/* We return the ID of the first private keycapable of
signing. */
for (prkdf = app->app_local->private_key_info; prkdf;
prkdf = prkdf->next)
if (prkdf->usageflags.sign)
break;
if (prkdf)
{
buf = xtrymalloc (9 + prkdf->objidlen*2 + 1);
if (!buf)
return gpg_error_from_syserror ();
p = stpcpy (buf, "P15");
if (app->app_local->home_df)
{
snprintf (p, 6, "-%04X",
(unsigned int)(app->app_local->home_df & 0xffff));
p += 5;
}
p = stpcpy (p, ".");
bin2hex (prkdf->objid, prkdf->objidlen, p);
send_status_info (ctrl, name, buf, strlen (buf), NULL, 0);
xfree (buf);
return 0;
}
}
else if (!strcmp (name, "$DISPSERIALNO"))
{
/* For certain cards we return special IDs. There is no
general rule for it so we need to decide case by case. */
if (app->app_local->card_type == CARD_TYPE_BELPIC)
{
/* The eID card has a card number printed on the front matter
which seems to be a good indication. */
unsigned char *buffer;
const unsigned char *p;
size_t buflen, n;
unsigned short path[] = { 0x3F00, 0xDF01, 0x4031 };
err = select_ef_by_path (app, path, DIM(path) );
if (!err)
- err = iso7816_read_binary (app->slot, 0, 0, &buffer, &buflen);
+ err = iso7816_read_binary (app_get_slot (app), 0, 0,
+ &buffer, &buflen);
if (err)
{
log_error ("error accessing EF(ID): %s\n", gpg_strerror (err));
return err;
}
p = find_tlv (buffer, buflen, 1, &n);
if (p && n == 12)
{
char tmp[12+2+1];
memcpy (tmp, p, 3);
tmp[3] = '-';
memcpy (tmp+4, p+3, 7);
tmp[11] = '-';
memcpy (tmp+12, p+10, 2);
tmp[14] = 0;
send_status_info (ctrl, name, tmp, strlen (tmp), NULL, 0);
xfree (buffer);
return 0;
}
xfree (buffer);
}
}
return gpg_error (GPG_ERR_INV_NAME);
}
/* Micardo cards require special treatment. This is a helper for the
crypto functions to manage the security environment. We expect that
the key file has already been selected. FID is the one of the
selected key. */
static gpg_error_t
micardo_mse (app_t app, unsigned short fid)
{
gpg_error_t err;
int recno;
unsigned short refdata = 0;
int se_num;
unsigned char msebuf[10];
/* Read the KeyD file containing extra information on keys. */
- err = iso7816_select_file (app->slot, 0x0013, 0);
+ err = iso7816_select_file (app_get_slot (app), 0x0013, 0);
if (err)
{
log_error ("error reading EF_keyD: %s\n", gpg_strerror (err));
return err;
}
for (recno = 1, se_num = -1; ; recno++)
{
unsigned char *buffer;
size_t buflen;
size_t n, nn;
const unsigned char *p, *pp;
- err = iso7816_read_record (app->slot, recno, 1, 0, &buffer, &buflen);
+ err = iso7816_read_record (app_get_slot (app), recno, 1, 0,
+ &buffer, &buflen);
if (gpg_err_code (err) == GPG_ERR_NOT_FOUND)
break; /* ready */
if (err)
{
log_error ("error reading EF_keyD record: %s\n",
gpg_strerror (err));
return err;
}
log_printhex (buffer, buflen, "keyD record:");
p = find_tlv (buffer, buflen, 0x83, &n);
if (p && n == 4 && ((p[2]<<8)|p[3]) == fid)
{
refdata = ((p[0]<<8)|p[1]);
/* Locate the SE DO and the there included sec env number. */
p = find_tlv (buffer, buflen, 0x7b, &n);
if (p && n)
{
pp = find_tlv (p, n, 0x80, &nn);
if (pp && nn == 1)
{
se_num = *pp;
xfree (buffer);
break; /* found. */
}
}
}
xfree (buffer);
}
if (se_num == -1)
{
log_error ("CRT for keyfile %04hX not found\n", fid);
return gpg_error (GPG_ERR_NOT_FOUND);
}
/* Restore the security environment to SE_NUM if needed */
if (se_num)
{
- err = iso7816_manage_security_env (app->slot, 0xf3, se_num, NULL, 0);
+ err = iso7816_manage_security_env (app_get_slot (app),
+ 0xf3, se_num, NULL, 0);
if (err)
{
log_error ("restoring SE to %d failed: %s\n",
se_num, gpg_strerror (err));
return err;
}
}
/* Set the DST reference data. */
msebuf[0] = 0x83;
msebuf[1] = 0x03;
msebuf[2] = 0x80;
msebuf[3] = (refdata >> 8);
msebuf[4] = refdata;
- err = iso7816_manage_security_env (app->slot, 0x41, 0xb6, msebuf, 5);
+ err = iso7816_manage_security_env (app_get_slot (app), 0x41, 0xb6, msebuf, 5);
if (err)
{
log_error ("setting SE to reference file %04hX failed: %s\n",
refdata, gpg_strerror (err));
return err;
}
return 0;
}
/* Handler for the PKSIGN command.
Create the signature and return the allocated result in OUTDATA.
If a PIN is required, the PINCB will be used to ask for the PIN;
that callback should return the PIN in an allocated buffer and
store that as the 3rd argument. */
static gpg_error_t
do_sign (app_t app, const char *keyidstr, int hashalgo,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const void *indata, size_t indatalen,
unsigned char **outdata, size_t *outdatalen )
{
static unsigned char sha1_prefix[15] = /* Object ID is 1.3.14.3.2.26 */
{ 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e, 0x03,
0x02, 0x1a, 0x05, 0x00, 0x04, 0x14 };
static unsigned char rmd160_prefix[15] = /* Object ID is 1.3.36.3.2.1 */
{ 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x24, 0x03,
0x02, 0x01, 0x05, 0x00, 0x04, 0x14 };
gpg_error_t err;
int i;
unsigned char data[36]; /* Must be large enough for a SHA-1 digest
+ the largest OID prefix above and also
fit the 36 bytes of md5sha1. */
prkdf_object_t prkdf; /* The private key object. */
aodf_object_t aodf; /* The associated authentication object. */
int no_data_padding = 0; /* True if the card want the data without padding.*/
int mse_done = 0; /* Set to true if the MSE has been done. */
if (!keyidstr || !*keyidstr)
return gpg_error (GPG_ERR_INV_VALUE);
if (indatalen != 20 && indatalen != 16 && indatalen != 35 && indatalen != 36)
return gpg_error (GPG_ERR_INV_VALUE);
err = prkdf_object_from_keyidstr (app, keyidstr, &prkdf);
if (err)
return err;
if (!(prkdf->usageflags.sign || prkdf->usageflags.sign_recover
||prkdf->usageflags.non_repudiation))
{
log_error ("key %s may not be used for signing\n", keyidstr);
return gpg_error (GPG_ERR_WRONG_KEY_USAGE);
}
if (!prkdf->authid)
{
log_error ("no authentication object defined for %s\n", keyidstr);
/* fixme: we might want to go ahead and do without PIN
verification. */
return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
}
/* Find the authentication object to this private key object. */
for (aodf = app->app_local->auth_object_info; aodf; aodf = aodf->next)
if (aodf->objidlen == prkdf->authidlen
&& !memcmp (aodf->objid, prkdf->authid, prkdf->authidlen))
break;
if (!aodf)
{
log_error ("authentication object for %s missing\n", keyidstr);
return gpg_error (GPG_ERR_INV_CARD);
}
if (aodf->authid)
{
log_error ("PIN verification is protected by an "
"additional authentication token\n");
return gpg_error (GPG_ERR_BAD_PIN_METHOD);
}
if (aodf->pinflags.integrity_protected
|| aodf->pinflags.confidentiality_protected)
{
log_error ("PIN verification requires unsupported protection method\n");
return gpg_error (GPG_ERR_BAD_PIN_METHOD);
}
if (!aodf->stored_length && aodf->pinflags.needs_padding)
{
log_error ("PIN verification requires padding but no length known\n");
return gpg_error (GPG_ERR_INV_CARD);
}
/* Select the key file. Note that this may change the security
environment thus we do it before PIN verification. */
err = select_ef_by_path (app, prkdf->path, prkdf->pathlen);
if (err)
{
log_error ("error selecting file for key %s: %s\n",
keyidstr, gpg_strerror (errno));
return err;
}
/* Due to the fact that the non-repudiation signature on a BELPIC
card requires a verify immediately before the DSO we set the
MSE before we do the verification. Other cards might also allow
this but I don't want to break anything, thus we do it only
for the BELPIC card here. */
if (app->app_local->card_type == CARD_TYPE_BELPIC)
{
unsigned char mse[5];
mse[0] = 4; /* Length of the template. */
mse[1] = 0x80; /* Algorithm reference tag. */
if (hashalgo == MD_USER_TLS_MD5SHA1)
mse[2] = 0x01; /* Let card do pkcs#1 0xFF padding. */
else
mse[2] = 0x02; /* RSASSA-PKCS1-v1.5 using SHA1. */
mse[3] = 0x84; /* Private key reference tag. */
mse[4] = prkdf->key_reference_valid? prkdf->key_reference : 0x82;
- err = iso7816_manage_security_env (app->slot,
+ err = iso7816_manage_security_env (app_get_slot (app),
0x41, 0xB6,
mse, sizeof mse);
no_data_padding = 1;
mse_done = 1;
}
if (err)
{
log_error ("MSE failed: %s\n", gpg_strerror (err));
return err;
}
/* Now that we have all the information available, prepare and run
the PIN verification.*/
if (1)
{
char *pinvalue;
size_t pinvaluelen;
const char *errstr;
const char *s;
if (prkdf->usageflags.non_repudiation
&& app->app_local->card_type == CARD_TYPE_BELPIC)
err = pincb (pincb_arg, "PIN (qualified signature!)", &pinvalue);
else
err = pincb (pincb_arg, "PIN", &pinvalue);
if (err)
{
log_info ("PIN callback returned error: %s\n", gpg_strerror (err));
return err;
}
/* We might need to cope with UTF8 things here. Not sure how
min_length etc. are exactly defined, for now we take them as
a plain octet count. */
if (strlen (pinvalue) < aodf->min_length)
{
log_error ("PIN is too short; minimum length is %lu\n",
aodf->min_length);
err = gpg_error (GPG_ERR_BAD_PIN);
}
else if (aodf->stored_length && strlen (pinvalue) > aodf->stored_length)
{
/* This would otherwise truncate the PIN silently. */
log_error ("PIN is too large; maximum length is %lu\n",
aodf->stored_length);
err = gpg_error (GPG_ERR_BAD_PIN);
}
else if (aodf->max_length_valid && strlen (pinvalue) > aodf->max_length)
{
log_error ("PIN is too large; maximum length is %lu\n",
aodf->max_length);
err = gpg_error (GPG_ERR_BAD_PIN);
}
if (err)
{
xfree (pinvalue);
return err;
}
errstr = NULL;
err = 0;
switch (aodf->pintype)
{
case PIN_TYPE_BCD:
case PIN_TYPE_ASCII_NUMERIC:
for (s=pinvalue; digitp (s); s++)
;
if (*s)
{
errstr = "Non-numeric digits found in PIN";
err = gpg_error (GPG_ERR_BAD_PIN);
}
break;
case PIN_TYPE_UTF8:
break;
case PIN_TYPE_HALF_NIBBLE_BCD:
errstr = "PIN type Half-Nibble-BCD is not supported";
break;
case PIN_TYPE_ISO9564_1:
errstr = "PIN type ISO9564-1 is not supported";
break;
default:
errstr = "Unknown PIN type";
break;
}
if (errstr)
{
log_error ("can't verify PIN: %s\n", errstr);
xfree (pinvalue);
return err? err : gpg_error (GPG_ERR_BAD_PIN_METHOD);
}
if (aodf->pintype == PIN_TYPE_BCD )
{
char *paddedpin;
int ndigits;
for (ndigits=0, s=pinvalue; *s; ndigits++, s++)
;
paddedpin = xtrymalloc (aodf->stored_length+1);
if (!paddedpin)
{
err = gpg_error_from_syserror ();
xfree (pinvalue);
return err;
}
i = 0;
paddedpin[i++] = 0x20 | (ndigits & 0x0f);
for (s=pinvalue; i < aodf->stored_length && *s && s[1]; s = s+2 )
paddedpin[i++] = (((*s - '0') << 4) | ((s[1] - '0') & 0x0f));
if (i < aodf->stored_length && *s)
paddedpin[i++] = (((*s - '0') << 4)
|((aodf->pad_char_valid?aodf->pad_char:0)&0x0f));
if (aodf->pinflags.needs_padding)
while (i < aodf->stored_length)
paddedpin[i++] = aodf->pad_char_valid? aodf->pad_char : 0;
xfree (pinvalue);
pinvalue = paddedpin;
pinvaluelen = i;
}
else if (aodf->pinflags.needs_padding)
{
char *paddedpin;
paddedpin = xtrymalloc (aodf->stored_length+1);
if (!paddedpin)
{
err = gpg_error_from_syserror ();
xfree (pinvalue);
return err;
}
for (i=0, s=pinvalue; i < aodf->stored_length && *s; i++, s++)
paddedpin[i] = *s;
/* Not sure what padding char to use if none has been set.
For now we use 0x00; maybe a space would be better. */
for (; i < aodf->stored_length; i++)
paddedpin[i] = aodf->pad_char_valid? aodf->pad_char : 0;
paddedpin[i] = 0;
pinvaluelen = i;
xfree (pinvalue);
pinvalue = paddedpin;
}
else
pinvaluelen = strlen (pinvalue);
- err = iso7816_verify (app->slot,
+ err = iso7816_verify (app_get_slot (app),
aodf->pin_reference_valid? aodf->pin_reference : 0,
pinvalue, pinvaluelen);
xfree (pinvalue);
if (err)
{
log_error ("PIN verification failed: %s\n", gpg_strerror (err));
return err;
}
log_debug ("PIN verification succeeded\n");
}
/* Prepare the DER object from INDATA. */
if (indatalen == 36)
{
/* No ASN.1 container used. */
if (hashalgo != MD_USER_TLS_MD5SHA1)
return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM);
memcpy (data, indata, indatalen);
}
else if (indatalen == 35)
{
/* Alright, the caller was so kind to send us an already
prepared DER object. Check that it is what we want and that
it matches the hash algorithm. */
if (hashalgo == GCRY_MD_SHA1 && !memcmp (indata, sha1_prefix, 15))
;
else if (hashalgo == GCRY_MD_RMD160
&& !memcmp (indata, rmd160_prefix, 15))
;
else
return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM);
memcpy (data, indata, indatalen);
}
else
{
/* Need to prepend the prefix. */
if (hashalgo == GCRY_MD_SHA1)
memcpy (data, sha1_prefix, 15);
else if (hashalgo == GCRY_MD_RMD160)
memcpy (data, rmd160_prefix, 15);
else
return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM);
memcpy (data+15, indata, indatalen);
}
/* Manage security environment needs to be weaked for certain cards. */
if (mse_done)
err = 0;
else if (app->app_local->card_type == CARD_TYPE_TCOS)
{
/* TCOS creates signatures always using the local key 0. MSE
may not be used. */
}
else if (app->app_local->card_type == CARD_TYPE_MICARDO)
{
if (!prkdf->pathlen)
err = gpg_error (GPG_ERR_BUG);
else
err = micardo_mse (app, prkdf->path[prkdf->pathlen-1]);
}
else if (prkdf->key_reference_valid)
{
unsigned char mse[3];
mse[0] = 0x84; /* Select asym. key. */
mse[1] = 1;
mse[2] = prkdf->key_reference;
- err = iso7816_manage_security_env (app->slot,
+ err = iso7816_manage_security_env (app_get_slot (app),
0x41, 0xB6,
mse, sizeof mse);
}
if (err)
{
log_error ("MSE failed: %s\n", gpg_strerror (err));
return err;
}
if (hashalgo == MD_USER_TLS_MD5SHA1)
- err = iso7816_compute_ds (app->slot, 0, data, 36, 0, outdata, outdatalen);
+ err = iso7816_compute_ds (app_get_slot (app),
+ 0, data, 36, 0, outdata, outdatalen);
else if (no_data_padding)
- err = iso7816_compute_ds (app->slot, 0, data+15, 20, 0,outdata,outdatalen);
+ err = iso7816_compute_ds (app_get_slot (app),
+ 0, data+15, 20, 0,outdata,outdatalen);
else
- err = iso7816_compute_ds (app->slot, 0, data, 35, 0, outdata, outdatalen);
+ err = iso7816_compute_ds (app_get_slot (app),
+ 0, data, 35, 0, outdata, outdatalen);
return err;
}
/* Handler for the PKAUTH command.
This is basically the same as the PKSIGN command but we first check
that the requested key is suitable for authentication; that is, it
must match the criteria used for the attribute $AUTHKEYID. See
do_sign for calling conventions; there is no HASHALGO, though. */
static gpg_error_t
do_auth (app_t app, const char *keyidstr,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const void *indata, size_t indatalen,
unsigned char **outdata, size_t *outdatalen )
{
gpg_error_t err;
prkdf_object_t prkdf;
int algo;
if (!keyidstr || !*keyidstr)
return gpg_error (GPG_ERR_INV_VALUE);
err = prkdf_object_from_keyidstr (app, keyidstr, &prkdf);
if (err)
return err;
if (!prkdf->usageflags.sign)
{
log_error ("key %s may not be used for authentication\n", keyidstr);
return gpg_error (GPG_ERR_WRONG_KEY_USAGE);
}
algo = indatalen == 36? MD_USER_TLS_MD5SHA1 : GCRY_MD_SHA1;
return do_sign (app, keyidstr, algo, pincb, pincb_arg,
indata, indatalen, outdata, outdatalen);
}
/* Assume that EF(DIR) has been selected. Read its content and figure
out the home EF of pkcs#15. Return that home DF or 0 if not found
and the value at the address of BELPIC indicates whether it was
found by the belpic aid. */
static unsigned short
read_home_df (int slot, int *r_belpic)
{
gpg_error_t err;
unsigned char *buffer;
const unsigned char *p, *pp;
size_t buflen, n, nn;
unsigned short result = 0;
*r_belpic = 0;
err = iso7816_read_binary (slot, 0, 0, &buffer, &buflen);
if (err)
{
log_error ("error reading EF{DIR}: %s\n", gpg_strerror (err));
return 0;
}
/* FIXME: We need to scan all records. */
p = find_tlv (buffer, buflen, 0x61, &n);
if (p && n)
{
pp = find_tlv (p, n, 0x4f, &nn);
if (pp && ((nn == sizeof pkcs15_aid && !memcmp (pp, pkcs15_aid, nn))
|| (*r_belpic = (nn == sizeof pkcs15be_aid
&& !memcmp (pp, pkcs15be_aid, nn)))))
{
pp = find_tlv (p, n, 0x50, &nn);
if (pp) /* fixme: Filter log value? */
log_info ("pkcs#15 application label from EF(DIR) is '%.*s'\n",
(int)nn, pp);
pp = find_tlv (p, n, 0x51, &nn);
if (pp && nn == 4 && *pp == 0x3f && !pp[1])
{
result = ((pp[2] << 8) | pp[3]);
log_info ("pkcs#15 application directory is 0x%04hX\n", result);
}
}
}
xfree (buffer);
return result;
}
/*
Select the PKCS#15 application on the card in SLOT.
*/
gpg_error_t
app_select_p15 (app_t app)
{
- int slot = app->slot;
+ int slot = app_get_slot (app);
int rc;
unsigned short def_home_df = 0;
card_type_t card_type = CARD_TYPE_UNKNOWN;
int direct = 0;
int is_belpic = 0;
rc = iso7816_select_application (slot, pkcs15_aid, sizeof pkcs15_aid, 0);
if (rc)
{ /* Not found: Try to locate it from 2F00. We use direct path
selection here because it seems that the Belgian eID card
does only allow for that. Many other cards supports this
selection method too. Note, that we don't use
select_application above for the Belgian card - the call
works but it seems that it did not switch to the correct DF.
Using the 2f02 just works. */
unsigned short path[1] = { 0x2f00 };
- rc = iso7816_select_path (app->slot, path, 1);
+ rc = iso7816_select_path (app_get_slot (app), path, 1);
if (!rc)
{
direct = 1;
def_home_df = read_home_df (slot, &is_belpic);
if (def_home_df)
{
path[0] = def_home_df;
- rc = iso7816_select_path (app->slot, path, 1);
+ rc = iso7816_select_path (app_get_slot (app), path, 1);
}
}
}
if (rc)
{ /* Still not found: Try the default DF. */
def_home_df = 0x5015;
rc = iso7816_select_file (slot, def_home_df, 1);
}
if (!rc)
{
/* Determine the type of the card. The general case is to look
it up from the ATR table. For the Belgian eID card we know
it instantly from the AID. */
if (is_belpic)
{
card_type = CARD_TYPE_BELPIC;
}
else
{
unsigned char *atr;
size_t atrlen;
int i;
- atr = apdu_get_atr (app->slot, &atrlen);
+ atr = apdu_get_atr (app_get_slot (app), &atrlen);
if (!atr)
rc = gpg_error (GPG_ERR_INV_CARD);
else
{
for (i=0; card_atr_list[i].atrlen; i++)
if (card_atr_list[i].atrlen == atrlen
&& !memcmp (card_atr_list[i].atr, atr, atrlen))
{
card_type = card_atr_list[i].type;
break;
}
xfree (atr);
}
}
}
if (!rc)
{
- app->apptype = "P15";
+ app->apptype = APPTYPE_P15;
app->app_local = xtrycalloc (1, sizeof *app->app_local);
if (!app->app_local)
{
rc = gpg_error_from_syserror ();
goto leave;
}
/* Set the home DF. Note that we currently can't do that if the
selection via application ID worked. This will store 0 there
instead. FIXME: We either need to figure the home_df via the
DIR file or using the return values from the select file
APDU. */
app->app_local->home_df = def_home_df;
/* Store the card type. FIXME: We might want to put this into
the common APP structure. */
app->app_local->card_type = card_type;
/* Store whether we may and should use direct path selection. */
app->app_local->direct_path_selection = direct;
/* Read basic information and thus check whether this is a real
card. */
rc = read_p15_info (app);
if (rc)
goto leave;
/* Special serial number munging. We need to check for a German
prototype card right here because we need to access to
EF(TokenInfo). We mark such a serial number by the using a
prefix of FF0100. */
- if (app->serialnolen == 12
- && !memcmp (app->serialno, "\xD2\x76\0\0\0\0\0\0\0\0\0\0", 12))
+ if (app->card->serialnolen == 12
+ && !memcmp (app->card->serialno, "\xD2\x76\0\0\0\0\0\0\0\0\0\0", 12))
{
/* This is a German card with a silly serial number. Try to get
the serial number from the EF(TokenInfo). . */
unsigned char *p;
/* FIXME: actually get it from EF(TokenInfo). */
- p = xtrymalloc (3 + app->serialnolen);
+ p = xtrymalloc (3 + app->card->serialnolen);
if (!p)
rc = gpg_error (gpg_err_code_from_errno (errno));
else
{
memcpy (p, "\xff\x01", 3);
- memcpy (p+3, app->serialno, app->serialnolen);
- app->serialnolen += 3;
- xfree (app->serialno);
- app->serialno = p;
+ memcpy (p+3, app->card->serialno, app->card->serialnolen);
+ app->card->serialnolen += 3;
+ xfree (app->card->serialno);
+ app->card->serialno = p;
}
}
app->fnc.deinit = do_deinit;
+ app->fnc.reselect = NULL;
app->fnc.learn_status = do_learn_status;
app->fnc.readcert = do_readcert;
app->fnc.getattr = do_getattr;
app->fnc.setattr = NULL;
app->fnc.genkey = NULL;
app->fnc.sign = do_sign;
app->fnc.auth = do_auth;
app->fnc.decipher = NULL;
app->fnc.change_pin = NULL;
app->fnc.check_pin = NULL;
leave:
if (rc)
do_deinit (app);
}
return rc;
}
diff --git a/scd/app-piv.c b/scd/app-piv.c
index 5748c70b8..3b94a28e4 100644
--- a/scd/app-piv.c
+++ b/scd/app-piv.c
@@ -1,3350 +1,3523 @@
/* app-piv.c - The OpenPGP card application.
* Copyright (C) 2019 g10 Code GmbH
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
/* Some notes:
* - Specs for PIV are at http://dx.doi.org/10.6028/NIST.SP.800-73-4
+ * - https://developers.yubico.com/PIV/Introduction/PIV_attestation.html
*
* - Access control matrix:
* | Action | 9B | PIN | PUK | |
* |--------------+-----+-----+-----+------------------------------|
* | Generate key | yes | | | |
* | Change 9B | yes | | | |
* | Change retry | yes | yes | | Yubikey only |
* | Import key | yes | | | |
* | Import cert | yes | | | |
* | Change CHUID | yes | | | |
* | Reset card | | | | PIN and PUK in blocked state |
* | Verify PIN | | yes | | |
* | Sign data | | yes | | |
* | Decrypt data | | yes | | |
* | Change PIN | | yes | | |
* | Change PUK | | | yes | |
* | Unblock PIN | | | yes | New PIN required |
* |---------------------------------------------------------------|
* (9B indicates the 24 byte PIV Card Application Administration Key)
*
* - When generating a key we store the created public key in the
* corresponding data object, so that gpg and gpgsm are able to get
* the public key, create a certificate and store that then in that
* data object. That is not standard compliant but due to the use
* of other tags, it should not harm. See do_genkey for the actual
* used tag structure.
*/
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <time.h>
#include "scdaemon.h"
#include "../common/util.h"
#include "../common/i18n.h"
#include "iso7816.h"
-#include "app-common.h"
#include "../common/tlv.h"
#include "../common/host2net.h"
#include "apdu.h" /* We use apdu_send_direct. */
#define PIV_ALGORITHM_3DES_ECB_0 0x00
#define PIV_ALGORITHM_2DES_ECB 0x01
#define PIV_ALGORITHM_2DES_CBC 0x02
#define PIV_ALGORITHM_3DES_ECB 0x03
#define PIV_ALGORITHM_3DES_CBC 0x04
#define PIV_ALGORITHM_RSA 0x07
#define PIV_ALGORITHM_AES128_ECB 0x08
#define PIV_ALGORITHM_AES128_CBC 0x09
#define PIV_ALGORITHM_AES192_ECB 0x0A
#define PIV_ALGORITHM_AES192_CBC 0x0B
#define PIV_ALGORITHM_AES256_ECB 0x0C
#define PIV_ALGORITHM_AES256_CBC 0x0D
#define PIV_ALGORITHM_ECC_P256 0x11
#define PIV_ALGORITHM_ECC_P384 0x14
+/* The AID for PIV. */
+static char const piv_aid[] = { 0xA0, 0x00, 0x00, 0x03, 0x08, /* RID=NIST */
+ 0x00, 0x00, 0x10, 0x00 /* PIX=PIV */ };
+
/* A table describing the DOs of a PIV card. */
struct data_object_s
{
unsigned int tag;
unsigned int mandatory:1;
unsigned int acr_contact:2; /* 0=always, 1=VCI, 2=PIN, 3=PINorOCC */
unsigned int acr_contactless:2; /* 0=always, 1=VCI, 2=VCIandPIN,
3=VCIand(PINorOCC) */
unsigned int dont_cache:1; /* Data item will not be cached. */
unsigned int flush_on_error:1; /* Flush cached item on error. */
unsigned int keypair:1; /* Has a public key for a keypair. */
const char keyref[3]; /* The key reference. */
const char *oidsuffix; /* Suffix of the OID. */
const char *usage; /* Usage string for a keypair or NULL. */
const char *desc; /* Description of the DO. */
};
typedef struct data_object_s *data_object_t;
static struct data_object_s data_objects[] = {
{ 0x5FC107, 1, 0,1, 0,0, 0, "", "1.219.0", NULL,
"Card Capability Container"},
{ 0x5FC102, 1, 0,0, 0,0, 0, "", "2.48.0", NULL,
"Cardholder Unique Id" },
{ 0x5FC105, 1, 0,1, 0,0, 1, "9A", "2.1.1", "a",
"Cert PIV Authentication" },
{ 0x5FC103, 1, 2,2, 0,0, 0, "", "2.96.16", NULL,
"Cardholder Fingerprints" },
{ 0x5FC106, 1, 0,1, 0,0, 0, "", "2.144.0", NULL,
"Security Object" },
{ 0x5FC108, 1, 2,2, 0,0, 0, "", "2.96.48", NULL,
"Cardholder Facial Image" },
{ 0x5FC101, 1, 0,0, 0,0, 1, "9E", "2.5.0", "a",
"Cert Card Authentication"},
{ 0x5FC10A, 0, 0,1, 0,0, 1, "9C", "2.1.0", "sc",
"Cert Digital Signature" },
{ 0x5FC10B, 0, 0,1, 0,0, 1, "9D", "2.1.2", "e",
"Cert Key Management" },
{ 0x5FC109, 0, 3,3, 0,0, 0, "", "2.48.1", NULL,
"Printed Information" },
{ 0x7E, 0, 0,0, 0,0, 0, "", "2.96.80", NULL,
"Discovery Object" },
{ 0x5FC10C, 0, 0,1, 0,0, 0, "", "2.96.96", NULL,
"Key History Object" },
{ 0x5FC10D, 0, 0,1, 0,0, 0, "82", "2.16.1", "e",
"Retired Cert Key Mgm 1" },
{ 0x5FC10E, 0, 0,1, 0,0, 0, "83", "2.16.2", "e",
"Retired Cert Key Mgm 2" },
{ 0x5FC10F, 0, 0,1, 0,0, 0, "84", "2.16.3", "e",
"Retired Cert Key Mgm 3" },
{ 0x5FC110, 0, 0,1, 0,0, 0, "85", "2.16.4", "e",
"Retired Cert Key Mgm 4" },
{ 0x5FC111, 0, 0,1, 0,0, 0, "86", "2.16.5", "e",
"Retired Cert Key Mgm 5" },
{ 0x5FC112, 0, 0,1, 0,0, 0, "87", "2.16.6", "e",
"Retired Cert Key Mgm 6" },
{ 0x5FC113, 0, 0,1, 0,0, 0, "88", "2.16.7", "e",
"Retired Cert Key Mgm 7" },
{ 0x5FC114, 0, 0,1, 0,0, 0, "89", "2.16.8", "e",
"Retired Cert Key Mgm 8" },
{ 0x5FC115, 0, 0,1, 0,0, 0, "8A", "2.16.9", "e",
"Retired Cert Key Mgm 9" },
{ 0x5FC116, 0, 0,1, 0,0, 0, "8B", "2.16.10", "e",
"Retired Cert Key Mgm 10" },
{ 0x5FC117, 0, 0,1, 0,0, 0, "8C", "2.16.11", "e",
"Retired Cert Key Mgm 11" },
{ 0x5FC118, 0, 0,1, 0,0, 0, "8D", "2.16.12", "e",
"Retired Cert Key Mgm 12" },
{ 0x5FC119, 0, 0,1, 0,0, 0, "8E", "2.16.13", "e",
"Retired Cert Key Mgm 13" },
{ 0x5FC11A, 0, 0,1, 0,0, 0, "8F", "2.16.14", "e",
"Retired Cert Key Mgm 14" },
{ 0x5FC11B, 0, 0,1, 0,0, 0, "90", "2.16.15", "e",
"Retired Cert Key Mgm 15" },
{ 0x5FC11C, 0, 0,1, 0,0, 0, "91", "2.16.16", "e",
"Retired Cert Key Mgm 16" },
{ 0x5FC11D, 0, 0,1, 0,0, 0, "92", "2.16.17", "e",
"Retired Cert Key Mgm 17" },
{ 0x5FC11E, 0, 0,1, 0,0, 0, "93", "2.16.18", "e",
"Retired Cert Key Mgm 18" },
{ 0x5FC11F, 0, 0,1, 0,0, 0, "94", "2.16.19", "e",
"Retired Cert Key Mgm 19" },
{ 0x5FC120, 0, 0,1, 0,0, 0, "95", "2.16.20", "e",
"Retired Cert Key Mgm 20" },
{ 0x5FC121, 0, 2,2, 0,0, 0, "", "2.16.21", NULL,
"Cardholder Iris Images" },
{ 0x7F61, 0, 0,0, 0,0, 0, "", "2.16.22", NULL,
"BIT Group Template" },
{ 0x5FC122, 0, 0,0, 0,0, 0, "", "2.16.23", NULL,
"SM Cert Signer" },
{ 0x5FC123, 0, 3,3, 0,0, 0, "", "2.16.24", NULL,
"Pairing Code Ref Data" },
{ 0 }
/* Other key reference values without a data object:
* "00" Global PIN (not cleared by application switching)
* "04" PIV Secure Messaging Key
* "80" PIV Application PIN
* "81" PIN Unblocking Key
* "96" Primary Finger OCC
* "97" Secondary Finger OCC
* "98" Pairing Code
* "9B" PIV Card Application Administration Key
*
* Yubikey specific data objects:
* "F9" Attestation key (preloaded can be replaced)
*/
};
/* One cache item for DOs. */
struct cache_s {
struct cache_s *next;
int tag;
size_t length;
unsigned char data[1];
};
/* Object with application specific data. */
struct app_local_s {
/* A linked list with cached DOs. */
struct cache_s *cache;
/* Various flags. */
struct
{
unsigned int yubikey:1; /* This is on a Yubikey. */
} flags;
};
/***** Local prototypes *****/
static gpg_error_t get_keygrip_by_tag (app_t app, unsigned int tag,
char **r_keygripstr, int *got_cert);
static gpg_error_t genkey_parse_rsa (const unsigned char *data, size_t datalen,
gcry_sexp_t *r_sexp);
static gpg_error_t genkey_parse_ecc (const unsigned char *data, size_t datalen,
int mechanism, gcry_sexp_t *r_sexp);
/* Deconstructor. */
static void
do_deinit (app_t app)
{
if (app && app->app_local)
{
struct cache_s *c, *c2;
for (c = app->app_local->cache; c; c = c2)
{
c2 = c->next;
xfree (c);
}
xfree (app->app_local);
app->app_local = NULL;
}
}
/* Wrapper around iso7816_get_data which first tries to get the data
* from the cache. With GET_IMMEDIATE passed as true, the cache is
* bypassed. The tag-53 container is also removed. */
static gpg_error_t
get_cached_data (app_t app, int tag,
unsigned char **result, size_t *resultlen,
int get_immediate)
{
gpg_error_t err;
int i;
unsigned char *p;
const unsigned char *s;
size_t len, n;
struct cache_s *c;
*result = NULL;
*resultlen = 0;
if (!get_immediate)
{
for (c=app->app_local->cache; c; c = c->next)
if (c->tag == tag)
{
if(c->length)
{
p = xtrymalloc (c->length);
if (!p)
return gpg_error_from_syserror ();
memcpy (p, c->data, c->length);
*result = p;
}
*resultlen = c->length;
return 0;
}
}
- err = iso7816_get_data_odd (app->slot, 0, tag, &p, &len);
+ err = iso7816_get_data_odd (app_get_slot (app), 0, tag, &p, &len);
if (err)
return err;
/* Unless the Discovery Object or the BIT Group Template is
* requested, remove the outer container.
* (SP800-73.4 Part 2, section 3.1.2) */
if (tag == 0x7E || tag == 0x7F61)
;
else if (len && *p == 0x53 && (s = find_tlv (p, len, 0x53, &n)))
{
memmove (p, s, n);
len = n;
}
if (len)
*result = p;
*resultlen = len;
/* Check whether we should cache this object. */
if (get_immediate)
return 0;
for (i=0; data_objects[i].tag; i++)
if (data_objects[i].tag == tag)
{
if (data_objects[i].dont_cache)
return 0;
break;
}
/* Okay, cache it. */
for (c=app->app_local->cache; c; c = c->next)
log_assert (c->tag != tag);
c = xtrymalloc (sizeof *c + len);
if (c)
{
if (len)
memcpy (c->data, p, len);
else
xfree (p);
c->length = len;
c->tag = tag;
c->next = app->app_local->cache;
app->app_local->cache = c;
}
return 0;
}
/* Remove data object described by TAG from the cache. If TAG is 0
* all cache iterms are flushed. */
static void
flush_cached_data (app_t app, int tag)
{
struct cache_s *c, *cprev;
for (c=app->app_local->cache, cprev=NULL; c; cprev=c, c = c->next)
if (c->tag == tag || !tag)
{
if (cprev)
cprev->next = c->next;
else
app->app_local->cache = c->next;
xfree (c);
for (c=app->app_local->cache; c ; c = c->next)
{
log_assert (c->tag != tag); /* Oops: duplicated entry. */
}
return;
}
}
/* Get the DO identified by TAG from the card in SLOT and return a
* buffer with its content in RESULT and NBYTES. The return value is
* NULL if not found or a pointer which must be used to release the
* buffer holding value. */
static void *
get_one_do (app_t app, int tag, unsigned char **result, size_t *nbytes,
int *r_err)
{
gpg_error_t err;
int i;
unsigned char *buffer;
size_t buflen;
unsigned char *value;
size_t valuelen;
gpg_error_t dummyerr;
if (!r_err)
r_err = &dummyerr;
*result = NULL;
*nbytes = 0;
*r_err = 0;
for (i=0; data_objects[i].tag && data_objects[i].tag != tag; i++)
;
value = NULL;
err = gpg_error (GPG_ERR_ENOENT);
if (!value) /* Not in a constructed DO, try simple. */
{
err = get_cached_data (app, tag, &buffer, &buflen,
data_objects[i].dont_cache);
if (!err)
{
value = buffer;
valuelen = buflen;
}
}
if (!err)
{
*nbytes = valuelen;
*result = value;
return buffer;
}
*r_err = err;
return NULL;
}
static void
dump_all_do (int slot)
{
gpg_error_t err;
int i;
unsigned char *buffer;
size_t buflen;
for (i=0; data_objects[i].tag; i++)
{
/* We don't try extended length APDU because such large DO would
be pretty useless in a log file. */
err = iso7816_get_data_odd (slot, 0, data_objects[i].tag,
&buffer, &buflen);
if (err)
{
if (gpg_err_code (err) == GPG_ERR_ENOENT
&& !data_objects[i].mandatory)
;
else
log_info ("DO '%s' not available: %s\n",
data_objects[i].desc, gpg_strerror (err));
}
else
{
if (data_objects[i].tag == 0x5FC109)
log_info ("DO '%s': '%.*s'\n", data_objects[i].desc,
(int)buflen, buffer);
else
{
log_info ("DO '%s': ", data_objects[i].desc);
if (buflen > 16 && opt.verbose < 2)
{
log_printhex (buffer, 16, NULL);
log_printf ("[...]\n");
}
else
log_printhex (buffer, buflen, "");
}
}
xfree (buffer); buffer = NULL;
}
}
/* Create a TLV tag and value and store it at BUFFER. Return the
* length of tag and length. A LENGTH greater than 65535 is
* truncated. TAG must be less or equal to 2^16. If BUFFER is NULL,
* only the required length is computed. */
static size_t
add_tlv (unsigned char *buffer, unsigned int tag, size_t length)
{
if (length > 0xffff)
length = 0xffff;
if (buffer)
{
unsigned char *p = buffer;
if (tag > 0xff)
*p++ = tag >> 8;
*p++ = tag;
if (length < 128)
*p++ = length;
else if (length < 256)
{
*p++ = 0x81;
*p++ = length;
}
else
{
*p++ = 0x82;
*p++ = length >> 8;
*p++ = length;
}
return p - buffer;
}
else
{
size_t n = 0;
if (tag > 0xff)
n++;
n++;
if (length < 128)
n++;
else if (length < 256)
n += 2;
else
n += 3;
return n;
}
}
/* Function to build a list of TLV and return the result in a mallcoed
* buffer. The varargs are tuples of (int,size_t,void) each with the
* tag, the length and the actual data. A (0,0,NULL) tuple terminates
* the list. Up to 10 tuples are supported. If SECMEM is true the
* returned buffer is allocated in secure memory. */
static gpg_error_t
concat_tlv_list (int secure, unsigned char **r_result, size_t *r_resultlen, ...)
{
gpg_error_t err;
va_list arg_ptr;
struct {
int tag;
unsigned int len;
unsigned int contlen;
const void *data;
} argv[10];
int i, j, argc;
unsigned char *data = NULL;
size_t datalen;
unsigned char *p;
size_t n;
*r_result = NULL;
*r_resultlen = 0;
/* Collect all args. Check that length is <= 2^16 to match the
* behaviour of add_tlv. */
va_start (arg_ptr, r_resultlen);
argc = 0;
while (((argv[argc].tag = va_arg (arg_ptr, int))))
{
argv[argc].len = va_arg (arg_ptr, size_t);
argv[argc].contlen = 0;
argv[argc].data = va_arg (arg_ptr, const void *);
if (argc >= DIM (argv)-1 || argv[argc].len > 0xffff)
{
va_end (arg_ptr);
err = gpg_error (GPG_ERR_EINVAL);
goto leave;
}
argc++;
}
va_end (arg_ptr);
/* Compute the required buffer length and allocate the buffer. */
datalen = 0;
for (i=0; i < argc; i++)
{
if (!argv[i].len && !argv[i].data)
{
/* Constructed tag. Compute its length. Note that we
* currently allow only one constructed tag in the list. */
for (n=0, j = i + 1; j < argc; j++)
{
log_assert (!(!argv[j].len && !argv[j].data));
n += add_tlv (NULL, argv[j].tag, argv[j].len);
n += argv[j].len;
}
argv[i].contlen = n;
datalen += add_tlv (NULL, argv[i].tag, n);
}
else
{
datalen += add_tlv (NULL, argv[i].tag, argv[i].len);
datalen += argv[i].len;
}
}
data = secure? xtrymalloc_secure (datalen) : xtrymalloc (datalen);
if (!data)
{
err = gpg_error_from_syserror ();
goto leave;
}
/* Copy that data to the buffer. */
p = data;
for (i=0; i < argc; i++)
{
if (!argv[i].len && !argv[i].data)
{
/* Constructed tag. */
p += add_tlv (p, argv[i].tag, argv[i].contlen);
}
else
{
p += add_tlv (p, argv[i].tag, argv[i].len);
memcpy (p, argv[i].data, argv[i].len);
p += argv[i].len;
}
}
log_assert ( data + datalen == p );
*r_result = data;
data = NULL;
*r_resultlen = datalen;
err = 0;
leave:
xfree (data);
return err;
}
/* Wrapper around iso7816_put_data_odd which also sets the tag into
* the '5C' data object. The varargs are tuples of (int,size_t,void)
* with the tag, the length and the actual data. A (0,0,NULL) tuple
* terminates the list. Up to 10 tuples are supported. */
static gpg_error_t
put_data (int slot, unsigned int tag, ...)
{
gpg_error_t err;
va_list arg_ptr;
struct {
int tag;
size_t len;
const void *data;
} argv[10];
int i, argc;
unsigned char data5c[5];
size_t data5clen;
unsigned char *data = NULL;
size_t datalen;
unsigned char *p;
size_t n;
/* Collect all args. Check that length is <= 2^16 to match the
* behaviour of add_tlv. */
va_start (arg_ptr, tag);
argc = 0;
while (((argv[argc].tag = va_arg (arg_ptr, int))))
{
argv[argc].len = va_arg (arg_ptr, size_t);
argv[argc].data = va_arg (arg_ptr, const void *);
if (argc >= DIM (argv)-1 || argv[argc].len > 0xffff)
{
va_end (arg_ptr);
return GPG_ERR_EINVAL;
}
argc++;
}
va_end (arg_ptr);
/* Build the TLV with the tag to be updated. */
data5c[0] = 0x5c; /* Tag list */
if (tag <= 0xff)
{
data5c[1] = 1;
data5c[2] = tag;
data5clen = 3;
}
else if (tag <= 0xffff)
{
data5c[1] = 2;
data5c[2] = (tag >> 8);
data5c[3] = tag;
data5clen = 4;
}
else
{
data5c[1] = 3;
data5c[2] = (tag >> 16);
data5c[3] = (tag >> 8);
data5c[4] = tag;
data5clen = 5;
}
/* Compute the required buffer length and allocate the buffer. */
n = 0;
for (i=0; i < argc; i++)
{
n += add_tlv (NULL, argv[i].tag, argv[i].len);
n += argv[i].len;
}
datalen = data5clen + add_tlv (NULL, 0x53, n) + n;
data = xtrymalloc (datalen);
if (!data)
{
err = gpg_error_from_syserror ();
goto leave;
}
/* Copy that data to the buffer. */
p = data;
memcpy (p, data5c, data5clen);
p += data5clen;
p += add_tlv (p, 0x53, n);
for (i=0; i < argc; i++)
{
p += add_tlv (p, argv[i].tag, argv[i].len);
memcpy (p, argv[i].data, argv[i].len);
p += argv[i].len;
}
log_assert ( data + datalen == p );
err = iso7816_put_data_odd (slot, -1 /* use command chaining */,
0x3fff, data, datalen);
leave:
xfree (data);
return err;
}
/* Parse the key reference KEYREFSTR which is expected to hold a key
* reference for a CHV object. Return the one octet keyref or -1 for
* an invalid reference. */
static int
parse_chv_keyref (const char *keyrefstr)
{
if (!keyrefstr)
return -1;
else if (!ascii_strcasecmp (keyrefstr, "PIV.00"))
return 0x00;
else if (!ascii_strcasecmp (keyrefstr, "PIV.80"))
return 0x80;
else if (!ascii_strcasecmp (keyrefstr, "PIV.81"))
return 0x81;
else
return -1;
}
/* Return an allocated string with the serial number in a format to be
* show to the user. With FAILMODE is true return NULL if such an
* abbreviated S/N is not available, else return the full serial
* number as a hex string. May return NULL on malloc problem. */
static char *
get_dispserialno (app_t app, int failmode)
{
char *result;
- if (app->serialno && app->serialnolen == 3+1+4
- && !memcmp (app->serialno, "\xff\x02\x00", 3))
+ if (app->card && app->card->serialno && app->card->serialnolen == 3+1+4
+ && !memcmp (app->card->serialno, "\xff\x02\x00", 3))
{
/* This is a 4 byte S/N of a Yubikey which seems to be printed
* on the token in decimal. Maybe they will print larger S/N
* also in decimal but we can't be sure, thus do it only for
* these 32 bit numbers. */
unsigned long sn;
- sn = app->serialno[4] * 16777216;
- sn += app->serialno[5] * 65536;
- sn += app->serialno[6] * 256;
- sn += app->serialno[7];
+ sn = app->card->serialno[4] * 16777216;
+ sn += app->card->serialno[5] * 65536;
+ sn += app->card->serialno[6] * 256;
+ sn += app->card->serialno[7];
result = xtryasprintf ("yk-%lu", sn);
}
else if (failmode)
result = NULL; /* No Abbreviated S/N. */
else
result = app_get_serialno (app);
return result;
}
/* The verify command can be used to retrieve the security status of
* the card. Given the PIN name (e.g. "PIV.80" for thge application
* pin, a status is returned:
*
* -1 = Error retrieving the data,
* -2 = No such PIN,
* -3 = PIN blocked,
* -5 = Verify still valid,
* n >= 0 = Number of verification attempts left.
*/
static int
get_chv_status (app_t app, const char *keyrefstr)
{
unsigned char apdu[4];
unsigned int sw;
int result;
int keyref;
keyref = parse_chv_keyref (keyrefstr);
if (!keyrefstr)
return -1;
apdu[0] = 0x00;
apdu[1] = ISO7816_VERIFY;
apdu[2] = 0x00;
apdu[3] = keyref;
- if (!iso7816_apdu_direct (app->slot, apdu, 4, 0, &sw, NULL, NULL))
+ if (!iso7816_apdu_direct (app_get_slot (app), apdu, 4, 0, &sw, NULL, NULL))
result = -5; /* No need to verification. */
else if (sw == 0x6a88 || sw == 0x6a80)
result = -2; /* No such PIN. */
else if (sw == 0x6983)
result = -3; /* PIN is blocked. */
else if ((sw & 0xfff0) == 0x63C0)
result = (sw & 0x000f);
else
result = -1; /* Error. */
return result;
}
/* Implementation of the GETATTR command. This is similar to the
* LEARN command but returns only one value via status lines. */
static gpg_error_t
do_getattr (app_t app, ctrl_t ctrl, const char *name)
{
static struct {
const char *name;
int tag;
int special;
} table[] = {
{ "SERIALNO", 0x0000, -1 },
- { "$AUTHKEYID", 0x0000, -2 }, /* Default key for ssh. */
+ { "$AUTHKEYID", 0x0000, -2 }, /* Default ssh key. */
+ { "$ENCRKEYID", 0x0000, -6 }, /* Default encryption key. */
+ { "$SIGNKEYID", 0x0000, -7 }, /* Default signing key. */
{ "$DISPSERIALNO",0x0000, -3 },
{ "CHV-STATUS", 0x0000, -4 },
{ "CHV-USAGE", 0x007E, -5 }
};
gpg_error_t err = 0;
int idx;
void *relptr;
unsigned char *value;
size_t valuelen;
const unsigned char *s;
size_t n;
for (idx=0; (idx < DIM (table)
&& ascii_strcasecmp (table[idx].name, name)); idx++)
;
if (!(idx < DIM (table)))
err = gpg_error (GPG_ERR_INV_NAME);
else if (table[idx].special == -1)
{
char *serial = app_get_serialno (app);
if (serial)
{
send_status_direct (ctrl, "SERIALNO", serial);
xfree (serial);
}
}
else if (table[idx].special == -2)
{
char const tmp[] = "PIV.9A"; /* Cert PIV Authenticate. */
send_status_info (ctrl, table[idx].name, tmp, strlen (tmp), NULL, 0);
}
else if (table[idx].special == -3)
{
char *tmp = get_dispserialno (app, 1);
if (tmp)
{
send_status_info (ctrl, table[idx].name,
tmp, strlen (tmp),
NULL, (size_t)0);
xfree (tmp);
}
else
err = gpg_error (GPG_ERR_INV_NAME); /* No Abbreviated S/N. */
}
else if (table[idx].special == -4) /* CHV-STATUS */
{
int tmp[4];
tmp[0] = get_chv_status (app, "PIV.00");
tmp[1] = get_chv_status (app, "PIV.80");
tmp[2] = get_chv_status (app, "PIV.81");
err = send_status_printf (ctrl, table[idx].name, "%d %d %d",
tmp[0], tmp[1], tmp[2]);
}
else if (table[idx].special == -5) /* CHV-USAGE (aka PIN Usage Policy) */
{
/* We return 2 hex bytes or nothing in case the discovery object
* is not supported. */
relptr = get_one_do (app, table[idx].tag, &value, &valuelen, &err);
if (relptr)
{
s = find_tlv (value, valuelen, 0x7E, &n);
if (s && n && (s = find_tlv (s, n, 0x5F2F, &n)) && n >=2 )
err = send_status_printf (ctrl, table[idx].name, "%02X %02X",
s[0], s[1]);
xfree (relptr);
}
}
+ else if (table[idx].special == -6)
+ {
+ char const tmp[] = "PIV.9D"; /* Key Management. */
+ send_status_info (ctrl, table[idx].name, tmp, strlen (tmp), NULL, 0);
+ }
+ else if (table[idx].special == -7)
+ {
+ char const tmp[] = "PIV.9C"; /* Digital Signature. */
+ send_status_info (ctrl, table[idx].name, tmp, strlen (tmp), NULL, 0);
+ }
else
{
relptr = get_one_do (app, table[idx].tag, &value, &valuelen, &err);
if (relptr)
{
send_status_info (ctrl, table[idx].name, value, valuelen, NULL, 0);
xfree (relptr);
}
}
return err;
}
/* Authenticate the card using the Card Application Administration
* Key. (VALUE,VALUELEN) has that 24 byte key. */
static gpg_error_t
auth_adm_key (app_t app, const unsigned char *value, size_t valuelen)
{
gpg_error_t err;
unsigned char tmpl[4+24];
size_t tmpllen;
unsigned char *outdata = NULL;
size_t outdatalen;
const unsigned char *s;
char witness[8];
size_t n;
gcry_cipher_hd_t cipher = NULL;
/* Prepare decryption. */
err = gcry_cipher_open (&cipher, GCRY_CIPHER_3DES, GCRY_CIPHER_MODE_ECB, 0);
if (err)
goto leave;
err = gcry_cipher_setkey (cipher, value, valuelen);
if (err)
goto leave;
/* Request a witness. */
tmpl[0] = 0x7c;
tmpl[1] = 0x02;
tmpl[2] = 0x80;
tmpl[3] = 0; /* (Empty witness requests a witness.) */
tmpllen = 4;
- err = iso7816_general_authenticate (app->slot, 0,
+ err = iso7816_general_authenticate (app_get_slot (app), 0,
PIV_ALGORITHM_3DES_ECB_0, 0x9B,
tmpl, tmpllen, 0,
&outdata, &outdatalen);
if (gpg_err_code (err) == GPG_ERR_BAD_PIN)
err = gpg_error (GPG_ERR_BAD_AUTH);
if (err)
goto leave;
if (!(outdatalen && *outdata == 0x7c
&& (s = find_tlv (outdata, outdatalen, 0x80, &n))
&& n == 8))
{
err = gpg_error (GPG_ERR_CARD);
log_error ("piv: improper witness received\n");
goto leave;
}
err = gcry_cipher_decrypt (cipher, witness, 8, s, 8);
if (err)
goto leave;
/* Return decrypted witness and send our challenge. */
tmpl[0] = 0x7c;
tmpl[1] = 22;
tmpl[2] = 0x80;
tmpl[3] = 8;
memcpy (tmpl+4, witness, 8);
tmpl[12] = 0x81;
tmpl[13] = 8;
gcry_create_nonce (tmpl+14, 8);
tmpl[22] = 0x80;
tmpl[23] = 0;
tmpllen = 24;
xfree (outdata);
- err = iso7816_general_authenticate (app->slot, 0,
+ err = iso7816_general_authenticate (app_get_slot (app), 0,
PIV_ALGORITHM_3DES_ECB_0, 0x9B,
tmpl, tmpllen, 0,
&outdata, &outdatalen);
if (gpg_err_code (err) == GPG_ERR_BAD_PIN)
err = gpg_error (GPG_ERR_BAD_AUTH);
if (err)
goto leave;
if (!(outdatalen && *outdata == 0x7c
&& (s = find_tlv (outdata, outdatalen, 0x82, &n))
&& n == 8))
{
err = gpg_error (GPG_ERR_CARD);
log_error ("piv: improper challenge received\n");
goto leave;
}
/* (We reuse the witness buffer.) */
err = gcry_cipher_decrypt (cipher, witness, 8, s, 8);
if (err)
goto leave;
if (memcmp (witness, tmpl+14, 8))
{
err = gpg_error (GPG_ERR_BAD_AUTH);
goto leave;
}
leave:
xfree (outdata);
gcry_cipher_close (cipher);
return err;
}
/* Set a new admin key. */
static gpg_error_t
set_adm_key (app_t app, const unsigned char *value, size_t valuelen)
{
gpg_error_t err;
unsigned char apdu[8+24];
unsigned int sw;
/* Check whether it is a weak key and that it is of proper length. */
{
gcry_cipher_hd_t cipher;
err = gcry_cipher_open (&cipher, GCRY_CIPHER_3DES, GCRY_CIPHER_MODE_ECB, 0);
if (!err)
{
err = gcry_cipher_setkey (cipher, value, valuelen);
gcry_cipher_close (cipher);
}
if (err)
goto leave;
}
if (app->app_local->flags.yubikey)
{
/* This is a Yubikey. */
if (valuelen != 24)
{
err = gpg_error (GPG_ERR_INV_LENGTH);
goto leave;
}
/* We use a proprietary Yubikey command. */
apdu[0] = 0;
apdu[1] = 0xff;
apdu[2] = 0xff;
apdu[3] = 0xff; /* touch policy: 0xff=never, 0xfe = always. */
apdu[4] = 3 + 24;
apdu[5] = PIV_ALGORITHM_3DES_ECB;
apdu[6] = 0x9b;
apdu[7] = 24;
memcpy (apdu+8, value, 24);
- err = iso7816_apdu_direct (app->slot, apdu, 8+24, 0, &sw, NULL, NULL);
+ err = iso7816_apdu_direct (app_get_slot (app), apdu, 8+24, 0,
+ &sw, NULL, NULL);
wipememory (apdu+8, 24);
if (err)
log_error ("piv: setting admin key failed; sw=%04x\n", sw);
/* A PIN is not required, thus use a better error code. */
if (gpg_err_code (err) == GPG_ERR_BAD_PIN)
err = gpg_error (GPG_ERR_NO_AUTH);
}
else
err = gpg_error (GPG_ERR_NOT_SUPPORTED);
leave:
return err;
}
/* Handle the SETATTR operation. All arguments are already basically
* checked. */
static gpg_error_t
do_setattr (app_t app, const char *name,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const unsigned char *value, size_t valuelen)
{
gpg_error_t err;
static struct {
const char *name;
unsigned short tag;
unsigned short flush_tag; /* The tag which needs to be flushed or 0. */
int special; /* Special mode to use for thus NAME. */
} table[] = {
/* Authenticate using the PIV Card Application Administration Key
* (0x0B). Note that Yubico calls this key the "management key"
* which we don't do because that term is too similar to "Cert
* Management Key" (0x9D). */
{ "AUTH-ADM-KEY", 0x0000, 0x0000, 1 },
{ "SET-ADM-KEY", 0x0000, 0x0000, 2 }
};
int idx;
(void)pincb;
(void)pincb_arg;
for (idx=0; (idx < DIM (table)
&& ascii_strcasecmp (table[idx].name, name)); idx++)
;
if (!(idx < DIM (table)))
return gpg_error (GPG_ERR_INV_NAME);
/* Flush the cache before writing it, so that the next get operation
* will reread the data from the card and thus get synced in case of
* errors (e.g. data truncated by the card). */
if (table[idx].tag)
flush_cached_data (app, table[idx].flush_tag? table[idx].flush_tag
/* */ : table[idx].tag);
switch (table[idx].special)
{
case 1:
err = auth_adm_key (app, value, valuelen);
break;
case 2:
err = set_adm_key (app, value, valuelen);
break;
default:
err = gpg_error (GPG_ERR_BUG);
break;
}
return err;
}
/* Send the KEYPAIRINFO back. DOBJ describes the data object carrying
* the key. This is used by the LEARN command. */
static gpg_error_t
send_keypair_and_cert_info (app_t app, ctrl_t ctrl, data_object_t dobj,
int only_keypair)
{
gpg_error_t err = 0;
char *keygripstr = NULL;
int got_cert;
char idbuf[50];
const char *usage;
err = get_keygrip_by_tag (app, dobj->tag, &keygripstr, &got_cert);
if (err)
goto leave;
usage = dobj->usage? dobj->usage : "";
snprintf (idbuf, sizeof idbuf, "PIV.%s", dobj->keyref);
send_status_info (ctrl, "KEYPAIRINFO",
keygripstr, strlen (keygripstr),
idbuf, strlen (idbuf),
usage, strlen (usage),
NULL, (size_t)0);
if (!only_keypair && got_cert)
{
/* All certificates are of type 100 (Regular X.509 Cert). */
send_status_info (ctrl, "CERTINFO",
"100", 3,
idbuf, strlen (idbuf),
NULL, (size_t)0);
}
leave:
xfree (keygripstr);
return err;
}
/* Handle the LEARN command. */
static gpg_error_t
do_learn_status (app_t app, ctrl_t ctrl, unsigned int flags)
{
int i;
(void)flags;
do_getattr (app, ctrl, "CHV-USAGE");
do_getattr (app, ctrl, "CHV-STATUS");
for (i=0; data_objects[i].tag; i++)
if (data_objects[i].keypair)
send_keypair_and_cert_info (app, ctrl, data_objects + i, !!(flags & 1));
return 0;
}
/* Core of do_readcert which fetches the certificate based on the
* given tag and returns it in a freshly allocated buffer stored at
* R_CERT and the length of the certificate stored at R_CERTLEN. If
* on success a non-zero value is stored at R_MECHANISM, the returned
* data is not a certificate but a public key (in the format used by the
* container '7f49'. */
static gpg_error_t
readcert_by_tag (app_t app, unsigned int tag,
unsigned char **r_cert, size_t *r_certlen, int *r_mechanism)
{
gpg_error_t err;
unsigned char *buffer;
size_t buflen;
void *relptr;
const unsigned char *s, *s2;
size_t n, n2;
*r_cert = NULL;
*r_certlen = 0;
*r_mechanism = 0;
relptr = get_one_do (app, tag, &buffer, &buflen, NULL);
if (!relptr || !buflen)
{
err = gpg_error (GPG_ERR_NOT_FOUND);
goto leave;
}
s = find_tlv (buffer, buflen, 0x71, &n);
if (!s)
{
/* No certificate; check whether a public key has been stored
* using our own scheme. */
s = find_tlv (buffer, buflen, 0x7f49, &n);
if (!s || !n)
{
log_error ("piv: No public key in 0x%X\n", tag);
err = gpg_error (GPG_ERR_NO_PUBKEY);
goto leave;
}
s2 = find_tlv (buffer, buflen, 0x80, &n2);
if (!s2 || n2 != 1 || !*s2)
{
log_error ("piv: No mechanism for public key in 0x%X\n", tag);
err = gpg_error (GPG_ERR_NO_PUBKEY);
goto leave;
}
*r_mechanism = *s2;
}
else
{
if (n != 1)
{
log_error ("piv: invalid CertInfo in 0x%X\n", tag);
err = gpg_error (GPG_ERR_INV_CERT_OBJ);
goto leave;
}
if (*s == 0x01)
{
log_error ("piv: gzip compression not yet supported (tag 0x%X)\n",
tag);
err = gpg_error (GPG_ERR_UNSUPPORTED_ENCODING);
goto leave;
}
if (*s)
{
log_error ("piv: invalid CertInfo 0x%02x in 0x%X\n", *s, tag);
err = gpg_error (GPG_ERR_INV_CERT_OBJ);
goto leave;
}
/* Note: We don't check that the LRC octet has a length of zero
* as required by the specs. */
/* Get the cert from the container. */
s = find_tlv (buffer, buflen, 0x70, &n);
if (!s || !n)
{
err = gpg_error (GPG_ERR_NOT_FOUND);
goto leave;
}
}
/* The next is common for certificate and public key. */
if (!(*r_cert = xtrymalloc (n)))
{
err = gpg_error_from_syserror ();
goto leave;
}
memcpy (*r_cert, s, n);
*r_certlen = n;
err = 0;
leave:
xfree (relptr);
return err;
}
/* Get the keygrip in hex format of a key from the certificate stored
* at TAG. Caller must free the string at R_KEYGRIPSTR. */
static gpg_error_t
get_keygrip_by_tag (app_t app, unsigned int tag,
char **r_keygripstr, int *r_got_cert)
{
gpg_error_t err;
unsigned char *certbuf = NULL;
size_t certbuflen;
int mechanism;
gcry_sexp_t s_pkey = NULL;
ksba_cert_t cert = NULL;
unsigned char grip[KEYGRIP_LEN];
*r_got_cert = 0;
*r_keygripstr = xtrymalloc (2*KEYGRIP_LEN+1);
if (!r_keygripstr)
{
err = gpg_error_from_syserror ();
goto leave;
}
/* We need to get the public key from the certificate. */
err = readcert_by_tag (app, tag, &certbuf, &certbuflen, &mechanism);
if (err)
goto leave;
if (mechanism) /* Compute keygrip from public key. */
{
if (mechanism == PIV_ALGORITHM_RSA)
err = genkey_parse_rsa (certbuf, certbuflen, &s_pkey);
else if (mechanism == PIV_ALGORITHM_ECC_P256
|| mechanism == PIV_ALGORITHM_ECC_P384)
err = genkey_parse_ecc (certbuf, certbuflen, mechanism, &s_pkey);
else
err = gpg_error (GPG_ERR_PUBKEY_ALGO);
if (err)
goto leave;
if (!gcry_pk_get_keygrip (s_pkey, grip))
{
log_error ("piv: error computing keygrip\n");
err = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
bin2hex (grip, sizeof grip, *r_keygripstr);
}
else /* Compute keygrip from certificate. */
{
*r_got_cert = 0;
err = ksba_cert_new (&cert);
if (err)
goto leave;
err = ksba_cert_init_from_mem (cert, certbuf, certbuflen);
if (err)
goto leave;
err = app_help_get_keygrip_string (cert, *r_keygripstr);
}
leave:
gcry_sexp_release (s_pkey);
ksba_cert_release (cert);
xfree (certbuf);
if (err)
{
xfree (*r_keygripstr);
*r_keygripstr = NULL;
}
return err;
}
/* Locate the data object from the given KEYREF. The KEYREF may also
* be the corresponding OID of the key object. Returns the data
* object or NULL if not found. */
static data_object_t
find_dobj_by_keyref (app_t app, const char *keyref)
{
int i;
(void)app;
if (!ascii_strncasecmp (keyref, "PIV.", 4))
{
keyref += 4;
for (i=0; data_objects[i].tag; i++)
if (*data_objects[i].keyref
&& !ascii_strcasecmp (keyref, data_objects[i].keyref))
{
return data_objects + i;
}
}
else if (!strncmp (keyref, "2.16.840.1.101.3.7.", 19))
{
keyref += 19;
for (i=0; data_objects[i].tag; i++)
if (*data_objects[i].keyref
&& !strcmp (keyref, data_objects[i].oidsuffix))
{
return data_objects + i;
}
}
return NULL;
}
/* Return the keyref from DOBJ as an integer. If it does not exist,
* return -1. */
static int
keyref_from_dobj (data_object_t dobj)
{
if (!dobj || !hexdigitp (dobj->keyref) || !hexdigitp (dobj->keyref+1))
return -1;
return xtoi_2 (dobj->keyref);
}
/* Read a certificate from the card and returned in a freshly
* allocated buffer stored at R_CERT and the length of the certificate
* stored at R_CERTLEN. CERTID is either the OID of the cert's
* container or of the form "PIV.<two_hexdigit_keyref>" */
static gpg_error_t
do_readcert (app_t app, const char *certid,
unsigned char **r_cert, size_t *r_certlen)
{
gpg_error_t err;
data_object_t dobj;
int mechanism;
*r_cert = NULL;
*r_certlen = 0;
/* Hack to read a Yubikey attestation certificate. */
if (app->app_local->flags.yubikey
&& strlen (certid) == 11
&& !ascii_strncasecmp (certid, "PIV.ATST.", 9)
&& hexdigitp (certid+9) && hexdigitp (certid+10))
{
unsigned char apdu[4];
unsigned char *result;
size_t resultlen;
apdu[0] = 0;
apdu[1] = 0xf9; /* Yubikey: Get attestation cert. */
apdu[2] = xtoi_2 (certid+9);
apdu[3] = 0;
- err = iso7816_apdu_direct (app->slot, apdu, 4, 1,
+ err = iso7816_apdu_direct (app_get_slot (app), apdu, 4, 1,
NULL, &result, &resultlen);
if (!err)
{
*r_cert = result;
*r_certlen = resultlen;
}
return err;
}
dobj = find_dobj_by_keyref (app, certid);
if (!dobj)
return gpg_error (GPG_ERR_INV_ID);
err = readcert_by_tag (app, dobj->tag, r_cert, r_certlen, &mechanism);
if (!err && mechanism)
{
/* Well, no certificate but a public key - we don't want it. */
xfree (*r_cert);
*r_cert = NULL;
*r_certlen = 0;
err = gpg_error (GPG_ERR_NOT_FOUND);
}
return err;
}
/* Return a public key in a freshly allocated buffer. This will only
* work for a freshly generated key as long as no reset of the
* application has been performed. This is because we return a cached
* result from key generation. If no cached result is available, the
* error GPG_ERR_UNSUPPORTED_OPERATION is returned so that the higher
* layer can then get the key by reading the matching certificate.
* On success a canonical encoded s-expression with the public key is
* stored at (R_PK,R_PKLEN); the caller must release that buffer. On
* error R_PK and R_PKLEN are not changed and an error code is
* returned.
*/
static gpg_error_t
-do_readkey (app_t app, const char *keyrefstr,
+do_readkey (app_t app, ctrl_t ctrl, const char *keyrefstr, unsigned int flags,
unsigned char **r_pk, size_t *r_pklen)
{
gpg_error_t err;
data_object_t dobj;
int keyref;
unsigned char *cert = NULL;
size_t certlen;
int mechanism;
gcry_sexp_t s_pkey = NULL;
unsigned char *pk = NULL;
size_t pklen;
dobj = find_dobj_by_keyref (app, keyrefstr);
if ((keyref = keyref_from_dobj (dobj)) == -1)
{
err = gpg_error (GPG_ERR_INV_ID);
goto leave;
}
err = readcert_by_tag (app, dobj->tag, &cert, &certlen, &mechanism);
if (err)
goto leave;
if (!mechanism)
{
/* We got a certificate. Extract the pubkey from it. */
err = app_help_pubkey_from_cert (cert, certlen, &pk, &pklen);
if (err)
{
log_error ("failed to parse the certificate: %s\n",
gpg_strerror (err));
goto leave;
}
}
else
{
/* Convert the public key into the expected s-expression. */
if (mechanism == PIV_ALGORITHM_RSA)
err = genkey_parse_rsa (cert, certlen, &s_pkey);
else if (mechanism == PIV_ALGORITHM_ECC_P256
|| mechanism == PIV_ALGORITHM_ECC_P384)
err = genkey_parse_ecc (cert, certlen, mechanism, &s_pkey);
else
err = gpg_error (GPG_ERR_PUBKEY_ALGO);
if (err)
goto leave;
err = make_canon_sexp (s_pkey, &pk, &pklen);
if (err)
goto leave;
}
- *r_pk = pk;
- pk = NULL;
- *r_pklen = pklen;
+ if ((flags & APP_READKEY_FLAG_INFO))
+ {
+ char keygripstr[KEYGRIP_LEN*2+1];
+ char idbuf[50];
+ const char *usage;
+
+ err = app_help_get_keygrip_string_pk (pk, pklen, keygripstr);
+ if (err)
+ {
+ log_error ("app_help_get_keygrip_string_pk failed: %s\n",
+ gpg_strerror (err));
+ goto leave;
+ }
+ usage = dobj->usage? dobj->usage : "";
+
+ snprintf (idbuf, sizeof idbuf, "PIV.%s", dobj->keyref);
+ send_status_info (ctrl, "KEYPAIRINFO",
+ keygripstr, strlen (keygripstr),
+ idbuf, strlen (idbuf),
+ usage, strlen (usage),
+ NULL, (size_t)0);
+ }
+
+ if (r_pk && r_pklen)
+ {
+ *r_pk = pk;
+ pk = NULL;
+ *r_pklen = pklen;
+ }
leave:
gcry_sexp_release (s_pkey);
xfree (pk);
xfree (cert);
return err;
}
/* Given a data object DOBJ return the corresponding PIV algorithm and
* store it at R_ALGO. The algorithm is taken from the corresponding
* certificate or from a cache. */
static gpg_error_t
get_key_algorithm_by_dobj (app_t app, data_object_t dobj, int *r_mechanism)
{
gpg_error_t err;
unsigned char *certbuf = NULL;
size_t certbuflen;
int mechanism;
ksba_cert_t cert = NULL;
ksba_sexp_t k_pkey = NULL;
gcry_sexp_t s_pkey = NULL;
gcry_sexp_t l1 = NULL;
char *algoname = NULL;
int algo;
size_t n;
const char *curve_name;
*r_mechanism = 0;
err = readcert_by_tag (app, dobj->tag, &certbuf, &certbuflen, &mechanism);
if (err)
goto leave;
if (mechanism)
{
/* A public key was found. That makes it easy. */
switch (mechanism)
{
case PIV_ALGORITHM_RSA:
case PIV_ALGORITHM_ECC_P256:
case PIV_ALGORITHM_ECC_P384:
*r_mechanism = mechanism;
break;
default:
err = gpg_error (GPG_ERR_PUBKEY_ALGO);
log_error ("piv: unknown mechanism %d in public key at %s\n",
mechanism, dobj->keyref);
break;
}
goto leave;
}
err = ksba_cert_new (&cert);
if (err)
goto leave;
err = ksba_cert_init_from_mem (cert, certbuf, certbuflen);
if (err)
{
log_error ("piv: failed to parse the certificate %s: %s\n",
dobj->keyref, gpg_strerror (err));
goto leave;
}
xfree (certbuf);
certbuf = NULL;
k_pkey = ksba_cert_get_public_key (cert);
if (!k_pkey)
{
err = gpg_error (GPG_ERR_NO_PUBKEY);
goto leave;
}
n = gcry_sexp_canon_len (k_pkey, 0, NULL, NULL);
err = gcry_sexp_new (&s_pkey, k_pkey, n, 0);
if (err)
goto leave;
l1 = gcry_sexp_find_token (s_pkey, "public-key", 0);
if (!l1)
{
err = gpg_error (GPG_ERR_NO_PUBKEY);
goto leave;
}
{
gcry_sexp_t l_tmp = gcry_sexp_cadr (l1);
gcry_sexp_release (l1);
l1 = l_tmp;
}
algoname = gcry_sexp_nth_string (l1, 0);
if (!algoname)
{
err = gpg_error_from_syserror ();
goto leave;
}
algo = gcry_pk_map_name (algoname);
switch (algo)
{
case GCRY_PK_RSA:
algo = PIV_ALGORITHM_RSA;
break;
case GCRY_PK_ECC:
case GCRY_PK_ECDSA:
case GCRY_PK_ECDH:
curve_name = gcry_pk_get_curve (s_pkey, 0, NULL);
if (curve_name && !strcmp (curve_name, "NIST P-256"))
algo = PIV_ALGORITHM_ECC_P256;
else if (curve_name && !strcmp (curve_name, "NIST P-384"))
algo = PIV_ALGORITHM_ECC_P384;
else
{
err = gpg_error (GPG_ERR_UNKNOWN_CURVE);
log_error ("piv: certificate %s, curve '%s': %s\n",
dobj->keyref, curve_name, gpg_strerror (err));
goto leave;
}
break;
default:
err = gpg_error (GPG_ERR_PUBKEY_ALGO);
log_error ("piv: certificate %s, pubkey algo '%s': %s\n",
dobj->keyref, algoname, gpg_strerror (err));
goto leave;
}
*r_mechanism = algo;
leave:
gcry_free (algoname);
gcry_sexp_release (l1);
gcry_sexp_release (s_pkey);
ksba_free (k_pkey);
xfree (certbuf);
return err;
}
/* Return an allocated string to be used as prompt. Returns NULL on
* malloc error. */
static char *
make_prompt (app_t app, int remaining, const char *firstline)
{
char *serial, *tmpbuf, *result;
serial = get_dispserialno (app, 0);
if (!serial)
return NULL;
/* TRANSLATORS: Put a \x1f right before a colon. This can be
* used by pinentry to nicely align the names and values. Keep
* the %s at the start and end of the string. */
result = xtryasprintf (_("%s"
"Number\x1f: %s%%0A"
"Holder\x1f: %s"
"%s"),
"\x1e",
serial,
"Unknown", /* Fixme */
"");
xfree (serial);
/* Append a "remaining attempts" info if needed. */
if (remaining != -1 && remaining < 3)
{
char *rembuf;
/* TRANSLATORS: This is the number of remaining attempts to
* enter a PIN. Use %%0A (double-percent,0A) for a linefeed. */
rembuf = xtryasprintf (_("Remaining attempts: %d"), remaining);
if (rembuf)
{
tmpbuf = strconcat (firstline, "%0A%0A", result,
"%0A%0A", rembuf, NULL);
xfree (rembuf);
}
else
tmpbuf = NULL;
xfree (result);
result = tmpbuf;
}
else
{
tmpbuf = strconcat (firstline, "%0A%0A", result, NULL);
xfree (result);
result = tmpbuf;
}
return result;
}
/* Helper for verify_chv to ask for the PIN and to prepare/pad it. On
* success the result is stored at (R_PIN,R_PINLEN). */
static gpg_error_t
ask_and_prepare_chv (app_t app, int keyref, int ask_new, int remaining,
gpg_error_t (*pincb)(void*,const char *,char **),
void *pincb_arg, char **r_pin, unsigned int *r_pinlen)
{
gpg_error_t err;
const char *label;
char *prompt;
char *pinvalue = NULL;
unsigned int pinlen;
char *pinbuffer = NULL;
int minlen, maxlen, padding, onlydigits;
*r_pin = NULL;
*r_pinlen = 0;
if (ask_new)
remaining = -1;
if (remaining != -1)
log_debug ("piv: CHV %02X has %d attempts left\n", keyref, remaining);
switch (keyref)
{
case 0x00:
minlen = 6;
maxlen = 8;
padding = 1;
onlydigits = 1;
label = (ask_new? _("|N|Please enter the new Global-PIN")
/**/ : _("||Please enter the Global-PIN of your PIV card"));
break;
case 0x80:
minlen = 6;
maxlen = 8;
padding = 1;
onlydigits = 1;
label = (ask_new? _("|N|Please enter the new PIN")
/**/ : _("||Please enter the PIN of your PIV card"));
break;
case 0x81:
minlen = 8;
maxlen = 8;
padding = 0;
onlydigits = 0;
label = (ask_new? _("|N|Please enter the new Unblocking Key")
/**/ :_("||Please enter the Unblocking Key of your PIV card"));
break;
case 0x96:
case 0x97:
case 0x98:
case 0x9B:
return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
default:
return gpg_error (GPG_ERR_INV_ID);
}
/* Ask for the PIN. */
prompt = make_prompt (app, remaining, label);
err = pincb (pincb_arg, prompt, &pinvalue);
xfree (prompt);
prompt = NULL;
if (err)
{
log_info (_("PIN callback returned error: %s\n"), gpg_strerror (err));
return err;
}
pinlen = pinvalue? strlen (pinvalue) : 0;
if (pinlen < minlen)
{
log_error (_("PIN for is too short; minimum length is %d\n"), minlen);
if (pinvalue)
wipememory (pinvalue, pinlen);
xfree (pinvalue);
return gpg_error (GPG_ERR_BAD_PIN);
}
if (pinlen > maxlen)
{
log_error (_("PIN for is too long; maximum length is %d\n"), maxlen);
wipememory (pinvalue, pinlen);
xfree (pinvalue);
return gpg_error (GPG_ERR_BAD_PIN);
}
if (onlydigits && strspn (pinvalue, "0123456789") != pinlen)
{
log_error (_("PIN has invalid characters; only digits are allowed\n"));
wipememory (pinvalue, pinlen);
xfree (pinvalue);
return gpg_error (GPG_ERR_BAD_PIN);
}
pinbuffer = xtrymalloc_secure (maxlen);
if (!pinbuffer)
{
err = gpg_error_from_syserror ();
wipememory (pinvalue, pinlen);
xfree (pinvalue);
return err;
}
memcpy (pinbuffer, pinvalue, pinlen);
wipememory (pinvalue, pinlen);
xfree (pinvalue);
if (padding)
{
memset (pinbuffer + pinlen, 0xff, maxlen - pinlen);
pinlen = maxlen;
}
*r_pin = pinbuffer;
*r_pinlen = pinlen;
return 0;
}
/* Verify the card holder verification identified by KEYREF. This is
* either the Appication PIN or the Global PIN. If FORCE is true a
* verification is always done. */
static gpg_error_t
verify_chv (app_t app, int keyref, int force,
gpg_error_t (*pincb)(void*,const char *,char **), void *pincb_arg)
{
gpg_error_t err;
unsigned char apdu[4];
unsigned int sw;
int remaining;
char *pin = NULL;
unsigned int pinlen;
/* First check whether a verify is at all needed. This is done with
* P1 being 0 and no Lc and command data send. */
apdu[0] = 0x00;
apdu[1] = ISO7816_VERIFY;
apdu[2] = 0x00;
apdu[3] = keyref;
- if (!iso7816_apdu_direct (app->slot, apdu, 4, 0, &sw, NULL, NULL))
+ if (!iso7816_apdu_direct (app_get_slot (app), apdu, 4, 0, &sw, NULL, NULL))
{
if (!force) /* No need to verification. */
return 0; /* All fine. */
remaining = -1;
}
else if ((sw & 0xfff0) == 0x63C0)
remaining = (sw & 0x000f); /* PIN has REMAINING tries left. */
else
remaining = -1;
err = ask_and_prepare_chv (app, keyref, 0, remaining, pincb, pincb_arg,
&pin, &pinlen);
if (err)
return err;
- err = iso7816_verify (app->slot, keyref, pin, pinlen);
+ err = iso7816_verify (app_get_slot (app), keyref, pin, pinlen);
wipememory (pin, pinlen);
xfree (pin);
if (err)
log_error ("CHV %02X verification failed: %s\n",
keyref, gpg_strerror (err));
return err;
}
/* Handle the PASSWD command. Valid values for PWIDSTR are
* key references related to PINs; in particular:
* PIV.00 - The Global PIN
* PIV.80 - The Application PIN
* PIV.81 - The PIN Unblocking key
* The supported flags are:
* APP_CHANGE_FLAG_CLEAR Clear the PIN verification state.
* APP_CHANGE_FLAG_RESET Reset a PIN using the PUK. Only
* allowed with PIV.80.
*/
static gpg_error_t
do_change_chv (app_t app, ctrl_t ctrl, const char *pwidstr,
unsigned int flags,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg)
{
gpg_error_t err;
int keyref, targetkeyref;
unsigned char apdu[4];
unsigned int sw;
int remaining;
char *oldpin = NULL;
unsigned int oldpinlen;
char *newpin = NULL;
unsigned int newpinlen;
(void)ctrl;
/* Check for unknown flags. */
if ((flags & ~(APP_CHANGE_FLAG_CLEAR|APP_CHANGE_FLAG_RESET)))
{
err = gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
goto leave;
}
/* Parse the keyref. */
targetkeyref = keyref = parse_chv_keyref (pwidstr);
if (keyref == -1)
{
err = gpg_error (GPG_ERR_INV_ID);
goto leave;
}
/* First see whether the special --clear mode has been requested. */
if ((flags & APP_CHANGE_FLAG_CLEAR))
{
apdu[0] = 0x00;
apdu[1] = ISO7816_VERIFY;
apdu[2] = 0xff;
apdu[3] = keyref;
- err = iso7816_apdu_direct (app->slot, apdu, 4, 0, NULL, NULL, NULL);
+ err = iso7816_apdu_direct (app_get_slot (app), apdu, 4, 0,
+ NULL, NULL, NULL);
goto leave;
}
/* Prepare reset mode. */
if ((flags & APP_CHANGE_FLAG_RESET))
{
if (keyref == 0x81)
{
err = gpg_error (GPG_ERR_INV_ID); /* Can't reset the PUK. */
goto leave;
}
/* Set the keyref to the PUK and keep the TARGETKEYREF. */
keyref = 0x81;
}
/* Get the remaining tries count. This is done by using the check
* for verified state feature. */
apdu[0] = 0x00;
apdu[1] = ISO7816_VERIFY;
apdu[2] = 0x00;
apdu[3] = keyref;
- if (!iso7816_apdu_direct (app->slot, apdu, 4, 0, &sw, NULL, NULL))
+ if (!iso7816_apdu_direct (app_get_slot (app), apdu, 4, 0, &sw, NULL, NULL))
remaining = -1; /* Already verified, thus full number of tries. */
else if ((sw & 0xfff0) == 0x63C0)
remaining = (sw & 0x000f); /* PIN has REMAINING tries left. */
else
remaining = -1;
/* Ask for the old pin or puk. */
err = ask_and_prepare_chv (app, keyref, 0, remaining, pincb, pincb_arg,
&oldpin, &oldpinlen);
if (err)
return err;
/* Verify the old pin so that we don't prompt for the new pin if the
* old is wrong. This is not possible for the PUK, though. */
if (keyref != 0x81)
{
- err = iso7816_verify (app->slot, keyref, oldpin, oldpinlen);
+ err = iso7816_verify (app_get_slot (app), keyref, oldpin, oldpinlen);
if (err)
{
log_error ("CHV %02X verification failed: %s\n",
keyref, gpg_strerror (err));
goto leave;
}
}
/* Ask for the new pin. */
err = ask_and_prepare_chv (app, targetkeyref, 1, -1, pincb, pincb_arg,
&newpin, &newpinlen);
if (err)
return err;
if ((flags & APP_CHANGE_FLAG_RESET))
{
char *buf = xtrymalloc_secure (oldpinlen + newpinlen);
if (!buf)
{
err = gpg_error_from_syserror ();
goto leave;
}
memcpy (buf, oldpin, oldpinlen);
memcpy (buf+oldpinlen, newpin, newpinlen);
- err = iso7816_reset_retry_counter_with_rc (app->slot, targetkeyref,
+ err = iso7816_reset_retry_counter_with_rc (app_get_slot (app),
+ targetkeyref,
buf, oldpinlen+newpinlen);
xfree (buf);
if (err)
log_error ("resetting CHV %02X using CHV %02X failed: %s\n",
targetkeyref, keyref, gpg_strerror (err));
}
else
{
- err = iso7816_change_reference_data (app->slot, keyref,
+ err = iso7816_change_reference_data (app_get_slot (app), keyref,
oldpin, oldpinlen,
newpin, newpinlen);
if (err)
log_error ("CHV %02X changing PIN failed: %s\n",
keyref, gpg_strerror (err));
}
leave:
xfree (oldpin);
xfree (newpin);
return err;
}
/* Perform a simple verify operation for the PIN specified by PWIDSTR.
* For valid values see do_change_chv. */
static gpg_error_t
do_check_chv (app_t app, const char *pwidstr,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg)
{
int keyref;
keyref = parse_chv_keyref (pwidstr);
if (keyref == -1)
return gpg_error (GPG_ERR_INV_ID);
return verify_chv (app, keyref, 0, pincb, pincb_arg);
}
/* Compute a digital signature using the GENERAL AUTHENTICATE command
* on INDATA which is expected to be the raw message digest. The
* KEYIDSTR has the key reference or its OID (e.g. "PIV.9A"). The
* result is stored at (R_OUTDATA,R_OUTDATALEN); on error (NULL,0) is
* stored there and an error code returned. For ECDSA the result is
* the simple concatenation of R and S without any DER encoding. R
* and S are left extended with zeroes to make sure they have an equal
* length. If HASHALGO is not zero, the function prepends the hash's
* OID to the indata or checks that it is consistent.
*/
static gpg_error_t
do_sign (app_t app, const char *keyidstr, int hashalgo,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const void *indata_arg, size_t indatalen,
unsigned char **r_outdata, size_t *r_outdatalen)
{
const unsigned char *indata = indata_arg;
gpg_error_t err;
data_object_t dobj;
unsigned char oidbuf[64];
size_t oidbuflen;
unsigned char *outdata = NULL;
size_t outdatalen;
const unsigned char *s;
size_t n;
int keyref, mechanism;
unsigned char *indata_buffer = NULL; /* Malloced helper. */
unsigned char *apdudata = NULL;
size_t apdudatalen;
int force_verify;
if (!keyidstr || !*keyidstr)
{
err = gpg_error (GPG_ERR_INV_VALUE);
goto leave;
}
dobj = find_dobj_by_keyref (app, keyidstr);
if ((keyref = keyref_from_dobj (dobj)) == -1)
{
err = gpg_error (GPG_ERR_INV_ID);
goto leave;
}
/* According to table 4b of SP800-73-4 the signing key always
* requires a verify. */
switch (keyref)
{
case 0x9c: force_verify = 1; break;
default: force_verify = 0; break;
}
err = get_key_algorithm_by_dobj (app, dobj, &mechanism);
if (err)
goto leave;
/* For ECC we need to remove the ASN.1 prefix from INDATA. For RSA
* we need to add the padding and possible also the ASN.1 prefix. */
if (mechanism == PIV_ALGORITHM_ECC_P256
|| mechanism == PIV_ALGORITHM_ECC_P384)
{
int need_algo, need_digestlen;
if (mechanism == PIV_ALGORITHM_ECC_P256)
{
need_algo = GCRY_MD_SHA256;
need_digestlen = 32;
}
else
{
need_algo = GCRY_MD_SHA384;
need_digestlen = 48;
}
if (hashalgo && hashalgo != need_algo)
{
err = gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM);
log_error ("piv: hash algo %d does not match mechanism %d\n",
need_algo, mechanism);
goto leave;
}
if (indatalen > need_digestlen)
{
oidbuflen = sizeof oidbuf;
err = gcry_md_get_asnoid (need_algo, &oidbuf, &oidbuflen);
if (err)
{
err = gpg_error (GPG_ERR_INTERNAL);
log_debug ("piv: no OID for hash algo %d\n", need_algo);
goto leave;
}
if (indatalen != oidbuflen + need_digestlen
|| memcmp (indata, oidbuf, oidbuflen))
{
err = gpg_error (GPG_ERR_INV_VALUE);
log_error ("piv: bad input for signing with mechanism %d\n",
mechanism);
goto leave;
}
indata += oidbuflen;
indatalen -= oidbuflen;
}
}
else if (mechanism == PIV_ALGORITHM_RSA)
{
/* PIV requires 2048 bit RSA. */
unsigned int framelen = 2048 / 8;
unsigned char *frame;
int i;
oidbuflen = sizeof oidbuf;
if (!hashalgo)
{
/* We assume that indata already has the required
* digestinfo; thus merely prepend the padding below. */
}
else if ((err = gcry_md_get_asnoid (hashalgo, &oidbuf, &oidbuflen)))
{
log_debug ("piv: no OID for hash algo %d\n", hashalgo);
goto leave;
}
else
{
unsigned int digestlen = gcry_md_get_algo_dlen (hashalgo);
if (indatalen == digestlen)
{
/* Plain hash in INDATA; prepend the digestinfo. */
indata_buffer = xtrymalloc (oidbuflen + indatalen);
if (!indata_buffer)
{
err = gpg_error_from_syserror ();
goto leave;
}
memcpy (indata_buffer, oidbuf, oidbuflen);
memcpy (indata_buffer+oidbuflen, indata, indatalen);
indata = indata_buffer;
indatalen = oidbuflen + indatalen;
}
else if (indatalen == oidbuflen + digestlen
&& !memcmp (indata, oidbuf, oidbuflen))
; /* Correct prefix. */
else
{
err = gpg_error (GPG_ERR_INV_VALUE);
log_error ("piv: bad input for signing with RSA and hash %d\n",
hashalgo);
goto leave;
}
}
/* Now prepend the pkcs#v1.5 padding. We require at least 8
* byte of padding and 3 extra bytes for the prefix and the
* delimiting nul. */
if (!indatalen || indatalen + 8 + 4 > framelen)
{
err = gpg_error (GPG_ERR_INV_VALUE);
log_error ("piv: input does not fit into a %u bit PKCS#v1.5 frame\n",
8*framelen);
goto leave;
}
frame = xtrymalloc (framelen);
if (!frame)
{
err = gpg_error_from_syserror ();
goto leave;
}
n = 0;
frame[n++] = 0;
frame[n++] = 1; /* Block type. */
i = framelen - indatalen - 3 ;
memset (frame+n, 0xff, i);
n += i;
frame[n++] = 0; /* Delimiter. */
memcpy (frame+n, indata, indatalen);
n += indatalen;
log_assert (n == framelen);
/* And now put it into the indata_buffer. */
xfree (indata_buffer);
indata_buffer = frame;
indata = indata_buffer;
indatalen = framelen;
}
else
{
err = gpg_error (GPG_ERR_INTERNAL);
log_debug ("piv: unknown PIV mechanism %d while signing\n", mechanism);
goto leave;
}
/* Now verify the Application PIN. */
err = verify_chv (app, 0x80, force_verify, pincb, pincb_arg);
if (err)
return err;
/* Build the Dynamic Authentication Template. */
err = concat_tlv_list (0, &apdudata, &apdudatalen,
(int)0x7c, (size_t)0, NULL, /* Constructed. */
(int)0x82, (size_t)0, "",
(int)0x81, (size_t)indatalen, indata,
(int)0, (size_t)0, NULL);
if (err)
goto leave;
/* Note: the -1 requests command chaining. */
- err = iso7816_general_authenticate (app->slot, -1,
+ err = iso7816_general_authenticate (app_get_slot (app), -1,
mechanism, keyref,
apdudata, (int)apdudatalen, 0,
&outdata, &outdatalen);
if (err)
goto leave;
/* Parse the response. */
if (outdatalen && *outdata == 0x7c
&& (s = find_tlv (outdata, outdatalen, 0x82, &n)))
{
if (mechanism == PIV_ALGORITHM_RSA)
{
memmove (outdata, outdata + (s - outdata), n);
outdatalen = n;
}
else /* ECC */
{
const unsigned char *rval, *sval;
size_t rlen, rlenx, slen, slenx, resultlen;
char *result;
/* The result of an ECDSA signature is
* SEQUENCE { r INTEGER, s INTEGER }
* We re-pack that by concatenating R and S and making sure
* that both have the same length. We simplify parsing by
* using find_tlv and not a proper DER parser. */
s = find_tlv (s, n, 0x30, &n);
if (!s)
goto bad_der;
rval = find_tlv (s, n, 0x02, &rlen);
if (!rval)
goto bad_der;
log_assert (n >= (rval-s)+rlen);
sval = find_tlv (rval+rlen, n-((rval-s)+rlen), 0x02, &slen);
if (!rval)
goto bad_der;
rlenx = slenx = 0;
if (rlen > slen)
slenx = rlen - slen;
else if (slen > rlen)
rlenx = slen - rlen;
resultlen = rlen + rlenx + slen + slenx;
result = xtrycalloc (1, resultlen);
if (!result)
{
err = gpg_error_from_syserror ();
goto leave;
}
memcpy (result + rlenx, rval, rlen);
memcpy (result + rlenx + rlen + slenx, sval, slen);
xfree (outdata);
outdata = result;
outdatalen = resultlen;
}
}
else
{
bad_der:
err = gpg_error (GPG_ERR_CARD);
log_error ("piv: response does not contain a proper result\n");
goto leave;
}
leave:
if (err)
{
xfree (outdata);
*r_outdata = NULL;
*r_outdatalen = 0;
}
else
{
*r_outdata = outdata;
*r_outdatalen = outdatalen;
}
xfree (apdudata);
xfree (indata_buffer);
return err;
}
/* AUTH for PIV cards is actually the same as SIGN. The difference
* between AUTH and SIGN is that AUTH expects that pkcs#1.5 padding
* for RSA has already been done (digestInfo part w/o the padding)
* whereas SIGN may accept a plain digest and does the padding if
* needed. This is also the reason why SIGN takes a hashalgo. */
static gpg_error_t
do_auth (app_t app, const char *keyidstr,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const void *indata, size_t indatalen,
unsigned char **r_outdata, size_t *r_outdatalen)
{
return do_sign (app, keyidstr, 0, pincb, pincb_arg, indata, indatalen,
r_outdata, r_outdatalen);
}
/* Decrypt the data in (INDATA,INDATALEN) and on success store the
* mallocated result at (R_OUTDATA,R_OUTDATALEN). */
static gpg_error_t
do_decipher (app_t app, const char *keyidstr,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const void *indata_arg, size_t indatalen,
unsigned char **r_outdata, size_t *r_outdatalen,
unsigned int *r_info)
{
const unsigned char *indata = indata_arg;
gpg_error_t err;
data_object_t dobj;
unsigned char *outdata = NULL;
size_t outdatalen;
const unsigned char *s;
size_t n;
int keyref, mechanism;
unsigned int framelen;
unsigned char *indata_buffer = NULL; /* Malloced helper. */
unsigned char *apdudata = NULL;
size_t apdudatalen;
if (!keyidstr || !*keyidstr)
{
err = gpg_error (GPG_ERR_INV_VALUE);
goto leave;
}
dobj = find_dobj_by_keyref (app, keyidstr);
if ((keyref = keyref_from_dobj (dobj)) == -1)
{
err = gpg_error (GPG_ERR_INV_ID);
goto leave;
}
if (keyref == 0x9A || keyref == 0x9C || keyref == 0x9E)
{
/* Signing only reference. We only allow '9D' and the retired
* cert key management DOs. */
err = gpg_error (GPG_ERR_INV_ID);
goto leave;
}
err = get_key_algorithm_by_dobj (app, dobj, &mechanism);
if (err)
goto leave;
switch (mechanism)
{
case PIV_ALGORITHM_ECC_P256:
framelen = 1+32+32;
break;
case PIV_ALGORITHM_ECC_P384:
framelen = 1+48+48;
break;
case PIV_ALGORITHM_RSA:
framelen = 2048 / 8;
break;
default:
err = gpg_error (GPG_ERR_INTERNAL);
log_debug ("piv: unknown PIV mechanism %d while decrypting\n", mechanism);
goto leave;
}
/* Check that the ciphertext has the right length; due to internal
* convey mechanism using MPIs leading zero bytes might have been
- * lost. Adjust for this. Note that for ECC this actually
- * superfluous because the first octet is always '04' to indicate an
+ * lost. Adjust for this. Unfortunately the ciphertext might have
+ * also been prefixed with a leading zero to make it a positive
+ * number; that may be a too long frame and we need to adjust for
+ * this too. Note that for ECC thoses fixes are not reqquired
+ * because the first octet is always '04' to indicate an
* uncompressed point. */
if (indatalen > framelen)
{
- err = gpg_error (GPG_ERR_INV_VALUE);
- log_error ("piv: input of %zu octets too large for mechanism %d\n",
- indatalen, mechanism);
- goto leave;
+ if (mechanism == PIV_ALGORITHM_RSA
+ && indatalen == framelen + 1 && !*indata)
+ {
+ indata_buffer = xtrycalloc (1, framelen);
+ if (!indata_buffer)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+ memcpy (indata_buffer, indata+1, framelen);
+ indata = indata_buffer;
+ indatalen = framelen;
+ }
+ else
+ {
+ err = gpg_error (GPG_ERR_INV_VALUE);
+ log_error ("piv: input of %zu octets too large for mechanism %d\n",
+ indatalen, mechanism);
+ goto leave;
+ }
}
if (indatalen < framelen)
{
indata_buffer = xtrycalloc (1, framelen);
if (!indata_buffer)
{
err = gpg_error_from_syserror ();
goto leave;
}
memcpy (indata_buffer+(framelen-indatalen), indata, indatalen);
indata = indata_buffer;
indatalen = framelen;
}
/* Now verify the Application PIN. */
err = verify_chv (app, 0x80, 0, pincb, pincb_arg);
if (err)
return err;
/* Build the Dynamic Authentication Template. */
err = concat_tlv_list (0, &apdudata, &apdudatalen,
(int)0x7c, (size_t)0, NULL, /* Constructed. */
(int)0x82, (size_t)0, "",
mechanism == PIV_ALGORITHM_RSA?
(int)0x81 : (int)0x85, (size_t)indatalen, indata,
(int)0, (size_t)0, NULL);
if (err)
goto leave;
/* Note: the -1 requests command chaining. */
- err = iso7816_general_authenticate (app->slot, -1,
+ err = iso7816_general_authenticate (app_get_slot (app), -1,
mechanism, keyref,
apdudata, (int)apdudatalen, 0,
&outdata, &outdatalen);
if (err)
goto leave;
/* Parse the response. */
if (outdatalen && *outdata == 0x7c
&& (s = find_tlv (outdata, outdatalen, 0x82, &n)))
{
memmove (outdata, outdata + (s - outdata), n);
outdatalen = n;
}
else
{
err = gpg_error (GPG_ERR_CARD);
log_error ("piv: response does not contain a proper result\n");
goto leave;
}
leave:
if (err)
{
xfree (outdata);
*r_outdata = NULL;
*r_outdatalen = 0;
}
else
{
*r_outdata = outdata;
*r_outdatalen = outdatalen;
}
*r_info = 0;
xfree (apdudata);
xfree (indata_buffer);
return err;
}
/* Check whether a key for DOBJ already exists. We detect this by
* reading the certificate described by DOBJ. If FORCE is TRUE a
* diagnositic will be printed but no error returned if the key
* already exists. The flag GENERATING is used to select a
* diagnositic. */
static gpg_error_t
does_key_exist (app_t app, data_object_t dobj, int generating, int force)
{
void *relptr;
unsigned char *buffer;
size_t buflen;
int found;
relptr = get_one_do (app, dobj->tag, &buffer, &buflen, NULL);
found = (relptr && buflen);
xfree (relptr);
if (found && !force)
{
log_error (_("key already exists\n"));
return gpg_error (GPG_ERR_EEXIST);
}
if (found)
log_info (_("existing key will be replaced\n"));
else if (generating)
log_info (_("generating new key\n"));
else
log_info (_("writing new key\n"));
return 0;
}
/* Helper for do_writekey; here the RSA part. BUF, BUFLEN, and DEPTH
* are the current parser state of the S-expression with the key. */
static gpg_error_t
writekey_rsa (app_t app, data_object_t dobj, int keyref,
const unsigned char *buf, size_t buflen, int depth)
{
gpg_error_t err;
const unsigned char *tok;
size_t toklen;
int last_depth1, last_depth2;
const unsigned char *rsa_n = NULL;
const unsigned char *rsa_e = NULL;
const unsigned char *rsa_p = NULL;
const unsigned char *rsa_q = NULL;
unsigned char *rsa_dpm1 = NULL;
unsigned char *rsa_dqm1 = NULL;
unsigned char *rsa_qinv = NULL;
size_t rsa_n_len, rsa_e_len, rsa_p_len, rsa_q_len;
size_t rsa_dpm1_len, rsa_dqm1_len, rsa_qinv_len;
unsigned char *apdudata = NULL;
size_t apdudatalen;
unsigned char tmpl[1];
last_depth1 = depth;
while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))
&& depth && depth >= last_depth1)
{
if (tok)
{
err = gpg_error (GPG_ERR_UNKNOWN_SEXP);
goto leave;
}
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
goto leave;
if (tok && toklen == 1)
{
const unsigned char **mpi;
size_t *mpi_len;
switch (*tok)
{
case 'n': mpi = &rsa_n; mpi_len = &rsa_n_len; break;
case 'e': mpi = &rsa_e; mpi_len = &rsa_e_len; break;
case 'p': mpi = &rsa_p; mpi_len = &rsa_p_len; break;
case 'q': mpi = &rsa_q; mpi_len = &rsa_q_len; break;
default: mpi = NULL; mpi_len = NULL; break;
}
if (mpi && *mpi)
{
err = gpg_error (GPG_ERR_DUP_VALUE);
goto leave;
}
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
goto leave;
if (tok && mpi)
{
/* Strip off leading zero bytes and save. */
for (;toklen && !*tok; toklen--, tok++)
;
*mpi = tok;
*mpi_len = toklen;
}
}
/* Skip until end of list. */
last_depth2 = depth;
while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))
&& depth && depth >= last_depth2)
;
if (err)
goto leave;
}
/* Check that we have all parameters. */
if (!rsa_n || !rsa_e || !rsa_p || !rsa_q)
{
err = gpg_error (GPG_ERR_BAD_SECKEY);
goto leave;
}
/* Fixme: Shall we check whether n == pq ? */
if (opt.verbose)
log_info ("RSA private key size is %u bytes\n", (unsigned int)rsa_n_len);
/* Compute the dp, dq and u components. */
{
gcry_mpi_t mpi_e, mpi_p, mpi_q;
gcry_mpi_t mpi_dpm1 = gcry_mpi_snew (0);
gcry_mpi_t mpi_dqm1 = gcry_mpi_snew (0);
gcry_mpi_t mpi_qinv = gcry_mpi_snew (0);
gcry_mpi_t mpi_tmp = gcry_mpi_snew (0);
gcry_mpi_scan (&mpi_e, GCRYMPI_FMT_USG, rsa_e, rsa_e_len, NULL);
gcry_mpi_scan (&mpi_p, GCRYMPI_FMT_USG, rsa_p, rsa_p_len, NULL);
gcry_mpi_scan (&mpi_q, GCRYMPI_FMT_USG, rsa_q, rsa_q_len, NULL);
gcry_mpi_sub_ui (mpi_tmp, mpi_p, 1);
gcry_mpi_invm (mpi_dpm1, mpi_e, mpi_tmp);
gcry_mpi_sub_ui (mpi_tmp, mpi_q, 1);
gcry_mpi_invm (mpi_dqm1, mpi_e, mpi_tmp);
gcry_mpi_invm (mpi_qinv, mpi_q, mpi_p);
gcry_mpi_aprint (GCRYMPI_FMT_USG, &rsa_dpm1, &rsa_dpm1_len, mpi_dpm1);
gcry_mpi_aprint (GCRYMPI_FMT_USG, &rsa_dqm1, &rsa_dqm1_len, mpi_dqm1);
gcry_mpi_aprint (GCRYMPI_FMT_USG, &rsa_qinv, &rsa_qinv_len, mpi_qinv);
gcry_mpi_release (mpi_e);
gcry_mpi_release (mpi_p);
gcry_mpi_release (mpi_q);
gcry_mpi_release (mpi_dpm1);
gcry_mpi_release (mpi_dqm1);
gcry_mpi_release (mpi_qinv);
gcry_mpi_release (mpi_tmp);
}
err = concat_tlv_list (1, &apdudata, &apdudatalen,
(int)0x01, (size_t)rsa_p_len, rsa_p,
(int)0x02, (size_t)rsa_q_len, rsa_q,
(int)0x03, (size_t)rsa_dpm1_len, rsa_dpm1,
(int)0x04, (size_t)rsa_dqm1_len, rsa_dqm1,
(int)0x05, (size_t)rsa_qinv_len, rsa_qinv,
(int)0, (size_t)0, NULL);
if (err)
goto leave;
- err = iso7816_send_apdu (app->slot,
+ err = iso7816_send_apdu (app_get_slot (app),
-1, /* Use command chaining. */
0, /* Class */
0xfe, /* Ins: Yubikey Import Asym. Key. */
PIV_ALGORITHM_RSA, /* P1 */
keyref, /* P2 */
apdudatalen,/* Lc */
apdudata, /* data */
NULL, NULL, NULL);
if (err)
goto leave;
/* Write the public key to the cert object. */
xfree (apdudata);
err = concat_tlv_list (0, &apdudata, &apdudatalen,
(int)0x81, (size_t)rsa_n_len, rsa_n,
(int)0x82, (size_t)rsa_e_len, rsa_e,
(int)0, (size_t)0, NULL);
if (err)
goto leave;
tmpl[0] = PIV_ALGORITHM_RSA;
- err = put_data (app->slot, dobj->tag,
+ err = put_data (app_get_slot (app), dobj->tag,
(int)0x80, (size_t)1, tmpl,
(int)0x7f49, (size_t)apdudatalen, apdudata,
(int)0, (size_t)0, NULL);
leave:
xfree (rsa_dpm1);
xfree (rsa_dqm1);
xfree (rsa_qinv);
xfree (apdudata);
return err;
}
/* Helper for do_writekey; here the ECC part. BUF, BUFLEN, and DEPTH
* are the current parser state of the S-expression with the key. */
static gpg_error_t
writekey_ecc (app_t app, data_object_t dobj, int keyref,
const unsigned char *buf, size_t buflen, int depth)
{
gpg_error_t err;
const unsigned char *tok;
size_t toklen;
int last_depth1, last_depth2;
int mechanism = 0;
const unsigned char *ecc_q = NULL;
const unsigned char *ecc_d = NULL;
size_t ecc_q_len, ecc_d_len;
unsigned char *apdudata = NULL;
size_t apdudatalen;
unsigned char tmpl[1];
last_depth1 = depth;
while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))
&& depth && depth >= last_depth1)
{
if (tok)
{
err = gpg_error (GPG_ERR_UNKNOWN_SEXP);
goto leave;
}
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
goto leave;
if (tok && toklen == 5 && !memcmp (tok, "curve", 5))
{
char *name;
const char *xname;
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
goto leave;
name = xtrymalloc (toklen+1);
if (!name)
{
err = gpg_error_from_syserror ();
goto leave;
}
memcpy (name, tok, toklen);
name[toklen] = 0;
/* Canonicalize the curve name. We use the openpgp
* functions here because Libgcrypt has no generic curve
* alias lookup feature and the PIV suppotred curves alre
* also supported by OpenPGP. */
xname = openpgp_oid_to_curve (openpgp_curve_to_oid (name, NULL), 0);
xfree (name);
if (xname && !strcmp (xname, "nistp256"))
mechanism = PIV_ALGORITHM_ECC_P256;
else if (xname && !strcmp (xname, "nistp384"))
mechanism = PIV_ALGORITHM_ECC_P384;
else
{
err = gpg_error (GPG_ERR_UNKNOWN_CURVE);
goto leave;
}
}
else if (tok && toklen == 1)
{
const unsigned char **mpi;
size_t *mpi_len;
switch (*tok)
{
case 'q': mpi = &ecc_q; mpi_len = &ecc_q_len; break;
case 'd': mpi = &ecc_d; mpi_len = &ecc_d_len; break;
default: mpi = NULL; mpi_len = NULL; break;
}
if (mpi && *mpi)
{
err = gpg_error (GPG_ERR_DUP_VALUE);
goto leave;
}
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
goto leave;
if (tok && mpi)
{
/* Strip off leading zero bytes and save. */
for (;toklen && !*tok; toklen--, tok++)
;
*mpi = tok;
*mpi_len = toklen;
}
}
/* Skip until end of list. */
last_depth2 = depth;
while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))
&& depth && depth >= last_depth2)
;
if (err)
goto leave;
}
/* Check that we have all parameters. */
if (!mechanism || !ecc_q || !ecc_d)
{
err = gpg_error (GPG_ERR_BAD_SECKEY);
goto leave;
}
if (opt.verbose)
log_info ("ECC private key size is %u bytes\n", (unsigned int)ecc_d_len);
err = concat_tlv_list (1, &apdudata, &apdudatalen,
(int)0x06, (size_t)ecc_d_len, ecc_d,
(int)0, (size_t)0, NULL);
if (err)
goto leave;
- err = iso7816_send_apdu (app->slot,
+ err = iso7816_send_apdu (app_get_slot (app),
-1, /* Use command chaining. */
0, /* Class */
0xfe, /* Ins: Yubikey Import Asym. Key. */
mechanism, /* P1 */
keyref, /* P2 */
apdudatalen,/* Lc */
apdudata, /* data */
NULL, NULL, NULL);
if (err)
goto leave;
/* Write the public key to the cert object. */
xfree (apdudata);
err = concat_tlv_list (0, &apdudata, &apdudatalen,
(int)0x86, (size_t)ecc_q_len, ecc_q,
(int)0, (size_t)0, NULL);
if (err)
goto leave;
tmpl[0] = mechanism;
- err = put_data (app->slot, dobj->tag,
+ err = put_data (app_get_slot (app), dobj->tag,
(int)0x80, (size_t)1, tmpl,
(int)0x7f49, (size_t)apdudatalen, apdudata,
(int)0, (size_t)0, NULL);
leave:
xfree (apdudata);
return err;
}
/* Write a key to a slot. This command requires proprietary
* extensions of the PIV specification and is thus only implemnted for
* supported card types. The input is a canonical encoded
* S-expression with the secret key in KEYDATA and its length (for
* assertion) in KEYDATALEN. KEYREFSTR needs to be the usual 2
* hexdigit slot number prefixed with "PIV." PINCB and PINCB_ARG are
* not used for PIV cards.
*
* Supported FLAGS are:
* APP_WRITEKEY_FLAG_FORCE Overwrite existing key.
*/
static gpg_error_t
do_writekey (app_t app, ctrl_t ctrl,
const char *keyrefstr, unsigned int flags,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const unsigned char *keydata, size_t keydatalen)
{
gpg_error_t err;
int force = !!(flags & APP_WRITEKEY_FLAG_FORCE);
data_object_t dobj;
int keyref;
const unsigned char *buf, *tok;
size_t buflen, toklen;
int depth;
(void)ctrl;
(void)pincb;
(void)pincb_arg;
if (!app->app_local->flags.yubikey)
{
err = gpg_error (GPG_ERR_NOT_SUPPORTED);
goto leave;
}
/* Check keyref and test whether a key already exists. */
dobj = find_dobj_by_keyref (app, keyrefstr);
if ((keyref = keyref_from_dobj (dobj)) == -1)
{
err = gpg_error (GPG_ERR_INV_ID);
goto leave;
}
err = does_key_exist (app, dobj, 0, force);
if (err)
goto leave;
/* Parse the S-expression with the key. */
buf = keydata;
buflen = keydatalen;
depth = 0;
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
goto leave;
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
goto leave;
if (!tok || toklen != 11 || memcmp ("private-key", tok, toklen))
{
if (!tok)
;
else if (toklen == 21 && !memcmp ("protected-private-key", tok, toklen))
log_info ("protected-private-key passed to writekey\n");
else if (toklen == 20 && !memcmp ("shadowed-private-key", tok, toklen))
log_info ("shadowed-private-key passed to writekey\n");
err = gpg_error (GPG_ERR_BAD_SECKEY);
goto leave;
}
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
goto leave;
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
goto leave;
/* First clear an existing key. We do this by writing an empty 7f49
* tag. This will return GPG_ERR_NO_PUBKEY on a later read. */
flush_cached_data (app, dobj->tag);
- err = put_data (app->slot, dobj->tag,
+ err = put_data (app_get_slot (app), dobj->tag,
(int)0x7f49, (size_t)0, "",
(int)0, (size_t)0, NULL);
if (err)
{
log_error ("piv: failed to clear the cert DO %s: %s\n",
dobj->keyref, gpg_strerror (err));
goto leave;
}
/* Divert to the algo specific implementation. */
if (tok && toklen == 3 && memcmp ("rsa", tok, toklen) == 0)
err = writekey_rsa (app, dobj, keyref, buf, buflen, depth);
else if (tok && toklen == 3 && memcmp ("ecc", tok, toklen) == 0)
err = writekey_ecc (app, dobj, keyref, buf, buflen, depth);
else
err = gpg_error (GPG_ERR_WRONG_PUBKEY_ALGO);
if (err)
{
/* A PIN is not required, thus use a better error code. */
if (gpg_err_code (err) == GPG_ERR_BAD_PIN)
err = gpg_error (GPG_ERR_NO_AUTH);
log_error (_("failed to store the key: %s\n"), gpg_strerror (err));
}
leave:
return err;
}
/* Parse an RSA response object, consisting of the content of tag
* 0x7f49, into a gcrypt s-expression object and store that R_SEXP.
* On error NULL is stored at R_SEXP. */
static gpg_error_t
genkey_parse_rsa (const unsigned char *data, size_t datalen,
gcry_sexp_t *r_sexp)
{
gpg_error_t err;
const unsigned char *m, *e;
unsigned char *mbuf = NULL;
unsigned char *ebuf = NULL;
size_t mlen, elen;
*r_sexp = NULL;
m = find_tlv (data, datalen, 0x0081, &mlen);
if (!m)
{
log_error (_("response does not contain the RSA modulus\n"));
err = gpg_error (GPG_ERR_CARD);
goto leave;
}
e = find_tlv (data, datalen, 0x0082, &elen);
if (!e)
{
log_error (_("response does not contain the RSA public exponent\n"));
err = gpg_error (GPG_ERR_CARD);
goto leave;
}
for (; mlen && !*m; mlen--, m++) /* Strip leading zeroes */
;
for (; elen && !*e; elen--, e++) /* Strip leading zeroes */
;
mbuf = xtrymalloc (mlen + 1);
if (!mbuf)
{
err = gpg_error_from_syserror ();
goto leave;
}
/* Prepend numbers with a 0 if needed. */
if (mlen && (*m & 0x80))
{
*mbuf = 0;
memcpy (mbuf+1, m, mlen);
mlen++;
}
else
memcpy (mbuf, m, mlen);
ebuf = xtrymalloc (elen + 1);
if (!ebuf)
{
err = gpg_error_from_syserror ();
goto leave;
}
/* Prepend numbers with a 0 if needed. */
if (elen && (*e & 0x80))
{
*ebuf = 0;
memcpy (ebuf+1, e, elen);
elen++;
}
else
memcpy (ebuf, e, elen);
err = gcry_sexp_build (r_sexp, NULL, "(public-key(rsa(n%b)(e%b)))",
(int)mlen, mbuf, (int)elen, ebuf);
leave:
xfree (mbuf);
xfree (ebuf);
return err;
}
/* Parse an ECC response object, consisting of the content of tag
* 0x7f49, into a gcrypt s-expression object and store that R_SEXP.
* On error NULL is stored at R_SEXP. MECHANISM specifies the
* curve. */
static gpg_error_t
genkey_parse_ecc (const unsigned char *data, size_t datalen, int mechanism,
gcry_sexp_t *r_sexp)
{
gpg_error_t err;
const unsigned char *ecc_q;
size_t ecc_qlen;
const char *curve;
*r_sexp = NULL;
ecc_q = find_tlv (data, datalen, 0x0086, &ecc_qlen);
if (!ecc_q)
{
log_error (_("response does not contain the EC public key\n"));
err = gpg_error (GPG_ERR_CARD);
goto leave;
}
if (mechanism == PIV_ALGORITHM_ECC_P256)
curve = "nistp256";
else if (mechanism == PIV_ALGORITHM_ECC_P384)
curve = "nistp384";
else
{
err = gpg_error (GPG_ERR_BUG); /* Call with wrong parameters. */
goto leave;
}
err = gcry_sexp_build (r_sexp, NULL, "(public-key(ecc(curve%s)(q%b)))",
curve, (int)ecc_qlen, ecc_q);
leave:
return err;
}
/* Create a new keypair for KEYREF. If KEYTYPE is NULL a default
* keytype is selected, else it may be one of the strings:
* "rsa2048", "nistp256, or "nistp384".
*
* Supported FLAGS are:
* APP_GENKEY_FLAG_FORCE Overwrite existing key.
*
* Note that CREATETIME is not used for PIV cards.
*
* Because there seems to be no way to read the public key we need to
* retrieve it from a certificate. The GnuPG system however requires
* the use of app_readkey to fetch the public key from the card to
* create the certificate; to support this we temporary store the
* generated public key in the local context for use by app_readkey.
*/
static gpg_error_t
do_genkey (app_t app, ctrl_t ctrl, const char *keyrefstr, const char *keytype,
unsigned int flags, time_t createtime,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg)
{
gpg_error_t err;
data_object_t dobj;
unsigned char *buffer = NULL;
size_t buflen;
int force = !!(flags & APP_GENKEY_FLAG_FORCE);
int mechanism;
time_t start_at;
int keyref;
unsigned char tmpl[5];
size_t tmpllen;
const unsigned char *keydata;
size_t keydatalen;
(void)ctrl;
(void)createtime;
(void)pincb;
(void)pincb_arg;
if (!keytype)
keytype = "rsa2048";
if (!strcmp (keytype, "rsa2048"))
mechanism = PIV_ALGORITHM_RSA;
else if (!strcmp (keytype, "nistp256"))
mechanism = PIV_ALGORITHM_ECC_P256;
else if (!strcmp (keytype, "nistp384"))
mechanism = PIV_ALGORITHM_ECC_P384;
else
return gpg_error (GPG_ERR_UNKNOWN_CURVE);
/* We flush the cache to increase the I/O traffic before a key
* generation. This _might_ help the card to gather more entropy
* and is anyway a prerequisite for does_key_exist. */
flush_cached_data (app, 0);
/* Check whether a key already exists. */
dobj = find_dobj_by_keyref (app, keyrefstr);
if ((keyref = keyref_from_dobj (dobj)) == -1)
{
err = gpg_error (GPG_ERR_INV_ID);
goto leave;
}
err = does_key_exist (app, dobj, 1, force);
if (err)
goto leave;
/* Create the key. */
log_info (_("please wait while key is being generated ...\n"));
start_at = time (NULL);
tmpl[0] = 0xac;
tmpl[1] = 3;
tmpl[2] = 0x80;
tmpl[3] = 1;
tmpl[4] = mechanism;
tmpllen = 5;
- err = iso7816_generate_keypair (app->slot, 0, 0, keyref,
+ err = iso7816_generate_keypair (app_get_slot (app), 0, 0, keyref,
tmpl, tmpllen, 0, &buffer, &buflen);
if (err)
{
/* A PIN is not required, thus use a better error code. */
if (gpg_err_code (err) == GPG_ERR_BAD_PIN)
err = gpg_error (GPG_ERR_NO_AUTH);
log_error (_("generating key failed\n"));
return err;
}
{
int nsecs = (int)(time (NULL) - start_at);
log_info (ngettext("key generation completed (%d second)\n",
"key generation completed (%d seconds)\n",
nsecs), nsecs);
}
/* Parse the result and store it as an s-expression in a dedicated
* cache for later retrieval by app_readkey. */
keydata = find_tlv (buffer, buflen, 0x7F49, &keydatalen);
if (!keydata || !keydatalen)
{
err = gpg_error (GPG_ERR_CARD);
log_error (_("response does not contain the public key data\n"));
goto leave;
}
tmpl[0] = mechanism;
flush_cached_data (app, dobj->tag);
- err = put_data (app->slot, dobj->tag,
+ err = put_data (app_get_slot (app), dobj->tag,
(int)0x80, (size_t)1, tmpl,
(int)0x7f49, (size_t)keydatalen, keydata,
(int)0, (size_t)0, NULL);
if (err)
{
log_error ("piv: failed to write key to the cert DO %s: %s\n",
dobj->keyref, gpg_strerror (err));
goto leave;
}
leave:
xfree (buffer);
return err;
}
/* Write the certificate (CERT,CERTLEN) to the card at CERTREFSTR.
* CERTREFSTR is either the OID of the certificate's container data
* object or of the form "PIV.<two_hexdigit_keyref>". */
static gpg_error_t
do_writecert (app_t app, ctrl_t ctrl,
const char *certrefstr,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const unsigned char *cert, size_t certlen)
{
gpg_error_t err;
data_object_t dobj;
unsigned char *pk = NULL;
unsigned char *orig_pk = NULL;
size_t pklen, orig_pklen;
(void)ctrl;
(void)pincb; /* Not used; instead authentication is needed. */
(void)pincb_arg;
if (!certlen)
return gpg_error (GPG_ERR_INV_CERT_OBJ);
dobj = find_dobj_by_keyref (app, certrefstr);
if (!dobj || !*dobj->keyref)
return gpg_error (GPG_ERR_INV_ID);
flush_cached_data (app, dobj->tag);
/* Check that the public key parameters from the certificate match
* an already stored key. Note that we do not allow writing a
* certificate if no key has yet been created (GPG_ERR_NOT_FOUND) or
* if there is a problem reading the public key from the certificate
* GPG_ERR_NO_PUBKEY). We enforce this because otherwise the only
* way to detect whether a key exists is by trying to use that
* key. */
- err = do_readkey (app, certrefstr, &orig_pk, &orig_pklen);
+ err = do_readkey (app, ctrl, certrefstr, 0, &orig_pk, &orig_pklen);
if (err)
{
if (gpg_err_code (err) == GPG_ERR_NOT_FOUND)
err = gpg_error (GPG_ERR_NO_SECKEY); /* Use a better error code. */
goto leave;
}
/* Compare pubkeys. */
err = app_help_pubkey_from_cert (cert, certlen, &pk, &pklen);
if (err)
goto leave; /* No public key in new certificate. */
if (orig_pklen != pklen || memcmp (orig_pk, pk, pklen))
{
err = gpg_error (GPG_ERR_CONFLICT);
goto leave;
}
- err = put_data (app->slot, dobj->tag,
+ err = put_data (app_get_slot (app), dobj->tag,
(int)0x70, (size_t)certlen, cert,/* Certificate */
(int)0x71, (size_t)1, "", /* No compress */
(int)0xfe, (size_t)0, "", /* Empty LRC. */
(int)0, (size_t)0, NULL);
/* A PIN is not required, thus use a better error code. */
if (gpg_err_code (err) == GPG_ERR_BAD_PIN)
err = gpg_error (GPG_ERR_NO_AUTH);
if (err)
log_error ("piv: failed to write cert to %s: %s\n",
dobj->keyref, gpg_strerror (err));
leave:
xfree (pk);
xfree (orig_pk);
return err;
}
+/* Process the various keygrip based info requests. */
+static gpg_error_t
+do_with_keygrip (app_t app, ctrl_t ctrl, int action,
+ const char *want_keygripstr)
+{
+ gpg_error_t err;
+ char *keygripstr = NULL;
+ char *serialno = NULL;
+ char idbuf[20];
+ int data = 0;
+ int i, tag, dummy_got_cert;
+
+ /* First a quick check for valid parameters. */
+ switch (action)
+ {
+ case KEYGRIP_ACTION_LOOKUP:
+ if (!want_keygripstr)
+ {
+ err = gpg_error (GPG_ERR_NOT_FOUND);
+ goto leave;
+ }
+ break;
+ case KEYGRIP_ACTION_SEND_DATA:
+ data = 1;
+ break;
+ case KEYGRIP_ACTION_WRITE_STATUS:
+ break;
+ default:
+ err = gpg_error (GPG_ERR_INV_ARG);
+ goto leave;
+ }
+
+ /* Allocate the s/n string if needed. */
+ if (action != KEYGRIP_ACTION_LOOKUP)
+ {
+ serialno = app_get_serialno (app);
+ if (!serialno)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+ }
+
+ for (i = 0; (tag = data_objects[i].tag); i++)
+ {
+ if (!data_objects[i].keypair)
+ continue;
+
+ xfree (keygripstr);
+ if (get_keygrip_by_tag (app, tag, &keygripstr, &dummy_got_cert))
+ continue;
+
+ if (action == KEYGRIP_ACTION_LOOKUP)
+ {
+ if (!strcmp (keygripstr, want_keygripstr))
+ {
+ err = 0; /* Found */
+ goto leave;
+ }
+ }
+ else if (!want_keygripstr || !strcmp (keygripstr, want_keygripstr))
+ {
+ snprintf (idbuf, sizeof idbuf, "PIV.%s", data_objects[i].keyref);
+ send_keyinfo (ctrl, data, keygripstr, serialno, idbuf);
+ if (want_keygripstr)
+ {
+ err = 0; /* Found */
+ goto leave;
+ }
+ }
+ }
+
+ /* Return an error so that the dispatcher keeps on looping over the
+ * other applications. For clarity we use a different error code
+ * when listing all keys. Note that in lookup mode WANT_KEYGRIPSTR
+ * is not NULL. */
+ if (!want_keygripstr)
+ err = gpg_error (GPG_ERR_TRUE);
+ else
+ err = gpg_error (GPG_ERR_NOT_FOUND);
+
+ leave:
+ xfree (keygripstr);
+ xfree (serialno);
+ return err;
+}
+
+
+/* Reselect the application. This is used by cards which support
+ * on-the-fly switching between applications. */
+static gpg_error_t
+do_reselect (app_t app, ctrl_t ctrl)
+{
+ gpg_error_t err;
+
+ (void)ctrl;
+
+ /* An extra check which should not be necessary because the caller
+ * should have made sure that a re-select is only called for
+ * approriate cards. */
+ if (!app->app_local->flags.yubikey)
+ return gpg_error (GPG_ERR_NOT_SUPPORTED);
+
+ err = iso7816_select_application (app_get_slot (app),
+ piv_aid, sizeof piv_aid, 0x0001);
+ return err;
+}
+
+
/* Select the PIV application on the card in SLOT. This function must
* be used before any other PIV application functions. */
gpg_error_t
app_select_piv (app_t app)
{
- static char const aid[] = { 0xA0, 0x00, 0x00, 0x03, 0x08, /* RID=NIST */
- 0x00, 0x00, 0x10, 0x00 /* PIX=PIV */ };
- int slot = app->slot;
+ int slot = app_get_slot (app);
gpg_error_t err;
unsigned char *apt = NULL;
size_t aptlen;
const unsigned char *s;
size_t n;
/* Note that we select using the AID without the 2 octet version
* number. This allows for better reporting of future specs. We
* need to use the use-zero-for-P2-flag. */
- err = iso7816_select_application_ext (slot, aid, sizeof aid, 0x0001,
+ err = iso7816_select_application_ext (slot, piv_aid, sizeof piv_aid, 0x0001,
&apt, &aptlen);
if (err)
goto leave;
- app->apptype = "PIV";
+ app->apptype = APPTYPE_PIV;
app->did_chv1 = 0;
app->did_chv2 = 0;
app->did_chv3 = 0;
app->app_local = NULL;
/* Check the Application Property Template. */
if (opt.verbose)
{
/* We use a separate log_info to avoid the "DBG:" prefix. */
log_info ("piv: APT=");
log_printhex (apt, aptlen, "");
}
s = find_tlv (apt, aptlen, 0x4F, &n);
- if (!s || n != 6 || memcmp (s, aid+5, 4))
+ if (!s || n != 6 || memcmp (s, piv_aid+5, 4))
{
/* The PIX does not match. */
log_error ("piv: missing or invalid DO 0x4F in APT\n");
err = gpg_error (GPG_ERR_CARD);
goto leave;
}
if (s[4] != 1 || s[5] != 0)
{
log_error ("piv: unknown PIV version %u.%u\n", s[4], s[5]);
err = gpg_error (GPG_ERR_CARD);
goto leave;
}
app->appversion = ((s[4] << 8) | s[5]);
s = find_tlv (apt, aptlen, 0x79, &n);
if (!s || n < 7)
{
log_error ("piv: missing or invalid DO 0x79 in APT\n");
err = gpg_error (GPG_ERR_CARD);
goto leave;
}
s = find_tlv (s, n, 0x4F, &n);
- if (!s || n != 5 || memcmp (s, aid, 5))
+ if (!s || n != 5 || memcmp (s, piv_aid, 5))
{
/* The RID does not match. */
log_error ("piv: missing or invalid DO 0x79.4F in APT\n");
err = gpg_error (GPG_ERR_CARD);
goto leave;
}
app->app_local = xtrycalloc (1, sizeof *app->app_local);
if (!app->app_local)
{
err = gpg_error_from_syserror ();
goto leave;
}
- if (app->cardtype && !strcmp (app->cardtype, "yubikey"))
+ if (app->card->cardtype == CARDTYPE_YUBIKEY)
app->app_local->flags.yubikey = 1;
/* FIXME: Parse the optional and conditional DOs in the APT. */
if (opt.verbose)
dump_all_do (slot);
app->fnc.deinit = do_deinit;
+ app->fnc.reselect = do_reselect;
app->fnc.learn_status = do_learn_status;
app->fnc.readcert = do_readcert;
app->fnc.readkey = do_readkey;
app->fnc.getattr = do_getattr;
app->fnc.setattr = do_setattr;
app->fnc.writecert = do_writecert;
app->fnc.writekey = do_writekey;
app->fnc.genkey = do_genkey;
app->fnc.sign = do_sign;
app->fnc.auth = do_auth;
app->fnc.decipher = do_decipher;
app->fnc.change_pin = do_change_chv;
app->fnc.check_pin = do_check_chv;
+ app->fnc.with_keygrip = do_with_keygrip;
leave:
xfree (apt);
if (err)
do_deinit (app);
return err;
}
diff --git a/scd/app-sc-hsm.c b/scd/app-sc-hsm.c
index 8094b2463..16d25b581 100644
--- a/scd/app-sc-hsm.c
+++ b/scd/app-sc-hsm.c
@@ -1,2084 +1,2090 @@
/* app-sc-hsm.c - The SmartCard-HSM card application (www.smartcard-hsm.com).
* Copyright (C) 2005 Free Software Foundation, Inc.
* Copyright (C) 2014 Andreas Schwier <andreas.schwier@cardcontact.de>
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
/*
Code in this driver is based on app-p15.c with modifications.
*/
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <time.h>
#include "scdaemon.h"
#include "iso7816.h"
-#include "app-common.h"
#include "../common/tlv.h"
#include "apdu.h"
/* The AID of the SmartCard-HSM applet. */
static char const sc_hsm_aid[] = { 0xE8, 0x2B, 0x06, 0x01, 0x04, 0x01, 0x81,
0xC3, 0x1F, 0x02, 0x01 };
/* Special file identifier for SmartCard-HSM */
typedef enum
{
SC_HSM_PRKD_PREFIX = 0xC4,
SC_HSM_CD_PREFIX = 0xC8,
SC_HSM_DCOD_PREFIX = 0xC9,
SC_HSM_CA_PREFIX = 0xCA,
SC_HSM_KEY_PREFIX = 0xCC,
SC_HSM_EE_PREFIX = 0xCE
} fid_prefix_type_t;
/* The key types supported by the SmartCard-HSM */
typedef enum
{
KEY_TYPE_RSA,
KEY_TYPE_ECC
} key_type_t;
/* A bit array with for the key usage flags from the
commonKeyAttributes. */
struct keyusage_flags_s
{
unsigned int encrypt: 1;
unsigned int decrypt: 1;
unsigned int sign: 1;
unsigned int sign_recover: 1;
unsigned int wrap: 1;
unsigned int unwrap: 1;
unsigned int verify: 1;
unsigned int verify_recover: 1;
unsigned int derive: 1;
unsigned int non_repudiation: 1;
};
typedef struct keyusage_flags_s keyusage_flags_t;
/* This is an object to store information about a Certificate
Directory File (CDF) in a format suitable for further processing by
us. To keep memory management, simple we use a linked list of
items; i.e. one such object represents one certificate and the list
the entire CDF. */
struct cdf_object_s
{
/* Link to next item when used in a linked list. */
struct cdf_object_s *next;
/* Length and allocated buffer with the Id of this object. */
size_t objidlen;
unsigned char *objid;
/* To avoid reading a certificate more than once, we cache it in an
allocated memory IMAGE of IMAGELEN. */
size_t imagelen;
unsigned char *image;
/* EF containing certificate */
unsigned short fid;
};
typedef struct cdf_object_s *cdf_object_t;
/* This is an object to store information about a Private Key
Directory File (PrKDF) in a format suitable for further processing
by us. To keep memory management, simple we use a linked list of
items; i.e. one such object represents one certificate and the list
the entire PrKDF. */
struct prkdf_object_s
{
/* Link to next item when used in a linked list. */
struct prkdf_object_s *next;
/* Key type */
key_type_t keytype;
/* Key size in bits or 0 if unknown */
size_t keysize;
/* Length and allocated buffer with the Id of this object. */
size_t objidlen;
unsigned char *objid;
/* The key's usage flags. */
keyusage_flags_t usageflags;
/* The keyReference */
unsigned char key_reference;
};
typedef struct prkdf_object_s *prkdf_object_t;
/* Context local to this application. */
struct app_local_s
{
/* Information on all certificates. */
cdf_object_t certificate_info;
/* Information on all trusted certificates. */
cdf_object_t trusted_certificate_info;
/* Information on all private keys. */
prkdf_object_t private_key_info;
};
/*** Local prototypes. ***/
static gpg_error_t readcert_by_cdf (app_t app, cdf_object_t cdf,
unsigned char **r_cert, size_t *r_certlen);
/* Release the CDF object A */
static void
release_cdflist (cdf_object_t a)
{
while (a)
{
cdf_object_t tmp = a->next;
xfree (a->image);
xfree (a->objid);
xfree (a);
a = tmp;
}
}
/* Release the PrKDF object A. */
static void
release_prkdflist (prkdf_object_t a)
{
while (a)
{
prkdf_object_t tmp = a->next;
xfree (a->objid);
xfree (a);
a = tmp;
}
}
/* Release all local resources. */
static void
do_deinit (app_t app)
{
if (app && app->app_local)
{
release_cdflist (app->app_local->certificate_info);
release_cdflist (app->app_local->trusted_certificate_info);
release_prkdflist (app->app_local->private_key_info);
xfree (app->app_local);
app->app_local = NULL;
}
}
/* Get the list of EFs from the SmartCard-HSM.
* On success a dynamically buffer containing the EF list is returned.
* The caller is responsible for freeing the buffer.
*/
static gpg_error_t
list_ef (int slot, unsigned char **result, size_t *resultlen)
{
int sw;
if (!result || !resultlen)
return gpg_error (GPG_ERR_INV_VALUE);
*result = NULL;
*resultlen = 0;
sw = apdu_send_le (slot, 1, 0x80, 0x58, 0x00, 0x00, -1, NULL, 65536,
result, resultlen);
if (sw != SW_SUCCESS)
{
/* Make sure that pending buffers are released. */
xfree (*result);
*result = NULL;
*resultlen = 0;
}
return iso7816_map_sw (sw);
}
/* Do a select and a read for the file with EFID. EFID_DESC is a
description of the EF to be used with error messages. On success
BUFFER and BUFLEN contain the entire content of the EF. The caller
must free BUFFER only on success. */
static gpg_error_t
select_and_read_binary (int slot, unsigned short efid, const char *efid_desc,
unsigned char **buffer, size_t *buflen, int maxread)
{
gpg_error_t err;
unsigned char cdata[4];
int sw;
cdata[0] = 0x54; /* Create ISO 7861-4 odd ins READ BINARY */
cdata[1] = 0x02;
cdata[2] = 0x00;
cdata[3] = 0x00;
sw = apdu_send_le(slot, 1, 0x00, 0xB1, efid >> 8, efid & 0xFF,
4, cdata, maxread, buffer, buflen);
if (sw == SW_EOF_REACHED)
sw = SW_SUCCESS;
err = iso7816_map_sw (sw);
if (err)
{
log_error ("error reading %s (0x%04X): %s\n",
efid_desc, efid, gpg_strerror (err));
return err;
}
return 0;
}
/* Parse a cert Id string (or a key Id string) and return the binary
object Id string in a newly allocated buffer stored at R_OBJID and
R_OBJIDLEN. On Error NULL will be stored there and an error code
returned. On success caller needs to free the buffer at R_OBJID. */
static gpg_error_t
parse_certid (const char *certid, unsigned char **r_objid, size_t *r_objidlen)
{
const char *s;
size_t objidlen;
unsigned char *objid;
int i;
*r_objid = NULL;
*r_objidlen = 0;
if (strncmp (certid, "HSM.", 4))
return gpg_error (GPG_ERR_INV_ID);
certid += 4;
for (s=certid, objidlen=0; hexdigitp (s); s++, objidlen++)
;
if (*s || !objidlen || (objidlen%2))
return gpg_error (GPG_ERR_INV_ID);
objidlen /= 2;
objid = xtrymalloc (objidlen);
if (!objid)
return gpg_error_from_syserror ();
for (s=certid, i=0; i < objidlen; i++, s+=2)
objid[i] = xtoi_2 (s);
*r_objid = objid;
*r_objidlen = objidlen;
return 0;
}
/* Find a certificate object by the certificate ID CERTID and store a
pointer to it at R_CDF. */
static gpg_error_t
cdf_object_from_certid (app_t app, const char *certid, cdf_object_t *r_cdf)
{
gpg_error_t err;
size_t objidlen;
unsigned char *objid;
cdf_object_t cdf;
err = parse_certid (certid, &objid, &objidlen);
if (err)
return err;
for (cdf = app->app_local->certificate_info; cdf; cdf = cdf->next)
if (cdf->objidlen == objidlen && !memcmp (cdf->objid, objid, objidlen))
break;
if (!cdf)
for (cdf = app->app_local->trusted_certificate_info; cdf; cdf = cdf->next)
if (cdf->objidlen == objidlen && !memcmp (cdf->objid, objid, objidlen))
break;
xfree (objid);
if (!cdf)
return gpg_error (GPG_ERR_NOT_FOUND);
*r_cdf = cdf;
return 0;
}
/* Find a private key object by the key Id string KEYIDSTR and store a
pointer to it at R_PRKDF. */
static gpg_error_t
prkdf_object_from_keyidstr (app_t app, const char *keyidstr,
prkdf_object_t *r_prkdf)
{
gpg_error_t err;
size_t objidlen;
unsigned char *objid;
prkdf_object_t prkdf;
err = parse_certid (keyidstr, &objid, &objidlen);
if (err)
return err;
for (prkdf = app->app_local->private_key_info; prkdf; prkdf = prkdf->next)
if (prkdf->objidlen == objidlen && !memcmp (prkdf->objid, objid, objidlen))
break;
xfree (objid);
if (!prkdf)
return gpg_error (GPG_ERR_NOT_FOUND);
*r_prkdf = prkdf;
return 0;
}
/* Parse the BIT STRING with the keyUsageFlags from the
CommonKeyAttributes. */
static gpg_error_t
parse_keyusage_flags (const unsigned char *der, size_t derlen,
keyusage_flags_t *usageflags)
{
unsigned int bits, mask;
int i, unused, full;
memset (usageflags, 0, sizeof *usageflags);
if (!derlen)
return gpg_error (GPG_ERR_INV_OBJ);
unused = *der++; derlen--;
if ((!derlen && unused) || unused/8 > derlen)
return gpg_error (GPG_ERR_ENCODING_PROBLEM);
full = derlen - (unused+7)/8;
unused %= 8;
mask = 0;
for (i=1; unused; i <<= 1, unused--)
mask |= i;
/* First octet */
if (derlen)
{
bits = *der++; derlen--;
if (full)
full--;
else
{
bits &= ~mask;
mask = 0;
}
}
else
bits = 0;
if ((bits & 0x80)) usageflags->encrypt = 1;
if ((bits & 0x40)) usageflags->decrypt = 1;
if ((bits & 0x20)) usageflags->sign = 1;
if ((bits & 0x10)) usageflags->sign_recover = 1;
if ((bits & 0x08)) usageflags->wrap = 1;
if ((bits & 0x04)) usageflags->unwrap = 1;
if ((bits & 0x02)) usageflags->verify = 1;
if ((bits & 0x01)) usageflags->verify_recover = 1;
/* Second octet. */
if (derlen)
{
bits = *der++; derlen--;
if (full)
full--;
else
{
bits &= ~mask;
}
}
else
bits = 0;
if ((bits & 0x80)) usageflags->derive = 1;
if ((bits & 0x40)) usageflags->non_repudiation = 1;
return 0;
}
/* Read and parse a Private Key Directory File containing a single key
description in PKCS#15 format. For each private key a matching
certificate description is created, if the certificate EF exists
and contains a X.509 certificate.
Example data:
0000 30 2A 30 13 0C 11 4A 6F 65 20 44 6F 65 20 28 52 0*0...Joe Doe (R
0010 53 41 32 30 34 38 29 30 07 04 01 01 03 02 02 74 SA2048)0.......t
0020 A1 0A 30 08 30 02 04 00 02 02 08 00 ..0.0.......
Decoded example:
SEQUENCE SIZE( 42 )
SEQUENCE SIZE( 19 )
UTF8-STRING SIZE( 17 ) -- label
0000 4A 6F 65 20 44 6F 65 20 28 52 53 41 32 30 34 38 Joe Doe (RSA2048
0010 29 )
SEQUENCE SIZE( 7 )
OCTET-STRING SIZE( 1 ) -- id
0000 01
BIT-STRING SIZE( 2 ) -- key usage
0000 02 74
A1 [ CONTEXT 1 ] IMPLICIT SEQUENCE SIZE( 10 )
SEQUENCE SIZE( 8 )
SEQUENCE SIZE( 2 )
OCTET-STRING SIZE( 0 ) -- empty path, req object in PKCS#15
INTEGER SIZE( 2 ) -- modulus size in bits
0000 08 00
*/
static gpg_error_t
read_ef_prkd (app_t app, unsigned short fid, prkdf_object_t *prkdresult,
cdf_object_t *cdresult)
{
gpg_error_t err;
unsigned char *buffer = NULL;
size_t buflen;
const unsigned char *p;
size_t n, objlen, hdrlen;
int class, tag, constructed, ndef;
int i;
const unsigned char *pp;
size_t nn;
int where;
const char *errstr = NULL;
prkdf_object_t prkdf = NULL;
cdf_object_t cdf = NULL;
unsigned long ul;
const unsigned char *objid;
size_t objidlen;
keyusage_flags_t usageflags;
const char *s;
key_type_t keytype;
size_t keysize;
if (!fid)
return gpg_error (GPG_ERR_NO_DATA); /* No private keys. */
- err = select_and_read_binary (app->slot, fid, "PrKDF", &buffer, &buflen, 255);
+ err = select_and_read_binary (app_get_slot (app),
+ fid, "PrKDF", &buffer, &buflen, 255);
if (err)
return err;
p = buffer;
n = buflen;
err = parse_ber_header (&p, &n, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > n || (tag != TAG_SEQUENCE && tag != 0x00)))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
{
log_error ("error parsing PrKDF record: %s\n", gpg_strerror (err));
goto leave;
}
keytype = tag == 0x00 ? KEY_TYPE_ECC : KEY_TYPE_RSA;
pp = p;
nn = objlen;
p += objlen;
n -= objlen;
/* Parse the commonObjectAttributes. */
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nn || tag != TAG_SEQUENCE))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
{
const unsigned char *ppp = pp;
size_t nnn = objlen;
pp += objlen;
nn -= objlen;
/* Search the optional AuthId. We need to skip the optional Label
(UTF8STRING) and the optional CommonObjectFlags (BITSTRING). */
where = __LINE__;
err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nnn || class != CLASS_UNIVERSAL))
err = gpg_error (GPG_ERR_INV_OBJ);
if (gpg_err_code (err) == GPG_ERR_EOF)
goto no_authid;
if (err)
goto parse_error;
if (tag == TAG_UTF8_STRING)
{
ppp += objlen; /* Skip the Label. */
nnn -= objlen;
where = __LINE__;
err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nnn || class != CLASS_UNIVERSAL))
err = gpg_error (GPG_ERR_INV_OBJ);
if (gpg_err_code (err) == GPG_ERR_EOF)
goto no_authid;
if (err)
goto parse_error;
}
if (tag == TAG_BIT_STRING)
{
ppp += objlen; /* Skip the CommonObjectFlags. */
nnn -= objlen;
where = __LINE__;
err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nnn || class != CLASS_UNIVERSAL))
err = gpg_error (GPG_ERR_INV_OBJ);
if (gpg_err_code (err) == GPG_ERR_EOF)
goto no_authid;
if (err)
goto parse_error;
}
if (tag == TAG_OCTET_STRING && objlen)
{
/* AuthId ignored */
}
no_authid:
;
}
/* Parse the commonKeyAttributes. */
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nn || tag != TAG_SEQUENCE))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
{
const unsigned char *ppp = pp;
size_t nnn = objlen;
pp += objlen;
nn -= objlen;
/* Get the Id. */
where = __LINE__;
err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nnn
|| class != CLASS_UNIVERSAL || tag != TAG_OCTET_STRING))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
objid = ppp;
objidlen = objlen;
ppp += objlen;
nnn -= objlen;
/* Get the KeyUsageFlags. */
where = __LINE__;
err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nnn
|| class != CLASS_UNIVERSAL || tag != TAG_BIT_STRING))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
err = parse_keyusage_flags (ppp, objlen, &usageflags);
if (err)
goto parse_error;
ppp += objlen;
nnn -= objlen;
/* Find the keyReference */
where = __LINE__;
err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (gpg_err_code (err) == GPG_ERR_EOF)
goto leave_cki;
if (!err && objlen > nnn)
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
if (class == CLASS_UNIVERSAL && tag == TAG_BOOLEAN)
{
/* Skip the native element. */
ppp += objlen;
nnn -= objlen;
err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (gpg_err_code (err) == GPG_ERR_EOF)
goto leave_cki;
if (!err && objlen > nnn)
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
}
if (class == CLASS_UNIVERSAL && tag == TAG_BIT_STRING)
{
/* Skip the accessFlags. */
ppp += objlen;
nnn -= objlen;
err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (gpg_err_code (err) == GPG_ERR_EOF)
goto leave_cki;
if (!err && objlen > nnn)
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
}
if (class == CLASS_UNIVERSAL && tag == TAG_INTEGER)
{
/* Yep, this is the keyReference.
Note: UL is currently not used. */
for (ul=0; objlen; objlen--)
{
ul <<= 8;
ul |= (*ppp++) & 0xff;
nnn--;
}
}
leave_cki:
;
}
/* Skip subClassAttributes. */
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && objlen > nn)
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
if (class == CLASS_CONTEXT && tag == 0)
{
pp += objlen;
nn -= objlen;
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
}
/* Parse the keyAttributes. */
if (!err && (objlen > nn || class != CLASS_CONTEXT || tag != 1))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
nn = objlen;
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && objlen > nn)
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
nn = objlen;
/* Check that the reference is a Path object. */
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && objlen > nn)
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
if (class != CLASS_UNIVERSAL || tag != TAG_SEQUENCE)
{
errstr = "unsupported reference type";
goto parse_error;
}
pp += objlen;
nn -= objlen;
/* Parse the key size object. */
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && objlen > nn)
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
keysize = 0;
if (class == CLASS_UNIVERSAL && tag == TAG_INTEGER && objlen == 2)
{
keysize = *pp++ << 8;
keysize += *pp++;
}
/* Create a new PrKDF list item. */
prkdf = xtrycalloc (1, sizeof *prkdf);
if (!prkdf)
{
err = gpg_error_from_syserror ();
goto leave;
}
prkdf->keytype = keytype;
prkdf->keysize = keysize;
prkdf->objidlen = objidlen;
prkdf->objid = xtrymalloc (objidlen);
if (!prkdf->objid)
{
err = gpg_error_from_syserror ();
xfree (prkdf);
prkdf = NULL;
goto leave;
}
memcpy (prkdf->objid, objid, objidlen);
prkdf->usageflags = usageflags;
prkdf->key_reference = fid & 0xFF;
log_debug ("PrKDF %04hX: id=", fid);
for (i=0; i < prkdf->objidlen; i++)
log_printf ("%02X", prkdf->objid[i]);
log_printf (" keyref=0x%02X", prkdf->key_reference);
log_printf (" keysize=%zu", prkdf->keysize);
log_printf (" usage=");
s = "";
if (prkdf->usageflags.encrypt)
{
log_printf ("%sencrypt", s);
s = ",";
}
if (prkdf->usageflags.decrypt)
{
log_printf ("%sdecrypt", s);
s = ",";
}
if (prkdf->usageflags.sign)
{
log_printf ("%ssign", s);
s = ",";
}
if (prkdf->usageflags.sign_recover)
{
log_printf ("%ssign_recover", s);
s = ",";
}
if (prkdf->usageflags.wrap )
{
log_printf ("%swrap", s);
s = ",";
}
if (prkdf->usageflags.unwrap )
{
log_printf ("%sunwrap", s);
s = ",";
}
if (prkdf->usageflags.verify )
{
log_printf ("%sverify", s);
s = ",";
}
if (prkdf->usageflags.verify_recover)
{
log_printf ("%sverify_recover", s);
s = ",";
}
if (prkdf->usageflags.derive )
{
log_printf ("%sderive", s);
s = ",";
}
if (prkdf->usageflags.non_repudiation)
{
log_printf ("%snon_repudiation", s);
}
log_printf ("\n");
xfree (buffer);
buffer = NULL;
buflen = 0;
- err = select_and_read_binary (app->slot,
+ err = select_and_read_binary (app_get_slot (app),
((SC_HSM_EE_PREFIX << 8) | (fid & 0xFF)),
"CertEF", &buffer, &buflen, 1);
if (!err && buffer[0] == 0x30)
{
/* Create a matching CDF list item. */
cdf = xtrycalloc (1, sizeof *cdf);
if (!cdf)
{
err = gpg_error_from_syserror ();
goto leave;
}
cdf->objidlen = prkdf->objidlen;
cdf->objid = xtrymalloc (cdf->objidlen);
if (!cdf->objid)
{
err = gpg_error_from_syserror ();
xfree (cdf);
cdf = NULL;
goto leave;
}
memcpy (cdf->objid, prkdf->objid, objidlen);
cdf->fid = (SC_HSM_EE_PREFIX << 8) | (fid & 0xFF);
log_debug ("CDF %04hX: id=", fid);
for (i=0; i < cdf->objidlen; i++)
log_printf ("%02X", cdf->objid[i]);
log_printf (" fid=%04X\n", cdf->fid);
}
goto leave; /* Ready. */
parse_error:
log_error ("error parsing PrKDF record (%d): %s - skipped\n",
where, errstr? errstr : gpg_strerror (err));
err = 0;
leave:
xfree (buffer);
if (err)
{
if (prkdf)
{
if (prkdf->objid)
xfree (prkdf->objid);
xfree (prkdf);
}
if (cdf)
{
if (cdf->objid)
xfree (cdf->objid);
xfree (cdf);
}
}
else
{
if (prkdf)
prkdf->next = *prkdresult;
*prkdresult = prkdf;
if (cdf)
{
cdf->next = *cdresult;
*cdresult = cdf;
}
}
return err;
}
/* Read and parse the Certificate Description File identified by FID.
On success a the CDF list gets stored at RESULT and the caller is
then responsible of releasing the object.
Example data:
0000 30 35 30 11 0C 0B 43 65 72 74 69 66 69 63 61 74 050...Certificat
0010 65 03 02 06 40 30 16 04 14 C2 01 7C 2F BA A4 4A e...@0.....|/..J
0020 4A BB B8 49 11 DB 4A CA AA 7E 6A 2D 1B A1 08 30 J..I..J..~j-...0
0030 06 30 04 04 02 CA 00 .0.....
Decoded example:
SEQUENCE SIZE( 53 )
SEQUENCE SIZE( 17 )
UTF8-STRING SIZE( 11 ) -- label
0000 43 65 72 74 69 66 69 63 61 74 65 Certificate
BIT-STRING SIZE( 2 ) -- common object attributes
0000 06 40
SEQUENCE SIZE( 22 )
OCTET-STRING SIZE( 20 ) -- id
0000 C2 01 7C 2F BA A4 4A 4A BB B8 49 11 DB 4A CA AA
0010 7E 6A 2D 1B
A1 [ CONTEXT 1 ] IMPLICIT SEQUENCE SIZE( 8 )
SEQUENCE SIZE( 6 )
SEQUENCE SIZE( 4 )
OCTET-STRING SIZE( 2 ) -- path
0000 CA 00 ..
*/
static gpg_error_t
read_ef_cd (app_t app, unsigned short fid, cdf_object_t *result)
{
gpg_error_t err;
unsigned char *buffer = NULL;
size_t buflen;
const unsigned char *p;
size_t n, objlen, hdrlen;
int class, tag, constructed, ndef;
int i;
const unsigned char *pp;
size_t nn;
int where;
const char *errstr = NULL;
cdf_object_t cdf = NULL;
const unsigned char *objid;
size_t objidlen;
if (!fid)
return gpg_error (GPG_ERR_NO_DATA); /* No certificates. */
- err = select_and_read_binary (app->slot, fid, "CDF", &buffer, &buflen, 255);
+ err = select_and_read_binary (app_get_slot (app), fid, "CDF",
+ &buffer, &buflen, 255);
if (err)
return err;
p = buffer;
n = buflen;
err = parse_ber_header (&p, &n, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > n || tag != TAG_SEQUENCE))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
{
log_error ("error parsing CDF record: %s\n", gpg_strerror (err));
goto leave;
}
pp = p;
nn = objlen;
p += objlen;
n -= objlen;
/* Skip the commonObjectAttributes. */
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nn || tag != TAG_SEQUENCE))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
pp += objlen;
nn -= objlen;
/* Parse the commonCertificateAttributes. */
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nn || tag != TAG_SEQUENCE))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
{
const unsigned char *ppp = pp;
size_t nnn = objlen;
pp += objlen;
nn -= objlen;
/* Get the Id. */
where = __LINE__;
err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nnn
|| class != CLASS_UNIVERSAL || tag != TAG_OCTET_STRING))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
objid = ppp;
objidlen = objlen;
}
/* Parse the certAttribute. */
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nn || class != CLASS_CONTEXT || tag != 1))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
nn = objlen;
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nn
|| class != CLASS_UNIVERSAL || tag != TAG_SEQUENCE))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
nn = objlen;
/* Check that the reference is a Path object. */
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && objlen > nn)
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
if (class != CLASS_UNIVERSAL || tag != TAG_SEQUENCE)
{
err = gpg_error (GPG_ERR_INV_OBJ);
goto parse_error;
}
nn = objlen;
/* Parse the Path object. */
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && objlen > nn)
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
/* Make sure that the next element is a non zero path and of
even length (FID are two bytes each). */
if (class != CLASS_UNIVERSAL || tag != TAG_OCTET_STRING
|| (objlen & 1) )
{
errstr = "invalid path reference";
goto parse_error;
}
/* Create a new CDF list item. */
cdf = xtrycalloc (1, sizeof *cdf);
if (!cdf)
{
err = gpg_error_from_syserror ();
goto leave;
}
cdf->objidlen = objidlen;
cdf->objid = xtrymalloc (objidlen);
if (!cdf->objid)
{
err = gpg_error_from_syserror ();
xfree (cdf);
cdf = NULL;
goto leave;
}
memcpy (cdf->objid, objid, objidlen);
cdf->fid = (SC_HSM_CA_PREFIX << 8) | (fid & 0xFF);
log_debug ("CDF %04hX: id=", fid);
for (i=0; i < cdf->objidlen; i++)
log_printf ("%02X", cdf->objid[i]);
goto leave;
parse_error:
log_error ("error parsing CDF record (%d): %s - skipped\n",
where, errstr? errstr : gpg_strerror (err));
err = 0;
leave:
xfree (buffer);
if (err)
{
if (cdf)
{
if (cdf->objid)
xfree (cdf->objid);
xfree (cdf);
}
}
else
{
if (cdf)
cdf->next = *result;
*result = cdf;
}
return err;
}
/* Read the device certificate and extract the serial number.
EF.C_DevAut (2F02) contains two CVCs, the first is the device
certificate, the second is the issuer certificate.
Example data:
0000 7F 21 81 E2 7F 4E 81 9B 5F 29 01 00 42 0B 55 54 .!...N.._)..B.UT
0010 43 43 30 32 30 30 30 30 32 7F 49 4F 06 0A 04 00 CC0200002.IO....
0020 7F 00 07 02 02 02 02 03 86 41 04 6D FF D6 85 57 .........A.m...W
0030 40 FB 10 5D 94 71 8A 94 D2 5E 50 33 E7 1E C0 6C @..].q...^P3...l
0040 63 D5 C8 FC BA F3 02 1D 70 23 F6 47 E8 35 48 EF c.......p#.G.5H.
0050 B5 94 72 3C 6F BE C0 EB 9A C7 FB 06 59 26 CF 65 ..r<o.......Y&.e
0060 EF A1 72 E0 98 F3 F0 44 1B B7 71 5F 20 10 55 54 ..r....D..q_ .UT
0070 43 43 30 32 30 30 30 31 33 30 30 30 30 30 7F 4C CC020001300000.L
0080 10 06 0B 2B 06 01 04 01 81 C3 1F 03 01 01 53 01 ...+..........S.
0090 00 5F 25 06 01 04 00 07 01 01 5F 24 06 02 01 00 ._%......._$....
00A0 03 02 07 5F 37 40 7F 73 04 3B 06 63 79 41 BE 1A ..._7@.s.;.cyA..
00B0 9F FC F6 77 67 2B 8A 41 D1 11 F6 9B 54 44 AD 19 ...wg+.A....TD..
00C0 FB B8 0C C6 2F 34 71 8E 4F F6 92 59 34 61 D9 4F ..../4q.O..Y4a.O
00D0 4A 86 36 A8 D8 9A C6 3C 17 7E 71 CE A8 26 D0 C5 J.6....<.~q..&..
00E0 25 61 78 9D 01 F8 7F 21 81 E0 7F 4E 81 99 5F 29 %ax....!...N.._)
00F0 01 00 42 0E 55 54 53 52 43 41 43 43 31 30 30 30 ..B.UTSRCACC1000
0100 30 31 7F 49 4F 06 0A 04 00 7F 00 07 02 02 02 02 01.IO...........
0110 03 86 41 04 2F EA 33 47 7F 45 81 E2 FC CB 66 87 ..A./.3G.E....f.
0120 4B 96 21 1D 68 81 73 F2 9F 8F 6B 91 F0 DE 4B 54 K.!.h.s...k...KT
0130 8E D8 F0 82 3D CB BE 10 98 A3 1E 4F F0 72 5C E5 ....=......O.r\.
0140 7B 1E F7 3C 68 09 03 E8 A0 3F 3E 06 C1 B0 3C 18 {..<h....?>...<.
0150 6B AC 06 EA 5F 20 0B 55 54 43 43 30 32 30 30 30 k..._ .UTCC02000
0160 30 32 7F 4C 10 06 0B 2B 06 01 04 01 81 C3 1F 03 02.L...+........
0170 01 01 53 01 80 5F 25 06 01 03 00 03 02 08 5F 24 ..S.._%......._$
0180 06 02 01 00 03 02 07 5F 37 40 93 C1 42 8B B3 8E ......._7@..B...
0190 42 61 6F 2C 19 E6 98 41 BD AA 60 BD E0 DD 4E F0 Bao,...A..`...N.
01A0 15 D5 4F 71 B7 BB C3 3A F2 AD 27 5E DD EE 6D 12 ..Oq...:..'^..m.
01B0 76 E6 2B A0 4C 01 CA C1 26 0C 45 6D C6 CB EC 92 v.+.L...&.Em....
01C0 BF 38 18 AD 8F B2 29 40 A9 51 .8....)@.Q
The certificate format is defined in BSI TR-03110:
7F21 [ APPLICATION 33 ] IMPLICIT SEQUENCE SIZE( 226 )
7F4E [ APPLICATION 78 ] IMPLICIT SEQUENCE SIZE( 155 )
5F29 [ APPLICATION 41 ] SIZE( 1 ) -- profile id
0000 00
42 [ APPLICATION 2 ] SIZE( 11 ) -- CAR
0000 55 54 43 43 30 32 30 30 30 30 32 UTCC0200002
7F49 [ APPLICATION 73 ] IMPLICIT SEQUENCE SIZE( 79 ) -- public key
OBJECT IDENTIFIER = { id-TA-ECDSA-SHA-256 }
86 [ CONTEXT 6 ] SIZE( 65 )
0000 04 6D FF D6 85 57 40 FB 10 5D 94 71 8A 94 D2 5E
0010 50 33 E7 1E C0 6C 63 D5 C8 FC BA F3 02 1D 70 23
0020 F6 47 E8 35 48 EF B5 94 72 3C 6F BE C0 EB 9A C7
0030 FB 06 59 26 CF 65 EF A1 72 E0 98 F3 F0 44 1B B7
0040 71
5F20 [ APPLICATION 32 ] SIZE( 16 ) -- CHR
0000 55 54 43 43 30 32 30 30 30 31 33 30 30 30 30 30 UTCC020001300000
7F4C [ APPLICATION 76 ] IMPLICIT SEQUENCE SIZE( 16 ) -- CHAT
OBJECT IDENTIFIER = { 1 3 6 1 4 1 24991 3 1 1 }
53 [ APPLICATION 19 ] SIZE( 1 )
0000 00
5F25 [ APPLICATION 37 ] SIZE( 6 ) -- Valid from
0000 01 04 00 07 01 01
5F24 [ APPLICATION 36 ] SIZE( 6 ) -- Valid to
0000 02 01 00 03 02 07
5F37 [ APPLICATION 55 ] SIZE( 64 ) -- Signature
0000 7F 73 04 3B 06 63 79 41 BE 1A 9F FC F6 77 67 2B
0010 8A 41 D1 11 F6 9B 54 44 AD 19 FB B8 0C C6 2F 34
0020 71 8E 4F F6 92 59 34 61 D9 4F 4A 86 36 A8 D8 9A
0030 C6 3C 17 7E 71 CE A8 26 D0 C5 25 61 78 9D 01 F8
The serial number is contained in tag 5F20, while the last 5 digits
are truncated.
*/
static gpg_error_t
read_serialno(app_t app)
{
gpg_error_t err;
unsigned char *buffer = NULL;
size_t buflen;
const unsigned char *p,*chr;
size_t n, objlen, hdrlen, chrlen;
int class, tag, constructed, ndef;
- err = select_and_read_binary (app->slot, 0x2F02, "EF.C_DevAut",
+ err = select_and_read_binary (app_get_slot (app), 0x2F02, "EF.C_DevAut",
&buffer, &buflen, 512);
if (err)
return err;
p = buffer;
n = buflen;
err = parse_ber_header (&p, &n, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > n || tag != 0x21))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
{
log_error ("error parsing C_DevAut: %s\n", gpg_strerror (err));
goto leave;
}
chr = find_tlv (p, objlen, 0x5F20, &chrlen);
if (!chr || chrlen <= 5)
{
err = gpg_error (GPG_ERR_INV_OBJ);
log_error ("CHR not found in CVC\n");
goto leave;
}
chrlen -= 5;
- app->serialno = xtrymalloc (chrlen);
- if (!app->serialno)
+ app->card->serialno = xtrymalloc (chrlen);
+ if (!app->card->serialno)
{
err = gpg_error_from_syserror ();
goto leave;
}
- app->serialnolen = chrlen;
- memcpy (app->serialno, chr, chrlen);
+ app->card->serialnolen = chrlen;
+ memcpy (app->card->serialno, chr, chrlen);
leave:
xfree (buffer);
return err;
}
/* Get all the basic information from the SmartCard-HSM, check the
structure and initialize our local context. This is used once at
application initialization. */
static gpg_error_t
read_meta (app_t app)
{
gpg_error_t err;
unsigned char *eflist = NULL;
size_t eflistlen = 0;
int i;
err = read_serialno(app);
if (err)
return err;
- err = list_ef (app->slot, &eflist, &eflistlen);
+ err = list_ef (app_get_slot (app), &eflist, &eflistlen);
if (err)
return err;
for (i = 0; i < eflistlen; i += 2)
{
switch(eflist[i])
{
case SC_HSM_KEY_PREFIX:
if (eflist[i + 1] == 0) /* No key with ID=0 */
break;
err = read_ef_prkd (app, ((SC_HSM_PRKD_PREFIX << 8) | eflist[i + 1]),
&app->app_local->private_key_info,
&app->app_local->certificate_info);
if (gpg_err_code (err) == GPG_ERR_NO_DATA)
err = 0;
if (err)
return err;
break;
case SC_HSM_CD_PREFIX:
err = read_ef_cd (app, ((eflist[i] << 8) | eflist[i + 1]),
&app->app_local->trusted_certificate_info);
if (gpg_err_code (err) == GPG_ERR_NO_DATA)
err = 0;
if (err)
return err;
break;
}
}
xfree (eflist);
return err;
}
/* Helper to do_learn_status: Send information about all certificates
listed in CERTINFO back. Use CERTTYPE as type of the
certificate. */
static gpg_error_t
send_certinfo (ctrl_t ctrl, const char *certtype, cdf_object_t certinfo)
{
for (; certinfo; certinfo = certinfo->next)
{
char *buf, *p;
buf = xtrymalloc (4 + certinfo->objidlen*2 + 1);
if (!buf)
return gpg_error_from_syserror ();
p = stpcpy (buf, "HSM.");
bin2hex (certinfo->objid, certinfo->objidlen, p);
send_status_info (ctrl, "CERTINFO",
certtype, strlen (certtype),
buf, strlen (buf),
NULL, (size_t)0);
xfree (buf);
}
return 0;
}
/* Get the keygrip of the private key object PRKDF. On success the
keygrip gets returned in the caller provided 41 byte buffer
R_GRIPSTR. */
static gpg_error_t
keygripstr_from_prkdf (app_t app, prkdf_object_t prkdf, char *r_gripstr)
{
gpg_error_t err;
cdf_object_t cdf;
unsigned char *der;
size_t derlen;
ksba_cert_t cert;
/* Look for a matching certificate. A certificate matches if the Id
matches the one of the private key info. */
for (cdf = app->app_local->certificate_info; cdf; cdf = cdf->next)
if (cdf->objidlen == prkdf->objidlen
&& !memcmp (cdf->objid, prkdf->objid, prkdf->objidlen))
break;
if (!cdf)
return gpg_error (GPG_ERR_NOT_FOUND);
err = readcert_by_cdf (app, cdf, &der, &derlen);
if (err)
return err;
err = ksba_cert_new (&cert);
if (!err)
err = ksba_cert_init_from_mem (cert, der, derlen);
xfree (der);
if (!err)
err = app_help_get_keygrip_string (cert, r_gripstr);
ksba_cert_release (cert);
return err;
}
/* Helper to do_learn_status: Send information about all known
keypairs back. */
static gpg_error_t
send_keypairinfo (app_t app, ctrl_t ctrl, prkdf_object_t keyinfo)
{
gpg_error_t err;
for (; keyinfo; keyinfo = keyinfo->next)
{
char gripstr[40+1];
char *buf, *p;
buf = xtrymalloc (4 + keyinfo->objidlen*2 + 1);
if (!buf)
return gpg_error_from_syserror ();
p = stpcpy (buf, "HSM.");
bin2hex (keyinfo->objid, keyinfo->objidlen, p);
err = keygripstr_from_prkdf (app, keyinfo, gripstr);
if (err)
{
log_error ("can't get keygrip from %04X\n", keyinfo->key_reference);
}
else
{
assert (strlen (gripstr) == 40);
send_status_info (ctrl, "KEYPAIRINFO",
gripstr, 40,
buf, strlen (buf),
NULL, (size_t)0);
}
xfree (buf);
}
return 0;
}
/* This is the handler for the LEARN command. */
static gpg_error_t
do_learn_status (app_t app, ctrl_t ctrl, unsigned int flags)
{
gpg_error_t err;
if ((flags & 1))
err = 0;
else
{
err = send_certinfo (ctrl, "100", app->app_local->certificate_info);
if (!err)
err = send_certinfo (ctrl, "101",
app->app_local->trusted_certificate_info);
}
if (!err)
err = send_keypairinfo (app, ctrl, app->app_local->private_key_info);
return err;
}
/* Read a certificate using the information in CDF and return the
certificate in a newly allocated buffer R_CERT and its length
R_CERTLEN. */
static gpg_error_t
readcert_by_cdf (app_t app, cdf_object_t cdf,
unsigned char **r_cert, size_t *r_certlen)
{
gpg_error_t err;
unsigned char *buffer = NULL;
const unsigned char *p, *save_p;
size_t buflen, n;
int class, tag, constructed, ndef;
size_t totobjlen, objlen, hdrlen;
int rootca;
int i;
*r_cert = NULL;
*r_certlen = 0;
/* First check whether it has been cached. */
if (cdf->image)
{
*r_cert = xtrymalloc (cdf->imagelen);
if (!*r_cert)
return gpg_error_from_syserror ();
memcpy (*r_cert, cdf->image, cdf->imagelen);
*r_certlen = cdf->imagelen;
return 0;
}
- err = select_and_read_binary (app->slot, cdf->fid, "CD",
+ err = select_and_read_binary (app_get_slot (app), cdf->fid, "CD",
&buffer, &buflen, 4096);
if (err)
{
log_error ("error reading certificate with Id ");
for (i=0; i < cdf->objidlen; i++)
log_printf ("%02X", cdf->objid[i]);
log_printf (": %s\n", gpg_strerror (err));
goto leave;
}
/* Check whether this is really a certificate. */
p = buffer;
n = buflen;
err = parse_ber_header (&p, &n, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (err)
goto leave;
if (class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE && constructed)
rootca = 0;
else if ( class == CLASS_UNIVERSAL && tag == TAG_SET && constructed )
rootca = 1;
else
{
err = gpg_error (GPG_ERR_INV_OBJ);
goto leave;
}
totobjlen = objlen + hdrlen;
assert (totobjlen <= buflen);
err = parse_ber_header (&p, &n, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (err)
goto leave;
if (!rootca
&& class == CLASS_UNIVERSAL && tag == TAG_OBJECT_ID && !constructed)
{
/* The certificate seems to be contained in a userCertificate
container. Skip this and assume the following sequence is
the certificate. */
if (n < objlen)
{
err = gpg_error (GPG_ERR_INV_OBJ);
goto leave;
}
p += objlen;
n -= objlen;
save_p = p;
err = parse_ber_header (&p, &n, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (err)
goto leave;
if ( !(class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE && constructed) )
{
err = gpg_error (GPG_ERR_INV_OBJ);
goto leave;
}
totobjlen = objlen + hdrlen;
assert (save_p + totobjlen <= buffer + buflen);
memmove (buffer, save_p, totobjlen);
}
*r_cert = buffer;
buffer = NULL;
*r_certlen = totobjlen;
/* Try to cache it. */
if (!cdf->image && (cdf->image = xtrymalloc (*r_certlen)))
{
memcpy (cdf->image, *r_cert, *r_certlen);
cdf->imagelen = *r_certlen;
}
leave:
xfree (buffer);
return err;
}
/* Handler for the READCERT command.
Read the certificate with id CERTID (as returned by learn_status in
the CERTINFO status lines) and return it in the freshly allocated
buffer to be stored at R_CERT and its length at R_CERTLEN. A error
code will be returned on failure and R_CERT and R_CERTLEN will be
set to (NULL,0). */
static gpg_error_t
do_readcert (app_t app, const char *certid,
unsigned char **r_cert, size_t *r_certlen)
{
gpg_error_t err;
cdf_object_t cdf;
*r_cert = NULL;
*r_certlen = 0;
err = cdf_object_from_certid (app, certid, &cdf);
if (!err)
err = readcert_by_cdf (app, cdf, r_cert, r_certlen);
return err;
}
/* Implement the GETATTR command. This is similar to the LEARN
command but returns just one value via the status interface. */
static gpg_error_t
do_getattr (app_t app, ctrl_t ctrl, const char *name)
{
if (!strcmp (name, "$AUTHKEYID"))
{
char *buf, *p;
prkdf_object_t prkdf;
/* We return the ID of the first private key capable of
signing. */
for (prkdf = app->app_local->private_key_info; prkdf;
prkdf = prkdf->next)
if (prkdf->usageflags.sign)
break;
if (prkdf)
{
buf = xtrymalloc (4 + prkdf->objidlen*2 + 1);
if (!buf)
return gpg_error_from_syserror ();
p = stpcpy (buf, "HSM.");
bin2hex (prkdf->objid, prkdf->objidlen, p);
send_status_info (ctrl, name, buf, strlen (buf), NULL, 0);
xfree (buf);
return 0;
}
}
else if (!strcmp (name, "$DISPSERIALNO"))
{
- send_status_info (ctrl, name, app->serialno, app->serialnolen, NULL, 0);
+ send_status_info (ctrl, name,
+ app->card->serialno, app->card->serialnolen, NULL, 0);
return 0;
}
return gpg_error (GPG_ERR_INV_NAME);
}
/* Apply PKCS#1 V1.5 padding for signature operation. The function
* combines padding, digest info and the hash value. The buffer must
* be allocated by the caller matching the key size. */
static void
apply_PKCS_padding(const unsigned char *dig, int diglen,
const unsigned char *prefix, int prefixlen,
unsigned char *buff, int bufflen)
{
int i, n_ff;
/* Caller must ensure a sufficient buffer. */
if (diglen + prefixlen + 4 > bufflen)
return;
n_ff = bufflen - diglen - prefixlen - 3;
*buff++ = 0x00;
*buff++ = 0x01;
for (i=0; i < n_ff; i++)
*buff++ = 0xFF;
*buff++ = 0x00;
if (prefix)
memcpy (buff, prefix, prefixlen);
buff += prefixlen;
memcpy (buff, dig, diglen);
}
/* Decode a digest info structure (DI,DILEN) to extract the hash
* value. The buffer HASH to receive the digest must be provided by
* the caller with HASHLEN pointing to the inbound length. HASHLEN is
* updated to the outbound length. */
static int
hash_from_digestinfo (const unsigned char *di, size_t dilen,
unsigned char *hash, size_t *hashlen)
{
const unsigned char *p,*pp;
size_t n, nn, objlen, hdrlen;
int class, tag, constructed, ndef;
gpg_error_t err;
p = di;
n = dilen;
err = parse_ber_header (&p, &n, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > n || tag != TAG_SEQUENCE))
err = gpg_error (GPG_ERR_INV_OBJ);
if ( err )
return err;
pp = p;
nn = objlen;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nn || tag != TAG_SEQUENCE))
err = gpg_error (GPG_ERR_INV_OBJ);
if ( err )
return err;
pp += objlen;
nn -= objlen;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nn || tag != TAG_OCTET_STRING))
err = gpg_error (GPG_ERR_INV_OBJ);
if ( err )
return err;
if (*hashlen < objlen)
return gpg_error (GPG_ERR_TOO_SHORT);
memcpy (hash, pp, objlen);
*hashlen = objlen;
return 0;
}
/* Perform PIN verification
*/
static gpg_error_t
verify_pin (app_t app, gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg)
{
gpg_error_t err;
pininfo_t pininfo;
char *pinvalue;
char *prompt;
int sw;
- sw = apdu_send_simple (app->slot, 0, 0x00, ISO7816_VERIFY, 0x00, 0x81,
- -1, NULL);
+ sw = apdu_send_simple (app_get_slot (app),
+ 0, 0x00, ISO7816_VERIFY, 0x00, 0x81, -1, NULL);
if (sw == SW_SUCCESS)
return 0; /* PIN already verified */
if (sw == SW_REF_DATA_INV)
{
log_error ("SmartCard-HSM not initialized. Run sc-hsm-tool first\n");
return gpg_error (GPG_ERR_NO_PIN);
}
if (sw == SW_CHV_BLOCKED)
{
log_error ("PIN Blocked\n");
return gpg_error (GPG_ERR_PIN_BLOCKED);
}
memset (&pininfo, 0, sizeof pininfo);
pininfo.fixedlen = 0;
pininfo.minlen = 6;
pininfo.maxlen = 15;
prompt = "||Please enter the PIN";
if (!opt.disable_pinpad
- && !iso7816_check_pinpad (app->slot, ISO7816_VERIFY, &pininfo) )
+ && !iso7816_check_pinpad (app_get_slot (app), ISO7816_VERIFY, &pininfo) )
{
err = pincb (pincb_arg, prompt, NULL);
if (err)
{
log_info ("PIN callback returned error: %s\n", gpg_strerror (err));
return err;
}
- err = iso7816_verify_kp (app->slot, 0x81, &pininfo);
+ err = iso7816_verify_kp (app_get_slot (app), 0x81, &pininfo);
pincb (pincb_arg, NULL, NULL); /* Dismiss the prompt. */
}
else
{
err = pincb (pincb_arg, prompt, &pinvalue);
if (err)
{
log_info ("PIN callback returned error: %s\n", gpg_strerror (err));
return err;
}
- err = iso7816_verify (app->slot, 0x81, pinvalue, strlen(pinvalue));
+ err = iso7816_verify (app_get_slot (app),
+ 0x81, pinvalue, strlen(pinvalue));
xfree (pinvalue);
}
if (err)
{
log_error ("PIN verification failed: %s\n", gpg_strerror (err));
return err;
}
log_debug ("PIN verification succeeded\n");
return err;
}
/* Handler for the PKSIGN command.
Create the signature and return the allocated result in OUTDATA.
If a PIN is required, the PINCB will be used to ask for the PIN;
that callback should return the PIN in an allocated buffer and
store that as the 3rd argument.
The API is somewhat inconsistent: The caller can either supply
a plain hash and the algorithm in hashalgo or a complete
DigestInfo structure. The former is detect by characteristic length
of the provided data (20,28,32,48 or 64 byte).
The function returns the RSA block in the size of the modulus or
the ECDSA signature in X9.62 format (SEQ/INT(r)/INT(s))
*/
static gpg_error_t
do_sign (app_t app, const char *keyidstr, int hashalgo,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const void *indata, size_t indatalen,
unsigned char **outdata, size_t *outdatalen )
{
static unsigned char rmd160_prefix[15] = /* Object ID is 1.3.36.3.2.1 */
{ 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x24, 0x03,
0x02, 0x01, 0x05, 0x00, 0x04, 0x14 };
static unsigned char sha1_prefix[15] = /* (1.3.14.3.2.26) */
{ 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e, 0x03,
0x02, 0x1a, 0x05, 0x00, 0x04, 0x14 };
static unsigned char sha224_prefix[19] = /* (2.16.840.1.101.3.4.2.4) */
{ 0x30, 0x2D, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48,
0x01, 0x65, 0x03, 0x04, 0x02, 0x04, 0x05, 0x00, 0x04,
0x1C };
static unsigned char sha256_prefix[19] = /* (2.16.840.1.101.3.4.2.1) */
{ 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05,
0x00, 0x04, 0x20 };
static unsigned char sha384_prefix[19] = /* (2.16.840.1.101.3.4.2.2) */
{ 0x30, 0x41, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, 0x05,
0x00, 0x04, 0x30 };
static unsigned char sha512_prefix[19] = /* (2.16.840.1.101.3.4.2.3) */
{ 0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05,
0x00, 0x04, 0x40 };
gpg_error_t err;
unsigned char cdsblk[256]; /* Raw PKCS#1 V1.5 block with padding
(RSA) or hash. */
prkdf_object_t prkdf; /* The private key object. */
size_t cdsblklen;
unsigned char algoid;
int sw;
if (!keyidstr || !*keyidstr)
return gpg_error (GPG_ERR_INV_VALUE);
if (indatalen > 124) /* Limit for 1024 bit key */
return gpg_error (GPG_ERR_INV_VALUE);
err = prkdf_object_from_keyidstr (app, keyidstr, &prkdf);
if (err)
return err;
if (!(prkdf->usageflags.sign || prkdf->usageflags.sign_recover
||prkdf->usageflags.non_repudiation))
{
log_error ("key %s may not be used for signing\n", keyidstr);
return gpg_error (GPG_ERR_WRONG_KEY_USAGE);
}
if (prkdf->keytype == KEY_TYPE_RSA)
{
algoid = 0x20;
cdsblklen = prkdf->keysize >> 3;
if (!cdsblklen)
cdsblklen = 256;
if (hashalgo == GCRY_MD_SHA1 && indatalen == 20)
apply_PKCS_padding (indata, indatalen,
sha1_prefix, sizeof(sha1_prefix),
cdsblk, cdsblklen);
else if (hashalgo == GCRY_MD_MD5 && indatalen == 20)
apply_PKCS_padding (indata, indatalen,
rmd160_prefix, sizeof(rmd160_prefix),
cdsblk, cdsblklen);
else if (hashalgo == GCRY_MD_SHA224 && indatalen == 28)
apply_PKCS_padding (indata, indatalen,
sha224_prefix, sizeof(sha224_prefix),
cdsblk, cdsblklen);
else if (hashalgo == GCRY_MD_SHA256 && indatalen == 32)
apply_PKCS_padding (indata, indatalen,
sha256_prefix, sizeof(sha256_prefix),
cdsblk, cdsblklen);
else if (hashalgo == GCRY_MD_SHA384 && indatalen == 48)
apply_PKCS_padding (indata, indatalen,
sha384_prefix, sizeof(sha384_prefix),
cdsblk, cdsblklen);
else if (hashalgo == GCRY_MD_SHA512 && indatalen == 64)
apply_PKCS_padding (indata, indatalen,
sha512_prefix, sizeof(sha512_prefix),
cdsblk, cdsblklen);
else /* Assume it's already a digest info or TLS_MD5SHA1 */
apply_PKCS_padding (indata, indatalen, NULL, 0, cdsblk, cdsblklen);
}
else
{
algoid = 0x70;
if (indatalen != 20 && indatalen != 28 && indatalen != 32
&& indatalen != 48 && indatalen != 64)
{
cdsblklen = sizeof(cdsblk);
err = hash_from_digestinfo (indata, indatalen, cdsblk, &cdsblklen);
if (err)
{
log_error ("DigestInfo invalid: %s\n", gpg_strerror (err));
return err;
}
}
else
{
memcpy (cdsblk, indata, indatalen);
cdsblklen = indatalen;
}
}
err = verify_pin (app, pincb, pincb_arg);
if (err)
return err;
- sw = apdu_send_le (app->slot, 1, 0x80, 0x68, prkdf->key_reference, algoid,
+ sw = apdu_send_le (app_get_slot (app),
+ 1, 0x80, 0x68, prkdf->key_reference, algoid,
cdsblklen, cdsblk, 0, outdata, outdatalen);
return iso7816_map_sw (sw);
}
/* Handler for the PKAUTH command.
This is basically the same as the PKSIGN command but we first check
that the requested key is suitable for authentication; that is, it
must match the criteria used for the attribute $AUTHKEYID. See
do_sign for calling conventions; there is no HASHALGO, though. */
static gpg_error_t
do_auth (app_t app, const char *keyidstr,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const void *indata, size_t indatalen,
unsigned char **outdata, size_t *outdatalen )
{
gpg_error_t err;
prkdf_object_t prkdf;
int algo;
if (!keyidstr || !*keyidstr)
return gpg_error (GPG_ERR_INV_VALUE);
err = prkdf_object_from_keyidstr (app, keyidstr, &prkdf);
if (err)
return err;
if (!prkdf->usageflags.sign)
{
log_error ("key %s may not be used for authentication\n", keyidstr);
return gpg_error (GPG_ERR_WRONG_KEY_USAGE);
}
algo = indatalen == 36? MD_USER_TLS_MD5SHA1 : GCRY_MD_SHA1;
return do_sign (app, keyidstr, algo, pincb, pincb_arg,
indata, indatalen, outdata, outdatalen);
}
/* Check PKCS#1 V1.5 padding and extract plain text. The function
* allocates a buffer for the plain text. The caller must release the
* buffer. */
static gpg_error_t
strip_PKCS15_padding(unsigned char *src, int srclen, unsigned char **dst,
size_t *dstlen)
{
unsigned char *p;
if (srclen < 2)
return gpg_error (GPG_ERR_DECRYPT_FAILED);
if (*src++ != 0x00)
return gpg_error (GPG_ERR_DECRYPT_FAILED);
if (*src++ != 0x02)
return gpg_error (GPG_ERR_DECRYPT_FAILED);
srclen -= 2;
while ((srclen > 0) && *src)
{
src++;
srclen--;
}
if (srclen < 2)
return gpg_error (GPG_ERR_DECRYPT_FAILED);
src++;
srclen--;
p = xtrymalloc (srclen);
if (!p)
return gpg_error_from_syserror ();
memcpy (p, src, srclen);
*dst = p;
*dstlen = srclen;
return 0;
}
/* Decrypt a PKCS#1 V1.5 formatted cryptogram using the referenced
key. */
static gpg_error_t
do_decipher (app_t app, const char *keyidstr,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const void *indata, size_t indatalen,
unsigned char **outdata, size_t *outdatalen,
unsigned int *r_info)
{
gpg_error_t err;
unsigned char p1blk[256]; /* Enciphered P1 block */
prkdf_object_t prkdf; /* The private key object. */
unsigned char *rspdata;
size_t rspdatalen;
size_t p1blklen;
int sw;
if (!keyidstr || !*keyidstr || !indatalen)
return gpg_error (GPG_ERR_INV_VALUE);
err = prkdf_object_from_keyidstr (app, keyidstr, &prkdf);
if (err)
return err;
if (!(prkdf->usageflags.decrypt || prkdf->usageflags.unwrap))
{
log_error ("key %s may not be used for deciphering\n", keyidstr);
return gpg_error (GPG_ERR_WRONG_KEY_USAGE);
}
if (prkdf->keytype != KEY_TYPE_RSA)
return gpg_error (GPG_ERR_NOT_SUPPORTED);
p1blklen = prkdf->keysize >> 3;
if (!p1blklen)
p1blklen = 256;
/* The input may be shorter (due to MPIs not storing leading zeroes)
or longer than the block size. We put INDATA right aligned into
the buffer. If INDATA is longer than the block size we truncate
it on the left. */
memset (p1blk, 0, sizeof(p1blk));
if (indatalen > p1blklen)
memcpy (p1blk, (unsigned char *)indata + (indatalen - p1blklen), p1blklen);
else
memcpy (p1blk + (p1blklen - indatalen), indata, indatalen);
err = verify_pin(app, pincb, pincb_arg);
if (err)
return err;
- sw = apdu_send_le (app->slot, 1, 0x80, 0x62, prkdf->key_reference, 0x21,
+ sw = apdu_send_le (app_get_slot (app),
+ 1, 0x80, 0x62, prkdf->key_reference, 0x21,
p1blklen, p1blk, 0, &rspdata, &rspdatalen);
err = iso7816_map_sw (sw);
if (err)
{
log_error ("Decrypt failed: %s\n", gpg_strerror (err));
return err;
}
err = strip_PKCS15_padding (rspdata, rspdatalen, outdata, outdatalen);
xfree (rspdata);
if (!err)
*r_info |= APP_DECIPHER_INFO_NOPAD;
return err;
}
/*
* Select the SmartCard-HSM application on the card in SLOT.
*/
gpg_error_t
app_select_sc_hsm (app_t app)
{
- int slot = app->slot;
+ int slot = app_get_slot (app);
int rc;
rc = iso7816_select_application (slot, sc_hsm_aid, sizeof sc_hsm_aid, 0);
if (!rc)
{
- app->apptype = "SC-HSM";
+ app->apptype = APPTYPE_SC_HSM;
app->app_local = xtrycalloc (1, sizeof *app->app_local);
if (!app->app_local)
{
rc = gpg_error_from_syserror ();
goto leave;
}
rc = read_meta (app);
if (rc)
goto leave;
app->fnc.deinit = do_deinit;
+ app->fnc.reselect = NULL;
app->fnc.learn_status = do_learn_status;
app->fnc.readcert = do_readcert;
app->fnc.getattr = do_getattr;
app->fnc.setattr = NULL;
app->fnc.genkey = NULL;
app->fnc.sign = do_sign;
app->fnc.auth = do_auth;
app->fnc.decipher = do_decipher;
app->fnc.change_pin = NULL;
app->fnc.check_pin = NULL;
leave:
if (rc)
do_deinit (app);
}
return rc;
}
diff --git a/scd/app.c b/scd/app.c
index 2ee104d9e..57c4b7743 100644
--- a/scd/app.c
+++ b/scd/app.c
@@ -1,1214 +1,1726 @@
/* app.c - Application selection.
* Copyright (C) 2003, 2004, 2005 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <npth.h>
#include "scdaemon.h"
#include "../common/exechelp.h"
-#include "app-common.h"
#include "iso7816.h"
#include "apdu.h"
#include "../common/tlv.h"
-static npth_mutex_t app_list_lock;
-static app_t app_top;
+/* Lock to protect the list of cards and its associated
+ * applications. */
+static npth_mutex_t card_list_lock;
+
+/* A list of card contexts. A card is a collection of applications
+ * (described by app_t) on the same physical token. */
+static card_t card_top;
+
+
+/* The list of application names and their select function. If no
+ * specific application is selected the first available application on
+ * a card is selected. */
+struct app_priority_list_s
+{
+ apptype_t apptype;
+ char const *name;
+ gpg_error_t (*select_func)(app_t);
+};
+
+static struct app_priority_list_s app_priority_list[] =
+ {{ APPTYPE_OPENPGP , "openpgp", app_select_openpgp },
+ { APPTYPE_PIV , "piv", app_select_piv },
+ { APPTYPE_NKS , "nks", app_select_nks },
+ { APPTYPE_P15 , "p15", app_select_p15 },
+ { APPTYPE_GELDKARTE, "geldkarte", app_select_geldkarte },
+ { APPTYPE_DINSIG , "dinsig", app_select_dinsig },
+ { APPTYPE_SC_HSM , "sc-hsm", app_select_sc_hsm },
+ { APPTYPE_NONE , NULL, NULL }
+ /* APPTYPE_UNDEFINED is special and not listed here. */
+ };
+
+
+
+
+/* Map a cardtype to a string. Never returns NULL. */
+const char *
+strcardtype (cardtype_t t)
+{
+ switch (t)
+ {
+ case CARDTYPE_GENERIC: return "generic";
+ case CARDTYPE_YUBIKEY: return "yubikey";
+ }
+ return "?";
+}
+
+
+/* Map an application type to a string. Never returns NULL. */
+const char *
+strapptype (apptype_t t)
+{
+ int i;
+
+ for (i=0; app_priority_list[i].apptype; i++)
+ if (app_priority_list[i].apptype == t)
+ return app_priority_list[i].name;
+ return t == APPTYPE_UNDEFINED? "undefined" : t? "?" : "none";
+}
+
+
+/* Return the apptype for NAME. */
+static apptype_t
+apptype_from_name (const char *name)
+{
+ int i;
+
+ if (!name)
+ return APPTYPE_NONE;
+
+ for (i=0; app_priority_list[i].apptype; i++)
+ if (!ascii_strcasecmp (app_priority_list[i].name, name))
+ return app_priority_list[i].apptype;
+ if (!ascii_strcasecmp ("undefined", name))
+ return APPTYPE_UNDEFINED;
+ return APPTYPE_NONE;
+}
+
+
+/* Initialization function to change the default app_priority_list.
+ * LIST is a list of comma or space separated strings with application
+ * names. Unknown names will only result in warning message.
+ * Application not mentioned in LIST are used in their original order
+ * after the given once. */
+void
+app_update_priority_list (const char *arg)
+{
+ struct app_priority_list_s save;
+ char **names;
+ int i, j, idx;
+
+ names = strtokenize (arg, ", ");
+ if (!names)
+ log_fatal ("strtokenize failed: %s\n",
+ gpg_strerror (gpg_error_from_syserror ()));
+
+ idx = 0;
+ for (i=0; names[i]; i++)
+ {
+ ascii_strlwr (names[i]);
+ for (j=0; j < i; j++)
+ if (!strcmp (names[j], names[i]))
+ break;
+ if (j < i)
+ {
+ log_info ("warning: duplicate application '%s' in priority list\n",
+ names[i]);
+ continue;
+ }
+
+ for (j=idx; app_priority_list[j].name; j++)
+ if (!strcmp (names[i], app_priority_list[j].name))
+ break;
+ if (!app_priority_list[j].name)
+ {
+ log_info ("warning: unknown application '%s' in priority list\n",
+ names[i]);
+ continue;
+ }
+ save = app_priority_list[idx];
+ app_priority_list[idx] = app_priority_list[j];
+ app_priority_list[j] = save;
+ idx++;
+ }
+ log_assert (idx < DIM (app_priority_list));
+
+ xfree (names);
+ for (i=0; app_priority_list[i].name; i++)
+ log_info ("app priority %d: %s\n", i, app_priority_list[i].name);
+}
+
+
static void
print_progress_line (void *opaque, const char *what, int pc, int cur, int tot)
{
ctrl_t ctrl = opaque;
char line[100];
if (ctrl)
{
snprintf (line, sizeof line, "%s %c %d %d", what, pc, cur, tot);
send_status_direct (ctrl, "PROGRESS", line);
}
}
-/* Lock the reader SLOT. This function shall be used right before
- calling any of the actual application functions to serialize access
- to the reader. We do this always even if the reader is not
- actually used. This allows an actual connection to assume that it
- never shares a reader (while performing one command). Returns 0 on
- success; only then the unlock_reader function must be called after
- returning from the handler. */
+/* Lock the CARD. This function shall be used right before calling
+ * any of the actual application functions to serialize access to the
+ * reader. We do this always even if the card is not actually used.
+ * This allows an actual connection to assume that it never shares a
+ * card (while performing one command). Returns 0 on success; only
+ * then the unlock_reader function must be called after returning from
+ * the handler. Right now we assume a that a reader has just one
+ * card; this may eventually need refinement. */
static gpg_error_t
-lock_app (app_t app, ctrl_t ctrl)
+lock_card (card_t card, ctrl_t ctrl)
{
- if (npth_mutex_lock (&app->lock))
+ if (npth_mutex_lock (&card->lock))
{
gpg_error_t err = gpg_error_from_syserror ();
- log_error ("failed to acquire APP lock for %p: %s\n",
- app, gpg_strerror (err));
+ log_error ("failed to acquire CARD lock for %p: %s\n",
+ card, gpg_strerror (err));
return err;
}
- apdu_set_progress_cb (app->slot, print_progress_line, ctrl);
- apdu_set_prompt_cb (app->slot, popup_prompt, ctrl);
+ apdu_set_progress_cb (card->slot, print_progress_line, ctrl);
+ apdu_set_prompt_cb (card->slot, popup_prompt, ctrl);
return 0;
}
-/* Release a lock on the reader. See lock_reader(). */
+
+/* Release a lock on a card. See lock_reader(). */
static void
-unlock_app (app_t app)
+unlock_card (card_t card)
{
- apdu_set_progress_cb (app->slot, NULL, NULL);
- apdu_set_prompt_cb (app->slot, NULL, NULL);
+ apdu_set_progress_cb (card->slot, NULL, NULL);
+ apdu_set_prompt_cb (card->slot, NULL, NULL);
- if (npth_mutex_unlock (&app->lock))
+ if (npth_mutex_unlock (&card->lock))
{
gpg_error_t err = gpg_error_from_syserror ();
- log_error ("failed to release APP lock for %p: %s\n",
- app, gpg_strerror (err));
+ log_error ("failed to release CARD lock for %p: %s\n",
+ card, gpg_strerror (err));
}
}
/* This function may be called to print information pertaining to the
- current state of this module to the log. */
+ * current state of this module to the log. */
void
app_dump_state (void)
{
+ card_t c;
app_t a;
- npth_mutex_lock (&app_list_lock);
- for (a = app_top; a; a = a->next)
- log_info ("app_dump_state: app=%p type='%s'\n", a, a->apptype);
- npth_mutex_unlock (&app_list_lock);
+ npth_mutex_lock (&card_list_lock);
+ for (c = card_top; c; c = c->next)
+ {
+ log_info ("app_dump_state: card=%p slot=%d type=%s\n",
+ c, c->slot, strcardtype (c->cardtype));
+ for (a=c->app; a; a = a->next)
+ log_info ("app_dump_state: app=%p type='%s'\n",
+ a, strapptype (a->apptype));
+ }
+ npth_mutex_unlock (&card_list_lock);
}
+
/* Check whether the application NAME is allowed. This does not mean
we have support for it though. */
static int
is_app_allowed (const char *name)
{
strlist_t l;
for (l=opt.disabled_applications; l; l = l->next)
if (!strcmp (l->d, name))
return 0; /* no */
return 1; /* yes */
}
-static gpg_error_t
-check_conflict (app_t app, const char *name)
+/* This function is mainly used by the serialno command to check for
+ * an application conflict which may appear if the serialno command is
+ * used to request a specific application and the connection has
+ * already done a select_application. Return values are:
+ * 0 - No conflict
+ * GPG_ERR_FALSE - Another application is in use but it is possible
+ * to switch to the requested application.
+ * Other code - Switching is not possible.
+ *
+ * If SERIALNO_BIN is not NULL a coflict is onl asserted if the
+ * serialno of the card matches.
+ */
+gpg_error_t
+check_application_conflict (card_t card, const char *name,
+ const unsigned char *serialno_bin,
+ size_t serialno_bin_len)
{
- if (!app || !name || (app->apptype && !ascii_strcasecmp (app->apptype, name)))
+ app_t app;
+
+ if (!card || !name)
return 0;
+ if (!card->app)
+ return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); /* Should not happen. */
- if (app->apptype && !strcmp (app->apptype, "UNDEFINED"))
+ if (serialno_bin && card->serialno)
+ {
+ if (serialno_bin_len != card->serialnolen
+ || memcmp (serialno_bin, card->serialno, card->serialnolen))
+ return 0; /* The card does not match the requested S/N. */
+ }
+
+ /* Check whether the requested NAME matches any already selected
+ * application. */
+ for (app = card->app; app; app = app->next)
+ if (!ascii_strcasecmp (strapptype (app->apptype), name))
+ return 0;
+
+ if (card->app->apptype == APPTYPE_UNDEFINED)
return 0;
+ if (card->cardtype == CARDTYPE_YUBIKEY)
+ {
+ if (card->app->apptype == APPTYPE_OPENPGP)
+ {
+ /* Current app is OpenPGP. */
+ if (!ascii_strcasecmp (name, "piv"))
+ return gpg_error (GPG_ERR_FALSE); /* Switching allowed. */
+ }
+ else if (card->app->apptype == APPTYPE_PIV)
+ {
+ /* Current app is PIV. */
+ if (!ascii_strcasecmp (name, "openpgp"))
+ return gpg_error (GPG_ERR_FALSE); /* Switching allowed. */
+ }
+ }
+
log_info ("application '%s' in use - can't switch\n",
- app->apptype? app->apptype : "<null>");
+ strapptype (card->app->apptype));
return gpg_error (GPG_ERR_CONFLICT);
}
-/* This function is used by the serialno command to check for an
- application conflict which may appear if the serialno command is
- used to request a specific application and the connection has
- already done a select_application. */
-gpg_error_t
-check_application_conflict (const char *name, app_t app)
-{
- return check_conflict (app, name);
-}
-
gpg_error_t
-app_reset (app_t app, ctrl_t ctrl, int send_reset)
+card_reset (card_t card, ctrl_t ctrl, int send_reset)
{
gpg_error_t err = 0;
if (send_reset)
{
int sw;
- lock_app (app, ctrl);
- sw = apdu_reset (app->slot);
+ lock_card (card, ctrl);
+ sw = apdu_reset (card->slot);
if (sw)
err = gpg_error (GPG_ERR_CARD_RESET);
- app->reset_requested = 1;
- unlock_app (app);
+ card->reset_requested = 1;
+ unlock_card (card);
scd_kick_the_loop ();
gnupg_sleep (1);
}
else
{
- ctrl->app_ctx = NULL;
- release_application (app, 0);
+ ctrl->card_ctx = NULL;
+ card_unref (card);
}
return err;
}
static gpg_error_t
app_new_register (int slot, ctrl_t ctrl, const char *name,
int periodical_check_needed)
{
gpg_error_t err = 0;
+ card_t card = NULL;
app_t app = NULL;
unsigned char *result = NULL;
size_t resultlen;
int want_undefined;
+ int i;
- /* Need to allocate a new one. */
- app = xtrycalloc (1, sizeof *app);
- if (!app)
+ /* Need to allocate a new card object */
+ card = xtrycalloc (1, sizeof *card);
+ if (!card)
{
err = gpg_error_from_syserror ();
log_info ("error allocating context: %s\n", gpg_strerror (err));
return err;
}
- app->slot = slot;
- app->card_status = (unsigned int)-1;
+ card->slot = slot;
+ card->card_status = (unsigned int)-1;
- if (npth_mutex_init (&app->lock, NULL))
+ if (npth_mutex_init (&card->lock, NULL))
{
err = gpg_error_from_syserror ();
log_error ("error initializing mutex: %s\n", gpg_strerror (err));
- xfree (app);
+ xfree (card);
return err;
}
- err = lock_app (app, ctrl);
+ err = lock_card (card, ctrl);
if (err)
{
- xfree (app);
+ xfree (card);
return err;
}
want_undefined = (name && !strcmp (name, "undefined"));
/* Try to read the GDO file first to get a default serial number.
We skip this if the undefined application has been requested. */
if (!want_undefined)
{
err = iso7816_select_file (slot, 0x3F00, 1);
if (gpg_err_code (err) == GPG_ERR_CARD)
{
/* Might be SW==0x7D00. Let's test whether it is a Yubikey
* by selecting its manager application and then reading the
* config. */
static char const yk_aid[] =
{ 0xA0, 0x00, 0x00, 0x05, 0x27, 0x47, 0x11, 0x17 }; /*MGR*/
+ static char const otp_aid[] =
+ { 0xA0, 0x00, 0x00, 0x05, 0x27, 0x20, 0x01 }; /*OTP*/
unsigned char *buf;
size_t buflen;
- const unsigned char *s0, *s1;
+ const unsigned char *s0;
+ unsigned char formfactor;
size_t n;
if (!iso7816_select_application (slot, yk_aid, sizeof yk_aid,
0x0001)
&& !iso7816_apdu_direct (slot, "\x00\x1d\x00\x00\x00", 5, 0,
NULL, &buf, &buflen))
{
- app->cardtype = "yubikey";
+ card->cardtype = CARDTYPE_YUBIKEY;
if (opt.verbose)
{
log_info ("Yubico: config=");
log_printhex (buf, buflen, "");
}
/* We skip the first byte which seems to be the total
* length of the config data. */
if (buflen > 1)
{
s0 = find_tlv (buf+1, buflen-1, 0x04, &n); /* Form factor */
- if (s0 && n == 1)
+ formfactor = (s0 && n == 1)? *s0 : 0;
+
+ s0 = find_tlv (buf+1, buflen-1, 0x02, &n); /* Serial */
+ if (s0 && n >= 4)
{
- s1 = find_tlv (buf+1, buflen-1, 0x02, &n); /* Serial */
- if (s1 && n >= 4)
+ card->serialno = xtrymalloc (3 + 1 + n);
+ if (card->serialno)
{
- app->serialno = xtrymalloc (3 + 1 + n);
- if (app->serialno)
- {
- app->serialnolen = 3 + 1 + n;
- app->serialno[0] = 0xff;
- app->serialno[1] = 0x02;
- app->serialno[2] = 0x0;
- app->serialno[3] = *s0;
- memcpy (app->serialno + 4, s1, n);
- /* Note that we do not clear the error
- * so that no further serial number
- * testing is done. After all we just
- * set the serial number. */
- }
+ card->serialnolen = 3 + 1 + n;
+ card->serialno[0] = 0xff;
+ card->serialno[1] = 0x02;
+ card->serialno[2] = 0x0;
+ card->serialno[3] = formfactor;
+ memcpy (card->serialno + 4, s0, n);
+ /* Note that we do not clear the error
+ * so that no further serial number
+ * testing is done. After all we just
+ * set the serial number. */
}
- s1 = find_tlv (buf+1, buflen-1, 0x05, &n); /* version */
- if (s1 && n == 3)
- app->cardversion = ((s1[0]<<16)|(s1[1]<<8)|s1[2]);
+ }
+
+ s0 = find_tlv (buf+1, buflen-1, 0x05, &n); /* version */
+ if (s0 && n == 3)
+ card->cardversion = ((s0[0]<<16)|(s0[1]<<8)|s0[2]);
+ else if (!s0)
+ {
+ /* No version - this is not a Yubikey 5. We now
+ * switch to the OTP app and take the first
+ * three bytes of the reponse as version
+ * number. */
+ xfree (buf);
+ buf = NULL;
+ if (!iso7816_select_application_ext (slot,
+ otp_aid, sizeof otp_aid,
+ 1, &buf, &buflen)
+ && buflen > 3)
+ card->cardversion = ((buf[0]<<16)|(buf[1]<<8)|buf[2]);
}
}
xfree (buf);
}
}
if (!err)
err = iso7816_select_file (slot, 0x2F02, 0);
if (!err)
err = iso7816_read_binary (slot, 0, 0, &result, &resultlen);
if (!err)
{
size_t n;
const unsigned char *p;
p = find_tlv_unchecked (result, resultlen, 0x5A, &n);
if (p)
resultlen -= (p-result);
if (p && n > resultlen && n == 0x0d && resultlen+1 == n)
{
- /* The object it does not fit into the buffer. This is an
+ /* The object does not fit into the buffer. This is an
invalid encoding (or the buffer is too short. However, I
have some test cards with such an invalid encoding and
therefore I use this ugly workaround to return something
I can further experiment with. */
log_info ("enabling BMI testcard workaround\n");
n--;
}
if (p && n <= resultlen)
{
/* The GDO file is pretty short, thus we simply reuse it for
storing the serial number. */
memmove (result, p, n);
- app->serialno = result;
- app->serialnolen = n;
- err = app_munge_serialno (app);
+ card->serialno = result;
+ card->serialnolen = n;
+ err = app_munge_serialno (card);
if (err)
goto leave;
}
else
xfree (result);
result = NULL;
}
}
+ /* Allocate a new app object. */
+ app = xtrycalloc (1, sizeof *app);
+ if (!app)
+ {
+ err = gpg_error_from_syserror ();
+ log_info ("error allocating app context: %s\n", gpg_strerror (err));
+ goto leave;
+ }
+ card->app = app;
+ app->card = card;
+
/* Figure out the application to use. */
if (want_undefined)
{
/* We switch to the "undefined" application only if explicitly
requested. */
- app->apptype = "UNDEFINED";
+ app->apptype = APPTYPE_UNDEFINED;
/* Clear the error so that we don't run through the application
* selection chain. */
err = 0;
}
else
{
/* For certain error codes, there is no need to try more. */
if (gpg_err_code (err) == GPG_ERR_CARD_NOT_PRESENT
|| gpg_err_code (err) == GPG_ERR_ENODEV)
goto leave;
/* Set a default error so that we run through the application
- * selecion chain. */
+ * selection chain. */
err = gpg_error (GPG_ERR_NOT_FOUND);
}
- if (err && is_app_allowed ("openpgp")
- && (!name || !strcmp (name, "openpgp")))
- err = app_select_openpgp (app);
- if (err && is_app_allowed ("piv") && (!name || !strcmp (name, "piv")))
- err = app_select_piv (app);
- if (err && is_app_allowed ("nks") && (!name || !strcmp (name, "nks")))
- err = app_select_nks (app);
- if (err && is_app_allowed ("p15") && (!name || !strcmp (name, "p15")))
- err = app_select_p15 (app);
- if (err && is_app_allowed ("geldkarte")
- && (!name || !strcmp (name, "geldkarte")))
- err = app_select_geldkarte (app);
- if (err && is_app_allowed ("dinsig") && (!name || !strcmp (name, "dinsig")))
- err = app_select_dinsig (app);
- if (err && is_app_allowed ("sc-hsm") && (!name || !strcmp (name, "sc-hsm")))
- err = app_select_sc_hsm (app);
+ /* Find the first available app if NAME is NULL or the matching
+ * NAME but only if that application is also enabled. */
+ for (i=0; err && app_priority_list[i].name; i++)
+ {
+ if (is_app_allowed (app_priority_list[i].name)
+ && (!name || !strcmp (name, app_priority_list[i].name)))
+ err = app_priority_list[i].select_func (app);
+ }
if (err && name && gpg_err_code (err) != GPG_ERR_OBJ_TERM_STATE)
err = gpg_error (GPG_ERR_NOT_SUPPORTED);
leave:
if (err)
{
if (name)
log_info ("can't select application '%s': %s\n",
name, gpg_strerror (err));
else
log_info ("no supported card application found: %s\n",
gpg_strerror (err));
- unlock_app (app);
+ unlock_card (card);
xfree (app);
+ xfree (card);
return err;
}
- app->periodical_check_needed = periodical_check_needed;
- app->next = app_top;
- app_top = app;
- unlock_app (app);
+ card->periodical_check_needed = periodical_check_needed;
+ card->next = card_top;
+ card_top = card;
+
+ unlock_card (card);
return 0;
}
+
/* If called with NAME as NULL, select the best fitting application
- and return a context; otherwise select the application with NAME
- and return a context. Returns an error code and stores NULL at
- R_APP if no application was found or no card is present. */
+ * and return its card context; otherwise select the application with
+ * NAME and return its card context. Returns an error code and stores
+ * NULL at R_CARD if no application was found or no card is present. */
gpg_error_t
-select_application (ctrl_t ctrl, const char *name, app_t *r_app,
+select_application (ctrl_t ctrl, const char *name, card_t *r_card,
int scan, const unsigned char *serialno_bin,
size_t serialno_bin_len)
{
gpg_error_t err = 0;
- app_t a, a_prev = NULL;
+ card_t card, card_prev = NULL;
- *r_app = NULL;
+ *r_card = NULL;
- npth_mutex_lock (&app_list_lock);
+ npth_mutex_lock (&card_list_lock);
- if (scan || !app_top)
+ if (scan || !card_top)
{
struct dev_list *l;
- int new_app = 0;
+ int new_card = 0;
/* Scan the devices to find new device(s). */
err = apdu_dev_list_start (opt.reader_port, &l);
if (err)
{
- npth_mutex_unlock (&app_list_lock);
+ npth_mutex_unlock (&card_list_lock);
return err;
}
while (1)
{
int slot;
int periodical_check_needed_this;
- slot = apdu_open_reader (l, !app_top);
+ slot = apdu_open_reader (l, !card_top);
if (slot < 0)
break;
periodical_check_needed_this = apdu_connect (slot);
if (periodical_check_needed_this < 0)
{
/* We close a reader with no card. */
err = gpg_error (GPG_ERR_ENODEV);
}
else
{
err = app_new_register (slot, ctrl, name,
periodical_check_needed_this);
- new_app++;
+ new_card++;
}
if (err)
apdu_close_reader (slot);
}
apdu_dev_list_finish (l);
/* If new device(s), kick the scdaemon loop. */
- if (new_app)
+ if (new_card)
scd_kick_the_loop ();
}
- for (a = app_top; a; a = a->next)
+ for (card = card_top; card; card = card->next)
{
- lock_app (a, ctrl);
+ lock_card (card, ctrl);
if (serialno_bin == NULL)
break;
- if (a->serialnolen == serialno_bin_len
- && !memcmp (a->serialno, serialno_bin, a->serialnolen))
+ if (card->serialnolen == serialno_bin_len
+ && !memcmp (card->serialno, serialno_bin, card->serialnolen))
break;
- unlock_app (a);
- a_prev = a;
+ unlock_card (card);
+ card_prev = card;
}
- if (a)
+ if (card)
{
- err = check_conflict (a, name);
+ err = check_application_conflict (card, name, NULL, 0);
if (!err)
{
- a->ref_count++;
- *r_app = a;
- if (a_prev)
+ /* Note: We do not use card_ref as we are already locked. */
+ card->ref_count++;
+ *r_card = card;
+ if (card_prev)
{
- a_prev->next = a->next;
- a->next = app_top;
- app_top = a;
+ card_prev->next = card->next;
+ card->next = card_top;
+ card_top = card;
}
- }
- unlock_app (a);
+
+ ctrl->current_apptype = card->app ? card->app->apptype : 0;
+ }
+ unlock_card (card);
}
else
err = gpg_error (GPG_ERR_ENODEV);
- npth_mutex_unlock (&app_list_lock);
+ npth_mutex_unlock (&card_list_lock);
return err;
}
+/* This function needs to be called with the NAME of the new
+ * application to be selected on CARD. On success the application is
+ * added to the list of the card's active applications as currently
+ * active application. On error no new application is allocated.
+ * Selecting an already selected application has no effect. */
+gpg_error_t
+select_additional_application (ctrl_t ctrl, const char *name)
+{
+ gpg_error_t err = 0;
+ apptype_t req_apptype;
+ card_t card;
+ app_t app = NULL;
+ int i;
+
+ req_apptype = apptype_from_name (name);
+ if (!req_apptype)
+ err = gpg_error (GPG_ERR_NOT_FOUND);
+
+ card = ctrl->card_ctx;
+ if (!card)
+ return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED);
+
+ err = lock_card (card, ctrl);
+ if (err)
+ return err;
+
+ /* Check that the requested app has not yet been put onto the list. */
+ for (app = card->app; app; app = app->next)
+ if (app->apptype == req_apptype)
+ {
+ /* We already got this one. Note that in this case we don't
+ * make it the current one but it doesn't matter because
+ * maybe_switch_app will do that anyway. */
+ err = 0;
+ app = NULL;
+ goto leave;
+ }
+ app = NULL;
+
+ /* Allocate a new app object. */
+ app = xtrycalloc (1, sizeof *app);
+ if (!app)
+ {
+ err = gpg_error_from_syserror ();
+ log_info ("error allocating app context: %s\n", gpg_strerror (err));
+ goto leave;
+ }
+
+ /* Find the app and run the select. */
+ for (i=0; app_priority_list[i].apptype; i++)
+ {
+ if (app_priority_list[i].apptype == req_apptype
+ && is_app_allowed (app_priority_list[i].name))
+ err = app_priority_list[i].select_func (app);
+ }
+ if (!app_priority_list[i].apptype
+ || (err && gpg_err_code (err) != GPG_ERR_OBJ_TERM_STATE))
+ err = gpg_error (GPG_ERR_NOT_SUPPORTED);
+
+ if (err)
+ goto leave;
+
+ /* Add this app. We make it the current one to avoid an extra
+ * reselect by maybe_switch_app after the select we just did. */
+ app->next = card->app;
+ card->app = app;
+ ctrl->current_apptype = app->apptype;
+ log_error ("added app '%s' to the card context\n", strapptype(app->apptype));
+
+ leave:
+ unlock_card (card);
+ xfree (app);
+ return err;
+}
+
+
char *
get_supported_applications (void)
{
- const char *list[] = {
- "openpgp",
- "piv",
- "nks",
- "p15",
- "geldkarte",
- "dinsig",
- "sc-hsm",
- /* Note: "undefined" is not listed here because it needs special
- treatment by the client. */
- NULL
- };
int idx;
size_t nbytes;
char *buffer, *p;
+ const char *s;
- for (nbytes=1, idx=0; list[idx]; idx++)
- nbytes += strlen (list[idx]) + 1 + 1;
+ for (nbytes=1, idx=0; (s=app_priority_list[idx].name); idx++)
+ nbytes += strlen (s) + 1 + 1;
buffer = xtrymalloc (nbytes);
if (!buffer)
return NULL;
- for (p=buffer, idx=0; list[idx]; idx++)
- if (is_app_allowed (list[idx]))
- p = stpcpy (stpcpy (p, list[idx]), ":\n");
+ for (p=buffer, idx=0; (s=app_priority_list[idx].name); idx++)
+ if (is_app_allowed (s))
+ p = stpcpy (stpcpy (p, s), ":\n");
*p = 0;
return buffer;
}
/* Deallocate the application. */
static void
-deallocate_app (app_t app)
+deallocate_card (card_t card)
{
- app_t a, a_prev = NULL;
+ card_t c, c_prev = NULL;
+ app_t a, anext;
- for (a = app_top; a; a = a->next)
- if (a == app)
+ for (c = card_top; c; c = c->next)
+ if (c == card)
{
- if (a_prev == NULL)
- app_top = a->next;
+ if (c_prev == NULL)
+ card_top = c->next;
else
- a_prev->next = a->next;
+ c_prev->next = c->next;
break;
}
else
- a_prev = a;
+ c_prev = c;
- if (app->ref_count)
- log_error ("trying to release context used yet (%d)\n", app->ref_count);
+ if (card->ref_count)
+ log_error ("releasing still used card context (%d)\n", card->ref_count);
- if (app->fnc.deinit)
+ for (a = card->app; a; a = anext)
{
- app->fnc.deinit (app);
- app->fnc.deinit = NULL;
+ if (a->fnc.deinit)
+ {
+ a->fnc.deinit (a);
+ a->fnc.deinit = NULL;
+ }
+ anext = a->next;
+ xfree (a);
}
- xfree (app->serialno);
+ scd_clear_current_app (card);
- unlock_app (app);
- xfree (app);
+ xfree (card->serialno);
+ unlock_card (card);
+ xfree (card);
+}
+
+
+/* Increment the reference counter of CARD. Returns CARD. */
+card_t
+card_ref (card_t card)
+{
+ lock_card (card, NULL);
+ ++card->ref_count;
+ unlock_card (card);
+ return card;
}
-/* Free the resources associated with the application APP. APP is
- allowed to be NULL in which case this is a no-op. Note that we are
- using reference counting to track the users of the application and
- actually deferring the deallocation to allow for a later reuse by
- a new connection. */
+
+/* Decrement the reference counter for CARD. Note that we are using
+ * reference counting to track the users of the card's application and
+ * are deferring the actual deallocation to allow for a later reuse by
+ * a new connection. Using NULL for CARD is a no-op. */
void
-release_application (app_t app, int locked_already)
+card_unref (card_t card)
{
- if (!app)
+ if (!card)
return;
- /* We don't deallocate app here. Instead, we keep it. This is
+ /* We don't deallocate CARD here. Instead, we keep it. This is
useful so that a card does not get reset even if only one session
is using the card - this way the PIN cache and other cached data
are preserved. */
- if (!locked_already)
- lock_app (app, NULL);
+ lock_card (card, NULL);
+ card_unref_locked (card);
+ unlock_card (card);
+}
+
+
+/* This is the same as card_unref but assumes that CARD is already
+ * locked. */
+void
+card_unref_locked (card_t card)
+{
+ if (!card)
+ return;
- if (!app->ref_count)
- log_bug ("trying to release an already released context\n");
+ if (!card->ref_count)
+ log_bug ("tried to release an already released card context\n");
- --app->ref_count;
- if (!locked_already)
- unlock_app (app);
+ --card->ref_count;
}
/* The serial number may need some cosmetics. Do it here. This
function shall only be called once after a new serial number has
been put into APP->serialno.
Prefixes we use:
FF 00 00 = For serial numbers starting with an FF
FF 01 00 = Some german p15 cards return an empty serial number so the
serial number from the EF(TokenInfo) is used instead.
FF 02 00 = Serial number from Yubikey config
FF 7F 00 = No serialno.
All other serial number not starting with FF are used as they are.
*/
gpg_error_t
-app_munge_serialno (app_t app)
+app_munge_serialno (card_t card)
{
- if (app->serialnolen && app->serialno[0] == 0xff)
+ if (card->serialnolen && card->serialno[0] == 0xff)
{
/* The serial number starts with our special prefix. This
requires that we put our default prefix "FF0000" in front. */
- unsigned char *p = xtrymalloc (app->serialnolen + 3);
+ unsigned char *p = xtrymalloc (card->serialnolen + 3);
if (!p)
return gpg_error_from_syserror ();
memcpy (p, "\xff\0", 3);
- memcpy (p+3, app->serialno, app->serialnolen);
- app->serialnolen += 3;
- xfree (app->serialno);
- app->serialno = p;
+ memcpy (p+3, card->serialno, card->serialnolen);
+ card->serialnolen += 3;
+ xfree (card->serialno);
+ card->serialno = p;
}
- else if (!app->serialnolen)
+ else if (!card->serialnolen)
{
unsigned char *p = xtrymalloc (3);
if (!p)
return gpg_error_from_syserror ();
memcpy (p, "\xff\x7f", 3);
- app->serialnolen = 3;
- xfree (app->serialno);
- app->serialno = p;
+ card->serialnolen = 3;
+ xfree (card->serialno);
+ card->serialno = p;
}
return 0;
}
/* Retrieve the serial number of the card. The serial number is
returned as a malloced string (hex encoded) in SERIAL. Caller must
free SERIAL unless the function returns an error. */
char *
-app_get_serialno (app_t app)
+card_get_serialno (card_t card)
{
char *serial;
- if (!app)
+ if (!card)
return NULL;
- if (!app->serialnolen)
+ if (!card->serialnolen)
serial = xtrystrdup ("FF7F00");
else
- serial = bin2hex (app->serialno, app->serialnolen, NULL);
+ serial = bin2hex (card->serialno, card->serialnolen, NULL);
return serial;
}
+/* Same as card_get_serialno but takes an APP object. */
+char *
+app_get_serialno (app_t app)
+{
+ if (!app || !app->card)
+ return NULL;
+ return card_get_serialno (app->card);
+}
+
+
+/* Check that the card has been initialized and whether we need to
+ * switch to another application on the same card. Switching means
+ * that the new active app will be moved to the head of the list at
+ * CARD->app. Thus function must be called with the card lock held. */
+static gpg_error_t
+maybe_switch_app (ctrl_t ctrl, card_t card)
+{
+ gpg_error_t err;
+ app_t app, app_prev;
+
+ if (!card->ref_count || !card->app)
+ return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED);
+ if (!ctrl->current_apptype)
+ {
+ /* For whatever reasons the current apptype has not been set -
+ * fix that and use the current app. */
+ ctrl->current_apptype = card->app->apptype;
+ return 0;
+ }
+ log_debug ("card=%p want=%s card->app=%s\n",
+ card, strapptype (ctrl->current_apptype),
+ strapptype (card->app->apptype));
+ app_dump_state ();
+ if (ctrl->current_apptype == card->app->apptype)
+ return 0; /* No need to switch. */
+ app_prev = card->app;
+ for (app = app_prev->next; app; app_prev = app, app = app->next)
+ if (app->apptype == ctrl->current_apptype)
+ break;
+ if (!app)
+ return gpg_error (GPG_ERR_WRONG_CARD);
+
+ if (!app->fnc.reselect)
+ {
+ log_error ("oops: reselect function missing for '%s'\n",
+ strapptype(app->apptype));
+ return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED);
+ }
+ err = app->fnc.reselect (app, ctrl);
+ if (err)
+ {
+ log_error ("error re-selecting '%s': %s\n",
+ strapptype(app->apptype), gpg_strerror (err));
+ return err;
+ }
+ /* Swap APP with the head of the app list. Note that APP is not the
+ * head of the list. */
+ app_prev->next = app->next;
+ app->next = card->app;
+ card->app = app;
+ ctrl->current_apptype = app->apptype;
+ log_error ("switched to '%s'\n", strapptype(app->apptype));
+
+ return 0;
+}
+
/* Write out the application specific status lines for the LEARN
command. */
gpg_error_t
-app_write_learn_status (app_t app, ctrl_t ctrl, unsigned int flags)
+app_write_learn_status (card_t card, ctrl_t ctrl, unsigned int flags)
{
gpg_error_t err;
+ app_t app;
- if (!app)
+ if (!card)
return gpg_error (GPG_ERR_INV_VALUE);
- if (!app->fnc.learn_status)
- return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
- /* We do not send CARD and APPTYPE if only keypairinfo is requested. */
- if (!(flags &1))
+ err = lock_card (card, ctrl);
+ if (err)
+ return err;
+
+ if ((err = maybe_switch_app (ctrl, card)))
+ ;
+ else if (!card->app->fnc.learn_status)
+ err = gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
+ else
{
- if (app->cardtype)
- send_status_direct (ctrl, "CARDTYPE", app->cardtype);
- if (app->cardversion)
- send_status_printf (ctrl, "CARDVERSION", "%X", app->cardversion);
- if (app->apptype)
- send_status_direct (ctrl, "APPTYPE", app->apptype);
- if (app->appversion)
- send_status_printf (ctrl, "APPVERSION", "%X", app->appversion);
+ app = card->app;
+
+ /* We do not send CARD and APPTYPE if only keypairinfo is requested. */
+ if (!(flags &1))
+ {
+ if (card->cardtype)
+ send_status_direct (ctrl, "CARDTYPE", strcardtype (card->cardtype));
+ if (card->cardversion)
+ send_status_printf (ctrl, "CARDVERSION", "%X", card->cardversion);
+ if (app->apptype)
+ send_status_direct (ctrl, "APPTYPE", strapptype (app->apptype));
+ if (app->appversion)
+ send_status_printf (ctrl, "APPVERSION", "%X", app->appversion);
+ /* FIXME: Send info for the other active apps of the card? */
+ }
+
+ err = app->fnc.learn_status (app, ctrl, flags);
}
- err = lock_app (app, ctrl);
- if (err)
- return err;
- err = app->fnc.learn_status (app, ctrl, flags);
- unlock_app (app);
+ unlock_card (card);
return err;
}
/* Read the certificate with id CERTID (as returned by learn_status in
the CERTINFO status lines) and return it in the freshly allocated
buffer put into CERT and the length of the certificate put into
CERTLEN. */
gpg_error_t
-app_readcert (app_t app, ctrl_t ctrl, const char *certid,
+app_readcert (card_t card, ctrl_t ctrl, const char *certid,
unsigned char **cert, size_t *certlen)
{
gpg_error_t err;
- if (!app)
+ if (!card)
return gpg_error (GPG_ERR_INV_VALUE);
- if (!app->ref_count)
- return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED);
- if (!app->fnc.readcert)
- return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
- err = lock_app (app, ctrl);
+ err = lock_card (card, ctrl);
if (err)
return err;
- err = app->fnc.readcert (app, certid, cert, certlen);
- unlock_app (app);
+
+ if ((err = maybe_switch_app (ctrl, card)))
+ ;
+ else if (!card->app->fnc.readcert)
+ err = gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
+ else
+ err = card->app->fnc.readcert (card->app, certid, cert, certlen);
+
+ unlock_card (card);
return err;
}
/* Read the key with ID KEYID. On success a canonical encoded
- S-expression with the public key will get stored at PK and its
- length (for assertions) at PKLEN; the caller must release that
- buffer. On error NULL will be stored at PK and PKLEN and an error
- code returned.
-
- This function might not be supported by all applications. */
+ * S-expression with the public key will get stored at PK and its
+ * length (for assertions) at PKLEN; the caller must release that
+ * buffer. On error NULL will be stored at PK and PKLEN and an error
+ * code returned. If the key is not required NULL may be passed for
+ * PK; this makse send if the APP_READKEY_FLAG_INFO has also been set.
+ *
+ * This function might not be supported by all applications. */
gpg_error_t
-app_readkey (app_t app, ctrl_t ctrl, const char *keyid,
+app_readkey (card_t card, ctrl_t ctrl, const char *keyid, unsigned int flags,
unsigned char **pk, size_t *pklen)
{
gpg_error_t err;
if (pk)
*pk = NULL;
if (pklen)
*pklen = 0;
- if (!app || !keyid || !pk || !pklen)
+ if (!card || !keyid)
return gpg_error (GPG_ERR_INV_VALUE);
- if (!app->ref_count)
- return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED);
- if (!app->fnc.readkey)
- return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
- err = lock_app (app, ctrl);
+ err = lock_card (card, ctrl);
if (err)
return err;
- err= app->fnc.readkey (app, keyid, pk, pklen);
- unlock_app (app);
+
+ if ((err = maybe_switch_app (ctrl, card)))
+ ;
+ else if (!card->app->fnc.readkey)
+ err = gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
+ else
+ err = card->app->fnc.readkey (card->app, ctrl, keyid, flags, pk, pklen);
+
+ unlock_card (card);
return err;
}
/* Perform a GETATTR operation. */
gpg_error_t
-app_getattr (app_t app, ctrl_t ctrl, const char *name)
+app_getattr (card_t card, ctrl_t ctrl, const char *name)
{
gpg_error_t err;
- if (!app || !name || !*name)
+ if (!card || !name || !*name)
return gpg_error (GPG_ERR_INV_VALUE);
- if (!app->ref_count)
- return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED);
+ err = lock_card (card, ctrl);
+ if (err)
+ return err;
- if (app->cardtype && name && !strcmp (name, "CARDTYPE"))
+ if ((err = maybe_switch_app (ctrl, card)))
+ ;
+ else if (name && !strcmp (name, "CARDTYPE"))
{
- send_status_direct (ctrl, "CARDTYPE", app->cardtype);
- return 0;
+ send_status_direct (ctrl, "CARDTYPE", strcardtype (card->cardtype));
}
- if (app->apptype && name && !strcmp (name, "APPTYPE"))
+ else if (name && !strcmp (name, "APPTYPE"))
{
- send_status_direct (ctrl, "APPTYPE", app->apptype);
- return 0;
+ send_status_direct (ctrl, "APPTYPE", strapptype (card->app->apptype));
}
- if (name && !strcmp (name, "SERIALNO"))
+ else if (name && !strcmp (name, "SERIALNO"))
{
char *serial;
- serial = app_get_serialno (app);
+ serial = card_get_serialno (card);
if (!serial)
- return gpg_error (GPG_ERR_INV_VALUE);
-
- send_status_direct (ctrl, "SERIALNO", serial);
- xfree (serial);
- return 0;
+ err = gpg_error (GPG_ERR_INV_VALUE);
+ else
+ {
+ send_status_direct (ctrl, "SERIALNO", serial);
+ xfree (serial);
+ }
}
+ else if (!card->app->fnc.getattr)
+ err = gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
+ else
+ err = card->app->fnc.getattr (card->app, ctrl, name);
- if (!app->fnc.getattr)
- return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
- err = lock_app (app, ctrl);
- if (err)
- return err;
- err = app->fnc.getattr (app, ctrl, name);
- unlock_app (app);
+ unlock_card (card);
return err;
}
+
/* Perform a SETATTR operation. */
gpg_error_t
-app_setattr (app_t app, ctrl_t ctrl, const char *name,
+app_setattr (card_t card, ctrl_t ctrl, const char *name,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const unsigned char *value, size_t valuelen)
{
gpg_error_t err;
- if (!app || !name || !*name || !value)
+ if (!card || !name || !*name || !value)
return gpg_error (GPG_ERR_INV_VALUE);
- if (!app->ref_count)
- return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED);
- if (!app->fnc.setattr)
- return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
- err = lock_app (app, ctrl);
+ err = lock_card (card, ctrl);
if (err)
return err;
- err = app->fnc.setattr (app, name, pincb, pincb_arg, value, valuelen);
- unlock_app (app);
+
+ if ((err = maybe_switch_app (ctrl, card)))
+ ;
+ else if (!card->app->fnc.setattr)
+ err = gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
+ else
+ err = card->app->fnc.setattr (card->app, name, pincb, pincb_arg,
+ value, valuelen);
+
+ unlock_card (card);
return err;
}
+
/* Create the signature and return the allocated result in OUTDATA.
If a PIN is required the PINCB will be used to ask for the PIN; it
should return the PIN in an allocated buffer and put it into PIN. */
gpg_error_t
-app_sign (app_t app, ctrl_t ctrl, const char *keyidstr, int hashalgo,
+app_sign (card_t card, ctrl_t ctrl, const char *keyidstr, int hashalgo,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const void *indata, size_t indatalen,
unsigned char **outdata, size_t *outdatalen )
{
gpg_error_t err;
- if (!app || !indata || !indatalen || !outdata || !outdatalen || !pincb)
+ if (!card || !indata || !indatalen || !outdata || !outdatalen || !pincb)
return gpg_error (GPG_ERR_INV_VALUE);
- if (!app->ref_count)
- return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED);
- if (!app->fnc.sign)
- return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
- err = lock_app (app, ctrl);
+ err = lock_card (card, ctrl);
if (err)
return err;
- err = app->fnc.sign (app, keyidstr, hashalgo,
- pincb, pincb_arg,
- indata, indatalen,
- outdata, outdatalen);
- unlock_app (app);
+
+ if ((err = maybe_switch_app (ctrl, card)))
+ ;
+ else if (!card->app->fnc.sign)
+ err = gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
+ else
+ err = card->app->fnc.sign (card->app, keyidstr, hashalgo,
+ pincb, pincb_arg,
+ indata, indatalen,
+ outdata, outdatalen);
+
+ unlock_card (card);
if (opt.verbose)
log_info ("operation sign result: %s\n", gpg_strerror (err));
return err;
}
+
/* Create the signature using the INTERNAL AUTHENTICATE command and
return the allocated result in OUTDATA. If a PIN is required the
PINCB will be used to ask for the PIN; it should return the PIN in
an allocated buffer and put it into PIN. */
gpg_error_t
-app_auth (app_t app, ctrl_t ctrl, const char *keyidstr,
+app_auth (card_t card, ctrl_t ctrl, const char *keyidstr,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const void *indata, size_t indatalen,
unsigned char **outdata, size_t *outdatalen )
{
gpg_error_t err;
- if (!app || !indata || !indatalen || !outdata || !outdatalen || !pincb)
+ if (!card || !indata || !indatalen || !outdata || !outdatalen || !pincb)
return gpg_error (GPG_ERR_INV_VALUE);
- if (!app->ref_count)
- return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED);
- if (!app->fnc.auth)
- return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
- err = lock_app (app, ctrl);
+ err = lock_card (card, ctrl);
if (err)
return err;
- err = app->fnc.auth (app, keyidstr,
- pincb, pincb_arg,
- indata, indatalen,
- outdata, outdatalen);
- unlock_app (app);
+
+ if ((err = maybe_switch_app (ctrl, card)))
+ ;
+ else if (!card->app->fnc.auth)
+ err = gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
+ else
+ err = card->app->fnc.auth (card->app, keyidstr,
+ pincb, pincb_arg,
+ indata, indatalen,
+ outdata, outdatalen);
+
+ unlock_card (card);
if (opt.verbose)
log_info ("operation auth result: %s\n", gpg_strerror (err));
return err;
}
/* Decrypt the data in INDATA and return the allocated result in OUTDATA.
If a PIN is required the PINCB will be used to ask for the PIN; it
should return the PIN in an allocated buffer and put it into PIN. */
gpg_error_t
-app_decipher (app_t app, ctrl_t ctrl, const char *keyidstr,
+app_decipher (card_t card, ctrl_t ctrl, const char *keyidstr,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const void *indata, size_t indatalen,
unsigned char **outdata, size_t *outdatalen,
unsigned int *r_info)
{
gpg_error_t err;
*r_info = 0;
- if (!app || !indata || !indatalen || !outdata || !outdatalen || !pincb)
+ if (!card || !indata || !indatalen || !outdata || !outdatalen || !pincb)
return gpg_error (GPG_ERR_INV_VALUE);
- if (!app->ref_count)
- return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED);
- if (!app->fnc.decipher)
- return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
- err = lock_app (app, ctrl);
+ err = lock_card (card, ctrl);
if (err)
return err;
- err = app->fnc.decipher (app, keyidstr,
- pincb, pincb_arg,
- indata, indatalen,
- outdata, outdatalen,
- r_info);
- unlock_app (app);
+
+ if ((err = maybe_switch_app (ctrl, card)))
+ ;
+ else if (!card->app->fnc.decipher)
+ err = gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
+ else
+ err = card->app->fnc.decipher (card->app, keyidstr,
+ pincb, pincb_arg,
+ indata, indatalen,
+ outdata, outdatalen,
+ r_info);
+
+ unlock_card (card);
if (opt.verbose)
log_info ("operation decipher result: %s\n", gpg_strerror (err));
return err;
}
/* Perform the WRITECERT operation. */
gpg_error_t
-app_writecert (app_t app, ctrl_t ctrl,
- const char *certidstr,
- gpg_error_t (*pincb)(void*, const char *, char **),
- void *pincb_arg,
- const unsigned char *data, size_t datalen)
+app_writecert (card_t card, ctrl_t ctrl,
+ const char *certidstr,
+ gpg_error_t (*pincb)(void*, const char *, char **),
+ void *pincb_arg,
+ const unsigned char *data, size_t datalen)
{
gpg_error_t err;
- if (!app || !certidstr || !*certidstr || !pincb)
+ if (!card || !certidstr || !*certidstr || !pincb)
return gpg_error (GPG_ERR_INV_VALUE);
- if (!app->ref_count)
- return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED);
- if (!app->fnc.writecert)
- return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
- err = lock_app (app, ctrl);
+ err = lock_card (card, ctrl);
if (err)
return err;
- err = app->fnc.writecert (app, ctrl, certidstr,
- pincb, pincb_arg, data, datalen);
- unlock_app (app);
+
+ if ((err = maybe_switch_app (ctrl, card)))
+ ;
+ else if (!card->app->fnc.writecert)
+ err = gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
+ else
+ err = card->app->fnc.writecert (card->app, ctrl, certidstr,
+ pincb, pincb_arg, data, datalen);
+
+ unlock_card (card);
if (opt.verbose)
log_info ("operation writecert result: %s\n", gpg_strerror (err));
return err;
}
/* Perform the WRITEKEY operation. */
gpg_error_t
-app_writekey (app_t app, ctrl_t ctrl,
+app_writekey (card_t card, ctrl_t ctrl,
const char *keyidstr, unsigned int flags,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const unsigned char *keydata, size_t keydatalen)
{
gpg_error_t err;
- if (!app || !keyidstr || !*keyidstr || !pincb)
+ if (!card || !keyidstr || !*keyidstr || !pincb)
return gpg_error (GPG_ERR_INV_VALUE);
- if (!app->ref_count)
- return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED);
- if (!app->fnc.writekey)
- return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
- err = lock_app (app, ctrl);
+ err = lock_card (card, ctrl);
if (err)
return err;
- err = app->fnc.writekey (app, ctrl, keyidstr, flags,
- pincb, pincb_arg, keydata, keydatalen);
- unlock_app (app);
+
+ if ((err = maybe_switch_app (ctrl, card)))
+ ;
+ else if (!card->app->fnc.writekey)
+ err = gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
+ else
+ err = card->app->fnc.writekey (card->app, ctrl, keyidstr, flags,
+ pincb, pincb_arg, keydata, keydatalen);
+
+ unlock_card (card);
if (opt.verbose)
log_info ("operation writekey result: %s\n", gpg_strerror (err));
return err;
}
-/* Perform a SETATTR operation. */
+/* Perform a GENKEY operation. */
gpg_error_t
-app_genkey (app_t app, ctrl_t ctrl, const char *keynostr,
+app_genkey (card_t card, ctrl_t ctrl, const char *keynostr,
const char *keytype, unsigned int flags, time_t createtime,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg)
{
gpg_error_t err;
- if (!app || !keynostr || !*keynostr || !pincb)
+ if (!card || !keynostr || !*keynostr || !pincb)
return gpg_error (GPG_ERR_INV_VALUE);
- if (!app->ref_count)
- return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED);
- if (!app->fnc.genkey)
- return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
- err = lock_app (app, ctrl);
+ err = lock_card (card, ctrl);
if (err)
return err;
- err = app->fnc.genkey (app, ctrl, keynostr, keytype, flags,
- createtime, pincb, pincb_arg);
- unlock_app (app);
+
+ if ((err = maybe_switch_app (ctrl, card)))
+ ;
+ else if (!card->app->fnc.genkey)
+ err = gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
+ else
+ err = card->app->fnc.genkey (card->app, ctrl, keynostr, keytype, flags,
+ createtime, pincb, pincb_arg);
+
+ unlock_card (card);
if (opt.verbose)
log_info ("operation genkey result: %s\n", gpg_strerror (err));
return err;
}
/* Perform a GET CHALLENGE operation. This function is special as it
directly accesses the card without any application specific
wrapper. */
gpg_error_t
-app_get_challenge (app_t app, ctrl_t ctrl, size_t nbytes, unsigned char *buffer)
+app_get_challenge (card_t card, ctrl_t ctrl,
+ size_t nbytes, unsigned char *buffer)
{
gpg_error_t err;
- if (!app || !nbytes || !buffer)
+ if (!card || !nbytes || !buffer)
return gpg_error (GPG_ERR_INV_VALUE);
- if (!app->ref_count)
- return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED);
- err = lock_app (app, ctrl);
+ err = lock_card (card, ctrl);
if (err)
return err;
- err = iso7816_get_challenge (app->slot, nbytes, buffer);
- unlock_app (app);
+
+ if (!card->ref_count)
+ err = gpg_error (GPG_ERR_CARD_NOT_INITIALIZED);
+ else
+ err = iso7816_get_challenge (card->slot, nbytes, buffer);
+
+ unlock_card (card);
return err;
}
-
/* Perform a CHANGE REFERENCE DATA or RESET RETRY COUNTER operation. */
gpg_error_t
-app_change_pin (app_t app, ctrl_t ctrl, const char *chvnostr,
+app_change_pin (card_t card, ctrl_t ctrl, const char *chvnostr,
unsigned int flags,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg)
{
gpg_error_t err;
- if (!app || !chvnostr || !*chvnostr || !pincb)
+ if (!card || !chvnostr || !*chvnostr || !pincb)
return gpg_error (GPG_ERR_INV_VALUE);
- if (!app->ref_count)
- return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED);
- if (!app->fnc.change_pin)
- return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
- err = lock_app (app, ctrl);
+ err = lock_card (card, ctrl);
if (err)
return err;
- err = app->fnc.change_pin (app, ctrl, chvnostr, flags, pincb, pincb_arg);
- unlock_app (app);
+
+ if ((err = maybe_switch_app (ctrl, card)))
+ ;
+ else if (!card->app->fnc.change_pin)
+ err = gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
+ else
+ err = card->app->fnc.change_pin (card->app, ctrl,
+ chvnostr, flags, pincb, pincb_arg);
+
+ unlock_card (card);
if (opt.verbose)
log_info ("operation change_pin result: %s\n", gpg_strerror (err));
return err;
}
/* Perform a VERIFY operation without doing anything else. This may
be used to initialize a the PIN cache for long lasting other
operations. Its use is highly application dependent. */
gpg_error_t
-app_check_pin (app_t app, ctrl_t ctrl, const char *keyidstr,
+app_check_pin (card_t card, ctrl_t ctrl, const char *keyidstr,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg)
{
gpg_error_t err;
- if (!app || !keyidstr || !*keyidstr || !pincb)
+ if (!card || !keyidstr || !*keyidstr || !pincb)
return gpg_error (GPG_ERR_INV_VALUE);
- if (!app->ref_count)
- return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED);
- if (!app->fnc.check_pin)
- return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
- err = lock_app (app, ctrl);
+ err = lock_card (card, ctrl);
if (err)
return err;
- err = app->fnc.check_pin (app, keyidstr, pincb, pincb_arg);
- unlock_app (app);
+
+ if ((err = maybe_switch_app (ctrl, card)))
+ ;
+ else if (!card->app->fnc.check_pin)
+ err = gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
+ else
+ err = card->app->fnc.check_pin (card->app, keyidstr, pincb, pincb_arg);
+
+ unlock_card (card);
if (opt.verbose)
log_info ("operation check_pin result: %s\n", gpg_strerror (err));
return err;
}
+
static void
report_change (int slot, int old_status, int cur_status)
{
char *homestr, *envstr;
char *fname;
char templ[50];
FILE *fp;
snprintf (templ, sizeof templ, "reader_%d.status", slot);
fname = make_filename (gnupg_homedir (), templ, NULL );
fp = fopen (fname, "w");
if (fp)
{
fprintf (fp, "%s\n",
(cur_status & 1)? "USABLE":
(cur_status & 4)? "ACTIVE":
(cur_status & 2)? "PRESENT": "NOCARD");
fclose (fp);
}
xfree (fname);
homestr = make_filename (gnupg_homedir (), NULL);
if (gpgrt_asprintf (&envstr, "GNUPGHOME=%s", homestr) < 0)
log_error ("out of core while building environment\n");
else
{
gpg_error_t err;
const char *args[9], *envs[2];
char numbuf1[30], numbuf2[30], numbuf3[30];
envs[0] = envstr;
envs[1] = NULL;
sprintf (numbuf1, "%d", slot);
sprintf (numbuf2, "0x%04X", old_status);
sprintf (numbuf3, "0x%04X", cur_status);
args[0] = "--reader-port";
args[1] = numbuf1;
args[2] = "--old-code";
args[3] = numbuf2;
args[4] = "--new-code";
args[5] = numbuf3;
args[6] = "--status";
args[7] = ((cur_status & 1)? "USABLE":
(cur_status & 4)? "ACTIVE":
(cur_status & 2)? "PRESENT": "NOCARD");
args[8] = NULL;
fname = make_filename (gnupg_homedir (), "scd-event", NULL);
err = gnupg_spawn_process_detached (fname, args, envs);
if (err && gpg_err_code (err) != GPG_ERR_ENOENT)
log_error ("failed to run event handler '%s': %s\n",
fname, gpg_strerror (err));
xfree (fname);
xfree (envstr);
}
xfree (homestr);
}
+
int
scd_update_reader_status_file (void)
{
- app_t a, app_next;
+ card_t card, card_next;
int periodical_check_needed = 0;
- npth_mutex_lock (&app_list_lock);
- for (a = app_top; a; a = app_next)
+ npth_mutex_lock (&card_list_lock);
+ for (card = card_top; card; card = card_next)
{
int sw;
unsigned int status;
- lock_app (a, NULL);
- app_next = a->next;
+ lock_card (card, NULL);
+ card_next = card->next;
- if (a->reset_requested)
+ if (card->reset_requested)
status = 0;
else
{
- sw = apdu_get_status (a->slot, 0, &status);
+ sw = apdu_get_status (card->slot, 0, &status);
if (sw == SW_HOST_NO_READER)
{
/* Most likely the _reader_ has been unplugged. */
status = 0;
}
else if (sw)
{
/* Get status failed. Ignore that. */
- if (a->periodical_check_needed)
+ if (card->periodical_check_needed)
periodical_check_needed = 1;
- unlock_app (a);
+ unlock_card (card);
continue;
}
}
- if (a->card_status != status)
+ if (card->card_status != status)
{
- report_change (a->slot, a->card_status, status);
- send_client_notifications (a, status == 0);
+ report_change (card->slot, card->card_status, status);
+ send_client_notifications (card, status == 0);
if (status == 0)
{
- log_debug ("Removal of a card: %d\n", a->slot);
- apdu_close_reader (a->slot);
- deallocate_app (a);
+ log_debug ("Removal of a card: %d\n", card->slot);
+ apdu_close_reader (card->slot);
+ deallocate_card (card);
}
else
{
- a->card_status = status;
- if (a->periodical_check_needed)
+ card->card_status = status;
+ if (card->periodical_check_needed)
periodical_check_needed = 1;
- unlock_app (a);
+ unlock_card (card);
}
}
else
{
- if (a->periodical_check_needed)
+ if (card->periodical_check_needed)
periodical_check_needed = 1;
- unlock_app (a);
+ unlock_card (card);
}
}
- npth_mutex_unlock (&app_list_lock);
+
+ npth_mutex_unlock (&card_list_lock);
return periodical_check_needed;
}
/* This function must be called once to initialize this module. This
has to be done before a second thread is spawned. We can't do the
static initialization because Pth emulation code might not be able
to do a static init; in particular, it is not possible for W32. */
gpg_error_t
initialize_module_command (void)
{
gpg_error_t err;
- if (npth_mutex_init (&app_list_lock, NULL))
+ if (npth_mutex_init (&card_list_lock, NULL))
{
err = gpg_error_from_syserror ();
log_error ("app: error initializing mutex: %s\n", gpg_strerror (err));
return err;
}
return apdu_init ();
}
-void
+
+/* Sort helper for app_send_card_list. */
+static int
+compare_card_list_items (const void *arg_a, const void *arg_b)
+{
+ const card_t a = *(const card_t *)arg_a;
+ const card_t b = *(const card_t *)arg_b;
+
+ return a->slot - b->slot;
+}
+
+
+/* Send status lines with the serialno of all inserted cards. */
+gpg_error_t
app_send_card_list (ctrl_t ctrl)
{
- app_t a;
+ gpg_error_t err;
+ card_t c;
char buf[65];
+ card_t *cardlist = NULL;
+ int n, ncardlist;
+
+ npth_mutex_lock (&card_list_lock);
+ for (n=0, c = card_top; c; c = c->next)
+ n++;
+ cardlist = xtrycalloc (n, sizeof *cardlist);
+ if (!cardlist)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+ for (ncardlist=0, c = card_top; c; c = c->next)
+ cardlist[ncardlist++] = c;
+ qsort (cardlist, ncardlist, sizeof *cardlist, compare_card_list_items);
- npth_mutex_lock (&app_list_lock);
- for (a = app_top; a; a = a->next)
+ for (n=0; n < ncardlist; n++)
{
- if (DIM (buf) < 2 * a->serialnolen + 1)
+ if (DIM (buf) < 2 * cardlist[n]->serialnolen + 1)
continue;
- bin2hex (a->serialno, a->serialnolen, buf);
+ bin2hex (cardlist[n]->serialno, cardlist[n]->serialnolen, buf);
send_status_direct (ctrl, "SERIALNO", buf);
}
- npth_mutex_unlock (&app_list_lock);
+
+ err = 0;
+
+ leave:
+ npth_mutex_unlock (&card_list_lock);
+ xfree (cardlist);
+ return err;
+}
+
+
+/* Execute an action for each app. ACTION can be one of:
+ *
+ * - KEYGRIP_ACTION_SEND_DATA
+ *
+ * If KEYGRIP_STR matches a public key of any active application
+ * send information as LF terminated data lines about the public
+ * key. The format of these lines is
+ * <keygrip> T <serialno> <idstr>
+ * If a match was found a pointer to the matching application is
+ * returned. With the KEYGRIP_STR given as NULL, lines for all
+ * keys will be send and the return value is NULL.
+ *
+ * - KEYGRIP_ACTION_WRITE_STATUS
+ *
+ * Same as KEYGRIP_ACTION_SEND_DATA but uses status lines instead
+ * of data lines.
+ *
+ * - KEYGRIP_ACTION_LOOKUP
+ *
+ * Returns a pointer to the application matching KEYGRIP_STR but
+ * does not emit any status or data lines. If no key with that
+ * keygrip is available or KEYGRIP_STR is NULL, NULL is returned.
+ */
+card_t
+app_do_with_keygrip (ctrl_t ctrl, int action, const char *keygrip_str)
+{
+ gpg_error_t err;
+ card_t c;
+ app_t a;
+
+ npth_mutex_lock (&card_list_lock);
+
+ for (c = card_top; c; c = c->next)
+ for (a = c->app; a; a = a->next)
+ if (a->fnc.with_keygrip)
+ {
+ if (!lock_card (c, ctrl))
+ {
+ err = a->fnc.with_keygrip (a, ctrl, action, keygrip_str);
+ unlock_card (c);
+ if (!err)
+ goto leave_the_loop;
+ }
+ }
+
+ leave_the_loop:
+
+ /* FIXME: Add app switching logic. The above code assumes that the
+ * actions can be performend without switching. This needs to be
+ * checked. */
+
+ /* Force switching of the app if the selected one is not the current
+ * one. Changing the current apptype is sufficient to do this. */
+ if (c && c->app && c->app->apptype != a->apptype)
+ ctrl->current_apptype = a->apptype;
+
+ npth_mutex_unlock (&card_list_lock);
+ return c;
}
diff --git a/scd/ccid-driver.c b/scd/ccid-driver.c
index 69df17355..d762490c8 100644
--- a/scd/ccid-driver.c
+++ b/scd/ccid-driver.c
@@ -1,3891 +1,3910 @@
/* ccid-driver.c - USB ChipCardInterfaceDevices driver
* Copyright (C) 2003, 2004, 2005, 2006, 2007
* 2008, 2009, 2013 Free Software Foundation, Inc.
* Written by Werner Koch.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*
* ALTERNATIVELY, this file may be distributed under the terms of the
* following license, in which case the provisions of this license are
* required INSTEAD OF the GNU General Public License. If you wish to
* allow use of your version of this file only under the terms of the
* GNU General Public License, and not to allow others to use your
* version of this file under the terms of the following license,
* indicate your decision by deleting this paragraph and the license
* below.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, and the entire permission notice in its entirety,
* including the disclaimer of warranties.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote
* products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/* CCID (ChipCardInterfaceDevices) is a specification for accessing
smartcard via a reader connected to the USB.
This is a limited driver allowing to use some CCID drivers directly
without any other specila drivers. This is a fallback driver to be
used when nothing else works or the system should be kept minimal
for security reasons. It makes use of the libusb library to gain
portable access to USB.
This driver has been tested with the SCM SCR335 and SPR532
smartcard readers and requires that a reader implements APDU or
TPDU level exchange and does fully automatic initialization.
*/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#if defined(HAVE_LIBUSB) || defined(TEST)
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <time.h>
#include <unistd.h>
#ifdef HAVE_NPTH
# include <npth.h>
#endif /*HAVE_NPTH*/
#include <libusb.h>
#include "scdaemon.h"
#include "iso7816.h"
#define CCID_DRIVER_INCLUDE_USB_IDS 1
#include "ccid-driver.h"
#define DRVNAME "ccid-driver: "
/* Max length of buffer with out CCID message header of 10-byte
Sending: 547 for RSA-4096 key import
APDU size = 540 (24+4+256+256)
commnd + lc + le = 4 + 3 + 0
Sending: write data object of cardholder certificate
APDU size = 2048
commnd + lc + le = 4 + 3 + 0
Receiving: 2048 for cardholder certificate
*/
#define CCID_MAX_BUF (2048+7+10)
/* CCID command timeout. */
#define CCID_CMD_TIMEOUT (5*1000)
-/* OpenPGPcard v2.1 requires huge timeout for key generation. */
-#define CCID_CMD_TIMEOUT_LONGER (60*1000)
/* Depending on how this source is used we either define our error
- output to go to stderr or to the GnuPG based logging functions. We
- use the latter when GNUPG_MAJOR_VERSION or GNUPG_SCD_MAIN_HEADER
- are defined. */
-#if defined(GNUPG_MAJOR_VERSION) || defined(GNUPG_SCD_MAIN_HEADER)
-
-#if defined(GNUPG_SCD_MAIN_HEADER)
-# include GNUPG_SCD_MAIN_HEADER
-#elif GNUPG_MAJOR_VERSION == 1 /* GnuPG Version is < 1.9. */
-# include "options.h"
-# include "util.h"
-# include "memory.h"
-# include "cardglue.h"
-# else /* This is the modularized GnuPG 1.9 or later. */
+ * output to go to stderr or to the GnuPG based logging functions. We
+ * use the latter when GNUPG_MAJOR_VERSION is defined. */
+#if defined(GNUPG_MAJOR_VERSION)
# include "scdaemon.h"
-#endif
-
# define DEBUGOUT(t) do { if (debug_level) \
log_debug (DRVNAME t); } while (0)
# define DEBUGOUT_1(t,a) do { if (debug_level) \
log_debug (DRVNAME t,(a)); } while (0)
# define DEBUGOUT_2(t,a,b) do { if (debug_level) \
log_debug (DRVNAME t,(a),(b)); } while (0)
# define DEBUGOUT_3(t,a,b,c) do { if (debug_level) \
log_debug (DRVNAME t,(a),(b),(c));} while (0)
# define DEBUGOUT_4(t,a,b,c,d) do { if (debug_level) \
log_debug (DRVNAME t,(a),(b),(c),(d));} while (0)
# define DEBUGOUT_CONT(t) do { if (debug_level) \
log_printf (t); } while (0)
# define DEBUGOUT_CONT_1(t,a) do { if (debug_level) \
log_printf (t,(a)); } while (0)
# define DEBUGOUT_CONT_2(t,a,b) do { if (debug_level) \
log_printf (t,(a),(b)); } while (0)
# define DEBUGOUT_CONT_3(t,a,b,c) do { if (debug_level) \
log_printf (t,(a),(b),(c)); } while (0)
# define DEBUGOUT_LF() do { if (debug_level) \
log_printf ("\n"); } while (0)
#else /* Other usage of this source - don't use gnupg specifics. */
# define DEBUGOUT(t) do { if (debug_level) \
fprintf (stderr, DRVNAME t); } while (0)
# define DEBUGOUT_1(t,a) do { if (debug_level) \
fprintf (stderr, DRVNAME t, (a)); } while (0)
# define DEBUGOUT_2(t,a,b) do { if (debug_level) \
fprintf (stderr, DRVNAME t, (a), (b)); } while (0)
# define DEBUGOUT_3(t,a,b,c) do { if (debug_level) \
fprintf (stderr, DRVNAME t, (a), (b), (c)); } while (0)
# define DEBUGOUT_4(t,a,b,c,d) do { if (debug_level) \
fprintf (stderr, DRVNAME t, (a), (b), (c), (d));} while(0)
# define DEBUGOUT_CONT(t) do { if (debug_level) \
fprintf (stderr, t); } while (0)
# define DEBUGOUT_CONT_1(t,a) do { if (debug_level) \
fprintf (stderr, t, (a)); } while (0)
# define DEBUGOUT_CONT_2(t,a,b) do { if (debug_level) \
fprintf (stderr, t, (a), (b)); } while (0)
# define DEBUGOUT_CONT_3(t,a,b,c) do { if (debug_level) \
fprintf (stderr, t, (a), (b), (c)); } while (0)
# define DEBUGOUT_LF() do { if (debug_level) \
putc ('\n', stderr); } while (0)
-#endif /* This source not used by scdaemon. */
+#endif /* This source is not used by scdaemon. */
#ifndef EAGAIN
#define EAGAIN EWOULDBLOCK
#endif
enum {
RDR_to_PC_NotifySlotChange= 0x50,
RDR_to_PC_HardwareError = 0x51,
PC_to_RDR_SetParameters = 0x61,
PC_to_RDR_IccPowerOn = 0x62,
PC_to_RDR_IccPowerOff = 0x63,
PC_to_RDR_GetSlotStatus = 0x65,
PC_to_RDR_Secure = 0x69,
PC_to_RDR_T0APDU = 0x6a,
PC_to_RDR_Escape = 0x6b,
PC_to_RDR_GetParameters = 0x6c,
PC_to_RDR_ResetParameters = 0x6d,
PC_to_RDR_IccClock = 0x6e,
PC_to_RDR_XfrBlock = 0x6f,
PC_to_RDR_Mechanical = 0x71,
PC_to_RDR_Abort = 0x72,
PC_to_RDR_SetDataRate = 0x73,
RDR_to_PC_DataBlock = 0x80,
RDR_to_PC_SlotStatus = 0x81,
RDR_to_PC_Parameters = 0x82,
RDR_to_PC_Escape = 0x83,
RDR_to_PC_DataRate = 0x84
};
/* Two macro to detect whether a CCID command has failed and to get
the error code. These macros assume that we can access the
mandatory first 10 bytes of a CCID message in BUF. */
#define CCID_COMMAND_FAILED(buf) ((buf)[7] & 0x40)
#define CCID_ERROR_CODE(buf) (((unsigned char *)(buf))[8])
/* Store information on the driver's state. A pointer to such a
structure is used as handle for most functions. */
struct ccid_driver_s
{
libusb_device_handle *idev;
unsigned int bai;
unsigned short id_vendor;
unsigned short id_product;
int ifc_no;
int ep_bulk_out;
int ep_bulk_in;
int ep_intr;
int seqno;
unsigned char t1_ns;
unsigned char t1_nr;
unsigned char nonnull_nad;
int max_ifsd;
int max_ccid_msglen;
int ifsc;
unsigned char apdu_level:2; /* Reader supports short APDU level
exchange. With a value of 2 short
and extended level is supported.*/
unsigned int auto_voltage:1;
unsigned int auto_param:1;
unsigned int auto_pps:1;
unsigned int auto_ifsd:1;
unsigned int has_pinpad:2;
unsigned int enodev_seen:1;
int powered_off;
time_t last_progress; /* Last time we sent progress line. */
/* The progress callback and its first arg as supplied to
ccid_set_progress_cb. */
void (*progress_cb)(void *, const char *, int, int, int);
void *progress_cb_arg;
void (*prompt_cb)(void *, int);
void *prompt_cb_arg;
unsigned char intr_buf[64];
struct libusb_transfer *transfer;
};
static int initialized_usb; /* Tracks whether USB has been initialized. */
static int debug_level; /* Flag to control the debug output.
0 = No debugging
1 = USB I/O info
2 = Level 1 + T=1 protocol tracing
3 = Level 2 + USB/I/O tracing of SlotStatus.
*/
static int ccid_usb_thread_is_alive;
static unsigned int compute_edc (const unsigned char *data, size_t datalen,
int use_crc);
static int bulk_out (ccid_driver_t handle, unsigned char *msg, size_t msglen,
int no_debug);
static int bulk_in (ccid_driver_t handle, unsigned char *buffer, size_t length,
size_t *nread, int expected_type, int seqno, int timeout,
int no_debug);
static int abort_cmd (ccid_driver_t handle, int seqno);
static int send_escape_cmd (ccid_driver_t handle, const unsigned char *data,
size_t datalen, unsigned char *result,
size_t resultmax, size_t *resultlen);
/* Convert a little endian stored 4 byte value into an unsigned
integer. */
static unsigned int
convert_le_u32 (const unsigned char *buf)
{
return buf[0] | (buf[1] << 8) | (buf[2] << 16) | ((unsigned int)buf[3] << 24);
}
/* Convert a little endian stored 2 byte value into an unsigned
integer. */
static unsigned int
convert_le_u16 (const unsigned char *buf)
{
return buf[0] | (buf[1] << 8);
}
static void
set_msg_len (unsigned char *msg, unsigned int length)
{
msg[1] = length;
msg[2] = length >> 8;
msg[3] = length >> 16;
msg[4] = length >> 24;
}
static void
print_progress (ccid_driver_t handle)
{
time_t ct = time (NULL);
/* We don't want to print progress lines too often. */
if (ct == handle->last_progress)
return;
if (handle->progress_cb)
handle->progress_cb (handle->progress_cb_arg, "card_busy", 'w', 0, 0);
handle->last_progress = ct;
}
/* Pint an error message for a failed CCID command including a textual
error code. MSG shall be the CCID message at a minimum of 10 bytes. */
static void
print_command_failed (const unsigned char *msg)
{
const char *t;
char buffer[100];
int ec;
if (!debug_level)
return;
ec = CCID_ERROR_CODE (msg);
switch (ec)
{
case 0x00: t = "Command not supported"; break;
case 0xE0: t = "Slot busy"; break;
case 0xEF: t = "PIN cancelled"; break;
case 0xF0: t = "PIN timeout"; break;
case 0xF2: t = "Automatic sequence ongoing"; break;
case 0xF3: t = "Deactivated Protocol"; break;
case 0xF4: t = "Procedure byte conflict"; break;
case 0xF5: t = "ICC class not supported"; break;
case 0xF6: t = "ICC protocol not supported"; break;
case 0xF7: t = "Bad checksum in ATR"; break;
case 0xF8: t = "Bad TS in ATR"; break;
case 0xFB: t = "An all inclusive hardware error occurred"; break;
case 0xFC: t = "Overrun error while talking to the ICC"; break;
case 0xFD: t = "Parity error while talking to the ICC"; break;
case 0xFE: t = "CCID timed out while talking to the ICC"; break;
case 0xFF: t = "Host aborted the current activity"; break;
default:
if (ec > 0 && ec < 128)
sprintf (buffer, "Parameter error at offset %d", ec);
else
sprintf (buffer, "Error code %02X", ec);
t = buffer;
break;
}
DEBUGOUT_1 ("CCID command failed: %s\n", t);
}
static void
print_pr_data (const unsigned char *data, size_t datalen, size_t off)
{
int any = 0;
for (; off < datalen; off++)
{
if (!any || !(off % 16))
{
if (any)
DEBUGOUT_LF ();
DEBUGOUT_1 (" [%04lu] ", (unsigned long) off);
}
DEBUGOUT_CONT_1 (" %02X", data[off]);
any = 1;
}
if (any && (off % 16))
DEBUGOUT_LF ();
}
static void
print_p2r_header (const char *name, const unsigned char *msg, size_t msglen)
{
DEBUGOUT_1 ("%s:\n", name);
if (msglen < 7)
return;
DEBUGOUT_1 (" dwLength ..........: %u\n", convert_le_u32 (msg+1));
DEBUGOUT_1 (" bSlot .............: %u\n", msg[5]);
DEBUGOUT_1 (" bSeq ..............: %u\n", msg[6]);
}
static void
print_p2r_iccpoweron (const unsigned char *msg, size_t msglen)
{
print_p2r_header ("PC_to_RDR_IccPowerOn", msg, msglen);
if (msglen < 10)
return;
DEBUGOUT_2 (" bPowerSelect ......: 0x%02x (%s)\n", msg[7],
msg[7] == 0? "auto":
msg[7] == 1? "5.0 V":
msg[7] == 2? "3.0 V":
msg[7] == 3? "1.8 V":"");
print_pr_data (msg, msglen, 8);
}
static void
print_p2r_iccpoweroff (const unsigned char *msg, size_t msglen)
{
print_p2r_header ("PC_to_RDR_IccPowerOff", msg, msglen);
print_pr_data (msg, msglen, 7);
}
static void
print_p2r_getslotstatus (const unsigned char *msg, size_t msglen)
{
print_p2r_header ("PC_to_RDR_GetSlotStatus", msg, msglen);
print_pr_data (msg, msglen, 7);
}
static void
print_p2r_xfrblock (const unsigned char *msg, size_t msglen)
{
unsigned int val;
print_p2r_header ("PC_to_RDR_XfrBlock", msg, msglen);
if (msglen < 10)
return;
DEBUGOUT_1 (" bBWI ..............: 0x%02x\n", msg[7]);
val = convert_le_u16 (msg+8);
DEBUGOUT_2 (" wLevelParameter ...: 0x%04x%s\n", val,
val == 1? " (continued)":
val == 2? " (continues+ends)":
val == 3? " (continues+continued)":
val == 16? " (DataBlock-expected)":"");
print_pr_data (msg, msglen, 10);
}
static void
print_p2r_getparameters (const unsigned char *msg, size_t msglen)
{
print_p2r_header ("PC_to_RDR_GetParameters", msg, msglen);
print_pr_data (msg, msglen, 7);
}
static void
print_p2r_resetparameters (const unsigned char *msg, size_t msglen)
{
print_p2r_header ("PC_to_RDR_ResetParameters", msg, msglen);
print_pr_data (msg, msglen, 7);
}
static void
print_p2r_setparameters (const unsigned char *msg, size_t msglen)
{
print_p2r_header ("PC_to_RDR_SetParameters", msg, msglen);
if (msglen < 10)
return;
DEBUGOUT_1 (" bProtocolNum ......: 0x%02x\n", msg[7]);
print_pr_data (msg, msglen, 8);
}
static void
print_p2r_escape (const unsigned char *msg, size_t msglen)
{
print_p2r_header ("PC_to_RDR_Escape", msg, msglen);
print_pr_data (msg, msglen, 7);
}
static void
print_p2r_iccclock (const unsigned char *msg, size_t msglen)
{
print_p2r_header ("PC_to_RDR_IccClock", msg, msglen);
if (msglen < 10)
return;
DEBUGOUT_1 (" bClockCommand .....: 0x%02x\n", msg[7]);
print_pr_data (msg, msglen, 8);
}
static void
print_p2r_to0apdu (const unsigned char *msg, size_t msglen)
{
print_p2r_header ("PC_to_RDR_T0APDU", msg, msglen);
if (msglen < 10)
return;
DEBUGOUT_1 (" bmChanges .........: 0x%02x\n", msg[7]);
DEBUGOUT_1 (" bClassGetResponse .: 0x%02x\n", msg[8]);
DEBUGOUT_1 (" bClassEnvelope ....: 0x%02x\n", msg[9]);
print_pr_data (msg, msglen, 10);
}
static void
print_p2r_secure (const unsigned char *msg, size_t msglen)
{
unsigned int val;
print_p2r_header ("PC_to_RDR_Secure", msg, msglen);
if (msglen < 10)
return;
DEBUGOUT_1 (" bBMI ..............: 0x%02x\n", msg[7]);
val = convert_le_u16 (msg+8);
DEBUGOUT_2 (" wLevelParameter ...: 0x%04x%s\n", val,
val == 1? " (continued)":
val == 2? " (continues+ends)":
val == 3? " (continues+continued)":
val == 16? " (DataBlock-expected)":"");
print_pr_data (msg, msglen, 10);
}
static void
print_p2r_mechanical (const unsigned char *msg, size_t msglen)
{
print_p2r_header ("PC_to_RDR_Mechanical", msg, msglen);
if (msglen < 10)
return;
DEBUGOUT_1 (" bFunction .........: 0x%02x\n", msg[7]);
print_pr_data (msg, msglen, 8);
}
static void
print_p2r_abort (const unsigned char *msg, size_t msglen)
{
print_p2r_header ("PC_to_RDR_Abort", msg, msglen);
print_pr_data (msg, msglen, 7);
}
static void
print_p2r_setdatarate (const unsigned char *msg, size_t msglen)
{
print_p2r_header ("PC_to_RDR_SetDataRate", msg, msglen);
if (msglen < 10)
return;
print_pr_data (msg, msglen, 7);
}
static void
print_p2r_unknown (const unsigned char *msg, size_t msglen)
{
print_p2r_header ("Unknown PC_to_RDR command", msg, msglen);
if (msglen < 10)
return;
print_pr_data (msg, msglen, 0);
}
static void
print_r2p_header (const char *name, const unsigned char *msg, size_t msglen)
{
DEBUGOUT_1 ("%s:\n", name);
if (msglen < 9)
return;
DEBUGOUT_1 (" dwLength ..........: %u\n", convert_le_u32 (msg+1));
DEBUGOUT_1 (" bSlot .............: %u\n", msg[5]);
DEBUGOUT_1 (" bSeq ..............: %u\n", msg[6]);
DEBUGOUT_1 (" bStatus ...........: %u\n", msg[7]);
if (msg[8])
DEBUGOUT_1 (" bError ............: %u\n", msg[8]);
}
static void
print_r2p_datablock (const unsigned char *msg, size_t msglen)
{
print_r2p_header ("RDR_to_PC_DataBlock", msg, msglen);
if (msglen < 10)
return;
if (msg[9])
DEBUGOUT_2 (" bChainParameter ...: 0x%02x%s\n", msg[9],
msg[9] == 1? " (continued)":
msg[9] == 2? " (continues+ends)":
msg[9] == 3? " (continues+continued)":
msg[9] == 16? " (XferBlock-expected)":"");
print_pr_data (msg, msglen, 10);
}
static void
print_r2p_slotstatus (const unsigned char *msg, size_t msglen)
{
print_r2p_header ("RDR_to_PC_SlotStatus", msg, msglen);
if (msglen < 10)
return;
DEBUGOUT_2 (" bClockStatus ......: 0x%02x%s\n", msg[9],
msg[9] == 0? " (running)":
msg[9] == 1? " (stopped-L)":
msg[9] == 2? " (stopped-H)":
msg[9] == 3? " (stopped)":"");
print_pr_data (msg, msglen, 10);
}
static void
print_r2p_parameters (const unsigned char *msg, size_t msglen)
{
print_r2p_header ("RDR_to_PC_Parameters", msg, msglen);
if (msglen < 10)
return;
DEBUGOUT_1 (" protocol ..........: T=%d\n", msg[9]);
if (msglen == 17 && msg[9] == 1)
{
/* Protocol T=1. */
DEBUGOUT_1 (" bmFindexDindex ....: %02X\n", msg[10]);
DEBUGOUT_1 (" bmTCCKST1 .........: %02X\n", msg[11]);
DEBUGOUT_1 (" bGuardTimeT1 ......: %02X\n", msg[12]);
DEBUGOUT_1 (" bmWaitingIntegersT1: %02X\n", msg[13]);
DEBUGOUT_1 (" bClockStop ........: %02X\n", msg[14]);
DEBUGOUT_1 (" bIFSC .............: %d\n", msg[15]);
DEBUGOUT_1 (" bNadValue .........: %d\n", msg[16]);
}
else
print_pr_data (msg, msglen, 10);
}
static void
print_r2p_escape (const unsigned char *msg, size_t msglen)
{
print_r2p_header ("RDR_to_PC_Escape", msg, msglen);
if (msglen < 10)
return;
DEBUGOUT_1 (" buffer[9] .........: %02X\n", msg[9]);
print_pr_data (msg, msglen, 10);
}
static void
print_r2p_datarate (const unsigned char *msg, size_t msglen)
{
print_r2p_header ("RDR_to_PC_DataRate", msg, msglen);
if (msglen < 10)
return;
if (msglen >= 18)
{
DEBUGOUT_1 (" dwClockFrequency ..: %u\n", convert_le_u32 (msg+10));
DEBUGOUT_1 (" dwDataRate ..... ..: %u\n", convert_le_u32 (msg+14));
print_pr_data (msg, msglen, 18);
}
else
print_pr_data (msg, msglen, 10);
}
static void
print_r2p_unknown (const unsigned char *msg, size_t msglen)
{
print_r2p_header ("Unknown RDR_to_PC command", msg, msglen);
if (msglen < 10)
return;
DEBUGOUT_1 (" bMessageType ......: %02X\n", msg[0]);
DEBUGOUT_1 (" buffer[9] .........: %02X\n", msg[9]);
print_pr_data (msg, msglen, 10);
}
/* Parse a CCID descriptor, optionally print all available features
and test whether this reader is usable by this driver. Returns 0
if it is usable.
Note, that this code is based on the one in lsusb.c of the
usb-utils package, I wrote on 2003-09-01. -wk. */
static int
parse_ccid_descriptor (ccid_driver_t handle, unsigned short bcd_device,
const unsigned char *buf, size_t buflen)
{
unsigned int i;
unsigned int us;
int have_t1 = 0, have_tpdu=0;
handle->nonnull_nad = 0;
handle->auto_ifsd = 0;
handle->max_ifsd = 32;
handle->has_pinpad = 0;
handle->apdu_level = 0;
handle->auto_voltage = 0;
handle->auto_param = 0;
handle->auto_pps = 0;
DEBUGOUT_3 ("idVendor: %04X idProduct: %04X bcdDevice: %04X\n",
handle->id_vendor, handle->id_product, bcd_device);
if (buflen < 54 || buf[0] < 54)
{
DEBUGOUT ("CCID device descriptor is too short\n");
return -1;
}
DEBUGOUT ("ChipCard Interface Descriptor:\n");
DEBUGOUT_1 (" bLength %5u\n", buf[0]);
DEBUGOUT_1 (" bDescriptorType %5u\n", buf[1]);
DEBUGOUT_2 (" bcdCCID %2x.%02x", buf[3], buf[2]);
if (buf[3] != 1 || buf[2] != 0)
DEBUGOUT_CONT(" (Warning: Only accurate for version 1.0)");
DEBUGOUT_LF ();
DEBUGOUT_1 (" nMaxSlotIndex %5u\n", buf[4]);
DEBUGOUT_2 (" bVoltageSupport %5u %s\n",
buf[5], (buf[5] == 1? "5.0V" : buf[5] == 2? "3.0V"
: buf[5] == 3? "1.8V":"?"));
us = convert_le_u32 (buf+6);
DEBUGOUT_1 (" dwProtocols %5u ", us);
if ((us & 1))
DEBUGOUT_CONT (" T=0");
if ((us & 2))
{
DEBUGOUT_CONT (" T=1");
have_t1 = 1;
}
if ((us & ~3))
DEBUGOUT_CONT (" (Invalid values detected)");
DEBUGOUT_LF ();
us = convert_le_u32(buf+10);
DEBUGOUT_1 (" dwDefaultClock %5u\n", us);
us = convert_le_u32(buf+14);
DEBUGOUT_1 (" dwMaxiumumClock %5u\n", us);
DEBUGOUT_1 (" bNumClockSupported %5u\n", buf[18]);
us = convert_le_u32(buf+19);
DEBUGOUT_1 (" dwDataRate %7u bps\n", us);
us = convert_le_u32(buf+23);
DEBUGOUT_1 (" dwMaxDataRate %7u bps\n", us);
DEBUGOUT_1 (" bNumDataRatesSupp. %5u\n", buf[27]);
us = convert_le_u32(buf+28);
DEBUGOUT_1 (" dwMaxIFSD %5u\n", us);
handle->max_ifsd = us;
us = convert_le_u32(buf+32);
DEBUGOUT_1 (" dwSyncProtocols %08X ", us);
if ((us&1))
DEBUGOUT_CONT ( " 2-wire");
if ((us&2))
DEBUGOUT_CONT ( " 3-wire");
if ((us&4))
DEBUGOUT_CONT ( " I2C");
DEBUGOUT_LF ();
us = convert_le_u32(buf+36);
DEBUGOUT_1 (" dwMechanical %08X ", us);
if ((us & 1))
DEBUGOUT_CONT (" accept");
if ((us & 2))
DEBUGOUT_CONT (" eject");
if ((us & 4))
DEBUGOUT_CONT (" capture");
if ((us & 8))
DEBUGOUT_CONT (" lock");
DEBUGOUT_LF ();
us = convert_le_u32(buf+40);
DEBUGOUT_1 (" dwFeatures %08X\n", us);
if ((us & 0x0002))
{
DEBUGOUT (" Auto configuration based on ATR (assumes auto voltage)\n");
handle->auto_voltage = 1;
}
if ((us & 0x0004))
DEBUGOUT (" Auto activation on insert\n");
if ((us & 0x0008))
{
DEBUGOUT (" Auto voltage selection\n");
handle->auto_voltage = 1;
}
if ((us & 0x0010))
DEBUGOUT (" Auto clock change\n");
if ((us & 0x0020))
DEBUGOUT (" Auto baud rate change\n");
if ((us & 0x0040))
{
DEBUGOUT (" Auto parameter negotiation made by CCID\n");
handle->auto_param = 1;
}
else if ((us & 0x0080))
{
DEBUGOUT (" Auto PPS made by CCID\n");
handle->auto_pps = 1;
}
if ((us & (0x0040 | 0x0080)) == (0x0040 | 0x0080))
DEBUGOUT (" WARNING: conflicting negotiation features\n");
if ((us & 0x0100))
DEBUGOUT (" CCID can set ICC in clock stop mode\n");
if ((us & 0x0200))
{
DEBUGOUT (" NAD value other than 0x00 accepted\n");
handle->nonnull_nad = 1;
}
if ((us & 0x0400))
{
DEBUGOUT (" Auto IFSD exchange\n");
handle->auto_ifsd = 1;
}
if ((us & 0x00010000))
{
DEBUGOUT (" TPDU level exchange\n");
have_tpdu = 1;
}
else if ((us & 0x00020000))
{
DEBUGOUT (" Short APDU level exchange\n");
handle->apdu_level = 1;
}
else if ((us & 0x00040000))
{
DEBUGOUT (" Short and extended APDU level exchange\n");
handle->apdu_level = 2;
}
else if ((us & 0x00070000))
DEBUGOUT (" WARNING: conflicting exchange levels\n");
us = convert_le_u32(buf+44);
DEBUGOUT_1 (" dwMaxCCIDMsgLen %5u\n", us);
handle->max_ccid_msglen = us;
DEBUGOUT ( " bClassGetResponse ");
if (buf[48] == 0xff)
DEBUGOUT_CONT ("echo\n");
else
DEBUGOUT_CONT_1 (" %02X\n", buf[48]);
DEBUGOUT ( " bClassEnvelope ");
if (buf[49] == 0xff)
DEBUGOUT_CONT ("echo\n");
else
DEBUGOUT_CONT_1 (" %02X\n", buf[48]);
DEBUGOUT ( " wlcdLayout ");
if (!buf[50] && !buf[51])
DEBUGOUT_CONT ("none\n");
else
DEBUGOUT_CONT_2 ("%u cols %u lines\n", buf[50], buf[51]);
DEBUGOUT_1 (" bPINSupport %5u ", buf[52]);
if ((buf[52] & 1))
{
DEBUGOUT_CONT ( " verification");
handle->has_pinpad |= 1;
}
if ((buf[52] & 2))
{
DEBUGOUT_CONT ( " modification");
handle->has_pinpad |= 2;
}
DEBUGOUT_LF ();
DEBUGOUT_1 (" bMaxCCIDBusySlots %5u\n", buf[53]);
if (buf[0] > 54)
{
DEBUGOUT (" junk ");
for (i=54; i < buf[0]-54; i++)
DEBUGOUT_CONT_1 (" %02X", buf[i]);
DEBUGOUT_LF ();
}
if (!have_t1 || !(have_tpdu || handle->apdu_level))
{
DEBUGOUT ("this drivers requires that the reader supports T=1, "
"TPDU or APDU level exchange - this is not available\n");
return -1;
}
/* SCM drivers get stuck in their internal USB stack if they try to
send a frame of n*wMaxPacketSize back to us. Given that
wMaxPacketSize is 64 for these readers we set the IFSD to a value
lower than that:
64 - 10 CCID header - 4 T1frame - 2 reserved = 48
Product Ids:
0xe001 - SCR 331
0x5111 - SCR 331-DI
0x5115 - SCR 335
0xe003 - SPR 532
The
0x5117 - SCR 3320 USB ID-000 reader
seems to be very slow but enabling this workaround boosts the
performance to a more or less acceptable level (tested by David).
*/
if (handle->id_vendor == VENDOR_SCM
&& handle->max_ifsd > 48
&& ( (handle->id_product == SCM_SCR331 && bcd_device < 0x0516)
||(handle->id_product == SCM_SCR331DI && bcd_device < 0x0620)
||(handle->id_product == SCM_SCR335 && bcd_device < 0x0514)
||(handle->id_product == SCM_SPR532 && bcd_device < 0x0504)
||(handle->id_product == SCM_SCR3320 && bcd_device < 0x0522)
))
{
DEBUGOUT ("enabling workaround for buggy SCM readers\n");
handle->max_ifsd = 48;
}
if (handle->id_vendor == VENDOR_GEMPC)
{
DEBUGOUT ("enabling product quirk: disable non-null NAD\n");
handle->nonnull_nad = 0;
}
return 0;
}
static char *
get_escaped_usb_string (libusb_device_handle *idev, int idx,
const char *prefix, const char *suffix)
{
int rc;
unsigned char buf[280];
unsigned char *s;
unsigned int langid;
size_t i, n, len;
char *result;
if (!idx)
return NULL;
/* Fixme: The next line is for the current Valgrid without support
for USB IOCTLs. */
memset (buf, 0, sizeof buf);
/* First get the list of supported languages and use the first one.
If we do don't find it we try to use English. Note that this is
all in a 2 bute Unicode encoding using little endian. */
#ifdef USE_NPTH
npth_unprotect ();
#endif
rc = libusb_control_transfer (idev, LIBUSB_ENDPOINT_IN,
LIBUSB_REQUEST_GET_DESCRIPTOR,
(LIBUSB_DT_STRING << 8), 0,
- (char*)buf, sizeof buf, 1000 /* ms timeout */);
+ buf, sizeof buf, 1000 /* ms timeout */);
#ifdef USE_NPTH
npth_protect ();
#endif
if (rc < 4)
langid = 0x0409; /* English. */
else
langid = (buf[3] << 8) | buf[2];
#ifdef USE_NPTH
npth_unprotect ();
#endif
rc = libusb_control_transfer (idev, LIBUSB_ENDPOINT_IN,
LIBUSB_REQUEST_GET_DESCRIPTOR,
(LIBUSB_DT_STRING << 8) + idx, langid,
- (char*)buf, sizeof buf, 1000 /* ms timeout */);
+ buf, sizeof buf, 1000 /* ms timeout */);
#ifdef USE_NPTH
npth_protect ();
#endif
if (rc < 2 || buf[1] != LIBUSB_DT_STRING)
return NULL; /* Error or not a string. */
len = buf[0];
if (len > rc)
return NULL; /* Larger than our buffer. */
for (s=buf+2, i=2, n=0; i+1 < len; i += 2, s += 2)
{
if (s[1])
n++; /* High byte set. */
else if (*s <= 0x20 || *s >= 0x7f || *s == '%' || *s == ':')
n += 3 ;
else
n++;
}
result = malloc (strlen (prefix) + n + strlen (suffix) + 1);
if (!result)
return NULL;
strcpy (result, prefix);
n = strlen (prefix);
for (s=buf+2, i=2; i+1 < len; i += 2, s += 2)
{
if (s[1])
result[n++] = '\xff'; /* High byte set. */
else if (*s <= 0x20 || *s >= 0x7f || *s == '%' || *s == ':')
{
sprintf (result+n, "%%%02X", *s);
n += 3;
}
else
result[n++] = *s;
}
strcpy (result+n, suffix);
return result;
}
/* This function creates an reader id to be used to find the same
physical reader after a reset. It returns an allocated and possibly
percent escaped string or NULL if not enough memory is available. */
static char *
make_reader_id (libusb_device_handle *idev,
unsigned int vendor, unsigned int product,
unsigned char serialno_index)
{
char *rid;
char prefix[20];
sprintf (prefix, "%04X:%04X:", (vendor & 0xffff), (product & 0xffff));
rid = get_escaped_usb_string (idev, serialno_index, prefix, ":0");
if (!rid)
{
rid = malloc (strlen (prefix) + 3 + 1);
if (!rid)
return NULL;
strcpy (rid, prefix);
strcat (rid, "X:0");
}
return rid;
}
/* Helper to find the endpoint from an interface descriptor. */
static int
find_endpoint (const struct libusb_interface_descriptor *ifcdesc, int mode)
{
int no;
int want_bulk_in = 0;
if (mode == 1)
want_bulk_in = 0x80;
for (no=0; no < ifcdesc->bNumEndpoints; no++)
{
const struct libusb_endpoint_descriptor *ep = ifcdesc->endpoint + no;
if (ep->bDescriptorType != LIBUSB_DT_ENDPOINT)
;
else if (mode == 2
&& ((ep->bmAttributes & LIBUSB_TRANSFER_TYPE_MASK)
== LIBUSB_TRANSFER_TYPE_INTERRUPT)
&& (ep->bEndpointAddress & 0x80))
return ep->bEndpointAddress;
else if ((mode == 0 || mode == 1)
&& ((ep->bmAttributes & LIBUSB_TRANSFER_TYPE_MASK)
== LIBUSB_TRANSFER_TYPE_BULK)
&& (ep->bEndpointAddress & 0x80) == want_bulk_in)
return ep->bEndpointAddress;
}
return -1;
}
/* Helper for scan_devices. This function returns true if a
requested device has been found or the caller should stop scanning
for other reasons. */
static void
scan_usb_device (int *count, char **rid_list, struct libusb_device *dev)
{
int ifc_no;
int set_no;
const struct libusb_interface_descriptor *ifcdesc;
char *rid;
libusb_device_handle *idev = NULL;
int err;
struct libusb_config_descriptor *config;
struct libusb_device_descriptor desc;
char *p;
err = libusb_get_device_descriptor (dev, &desc);
if (err)
return;
err = libusb_get_active_config_descriptor (dev, &config);
if (err)
return;
for (ifc_no=0; ifc_no < config->bNumInterfaces; ifc_no++)
for (set_no=0; set_no < config->interface[ifc_no].num_altsetting; set_no++)
{
ifcdesc = (config->interface[ifc_no].altsetting + set_no);
/* The second condition is for older SCM SPR 532 who did
not know about the assigned CCID class. The third
condition does the same for a Cherry SmartTerminal
ST-2000. Instead of trying to interpret the strings
we simply check the product ID. */
if (ifcdesc && ifcdesc->extra
&& ((ifcdesc->bInterfaceClass == 11
&& ifcdesc->bInterfaceSubClass == 0
&& ifcdesc->bInterfaceProtocol == 0)
|| (ifcdesc->bInterfaceClass == 255
&& desc.idVendor == VENDOR_SCM
&& desc.idProduct == SCM_SPR532)
|| (ifcdesc->bInterfaceClass == 255
&& desc.idVendor == VENDOR_CHERRY
&& desc.idProduct == CHERRY_ST2000)))
{
++*count;
err = libusb_open (dev, &idev);
if (err)
{
DEBUGOUT_1 ("usb_open failed: %s\n", libusb_error_name (err));
continue; /* with next setting. */
}
rid = make_reader_id (idev, desc.idVendor, desc.idProduct,
desc.iSerialNumber);
if (!rid)
{
libusb_free_config_descriptor (config);
return;
}
/* We are collecting infos about all available CCID
readers. Store them and continue. */
DEBUGOUT_2 ("found CCID reader %d (ID=%s)\n", *count, rid);
p = malloc ((*rid_list? strlen (*rid_list):0) + 1
+ strlen (rid) + 1);
if (p)
{
*p = 0;
if (*rid_list)
{
strcat (p, *rid_list);
free (*rid_list);
}
strcat (p, rid);
strcat (p, "\n");
*rid_list = p;
}
else /* Out of memory. */
{
libusb_free_config_descriptor (config);
free (rid);
return;
}
free (rid);
libusb_close (idev);
idev = NULL;
}
}
libusb_free_config_descriptor (config);
}
/* Scan all CCID devices.
The function returns 0 if a reader has been found or when a scan
returned without error.
R_RID should be the address where to store the list of reader_ids
we found. If on return this list is empty, no CCID device has been
found; otherwise it points to an allocated linked list of reader
IDs.
*/
static int
scan_devices (char **r_rid)
{
char *rid_list = NULL;
int count = 0;
libusb_device **dev_list = NULL;
libusb_device *dev;
int i;
ssize_t n;
/* Set return values to a default. */
if (r_rid)
*r_rid = NULL;
n = libusb_get_device_list (NULL, &dev_list);
for (i = 0; i < n; i++)
{
dev = dev_list[i];
scan_usb_device (&count, &rid_list, dev);
}
libusb_free_device_list (dev_list, 1);
*r_rid = rid_list;
return 0;
}
/* Set the level of debugging to LEVEL and return the old level. -1
just returns the old level. A level of 0 disables debugging, 1
enables debugging, 2 enables additional tracing of the T=1
protocol, 3 additionally enables debugging for GetSlotStatus, other
values are not yet defined.
Note that libusb may provide its own debugging feature which is
enabled by setting the envvar USB_DEBUG. */
int
ccid_set_debug_level (int level)
{
int old = debug_level;
if (level != -1)
debug_level = level;
return old;
}
char *
ccid_get_reader_list (void)
{
char *reader_list;
if (!initialized_usb)
{
int rc;
if ((rc = libusb_init (NULL)))
{
DEBUGOUT_1 ("usb_init failed: %s.\n", libusb_error_name (rc));
return NULL;
}
initialized_usb = 1;
}
if (scan_devices (&reader_list))
return NULL; /* Error. */
return reader_list;
}
/* Vendor specific custom initialization. */
static int
ccid_vendor_specific_init (ccid_driver_t handle)
{
if (handle->id_vendor == VENDOR_VEGA && handle->id_product == VEGA_ALPHA)
{
int r;
/*
* Vega alpha has a feature to show retry counter on the pinpad
* display. But it assumes that the card returns the value of
* retry counter by VERIFY with empty data (return code of
* 63Cx). Unfortunately, existing OpenPGP cards don't support
* VERIFY command with empty data. This vendor specific command
* sequence is to disable the feature.
*/
const unsigned char cmd[] = { '\xb5', '\x01', '\x00', '\x03', '\x00' };
r = send_escape_cmd (handle, cmd, sizeof (cmd), NULL, 0, NULL);
if (r != 0 && r != CCID_DRIVER_ERR_CARD_INACTIVE
&& r != CCID_DRIVER_ERR_NO_CARD)
return r;
}
return 0;
}
#define MAX_DEVICE 4 /* See MAX_READER in apdu.c. */
struct ccid_dev_table {
int n; /* Index to ccid_usb_dev_list */
int interface_number;
int setting_number;
unsigned char *ifcdesc_extra;
int ep_bulk_out;
int ep_bulk_in;
int ep_intr;
size_t ifcdesc_extra_len;
};
static libusb_device **ccid_usb_dev_list;
static struct ccid_dev_table ccid_dev_table[MAX_DEVICE];
gpg_error_t
ccid_dev_scan (int *idx_max_p, struct ccid_dev_table **t_p)
{
ssize_t n;
libusb_device *dev;
int i;
int ifc_no;
int set_no;
int idx = 0;
int err = 0;
*idx_max_p = 0;
*t_p = NULL;
if (!initialized_usb)
{
int rc;
if ((rc = libusb_init (NULL)))
{
DEBUGOUT_1 ("usb_init failed: %s.\n", libusb_error_name (rc));
return gpg_error (GPG_ERR_ENODEV);
}
initialized_usb = 1;
}
n = libusb_get_device_list (NULL, &ccid_usb_dev_list);
for (i = 0; i < n; i++)
{
struct libusb_config_descriptor *config;
struct libusb_device_descriptor desc;
dev = ccid_usb_dev_list[i];
if (libusb_get_device_descriptor (dev, &desc))
continue;
if (libusb_get_active_config_descriptor (dev, &config))
continue;
for (ifc_no=0; ifc_no < config->bNumInterfaces; ifc_no++)
for (set_no=0; set_no < config->interface[ifc_no].num_altsetting;
set_no++)
{
const struct libusb_interface_descriptor *ifcdesc;
ifcdesc = &config->interface[ifc_no].altsetting[set_no];
/* The second condition is for older SCM SPR 532 who did
not know about the assigned CCID class. The third
condition does the same for a Cherry SmartTerminal
ST-2000. Instead of trying to interpret the strings
we simply check the product ID. */
if (ifcdesc && ifcdesc->extra
&& ((ifcdesc->bInterfaceClass == 11
&& ifcdesc->bInterfaceSubClass == 0
&& ifcdesc->bInterfaceProtocol == 0)
|| (ifcdesc->bInterfaceClass == 255
&& desc.idVendor == VENDOR_SCM
&& desc.idProduct == SCM_SPR532)
|| (ifcdesc->bInterfaceClass == 255
&& desc.idVendor == VENDOR_CHERRY
&& desc.idProduct == CHERRY_ST2000)))
{
/* Found a reader. */
unsigned char *ifcdesc_extra;
ifcdesc_extra = malloc (ifcdesc->extra_length);
if (!ifcdesc_extra)
{
err = gpg_error_from_syserror ();
libusb_free_config_descriptor (config);
goto scan_finish;
}
memcpy (ifcdesc_extra, ifcdesc->extra, ifcdesc->extra_length);
ccid_dev_table[idx].n = i;
ccid_dev_table[idx].interface_number = ifc_no;
ccid_dev_table[idx].setting_number = set_no;
ccid_dev_table[idx].ifcdesc_extra = ifcdesc_extra;
ccid_dev_table[idx].ifcdesc_extra_len = ifcdesc->extra_length;
ccid_dev_table[idx].ep_bulk_out = find_endpoint (ifcdesc, 0);
ccid_dev_table[idx].ep_bulk_in = find_endpoint (ifcdesc, 1);
ccid_dev_table[idx].ep_intr = find_endpoint (ifcdesc, 2);
idx++;
if (idx >= MAX_DEVICE)
{
libusb_free_config_descriptor (config);
err = 0;
goto scan_finish;
}
}
}
libusb_free_config_descriptor (config);
}
scan_finish:
if (err)
{
for (i = 0; i < idx; i++)
{
free (ccid_dev_table[idx].ifcdesc_extra);
ccid_dev_table[idx].n = 0;
ccid_dev_table[idx].interface_number = 0;
ccid_dev_table[idx].setting_number = 0;
ccid_dev_table[idx].ifcdesc_extra = NULL;
ccid_dev_table[idx].ifcdesc_extra_len = 0;
ccid_dev_table[idx].ep_bulk_out = 0;
ccid_dev_table[idx].ep_bulk_in = 0;
ccid_dev_table[idx].ep_intr = 0;
}
libusb_free_device_list (ccid_usb_dev_list, 1);
ccid_usb_dev_list = NULL;
}
else
{
*idx_max_p = idx;
if (idx)
*t_p = ccid_dev_table;
else
*t_p = NULL;
}
return err;
}
void
ccid_dev_scan_finish (struct ccid_dev_table *tbl, int max)
{
int i;
for (i = 0; i < max; i++)
{
free (tbl[i].ifcdesc_extra);
tbl[i].n = 0;
tbl[i].interface_number = 0;
tbl[i].setting_number = 0;
tbl[i].ifcdesc_extra = NULL;
tbl[i].ifcdesc_extra_len = 0;
tbl[i].ep_bulk_out = 0;
tbl[i].ep_bulk_in = 0;
tbl[i].ep_intr = 0;
}
libusb_free_device_list (ccid_usb_dev_list, 1);
ccid_usb_dev_list = NULL;
}
unsigned int
ccid_get_BAI (int idx, struct ccid_dev_table *tbl)
{
int n;
int bus, addr, intf;
unsigned int bai;
libusb_device *dev;
n = tbl[idx].n;
dev = ccid_usb_dev_list[n];
bus = libusb_get_bus_number (dev);
addr = libusb_get_device_address (dev);
intf = tbl[idx].interface_number;
bai = (bus << 16) | (addr << 8) | intf;
return bai;
}
int
ccid_compare_BAI (ccid_driver_t handle, unsigned int bai)
{
return handle->bai == bai;
}
static void
intr_cb (struct libusb_transfer *transfer)
{
ccid_driver_t handle = transfer->user_data;
DEBUGOUT_1 ("CCID: interrupt callback %d\n", transfer->status);
if (transfer->status == LIBUSB_TRANSFER_TIMED_OUT)
{
int err;
submit_again:
/* Submit the URB again to keep watching the INTERRUPT transfer. */
err = libusb_submit_transfer (transfer);
if (err == LIBUSB_ERROR_NO_DEVICE)
goto device_removed;
DEBUGOUT_1 ("CCID submit transfer again %d\n", err);
}
else if (transfer->status == LIBUSB_TRANSFER_COMPLETED)
{
if (transfer->actual_length == 2
&& transfer->buffer[0] == 0x50
&& (transfer->buffer[1] & 1) == 0)
{
DEBUGOUT ("CCID: card removed\n");
handle->powered_off = 1;
+#if defined(GNUPG_MAJOR_VERSION)
scd_kick_the_loop ();
+#endif
}
else
{
/* Event other than card removal. */
goto submit_again;
}
}
else if (transfer->status == LIBUSB_TRANSFER_CANCELLED)
handle->powered_off = 1;
else
{
device_removed:
DEBUGOUT ("CCID: device removed\n");
handle->powered_off = 1;
+#if defined(GNUPG_MAJOR_VERSION)
scd_kick_the_loop ();
+#endif
}
}
static void
ccid_setup_intr (ccid_driver_t handle)
{
struct libusb_transfer *transfer;
int err;
transfer = libusb_alloc_transfer (0);
handle->transfer = transfer;
libusb_fill_interrupt_transfer (transfer, handle->idev, handle->ep_intr,
handle->intr_buf, sizeof (handle->intr_buf),
intr_cb, handle, 0);
err = libusb_submit_transfer (transfer);
DEBUGOUT_2 ("CCID submit transfer (%x): %d", handle->ep_intr, err);
}
static void *
ccid_usb_thread (void *arg)
{
libusb_context *ctx = arg;
while (ccid_usb_thread_is_alive)
{
#ifdef USE_NPTH
npth_unprotect ();
#endif
libusb_handle_events_completed (ctx, NULL);
#ifdef USE_NPTH
npth_protect ();
#endif
}
return NULL;
}
static int
ccid_open_usb_reader (const char *spec_reader_name,
int idx, struct ccid_dev_table *ccid_table,
ccid_driver_t *handle, char **rdrname_p)
{
libusb_device *dev;
libusb_device_handle *idev = NULL;
char *rid = NULL;
int rc = 0;
int ifc_no, set_no;
struct libusb_device_descriptor desc;
int n;
int bus, addr;
unsigned int bai;
n = ccid_table[idx].n;
ifc_no = ccid_table[idx].interface_number;
set_no = ccid_table[idx].setting_number;
dev = ccid_usb_dev_list[n];
bus = libusb_get_bus_number (dev);
addr = libusb_get_device_address (dev);
bai = (bus << 16) | (addr << 8) | ifc_no;
rc = libusb_open (dev, &idev);
if (rc)
{
DEBUGOUT_1 ("usb_open failed: %s\n", libusb_error_name (rc));
free (*handle);
*handle = NULL;
return rc;
}
if (ccid_usb_thread_is_alive++ == 0)
{
npth_t thread;
npth_attr_t tattr;
int err;
err = npth_attr_init (&tattr);
if (err)
{
DEBUGOUT_1 ("npth_attr_init failed: %s\n", strerror (err));
free (*handle);
*handle = NULL;
return err;
}
npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED);
err = npth_create (&thread, &tattr, ccid_usb_thread, NULL);
if (err)
{
DEBUGOUT_1 ("npth_create failed: %s\n", strerror (err));
free (*handle);
*handle = NULL;
return err;
}
npth_attr_destroy (&tattr);
}
rc = libusb_get_device_descriptor (dev, &desc);
if (rc)
{
DEBUGOUT ("get_device_descripor failed\n");
goto leave;
}
rid = make_reader_id (idev, desc.idVendor, desc.idProduct,
desc.iSerialNumber);
/* Check to see if reader name matches the spec. */
if (spec_reader_name
&& strncmp (rid, spec_reader_name, strlen (spec_reader_name)))
{
DEBUGOUT ("device not matched\n");
rc = CCID_DRIVER_ERR_NO_READER;
goto leave;
}
(*handle)->id_vendor = desc.idVendor;
(*handle)->id_product = desc.idProduct;
(*handle)->idev = idev;
(*handle)->bai = bai;
(*handle)->ifc_no = ifc_no;
(*handle)->ep_bulk_out = ccid_table[idx].ep_bulk_out;
(*handle)->ep_bulk_in = ccid_table[idx].ep_bulk_in;
(*handle)->ep_intr = ccid_table[idx].ep_intr;
DEBUGOUT_2 ("using CCID reader %d (ID=%s)\n", idx, rid);
if (parse_ccid_descriptor (*handle, desc.bcdDevice,
ccid_table[idx].ifcdesc_extra,
ccid_table[idx].ifcdesc_extra_len))
{
DEBUGOUT ("device not supported\n");
rc = CCID_DRIVER_ERR_NO_READER;
goto leave;
}
rc = libusb_claim_interface (idev, ifc_no);
if (rc)
{
DEBUGOUT_1 ("usb_claim_interface failed: %d\n", rc);
rc = CCID_DRIVER_ERR_CARD_IO_ERROR;
goto leave;
}
/* Submit SET_INTERFACE control transfer which can reset the device. */
rc = libusb_set_interface_alt_setting (idev, ifc_no, set_no);
if (rc)
{
DEBUGOUT_1 ("usb_set_interface_alt_setting failed: %d\n", rc);
rc = CCID_DRIVER_ERR_CARD_IO_ERROR;
goto leave;
}
rc = ccid_vendor_specific_init (*handle);
leave:
if (rc)
{
--ccid_usb_thread_is_alive;
free (rid);
libusb_close (idev);
free (*handle);
*handle = NULL;
}
else
{
if (rdrname_p)
*rdrname_p = rid;
else
free (rid);
}
return rc;
}
/* Open the reader with the internal number READERNO and return a
pointer to be used as handle in HANDLE. Returns 0 on success. */
int
ccid_open_reader (const char *spec_reader_name, int idx,
struct ccid_dev_table *ccid_table,
ccid_driver_t *handle, char **rdrname_p)
{
*handle = calloc (1, sizeof **handle);
if (!*handle)
{
DEBUGOUT ("out of memory\n");
return CCID_DRIVER_ERR_OUT_OF_CORE;
}
return ccid_open_usb_reader (spec_reader_name, idx, ccid_table,
handle, rdrname_p);
}
int
ccid_require_get_status (ccid_driver_t handle)
{
/* When a card reader supports interrupt transfer to check the
status of card, it is possible to submit only an interrupt
transfer, and no check is required by application layer. USB can
detect removal of a card and can detect removal of a reader.
*/
if (handle->ep_intr >= 0)
return 0;
/* Libusb actually detects the removal of USB device in use.
However, there is no good API to handle the removal (yet),
cleanly and with good portability.
There is libusb_set_pollfd_notifiers function, but it doesn't
offer libusb_device_handle* data to its callback. So, when it
watches multiple devices, there is no way to know which device is
removed.
Once, we will have a good programming interface of libusb, we can
list tokens (with no interrupt transfer support, but always with
card inserted) here to return 0, so that scdaemon can submit
minimum packet on wire.
*/
return 1;
}
-
-static void
-do_close_reader (ccid_driver_t handle)
+static int
+send_power_off (ccid_driver_t handle)
{
int rc;
unsigned char msg[100];
size_t msglen;
unsigned char seqno;
- if (!handle->powered_off)
- {
- msg[0] = PC_to_RDR_IccPowerOff;
- msg[5] = 0; /* slot */
- msg[6] = seqno = handle->seqno++;
- msg[7] = 0; /* RFU */
- msg[8] = 0; /* RFU */
- msg[9] = 0; /* RFU */
- set_msg_len (msg, 0);
- msglen = 10;
+ msg[0] = PC_to_RDR_IccPowerOff;
+ msg[5] = 0; /* slot */
+ msg[6] = seqno = handle->seqno++;
+ msg[7] = 0; /* RFU */
+ msg[8] = 0; /* RFU */
+ msg[9] = 0; /* RFU */
+ set_msg_len (msg, 0);
+ msglen = 10;
- rc = bulk_out (handle, msg, msglen, 0);
- if (!rc)
- bulk_in (handle, msg, sizeof msg, &msglen, RDR_to_PC_SlotStatus,
- seqno, 2000, 0);
- }
+ rc = bulk_out (handle, msg, msglen, 0);
+ if (!rc)
+ bulk_in (handle, msg, sizeof msg, &msglen, RDR_to_PC_SlotStatus,
+ seqno, 2000, 0);
+ return rc;
+}
+
+static void
+do_close_reader (ccid_driver_t handle)
+{
+ int rc;
+
+ if (!handle->powered_off)
+ send_power_off (handle);
if (handle->transfer)
{
if (!handle->powered_off)
{
DEBUGOUT ("libusb_cancel_transfer\n");
rc = libusb_cancel_transfer (handle->transfer);
if (rc != LIBUSB_ERROR_NOT_FOUND)
while (!handle->powered_off)
{
DEBUGOUT ("libusb_handle_events_completed\n");
#ifdef USE_NPTH
npth_unprotect ();
#endif
libusb_handle_events_completed (NULL, &handle->powered_off);
#ifdef USE_NPTH
npth_protect ();
#endif
}
}
libusb_free_transfer (handle->transfer);
handle->transfer = NULL;
}
libusb_release_interface (handle->idev, handle->ifc_no);
--ccid_usb_thread_is_alive;
libusb_close (handle->idev);
handle->idev = NULL;
}
int
ccid_set_progress_cb (ccid_driver_t handle,
void (*cb)(void *, const char *, int, int, int),
void *cb_arg)
{
if (!handle)
return CCID_DRIVER_ERR_INV_VALUE;
handle->progress_cb = cb;
handle->progress_cb_arg = cb_arg;
return 0;
}
int
ccid_set_prompt_cb (ccid_driver_t handle,
void (*cb)(void *, int), void *cb_arg)
{
if (!handle)
return CCID_DRIVER_ERR_INV_VALUE;
handle->prompt_cb = cb;
handle->prompt_cb_arg = cb_arg;
return 0;
}
/* Close the reader HANDLE. */
int
ccid_close_reader (ccid_driver_t handle)
{
if (!handle)
return 0;
do_close_reader (handle);
free (handle);
return 0;
}
/* Return False if a card is present and powered. */
int
ccid_check_card_presence (ccid_driver_t handle)
{
(void)handle; /* Not yet implemented. */
return -1;
}
/* Write a MSG of length MSGLEN to the designated bulk out endpoint.
Returns 0 on success. */
static int
bulk_out (ccid_driver_t handle, unsigned char *msg, size_t msglen,
int no_debug)
{
int rc;
int transferred;
/* No need to continue and clutter the log with USB write error
messages after we got the first ENODEV. */
if (handle->enodev_seen)
return CCID_DRIVER_ERR_NO_READER;
if (debug_level && (!no_debug || debug_level >= 3))
{
switch (msglen? msg[0]:0)
{
case PC_to_RDR_IccPowerOn:
print_p2r_iccpoweron (msg, msglen);
break;
case PC_to_RDR_IccPowerOff:
print_p2r_iccpoweroff (msg, msglen);
break;
case PC_to_RDR_GetSlotStatus:
print_p2r_getslotstatus (msg, msglen);
break;
case PC_to_RDR_XfrBlock:
print_p2r_xfrblock (msg, msglen);
break;
case PC_to_RDR_GetParameters:
print_p2r_getparameters (msg, msglen);
break;
case PC_to_RDR_ResetParameters:
print_p2r_resetparameters (msg, msglen);
break;
case PC_to_RDR_SetParameters:
print_p2r_setparameters (msg, msglen);
break;
case PC_to_RDR_Escape:
print_p2r_escape (msg, msglen);
break;
case PC_to_RDR_IccClock:
print_p2r_iccclock (msg, msglen);
break;
case PC_to_RDR_T0APDU:
print_p2r_to0apdu (msg, msglen);
break;
case PC_to_RDR_Secure:
print_p2r_secure (msg, msglen);
break;
case PC_to_RDR_Mechanical:
print_p2r_mechanical (msg, msglen);
break;
case PC_to_RDR_Abort:
print_p2r_abort (msg, msglen);
break;
case PC_to_RDR_SetDataRate:
print_p2r_setdatarate (msg, msglen);
break;
default:
print_p2r_unknown (msg, msglen);
break;
}
}
#ifdef USE_NPTH
npth_unprotect ();
#endif
rc = libusb_bulk_transfer (handle->idev, handle->ep_bulk_out,
- (char*)msg, msglen, &transferred,
+ msg, msglen, &transferred,
5000 /* ms timeout */);
#ifdef USE_NPTH
npth_protect ();
#endif
if (rc == 0 && transferred == msglen)
return 0;
if (rc)
{
DEBUGOUT_1 ("usb_bulk_write error: %s\n", libusb_error_name (rc));
if (rc == LIBUSB_ERROR_NO_DEVICE)
{
handle->enodev_seen = 1;
return CCID_DRIVER_ERR_NO_READER;
}
}
return 0;
}
/* Read a maximum of LENGTH bytes from the bulk in endpoint into
BUFFER and return the actual read number if bytes in NREAD. SEQNO
is the sequence number used to send the request and EXPECTED_TYPE
the type of message we expect. Does checks on the ccid
header. TIMEOUT is the timeout value in ms. NO_DEBUG may be set to
avoid debug messages in case of no error; this can be overridden
with a glibal debug level of at least 3. Returns 0 on success. */
static int
bulk_in (ccid_driver_t handle, unsigned char *buffer, size_t length,
size_t *nread, int expected_type, int seqno, int timeout,
int no_debug)
{
int rc;
int msglen;
int notified = 0;
/* Fixme: The next line for the current Valgrind without support
for USB IOCTLs. */
memset (buffer, 0, length);
retry:
#ifdef USE_NPTH
npth_unprotect ();
#endif
rc = libusb_bulk_transfer (handle->idev, handle->ep_bulk_in,
- (char*)buffer, length, &msglen, timeout);
+ buffer, length, &msglen, timeout);
#ifdef USE_NPTH
npth_protect ();
#endif
if (rc)
{
DEBUGOUT_1 ("usb_bulk_read error: %s\n", libusb_error_name (rc));
if (rc == LIBUSB_ERROR_NO_DEVICE)
{
handle->enodev_seen = 1;
return CCID_DRIVER_ERR_NO_READER;
}
return CCID_DRIVER_ERR_CARD_IO_ERROR;
}
if (msglen < 0)
return CCID_DRIVER_ERR_INV_VALUE; /* Faulty libusb. */
*nread = msglen;
if (msglen < 10)
{
DEBUGOUT_1 ("bulk-in msg too short (%u)\n", (unsigned int)msglen);
abort_cmd (handle, seqno);
return CCID_DRIVER_ERR_INV_VALUE;
}
if (buffer[5] != 0)
{
DEBUGOUT_1 ("unexpected bulk-in slot (%d)\n", buffer[5]);
return CCID_DRIVER_ERR_INV_VALUE;
}
if (buffer[6] != seqno)
{
DEBUGOUT_2 ("bulk-in seqno does not match (%d/%d)\n",
seqno, buffer[6]);
/* Retry until we are synced again. */
goto retry;
}
/* We need to handle the time extension request before we check that
we got the expected message type. This is in particular required
for the Cherry keyboard which sends a time extension request for
each key hit. */
if (!(buffer[7] & 0x03) && (buffer[7] & 0xC0) == 0x80)
{
/* Card present and active, time extension requested. */
DEBUGOUT_2 ("time extension requested (%02X,%02X)\n",
buffer[7], buffer[8]);
/* Gnuk enhancement to prompt user input by ack button */
if (buffer[8] == 0xff && !notified)
{
notified = 1;
handle->prompt_cb (handle->prompt_cb_arg, 1);
}
goto retry;
}
if (notified)
handle->prompt_cb (handle->prompt_cb_arg, 0);
if (buffer[0] != expected_type && buffer[0] != RDR_to_PC_SlotStatus)
{
DEBUGOUT_1 ("unexpected bulk-in msg type (%02x)\n", buffer[0]);
abort_cmd (handle, seqno);
return CCID_DRIVER_ERR_INV_VALUE;
}
if (debug_level && (!no_debug || debug_level >= 3))
{
switch (buffer[0])
{
case RDR_to_PC_DataBlock:
print_r2p_datablock (buffer, msglen);
break;
case RDR_to_PC_SlotStatus:
print_r2p_slotstatus (buffer, msglen);
break;
case RDR_to_PC_Parameters:
print_r2p_parameters (buffer, msglen);
break;
case RDR_to_PC_Escape:
print_r2p_escape (buffer, msglen);
break;
case RDR_to_PC_DataRate:
print_r2p_datarate (buffer, msglen);
break;
default:
print_r2p_unknown (buffer, msglen);
break;
}
}
if (CCID_COMMAND_FAILED (buffer))
print_command_failed (buffer);
/* Check whether a card is at all available. Note: If you add new
error codes here, check whether they need to be ignored in
send_escape_cmd. */
switch ((buffer[7] & 0x03))
{
case 0: /* no error */ break;
case 1: rc = CCID_DRIVER_ERR_CARD_INACTIVE; break;
case 2: rc = CCID_DRIVER_ERR_NO_CARD; break;
case 3: /* RFU */ break;
}
if (rc)
{
/*
* Communication failure by device side.
* Possibly, it was forcibly suspended and resumed.
*
* Only detect this kind of failure when interrupt transfer is
* not supported. For card reader with interrupt transfer
* support removal is detected by intr_cb.
*/
if (handle->ep_intr < 0)
{
DEBUGOUT ("CCID: card inactive/removed\n");
handle->powered_off = 1;
+#if defined(GNUPG_MAJOR_VERSION)
scd_kick_the_loop ();
+#endif
}
}
return rc;
}
/* Send an abort sequence and wait until everything settled. */
static int
abort_cmd (ccid_driver_t handle, int seqno)
{
int rc;
- char dummybuf[8];
+ unsigned char dummybuf[8];
unsigned char msg[100];
int msglen;
seqno &= 0xff;
DEBUGOUT_1 ("sending abort sequence for seqno %d\n", seqno);
/* Send the abort command to the control pipe. Note that we don't
need to keep track of sent abort commands because there should
never be another thread using the same slot concurrently. */
#ifdef USE_NPTH
npth_unprotect ();
#endif
rc = libusb_control_transfer (handle->idev,
0x21,/* bmRequestType: host-to-device,
class specific, to interface. */
1, /* ABORT */
(seqno << 8 | 0 /* slot */),
handle->ifc_no,
dummybuf, 0,
1000 /* ms timeout */);
#ifdef USE_NPTH
npth_protect ();
#endif
if (rc)
{
DEBUGOUT_1 ("usb_control_msg error: %s\n", libusb_error_name (rc));
return CCID_DRIVER_ERR_CARD_IO_ERROR;
}
/* Now send the abort command to the bulk out pipe using the same
SEQNO and SLOT. Do this in a loop to so that all seqno are
tried. */
seqno--; /* Adjust for next increment. */
do
{
int transferred;
seqno++;
msg[0] = PC_to_RDR_Abort;
msg[5] = 0; /* slot */
msg[6] = seqno;
msg[7] = 0; /* RFU */
msg[8] = 0; /* RFU */
msg[9] = 0; /* RFU */
msglen = 10;
set_msg_len (msg, 0);
#ifdef USE_NPTH
npth_unprotect ();
#endif
rc = libusb_bulk_transfer (handle->idev, handle->ep_bulk_out,
- (char*)msg, msglen, &transferred,
+ msg, msglen, &transferred,
5000 /* ms timeout */);
#ifdef USE_NPTH
npth_protect ();
#endif
if (rc == 0 && transferred == msglen)
rc = 0;
else if (rc)
DEBUGOUT_1 ("usb_bulk_write error in abort_cmd: %s\n",
libusb_error_name (rc));
if (rc)
return rc;
#ifdef USE_NPTH
npth_unprotect ();
#endif
rc = libusb_bulk_transfer (handle->idev, handle->ep_bulk_in,
- (char*)msg, sizeof msg, &msglen,
+ msg, sizeof msg, &msglen,
5000 /*ms timeout*/);
#ifdef USE_NPTH
npth_protect ();
#endif
if (rc)
{
DEBUGOUT_1 ("usb_bulk_read error in abort_cmd: %s\n",
libusb_error_name (rc));
return CCID_DRIVER_ERR_CARD_IO_ERROR;
}
if (msglen < 10)
{
DEBUGOUT_1 ("bulk-in msg in abort_cmd too short (%u)\n",
(unsigned int)msglen);
return CCID_DRIVER_ERR_INV_VALUE;
}
if (msg[5] != 0)
{
DEBUGOUT_1 ("unexpected bulk-in slot (%d) in abort_cmd\n", msg[5]);
return CCID_DRIVER_ERR_INV_VALUE;
}
DEBUGOUT_3 ("status: %02X error: %02X octet[9]: %02X\n",
msg[7], msg[8], msg[9]);
if (CCID_COMMAND_FAILED (msg))
print_command_failed (msg);
}
while (msg[0] != RDR_to_PC_SlotStatus && msg[5] != 0 && msg[6] != seqno);
handle->seqno = ((seqno + 1) & 0xff);
DEBUGOUT ("sending abort sequence succeeded\n");
return 0;
}
/* Note that this function won't return the error codes NO_CARD or
CARD_INACTIVE. IF RESULT is not NULL, the result from the
operation will get returned in RESULT and its length in RESULTLEN.
If the response is larger than RESULTMAX, an error is returned and
the required buffer length returned in RESULTLEN. */
static int
send_escape_cmd (ccid_driver_t handle,
const unsigned char *data, size_t datalen,
unsigned char *result, size_t resultmax, size_t *resultlen)
{
int rc;
unsigned char msg[100];
size_t msglen;
unsigned char seqno;
if (resultlen)
*resultlen = 0;
if (datalen > sizeof msg - 10)
return CCID_DRIVER_ERR_INV_VALUE; /* Escape data too large. */
msg[0] = PC_to_RDR_Escape;
msg[5] = 0; /* slot */
msg[6] = seqno = handle->seqno++;
msg[7] = 0; /* RFU */
msg[8] = 0; /* RFU */
msg[9] = 0; /* RFU */
memcpy (msg+10, data, datalen);
msglen = 10 + datalen;
set_msg_len (msg, datalen);
rc = bulk_out (handle, msg, msglen, 0);
if (rc)
return rc;
rc = bulk_in (handle, msg, sizeof msg, &msglen, RDR_to_PC_Escape,
seqno, 5000, 0);
if (result)
switch (rc)
{
/* We need to ignore certain errorcode here. */
case 0:
case CCID_DRIVER_ERR_CARD_INACTIVE:
case CCID_DRIVER_ERR_NO_CARD:
{
if (msglen > resultmax)
rc = CCID_DRIVER_ERR_INV_VALUE; /* Response too large. */
else
{
memcpy (result, msg, msglen);
if (resultlen)
*resultlen = msglen;
rc = 0;
}
}
break;
default:
break;
}
return rc;
}
int
ccid_transceive_escape (ccid_driver_t handle,
const unsigned char *data, size_t datalen,
unsigned char *resp, size_t maxresplen, size_t *nresp)
{
return send_escape_cmd (handle, data, datalen, resp, maxresplen, nresp);
}
/* experimental */
int
ccid_poll (ccid_driver_t handle)
{
int rc;
unsigned char msg[10];
int msglen;
int i, j;
rc = libusb_interrupt_transfer (handle->idev, handle->ep_intr,
- (char*)msg, sizeof msg, &msglen,
+ msg, sizeof msg, &msglen,
0 /* ms timeout */ );
if (rc == LIBUSB_ERROR_TIMEOUT)
return 0;
if (rc)
{
DEBUGOUT_1 ("usb_intr_read error: %s\n", libusb_error_name (rc));
return CCID_DRIVER_ERR_CARD_IO_ERROR;
}
if (msglen < 1)
{
DEBUGOUT ("intr-in msg too short\n");
return CCID_DRIVER_ERR_INV_VALUE;
}
if (msg[0] == RDR_to_PC_NotifySlotChange)
{
DEBUGOUT ("notify slot change:");
for (i=1; i < msglen; i++)
for (j=0; j < 4; j++)
DEBUGOUT_CONT_3 (" %d:%c%c",
(i-1)*4+j,
(msg[i] & (1<<(j*2)))? 'p':'-',
(msg[i] & (2<<(j*2)))? '*':' ');
DEBUGOUT_LF ();
}
else if (msg[0] == RDR_to_PC_HardwareError)
{
DEBUGOUT ("hardware error occurred\n");
}
else
{
DEBUGOUT_1 ("unknown intr-in msg of type %02X\n", msg[0]);
}
return 0;
}
/* Note that this function won't return the error codes NO_CARD or
CARD_INACTIVE */
int
ccid_slot_status (ccid_driver_t handle, int *statusbits, int on_wire)
{
int rc;
unsigned char msg[100];
size_t msglen;
unsigned char seqno;
int retries = 0;
if (handle->powered_off)
return CCID_DRIVER_ERR_NO_READER;
/* If the card (with its lower-level driver) doesn't require
GET_STATUS on wire (because it supports INTERRUPT transfer for
status change, or it's a token which has a card always inserted),
no need to send on wire. */
if (!on_wire && !ccid_require_get_status (handle))
{
/* Setup interrupt transfer at the initial call of slot_status
with ON_WIRE == 0 */
if (handle->transfer == NULL && handle->ep_intr >= 0)
ccid_setup_intr (handle);
*statusbits = 0;
return 0;
}
retry:
msg[0] = PC_to_RDR_GetSlotStatus;
msg[5] = 0; /* slot */
msg[6] = seqno = handle->seqno++;
msg[7] = 0; /* RFU */
msg[8] = 0; /* RFU */
msg[9] = 0; /* RFU */
set_msg_len (msg, 0);
rc = bulk_out (handle, msg, 10, 1);
if (rc)
return rc;
/* Note that we set the NO_DEBUG flag here, so that the logs won't
get cluttered up by a ticker function checking for the slot
status and debugging enabled. */
rc = bulk_in (handle, msg, sizeof msg, &msglen, RDR_to_PC_SlotStatus,
seqno, retries? 1000 : 200, 1);
if (rc == CCID_DRIVER_ERR_CARD_IO_ERROR && retries < 3)
{
if (!retries)
{
DEBUGOUT ("USB: CALLING USB_CLEAR_HALT\n");
#ifdef USE_NPTH
npth_unprotect ();
#endif
libusb_clear_halt (handle->idev, handle->ep_bulk_in);
libusb_clear_halt (handle->idev, handle->ep_bulk_out);
#ifdef USE_NPTH
npth_protect ();
#endif
}
else
DEBUGOUT ("USB: RETRYING bulk_in AGAIN\n");
retries++;
goto retry;
}
if (rc && rc != CCID_DRIVER_ERR_NO_CARD && rc != CCID_DRIVER_ERR_CARD_INACTIVE)
return rc;
*statusbits = (msg[7] & 3);
return 0;
}
/* Parse ATR string (of ATRLEN) and update parameters at PARAM.
Calling this routine, it should prepare default values at PARAM
beforehand. This routine assumes that card is accessed by T=1
protocol. It doesn't analyze historical bytes at all.
Returns < 0 value on error:
-1 for parse error or integrity check error
-2 for card doesn't support T=1 protocol
-3 for parameters are nod explicitly defined by ATR
-4 for this driver doesn't support CRC
Returns >= 0 on success:
0 for card is negotiable mode
1 for card is specific mode (and not negotiable)
*/
static int
update_param_by_atr (unsigned char *param, unsigned char *atr, size_t atrlen)
{
int i = -1;
int t, y, chk;
int historical_bytes_num, negotiable = 1;
#define NEXTBYTE() do { i++; if (atrlen <= i) return -1; } while (0)
NEXTBYTE ();
if (atr[i] == 0x3F)
param[1] |= 0x02; /* Convention is inverse. */
NEXTBYTE ();
y = (atr[i] >> 4);
historical_bytes_num = atr[i] & 0x0f;
NEXTBYTE ();
if ((y & 1))
{
param[0] = atr[i]; /* TA1 - Fi & Di */
NEXTBYTE ();
}
if ((y & 2))
NEXTBYTE (); /* TB1 - ignore */
if ((y & 4))
{
param[2] = atr[i]; /* TC1 - Guard Time */
NEXTBYTE ();
}
if ((y & 8))
{
y = (atr[i] >> 4); /* TD1 */
t = atr[i] & 0x0f;
NEXTBYTE ();
if ((y & 1))
{ /* TA2 - PPS mode */
if ((atr[i] & 0x0f) != 1)
return -2; /* Wrong card protocol (!= 1). */
if ((atr[i] & 0x10) != 0x10)
return -3; /* Transmission parameters are implicitly defined. */
negotiable = 0; /* TA2 means specific mode. */
NEXTBYTE ();
}
if ((y & 2))
NEXTBYTE (); /* TB2 - ignore */
if ((y & 4))
NEXTBYTE (); /* TC2 - ignore */
if ((y & 8))
{
y = (atr[i] >> 4); /* TD2 */
t = atr[i] & 0x0f;
NEXTBYTE ();
}
else
y = 0;
while (y)
{
if ((y & 1))
{ /* TAx */
if (t == 1)
param[5] = atr[i]; /* IFSC */
else if (t == 15)
/* XXX: check voltage? */
param[4] = (atr[i] >> 6); /* ClockStop */
NEXTBYTE ();
}
if ((y & 2))
{
if (t == 1)
param[3] = atr[i]; /* TBx - BWI & CWI */
NEXTBYTE ();
}
if ((y & 4))
{
if (t == 1)
param[1] |= (atr[i] & 0x01); /* TCx - LRC/CRC */
NEXTBYTE ();
if (param[1] & 0x01)
return -4; /* CRC not supported yet. */
}
if ((y & 8))
{
y = (atr[i] >> 4); /* TDx */
t = atr[i] & 0x0f;
NEXTBYTE ();
}
else
y = 0;
}
}
i += historical_bytes_num - 1;
NEXTBYTE ();
if (atrlen != i+1)
return -1;
#undef NEXTBYTE
chk = 0;
do
{
chk ^= atr[i];
i--;
}
while (i > 0);
if (chk != 0)
return -1;
return negotiable;
}
/* Return the ATR of the card. This is not a cached value and thus an
actual reset is done. */
int
ccid_get_atr (ccid_driver_t handle,
unsigned char *atr, size_t maxatrlen, size_t *atrlen)
{
int rc;
int statusbits;
unsigned char msg[100];
unsigned char *tpdu;
size_t msglen, tpdulen;
unsigned char seqno;
int use_crc = 0;
unsigned int edc;
int tried_iso = 0;
int got_param;
unsigned char param[7] = { /* For Protocol T=1 */
0x11, /* bmFindexDindex */
0x10, /* bmTCCKST1 */
0x00, /* bGuardTimeT1 */
0x4d, /* bmWaitingIntegersT1 */
0x00, /* bClockStop */
0x20, /* bIFSC */
0x00 /* bNadValue */
};
/* First check whether a card is available. */
rc = ccid_slot_status (handle, &statusbits, 1);
if (rc)
return rc;
if (statusbits == 2)
return CCID_DRIVER_ERR_NO_CARD;
/*
* In the first invocation of ccid_slot_status, card reader may
* return CCID_DRIVER_ERR_CARD_INACTIVE and handle->powered_off may
* become 1. Because inactive card is no problem (we are turning it
* ON here), clear the flag.
*/
handle->powered_off = 0;
/* For an inactive and also for an active card, issue the PowerOn
command to get the ATR. */
again:
msg[0] = PC_to_RDR_IccPowerOn;
msg[5] = 0; /* slot */
msg[6] = seqno = handle->seqno++;
/* power select (0=auto, 1=5V, 2=3V, 3=1.8V) */
msg[7] = handle->auto_voltage ? 0 : 1;
msg[8] = 0; /* RFU */
msg[9] = 0; /* RFU */
set_msg_len (msg, 0);
msglen = 10;
rc = bulk_out (handle, msg, msglen, 0);
if (rc)
return rc;
rc = bulk_in (handle, msg, sizeof msg, &msglen, RDR_to_PC_DataBlock,
seqno, 5000, 0);
if (rc)
return rc;
if (!tried_iso && CCID_COMMAND_FAILED (msg) && CCID_ERROR_CODE (msg) == 0xbb
&& ((handle->id_vendor == VENDOR_CHERRY
&& handle->id_product == 0x0005)
|| (handle->id_vendor == VENDOR_GEMPC
&& handle->id_product == 0x4433)
))
{
tried_iso = 1;
/* Try switching to ISO mode. */
if (!send_escape_cmd (handle, (const unsigned char*)"\xF1\x01", 2,
NULL, 0, NULL))
goto again;
}
+ else if (statusbits == 0 && CCID_COMMAND_FAILED (msg))
+ {
+ /* Card was active already, and something went wrong with
+ PC_to_RDR_IccPowerOn command. It may be baud-rate mismatch
+ between the card and the reader. To recover from this state,
+ send PC_to_RDR_IccPowerOff command to reset the card and try
+ again.
+ */
+ rc = send_power_off (handle);
+ if (rc)
+ return rc;
+
+ statusbits = 1;
+ goto again;
+ }
else if (CCID_COMMAND_FAILED (msg))
return CCID_DRIVER_ERR_CARD_IO_ERROR;
handle->powered_off = 0;
if (atr)
{
size_t n = msglen - 10;
if (n > maxatrlen)
n = maxatrlen;
memcpy (atr, msg+10, n);
*atrlen = n;
}
param[6] = handle->nonnull_nad? ((1 << 4) | 0): 0;
rc = update_param_by_atr (param, msg+10, msglen - 10);
if (rc < 0)
{
DEBUGOUT_1 ("update_param_by_atr failed: %d\n", rc);
return CCID_DRIVER_ERR_CARD_IO_ERROR;
}
got_param = 0;
if (handle->auto_param)
{
msg[0] = PC_to_RDR_GetParameters;
msg[5] = 0; /* slot */
msg[6] = seqno = handle->seqno++;
msg[7] = 0; /* RFU */
msg[8] = 0; /* RFU */
msg[9] = 0; /* RFU */
set_msg_len (msg, 0);
msglen = 10;
rc = bulk_out (handle, msg, msglen, 0);
if (!rc)
rc = bulk_in (handle, msg, sizeof msg, &msglen, RDR_to_PC_Parameters,
seqno, 2000, 0);
if (rc)
DEBUGOUT ("GetParameters failed\n");
else if (msglen == 17 && msg[9] == 1)
got_param = 1;
}
else if (handle->auto_pps)
;
else if (rc == 1) /* It's negotiable, send PPS. */
{
msg[0] = PC_to_RDR_XfrBlock;
msg[5] = 0; /* slot */
msg[6] = seqno = handle->seqno++;
msg[7] = 0;
msg[8] = 0;
msg[9] = 0;
msg[10] = 0xff; /* PPSS */
msg[11] = 0x11; /* PPS0: PPS1, Protocol T=1 */
msg[12] = param[0]; /* PPS1: Fi / Di */
msg[13] = 0xff ^ 0x11 ^ param[0]; /* PCK */
set_msg_len (msg, 4);
msglen = 10 + 4;
rc = bulk_out (handle, msg, msglen, 0);
if (rc)
return rc;
rc = bulk_in (handle, msg, sizeof msg, &msglen, RDR_to_PC_DataBlock,
seqno, 5000, 0);
if (rc)
return rc;
if (msglen != 10 + 4)
{
DEBUGOUT_1 ("Setting PPS failed: %zu\n", msglen);
return CCID_DRIVER_ERR_CARD_IO_ERROR;
}
if (msg[10] != 0xff || msg[11] != 0x11 || msg[12] != param[0])
{
DEBUGOUT_1 ("Setting PPS failed: 0x%02x\n", param[0]);
return CCID_DRIVER_ERR_CARD_IO_ERROR;
}
}
/* Setup parameters to select T=1. */
msg[0] = PC_to_RDR_SetParameters;
msg[5] = 0; /* slot */
msg[6] = seqno = handle->seqno++;
msg[7] = 1; /* Select T=1. */
msg[8] = 0; /* RFU */
msg[9] = 0; /* RFU */
if (!got_param)
memcpy (&msg[10], param, 7);
set_msg_len (msg, 7);
msglen = 10 + 7;
rc = bulk_out (handle, msg, msglen, 0);
if (rc)
return rc;
rc = bulk_in (handle, msg, sizeof msg, &msglen, RDR_to_PC_Parameters,
seqno, 5000, 0);
if (rc)
DEBUGOUT ("SetParameters failed (ignored)\n");
if (!rc && msglen > 15 && msg[15] >= 16 && msg[15] <= 254 )
handle->ifsc = msg[15];
else
handle->ifsc = 128; /* Something went wrong, assume 128 bytes. */
if (handle->nonnull_nad && msglen > 16 && msg[16] == 0)
{
DEBUGOUT ("Use Null-NAD, clearing handle->nonnull_nad.\n");
handle->nonnull_nad = 0;
}
handle->t1_ns = 0;
handle->t1_nr = 0;
/* Send an S-Block with our maximum IFSD to the CCID. */
if (!handle->apdu_level && !handle->auto_ifsd)
{
tpdu = msg+10;
/* NAD: DAD=1, SAD=0 */
tpdu[0] = handle->nonnull_nad? ((1 << 4) | 0): 0;
tpdu[1] = (0xc0 | 0 | 1); /* S-block request: change IFSD */
tpdu[2] = 1;
tpdu[3] = handle->max_ifsd? handle->max_ifsd : 32;
tpdulen = 4;
edc = compute_edc (tpdu, tpdulen, use_crc);
if (use_crc)
tpdu[tpdulen++] = (edc >> 8);
tpdu[tpdulen++] = edc;
msg[0] = PC_to_RDR_XfrBlock;
msg[5] = 0; /* slot */
msg[6] = seqno = handle->seqno++;
msg[7] = 0;
msg[8] = 0; /* RFU */
msg[9] = 0; /* RFU */
set_msg_len (msg, tpdulen);
msglen = 10 + tpdulen;
if (debug_level > 1)
DEBUGOUT_3 ("T=1: put %c-block seq=%d%s\n",
((msg[11] & 0xc0) == 0x80)? 'R' :
(msg[11] & 0x80)? 'S' : 'I',
((msg[11] & 0x80)? !!(msg[11]& 0x10)
: !!(msg[11] & 0x40)),
(!(msg[11] & 0x80) && (msg[11] & 0x20)? " [more]":""));
rc = bulk_out (handle, msg, msglen, 0);
if (rc)
return rc;
rc = bulk_in (handle, msg, sizeof msg, &msglen,
RDR_to_PC_DataBlock, seqno, 5000, 0);
if (rc)
return rc;
tpdu = msg + 10;
tpdulen = msglen - 10;
if (tpdulen < 4)
return CCID_DRIVER_ERR_ABORTED;
if (debug_level > 1)
DEBUGOUT_4 ("T=1: got %c-block seq=%d err=%d%s\n",
((msg[11] & 0xc0) == 0x80)? 'R' :
(msg[11] & 0x80)? 'S' : 'I',
((msg[11] & 0x80)? !!(msg[11]& 0x10)
: !!(msg[11] & 0x40)),
((msg[11] & 0xc0) == 0x80)? (msg[11] & 0x0f) : 0,
(!(msg[11] & 0x80) && (msg[11] & 0x20)? " [more]":""));
if ((tpdu[1] & 0xe0) != 0xe0 || tpdu[2] != 1)
{
DEBUGOUT ("invalid response for S-block (Change-IFSD)\n");
return -1;
}
DEBUGOUT_1 ("IFSD has been set to %d\n", tpdu[3]);
}
return 0;
}
static unsigned int
compute_edc (const unsigned char *data, size_t datalen, int use_crc)
{
if (use_crc)
{
return 0x42; /* Not yet implemented. */
}
else
{
unsigned char crc = 0;
for (; datalen; datalen--)
crc ^= *data++;
return crc;
}
}
/* Return true if APDU is an extended length one. */
static int
is_exlen_apdu (const unsigned char *apdu, size_t apdulen)
{
if (apdulen < 7 || apdu[4])
return 0; /* Too short or no Z byte. */
return 1;
}
/* Helper for ccid_transceive used for APDU level exchanges. */
static int
ccid_transceive_apdu_level (ccid_driver_t handle,
const unsigned char *apdu_buf, size_t apdu_len,
unsigned char *resp, size_t maxresplen,
size_t *nresp)
{
int rc;
unsigned char msg[CCID_MAX_BUF];
const unsigned char *apdu_p;
size_t apdu_part_len;
size_t msglen;
unsigned char seqno;
int bwi = 4;
unsigned char chain = 0;
if (apdu_len == 0 || apdu_len > sizeof (msg) - 10)
return CCID_DRIVER_ERR_INV_VALUE; /* Invalid length. */
apdu_p = apdu_buf;
while (1)
{
apdu_part_len = apdu_len;
if (apdu_part_len > handle->max_ccid_msglen - 10)
{
apdu_part_len = handle->max_ccid_msglen - 10;
chain |= 0x01;
}
msg[0] = PC_to_RDR_XfrBlock;
msg[5] = 0; /* slot */
msg[6] = seqno = handle->seqno++;
msg[7] = bwi;
msg[8] = chain;
msg[9] = 0;
memcpy (msg+10, apdu_p, apdu_part_len);
set_msg_len (msg, apdu_part_len);
msglen = 10 + apdu_part_len;
rc = bulk_out (handle, msg, msglen, 0);
if (rc)
return rc;
apdu_p += apdu_part_len;
apdu_len -= apdu_part_len;
rc = bulk_in (handle, msg, sizeof msg, &msglen,
RDR_to_PC_DataBlock, seqno, CCID_CMD_TIMEOUT, 0);
if (rc)
return rc;
if (!(chain & 0x01))
break;
chain = 0x02;
}
apdu_len = 0;
while (1)
{
apdu_part_len = msglen - 10;
if (resp && apdu_len + apdu_part_len <= maxresplen)
memcpy (resp + apdu_len, msg+10, apdu_part_len);
apdu_len += apdu_part_len;
if (!(msg[9] & 0x01))
break;
msg[0] = PC_to_RDR_XfrBlock;
msg[5] = 0; /* slot */
msg[6] = seqno = handle->seqno++;
msg[7] = bwi;
msg[8] = 0x10; /* Request next data block */
msg[9] = 0;
set_msg_len (msg, 0);
msglen = 10;
rc = bulk_out (handle, msg, msglen, 0);
if (rc)
return rc;
rc = bulk_in (handle, msg, sizeof msg, &msglen,
RDR_to_PC_DataBlock, seqno, CCID_CMD_TIMEOUT, 0);
if (rc)
return rc;
}
if (resp)
{
if (apdu_len > maxresplen)
{
DEBUGOUT_2 ("provided buffer too short for received data "
"(%u/%u)\n",
(unsigned int)apdu_len, (unsigned int)maxresplen);
return CCID_DRIVER_ERR_INV_VALUE;
}
*nresp = apdu_len;
}
return 0;
}
/*
Protocol T=1 overview
Block Structure:
Prologue Field:
1 byte Node Address (NAD)
1 byte Protocol Control Byte (PCB)
1 byte Length (LEN)
Information Field:
0-254 byte APDU or Control Information (INF)
Epilogue Field:
1 byte Error Detection Code (EDC)
NAD:
bit 7 unused
bit 4..6 Destination Node Address (DAD)
bit 3 unused
bit 2..0 Source Node Address (SAD)
If node addresses are not used, SAD and DAD should be set to 0 on
the first block sent to the card. If they are used they should
have different values (0 for one is okay); that first block sets up
the addresses of the nodes.
PCB:
Information Block (I-Block):
bit 7 0
bit 6 Sequence number (yep, that is modulo 2)
bit 5 Chaining flag
bit 4..0 reserved
Received-Ready Block (R-Block):
bit 7 1
bit 6 0
bit 5 0
bit 4 Sequence number
bit 3..0 0 = no error
1 = EDC or parity error
2 = other error
other values are reserved
Supervisory Block (S-Block):
bit 7 1
bit 6 1
bit 5 clear=request,set=response
- bit 4..0 0 = resyncronisation request
+ bit 4..0 0 = resynchronization request
1 = information field size request
2 = abort request
3 = extension of BWT request
4 = VPP error
other values are reserved
*/
int
ccid_transceive (ccid_driver_t handle,
const unsigned char *apdu_buf, size_t apdu_buflen,
unsigned char *resp, size_t maxresplen, size_t *nresp)
{
int rc;
/* The size of the buffer used to be 10+259. For the via_escape
hack we need one extra byte, thus 11+259. */
unsigned char send_buffer[11+259], recv_buffer[11+259];
const unsigned char *apdu;
size_t apdulen;
unsigned char *msg, *tpdu, *p;
size_t msglen, tpdulen, last_tpdulen, n;
unsigned char seqno;
unsigned int edc;
int use_crc = 0;
int hdrlen, pcboff;
size_t dummy_nresp;
int via_escape = 0;
int next_chunk = 1;
int sending = 1;
int retries = 0;
int resyncing = 0;
int nad_byte;
int wait_more = 0;
if (!nresp)
nresp = &dummy_nresp;
*nresp = 0;
/* Smarter readers allow sending APDUs directly; divert here. */
if (handle->apdu_level)
{
/* We employ a hack for Omnikey readers which are able to send
TPDUs using an escape sequence. There is no documentation
but the Windows driver does it this way. Tested using a
CM6121. This method works also for the Cherry XX44
keyboards; however there are problems with the
ccid_transceive_secure which leads to a loss of sync on the
CCID level. If Cherry wants to make their keyboard work
again, they should hand over some docs. */
if ((handle->id_vendor == VENDOR_OMNIKEY)
&& handle->apdu_level < 2
&& is_exlen_apdu (apdu_buf, apdu_buflen))
via_escape = 1;
else
return ccid_transceive_apdu_level (handle, apdu_buf, apdu_buflen,
resp, maxresplen, nresp);
}
/* The other readers we support require sending TPDUs. */
tpdulen = 0; /* Avoid compiler warning about no initialization. */
msg = send_buffer;
hdrlen = via_escape? 11 : 10;
/* NAD: DAD=1, SAD=0 */
nad_byte = handle->nonnull_nad? ((1 << 4) | 0): 0;
if (via_escape)
nad_byte = 0;
last_tpdulen = 0; /* Avoid gcc warning (controlled by RESYNCING). */
for (;;)
{
if (next_chunk)
{
next_chunk = 0;
apdu = apdu_buf;
apdulen = apdu_buflen;
assert (apdulen);
/* Construct an I-Block. */
tpdu = msg + hdrlen;
tpdu[0] = nad_byte;
tpdu[1] = ((handle->t1_ns & 1) << 6); /* I-block */
if (apdulen > handle->ifsc )
{
apdulen = handle->ifsc;
apdu_buf += handle->ifsc;
apdu_buflen -= handle->ifsc;
tpdu[1] |= (1 << 5); /* Set more bit. */
}
tpdu[2] = apdulen;
memcpy (tpdu+3, apdu, apdulen);
tpdulen = 3 + apdulen;
edc = compute_edc (tpdu, tpdulen, use_crc);
if (use_crc)
tpdu[tpdulen++] = (edc >> 8);
tpdu[tpdulen++] = edc;
}
if (via_escape)
{
msg[0] = PC_to_RDR_Escape;
msg[5] = 0; /* slot */
msg[6] = seqno = handle->seqno++;
msg[7] = 0; /* RFU */
msg[8] = 0; /* RFU */
msg[9] = 0; /* RFU */
msg[10] = 0x1a; /* Omnikey command to send a TPDU. */
set_msg_len (msg, 1 + tpdulen);
}
else
{
msg[0] = PC_to_RDR_XfrBlock;
msg[5] = 0; /* slot */
msg[6] = seqno = handle->seqno++;
- msg[7] = 4; /* bBWI */
+ msg[7] = (wait_more ? wait_more : 1); /* bBWI */
msg[8] = 0; /* RFU */
msg[9] = 0; /* RFU */
set_msg_len (msg, tpdulen);
}
msglen = hdrlen + tpdulen;
if (!resyncing)
last_tpdulen = tpdulen;
pcboff = hdrlen+1;
if (debug_level > 1)
DEBUGOUT_3 ("T=1: put %c-block seq=%d%s\n",
((msg[pcboff] & 0xc0) == 0x80)? 'R' :
(msg[pcboff] & 0x80)? 'S' : 'I',
((msg[pcboff] & 0x80)? !!(msg[pcboff]& 0x10)
: !!(msg[pcboff] & 0x40)),
(!(msg[pcboff] & 0x80) && (msg[pcboff] & 0x20)?
" [more]":""));
rc = bulk_out (handle, msg, msglen, 0);
if (rc)
return rc;
msg = recv_buffer;
rc = bulk_in (handle, msg, sizeof recv_buffer, &msglen,
via_escape? RDR_to_PC_Escape : RDR_to_PC_DataBlock, seqno,
- wait_more? CCID_CMD_TIMEOUT_LONGER: CCID_CMD_TIMEOUT, 0);
+ (wait_more ? wait_more : 1) * CCID_CMD_TIMEOUT, 0);
if (rc)
return rc;
tpdu = msg + hdrlen;
tpdulen = msglen - hdrlen;
resyncing = 0;
if (tpdulen < 4)
{
#ifdef USE_NPTH
npth_unprotect ();
#endif
libusb_clear_halt (handle->idev, handle->ep_bulk_in);
#ifdef USE_NPTH
npth_protect ();
#endif
return CCID_DRIVER_ERR_ABORTED;
}
if (debug_level > 1)
DEBUGOUT_4 ("T=1: got %c-block seq=%d err=%d%s\n",
((msg[pcboff] & 0xc0) == 0x80)? 'R' :
(msg[pcboff] & 0x80)? 'S' : 'I',
((msg[pcboff] & 0x80)? !!(msg[pcboff]& 0x10)
: !!(msg[pcboff] & 0x40)),
((msg[pcboff] & 0xc0) == 0x80)? (msg[pcboff] & 0x0f) : 0,
(!(msg[pcboff] & 0x80) && (msg[pcboff] & 0x20)?
" [more]":""));
+ wait_more = 0;
if (!(tpdu[1] & 0x80))
{ /* This is an I-block. */
retries = 0;
if (sending)
{ /* last block sent was successful. */
handle->t1_ns ^= 1;
sending = 0;
}
if (!!(tpdu[1] & 0x40) != handle->t1_nr)
{ /* Response does not match our sequence number. */
msg = send_buffer;
tpdu = msg + hdrlen;
tpdu[0] = nad_byte;
tpdu[1] = (0x80 | (handle->t1_nr & 1) << 4 | 2); /* R-block */
tpdu[2] = 0;
tpdulen = 3;
edc = compute_edc (tpdu, tpdulen, use_crc);
if (use_crc)
tpdu[tpdulen++] = (edc >> 8);
tpdu[tpdulen++] = edc;
continue;
}
handle->t1_nr ^= 1;
p = tpdu + 3; /* Skip the prologue field. */
n = tpdulen - 3 - 1; /* Strip the epilogue field. */
/* fixme: verify the checksum. */
if (resp)
{
if (n > maxresplen)
{
DEBUGOUT_2 ("provided buffer too short for received data "
"(%u/%u)\n",
(unsigned int)n, (unsigned int)maxresplen);
return CCID_DRIVER_ERR_INV_VALUE;
}
memcpy (resp, p, n);
resp += n;
*nresp += n;
maxresplen -= n;
}
if (!(tpdu[1] & 0x20))
return 0; /* No chaining requested - ready. */
msg = send_buffer;
tpdu = msg + hdrlen;
tpdu[0] = nad_byte;
tpdu[1] = (0x80 | (handle->t1_nr & 1) << 4); /* R-block */
tpdu[2] = 0;
tpdulen = 3;
edc = compute_edc (tpdu, tpdulen, use_crc);
if (use_crc)
tpdu[tpdulen++] = (edc >> 8);
tpdu[tpdulen++] = edc;
}
else if ((tpdu[1] & 0xc0) == 0x80)
{ /* This is a R-block. */
if ( (tpdu[1] & 0x0f))
{
retries++;
if (via_escape && retries == 1 && (msg[pcboff] & 0x0f))
{
/* Error probably due to switching to TPDU. Send a
resync request. We use the recv_buffer so that
we don't corrupt the send_buffer. */
msg = recv_buffer;
tpdu = msg + hdrlen;
tpdu[0] = nad_byte;
tpdu[1] = 0xc0; /* S-block resync request. */
tpdu[2] = 0;
tpdulen = 3;
edc = compute_edc (tpdu, tpdulen, use_crc);
if (use_crc)
tpdu[tpdulen++] = (edc >> 8);
tpdu[tpdulen++] = edc;
resyncing = 1;
DEBUGOUT ("T=1: requesting resync\n");
}
else if (retries > 3)
{
DEBUGOUT ("T=1: 3 failed retries\n");
return CCID_DRIVER_ERR_CARD_IO_ERROR;
}
else
{
/* Error: repeat last block */
msg = send_buffer;
tpdulen = last_tpdulen;
}
}
else if (sending && !!(tpdu[1] & 0x10) == handle->t1_ns)
{ /* Response does not match our sequence number. */
DEBUGOUT ("R-block with wrong seqno received on more bit\n");
return CCID_DRIVER_ERR_CARD_IO_ERROR;
}
else if (sending)
{ /* Send next chunk. */
retries = 0;
msg = send_buffer;
next_chunk = 1;
handle->t1_ns ^= 1;
}
else
{
DEBUGOUT ("unexpected ACK R-block received\n");
return CCID_DRIVER_ERR_CARD_IO_ERROR;
}
}
else
{ /* This is a S-block. */
retries = 0;
DEBUGOUT_2 ("T=1: S-block %s received cmd=%d\n",
(tpdu[1] & 0x20)? "response": "request",
(tpdu[1] & 0x1f));
if ( !(tpdu[1] & 0x20) && (tpdu[1] & 0x1f) == 1 && tpdu[2] == 1)
{
/* Information field size request. */
unsigned char ifsc = tpdu[3];
if (ifsc < 16 || ifsc > 254)
return CCID_DRIVER_ERR_CARD_IO_ERROR;
msg = send_buffer;
tpdu = msg + hdrlen;
tpdu[0] = nad_byte;
tpdu[1] = (0xc0 | 0x20 | 1); /* S-block response */
tpdu[2] = 1;
tpdu[3] = ifsc;
tpdulen = 4;
edc = compute_edc (tpdu, tpdulen, use_crc);
if (use_crc)
tpdu[tpdulen++] = (edc >> 8);
tpdu[tpdulen++] = edc;
DEBUGOUT_1 ("T=1: requesting an ifsc=%d\n", ifsc);
}
else if ( !(tpdu[1] & 0x20) && (tpdu[1] & 0x1f) == 3 && tpdu[2])
{
/* Wait time extension request. */
unsigned char bwi = tpdu[3];
- /* Check if it's unusual value which can't be expressed in ATR. */
- if (bwi > 15)
- wait_more = 1;
+ wait_more = bwi;
msg = send_buffer;
tpdu = msg + hdrlen;
tpdu[0] = nad_byte;
tpdu[1] = (0xc0 | 0x20 | 3); /* S-block response */
tpdu[2] = 1;
tpdu[3] = bwi;
tpdulen = 4;
edc = compute_edc (tpdu, tpdulen, use_crc);
if (use_crc)
tpdu[tpdulen++] = (edc >> 8);
tpdu[tpdulen++] = edc;
DEBUGOUT_1 ("T=1: waittime extension of bwi=%d\n", bwi);
print_progress (handle);
}
else if ( (tpdu[1] & 0x20) && (tpdu[1] & 0x1f) == 0 && !tpdu[2])
{
DEBUGOUT ("T=1: resync ack from reader\n");
/* Repeat previous block. */
msg = send_buffer;
tpdulen = last_tpdulen;
}
else
return CCID_DRIVER_ERR_CARD_IO_ERROR;
}
} /* end T=1 protocol loop. */
return 0;
}
/* Send the CCID Secure command to the reader. APDU_BUF should
contain the APDU template. PIN_MODE defines how the pin gets
formatted:
1 := The PIN is ASCII encoded and of variable length. The
length of the PIN entered will be put into Lc by the reader.
The APDU should me made up of 4 bytes without Lc.
PINLEN_MIN and PINLEN_MAX define the limits for the pin length. 0
may be used t enable reasonable defaults.
When called with RESP and NRESP set to NULL, the function will
merely check whether the reader supports the secure command for the
given APDU and PIN_MODE. */
int
ccid_transceive_secure (ccid_driver_t handle,
const unsigned char *apdu_buf, size_t apdu_buflen,
pininfo_t *pininfo,
unsigned char *resp, size_t maxresplen, size_t *nresp)
{
int rc;
unsigned char send_buffer[10+259], recv_buffer[10+259];
unsigned char *msg, *tpdu, *p;
size_t msglen, tpdulen, n;
unsigned char seqno;
size_t dummy_nresp;
int testmode;
int cherry_mode = 0;
int add_zero = 0;
int enable_varlen = 0;
testmode = !resp && !nresp;
if (!nresp)
nresp = &dummy_nresp;
*nresp = 0;
if (apdu_buflen >= 4 && apdu_buf[1] == 0x20 && (handle->has_pinpad & 1))
;
else if (apdu_buflen >= 4 && apdu_buf[1] == 0x24 && (handle->has_pinpad & 2))
;
else
return CCID_DRIVER_ERR_NO_PINPAD;
if (!pininfo->minlen)
pininfo->minlen = 1;
if (!pininfo->maxlen)
pininfo->maxlen = 15;
/* Note that the 25 is the maximum value the SPR532 allows. */
if (pininfo->minlen < 1 || pininfo->minlen > 25
|| pininfo->maxlen < 1 || pininfo->maxlen > 25
|| pininfo->minlen > pininfo->maxlen)
return CCID_DRIVER_ERR_INV_VALUE;
/* We have only tested a few readers so better don't risk anything
and do not allow the use with other readers. */
switch (handle->id_vendor)
{
case VENDOR_SCM: /* Tested with SPR 532. */
case VENDOR_KAAN: /* Tested with KAAN Advanced (1.02). */
case VENDOR_FSIJ: /* Tested with Gnuk (0.21). */
pininfo->maxlen = 25;
enable_varlen = 1;
break;
case VENDOR_REINER:/* Tested with cyberJack go */
case VENDOR_VASCO: /* Tested with DIGIPASS 920 */
enable_varlen = 1;
break;
case VENDOR_CHERRY:
pininfo->maxlen = 15;
enable_varlen = 1;
/* The CHERRY XX44 keyboard echos an asterisk for each entered
character on the keyboard channel. We use a special variant
of PC_to_RDR_Secure which directs these characters to the
smart card's bulk-in channel. We also need to append a zero
Lc byte to the APDU. It seems that it will be replaced with
the actual length instead of being appended before the APDU
is send to the card. */
add_zero = 1;
if (handle->id_product != CHERRY_ST2000)
cherry_mode = 1;
break;
case VENDOR_NXP:
if (handle->id_product == CRYPTOUCAN){
pininfo->maxlen = 25;
enable_varlen = 1;
}
break;
default:
if ((handle->id_vendor == VENDOR_GEMPC &&
handle->id_product == GEMPC_PINPAD)
|| (handle->id_vendor == VENDOR_VEGA &&
handle->id_product == VEGA_ALPHA))
{
enable_varlen = 0;
pininfo->minlen = 4;
pininfo->maxlen = 8;
break;
}
return CCID_DRIVER_ERR_NOT_SUPPORTED;
}
if (enable_varlen)
pininfo->fixedlen = 0;
if (testmode)
return 0; /* Success */
if (pininfo->fixedlen < 0 || pininfo->fixedlen >= 16)
return CCID_DRIVER_ERR_NOT_SUPPORTED;
msg = send_buffer;
if (handle->id_vendor == VENDOR_SCM)
{
DEBUGOUT ("sending escape sequence to switch to a case 1 APDU\n");
rc = send_escape_cmd (handle, (const unsigned char*)"\x80\x02\x00", 3,
NULL, 0, NULL);
if (rc)
return rc;
}
msg[0] = cherry_mode? 0x89 : PC_to_RDR_Secure;
msg[5] = 0; /* slot */
msg[6] = seqno = handle->seqno++;
msg[7] = 0; /* bBWI */
msg[8] = 0; /* RFU */
msg[9] = 0; /* RFU */
msg[10] = apdu_buf[1] == 0x20 ? 0 : 1;
/* Perform PIN verification or PIN modification. */
msg[11] = 0; /* Timeout in seconds. */
msg[12] = 0x82; /* bmFormatString: Byte, pos=0, left, ASCII. */
if (handle->id_vendor == VENDOR_SCM)
{
/* For the SPR532 the next 2 bytes need to be zero. We do this
for all SCM products. Kudos to Martin Paljak for this
hint. */
msg[13] = msg[14] = 0;
}
else
{
msg[13] = pininfo->fixedlen; /* bmPINBlockString:
0 bits of pin length to insert.
PIN block size by fixedlen. */
msg[14] = 0x00; /* bmPINLengthFormat:
Units are bytes, position is 0. */
}
msglen = 15;
if (apdu_buf[1] == 0x24)
{
msg[msglen++] = 0; /* bInsertionOffsetOld */
msg[msglen++] = pininfo->fixedlen; /* bInsertionOffsetNew */
}
/* The following is a little endian word. */
msg[msglen++] = pininfo->maxlen; /* wPINMaxExtraDigit-Maximum. */
msg[msglen++] = pininfo->minlen; /* wPINMaxExtraDigit-Minimum. */
if (apdu_buf[1] == 0x24)
msg[msglen++] = apdu_buf[2] == 0 ? 0x03 : 0x01;
/* bConfirmPIN
* 0x00: new PIN once
* 0x01: new PIN twice (confirmation)
* 0x02: old PIN and new PIN once
* 0x03: old PIN and new PIN twice (confirmation)
*/
msg[msglen] = 0x02; /* bEntryValidationCondition:
Validation key pressed */
if (pininfo->minlen && pininfo->maxlen && pininfo->minlen == pininfo->maxlen)
msg[msglen] |= 0x01; /* Max size reached. */
msglen++;
if (apdu_buf[1] == 0x20)
msg[msglen++] = 0x01; /* bNumberMessage. */
else
msg[msglen++] = 0x03; /* bNumberMessage. */
msg[msglen++] = 0x09; /* wLangId-Low: English FIXME: use the first entry. */
msg[msglen++] = 0x04; /* wLangId-High. */
if (apdu_buf[1] == 0x20)
msg[msglen++] = 0; /* bMsgIndex. */
else
{
msg[msglen++] = 0; /* bMsgIndex1. */
msg[msglen++] = 1; /* bMsgIndex2. */
msg[msglen++] = 2; /* bMsgIndex3. */
}
/* Calculate Lc. */
n = pininfo->fixedlen;
if (apdu_buf[1] == 0x24)
n += pininfo->fixedlen;
/* bTeoProlog follows: */
msg[msglen++] = handle->nonnull_nad? ((1 << 4) | 0): 0;
msg[msglen++] = ((handle->t1_ns & 1) << 6); /* I-block */
if (n)
msg[msglen++] = n + 5; /* apdulen should be filled for fixed length. */
else
msg[msglen++] = 0; /* The apdulen will be filled in by the reader. */
/* APDU follows: */
msg[msglen++] = apdu_buf[0]; /* CLA */
msg[msglen++] = apdu_buf[1]; /* INS */
msg[msglen++] = apdu_buf[2]; /* P1 */
msg[msglen++] = apdu_buf[3]; /* P2 */
if (add_zero)
msg[msglen++] = 0;
else if (pininfo->fixedlen != 0)
{
msg[msglen++] = n;
memset (&msg[msglen], 0xff, n);
msglen += n;
}
/* An EDC is not required. */
set_msg_len (msg, msglen - 10);
rc = bulk_out (handle, msg, msglen, 0);
if (rc)
return rc;
msg = recv_buffer;
rc = bulk_in (handle, msg, sizeof recv_buffer, &msglen,
RDR_to_PC_DataBlock, seqno, 30000, 0);
if (rc)
return rc;
tpdu = msg + 10;
tpdulen = msglen - 10;
if (handle->apdu_level)
{
if (resp)
{
if (tpdulen > maxresplen)
{
DEBUGOUT_2 ("provided buffer too short for received data "
"(%u/%u)\n",
(unsigned int)tpdulen, (unsigned int)maxresplen);
return CCID_DRIVER_ERR_INV_VALUE;
}
memcpy (resp, tpdu, tpdulen);
*nresp = tpdulen;
}
return 0;
}
if (tpdulen < 4)
{
#ifdef USE_NPTH
npth_unprotect ();
#endif
libusb_clear_halt (handle->idev, handle->ep_bulk_in);
#ifdef USE_NPTH
npth_protect ();
#endif
return CCID_DRIVER_ERR_ABORTED;
}
if (debug_level > 1)
DEBUGOUT_4 ("T=1: got %c-block seq=%d err=%d%s\n",
((msg[11] & 0xc0) == 0x80)? 'R' :
(msg[11] & 0x80)? 'S' : 'I',
((msg[11] & 0x80)? !!(msg[11]& 0x10) : !!(msg[11] & 0x40)),
((msg[11] & 0xc0) == 0x80)? (msg[11] & 0x0f) : 0,
(!(msg[11] & 0x80) && (msg[11] & 0x20)? " [more]":""));
if (!(tpdu[1] & 0x80))
{ /* This is an I-block. */
/* Last block sent was successful. */
handle->t1_ns ^= 1;
if (!!(tpdu[1] & 0x40) != handle->t1_nr)
{ /* Response does not match our sequence number. */
DEBUGOUT ("I-block with wrong seqno received\n");
return CCID_DRIVER_ERR_CARD_IO_ERROR;
}
handle->t1_nr ^= 1;
p = tpdu + 3; /* Skip the prologue field. */
n = tpdulen - 3 - 1; /* Strip the epilogue field. */
/* fixme: verify the checksum. */
if (resp)
{
if (n > maxresplen)
{
DEBUGOUT_2 ("provided buffer too short for received data "
"(%u/%u)\n",
(unsigned int)n, (unsigned int)maxresplen);
return CCID_DRIVER_ERR_INV_VALUE;
}
memcpy (resp, p, n);
*nresp += n;
}
if (!(tpdu[1] & 0x20))
return 0; /* No chaining requested - ready. */
DEBUGOUT ("chaining requested but not supported for Secure operation\n");
return CCID_DRIVER_ERR_CARD_IO_ERROR;
}
else if ((tpdu[1] & 0xc0) == 0x80)
{ /* This is a R-block. */
if ( (tpdu[1] & 0x0f))
{ /* Error: repeat last block */
DEBUGOUT ("No retries supported for Secure operation\n");
return CCID_DRIVER_ERR_CARD_IO_ERROR;
}
else if (!!(tpdu[1] & 0x10) == handle->t1_ns)
{ /* Response does not match our sequence number. */
DEBUGOUT ("R-block with wrong seqno received on more bit\n");
return CCID_DRIVER_ERR_CARD_IO_ERROR;
}
else
{ /* Send next chunk. */
DEBUGOUT ("chaining not supported on Secure operation\n");
return CCID_DRIVER_ERR_CARD_IO_ERROR;
}
}
else
{ /* This is a S-block. */
DEBUGOUT_2 ("T=1: S-block %s received cmd=%d for Secure operation\n",
(tpdu[1] & 0x20)? "response": "request",
(tpdu[1] & 0x1f));
return CCID_DRIVER_ERR_CARD_IO_ERROR;
}
return 0;
}
#ifdef TEST
static void
print_error (int err)
{
const char *p;
char buf[50];
switch (err)
{
case 0: p = "success";
case CCID_DRIVER_ERR_OUT_OF_CORE: p = "out of core"; break;
case CCID_DRIVER_ERR_INV_VALUE: p = "invalid value"; break;
case CCID_DRIVER_ERR_NO_DRIVER: p = "no driver"; break;
case CCID_DRIVER_ERR_NOT_SUPPORTED: p = "not supported"; break;
case CCID_DRIVER_ERR_LOCKING_FAILED: p = "locking failed"; break;
case CCID_DRIVER_ERR_BUSY: p = "busy"; break;
case CCID_DRIVER_ERR_NO_CARD: p = "no card"; break;
case CCID_DRIVER_ERR_CARD_INACTIVE: p = "card inactive"; break;
case CCID_DRIVER_ERR_CARD_IO_ERROR: p = "card I/O error"; break;
case CCID_DRIVER_ERR_GENERAL_ERROR: p = "general error"; break;
case CCID_DRIVER_ERR_NO_READER: p = "no reader"; break;
case CCID_DRIVER_ERR_ABORTED: p = "aborted"; break;
default: sprintf (buf, "0x%05x", err); p = buf; break;
}
fprintf (stderr, "operation failed: %s\n", p);
}
static void
print_data (const unsigned char *data, size_t length)
{
if (length >= 2)
{
fprintf (stderr, "operation status: %02X%02X\n",
data[length-2], data[length-1]);
length -= 2;
}
if (length)
{
fputs (" returned data:", stderr);
for (; length; length--, data++)
fprintf (stderr, " %02X", *data);
putc ('\n', stderr);
}
}
static void
print_result (int rc, const unsigned char *data, size_t length)
{
if (rc)
print_error (rc);
else if (data)
print_data (data, length);
}
int
main (int argc, char **argv)
{
- int rc;
+ gpg_error_t err;
ccid_driver_t ccid;
int slotstat;
unsigned char result[512];
size_t resultlen;
int no_pinpad = 0;
int verify_123456 = 0;
int did_verify = 0;
int no_poll = 0;
+ int idx_max;
+ struct ccid_dev_table *ccid_table;
if (argc)
{
argc--;
argv++;
}
while (argc)
{
if ( !strcmp (*argv, "--list"))
{
char *p;
p = ccid_get_reader_list ();
if (!p)
return 1;
fputs (p, stderr);
free (p);
return 0;
}
else if ( !strcmp (*argv, "--debug"))
{
ccid_set_debug_level (ccid_set_debug_level (-1)+1);
argc--; argv++;
}
else if ( !strcmp (*argv, "--no-poll"))
{
no_poll = 1;
argc--; argv++;
}
else if ( !strcmp (*argv, "--no-pinpad"))
{
no_pinpad = 1;
argc--; argv++;
}
else if ( !strcmp (*argv, "--verify-123456"))
{
verify_123456 = 1;
argc--; argv++;
}
else
break;
}
- rc = ccid_open_reader (&ccid, argc? *argv:NULL, NULL);
- if (rc)
+ err = ccid_dev_scan (&idx_max, &ccid_table);
+ if (err)
+ return 1;
+
+ if (idx_max == 0)
+ return 1;
+
+ err = ccid_open_reader (argc? *argv:NULL, 0, ccid_table, &ccid, NULL);
+ if (err)
return 1;
+ ccid_dev_scan_finish (ccid_table, idx_max);
+
if (!no_poll)
ccid_poll (ccid);
fputs ("getting ATR ...\n", stderr);
- rc = ccid_get_atr (ccid, NULL, 0, NULL);
- if (rc)
+ err = ccid_get_atr (ccid, NULL, 0, NULL);
+ if (err)
{
- print_error (rc);
+ print_error (err);
return 1;
}
if (!no_poll)
ccid_poll (ccid);
fputs ("getting slot status ...\n", stderr);
- rc = ccid_slot_status (ccid, &slotstat, 1);
- if (rc)
+ err = ccid_slot_status (ccid, &slotstat, 1);
+ if (err)
{
- print_error (rc);
+ print_error (err);
return 1;
}
if (!no_poll)
ccid_poll (ccid);
fputs ("selecting application OpenPGP ....\n", stderr);
{
static unsigned char apdu[] = {
0, 0xA4, 4, 0, 6, 0xD2, 0x76, 0x00, 0x01, 0x24, 0x01};
- rc = ccid_transceive (ccid,
- apdu, sizeof apdu,
- result, sizeof result, &resultlen);
- print_result (rc, result, resultlen);
+ err = ccid_transceive (ccid,
+ apdu, sizeof apdu,
+ result, sizeof result, &resultlen);
+ print_result (err, result, resultlen);
}
if (!no_poll)
ccid_poll (ccid);
fputs ("getting OpenPGP DO 0x65 ....\n", stderr);
{
static unsigned char apdu[] = { 0, 0xCA, 0, 0x65, 254 };
- rc = ccid_transceive (ccid, apdu, sizeof apdu,
- result, sizeof result, &resultlen);
- print_result (rc, result, resultlen);
+ err = ccid_transceive (ccid, apdu, sizeof apdu,
+ result, sizeof result, &resultlen);
+ print_result (err, result, resultlen);
}
if (!no_pinpad)
{
}
if (!no_pinpad)
{
static unsigned char apdu[] = { 0, 0x20, 0, 0x81 };
+ pininfo_t pininfo = { 0, 0, 0 };
-
- if (ccid_transceive_secure (ccid,
- apdu, sizeof apdu,
- 1, 0, 0, 0,
+ if (ccid_transceive_secure (ccid, apdu, sizeof apdu, &pininfo,
NULL, 0, NULL))
fputs ("can't verify using a PIN-Pad reader\n", stderr);
else
{
- fputs ("verifying CHV1 using the PINPad ....\n", stderr);
+ fputs ("verifying CHV1 using the PINPad ....\n", stderr);
- rc = ccid_transceive_secure (ccid,
- apdu, sizeof apdu,
- 1, 0, 0, 0,
- result, sizeof result, &resultlen);
- print_result (rc, result, resultlen);
+ err = ccid_transceive_secure (ccid, apdu, sizeof apdu, &pininfo,
+ result, sizeof result, &resultlen);
+ print_result (err, result, resultlen);
did_verify = 1;
}
}
if (verify_123456 && !did_verify)
{
fputs ("verifying that CHV1 is 123456....\n", stderr);
{
static unsigned char apdu[] = {0, 0x20, 0, 0x81,
6, '1','2','3','4','5','6'};
- rc = ccid_transceive (ccid, apdu, sizeof apdu,
- result, sizeof result, &resultlen);
- print_result (rc, result, resultlen);
+ err = ccid_transceive (ccid, apdu, sizeof apdu,
+ result, sizeof result, &resultlen);
+ print_result (err, result, resultlen);
}
}
- if (!rc)
+ if (!err)
{
fputs ("getting OpenPGP DO 0x5E ....\n", stderr);
{
static unsigned char apdu[] = { 0, 0xCA, 0, 0x5E, 254 };
- rc = ccid_transceive (ccid, apdu, sizeof apdu,
- result, sizeof result, &resultlen);
- print_result (rc, result, resultlen);
+ err = ccid_transceive (ccid, apdu, sizeof apdu,
+ result, sizeof result, &resultlen);
+ print_result (err, result, resultlen);
}
}
ccid_close_reader (ccid);
return 0;
}
/*
* Local Variables:
- * compile-command: "gcc -DTEST -Wall -I/usr/local/include -lusb -g ccid-driver.c"
+ * compile-command: "gcc -DTEST -DGPGRT_ENABLE_ES_MACROS -DHAVE_NPTH -DUSE_NPTH -Wall -I/usr/include/libusb-1.0 -I/usr/local/include -lusb-1.0 -g ccid-driver.c -lnpth -lgpg-error"
* End:
*/
#endif /*TEST*/
#endif /*HAVE_LIBUSB*/
diff --git a/scd/command.c b/scd/command.c
index 0d1a5cd3f..0096ca96d 100644
--- a/scd/command.c
+++ b/scd/command.c
@@ -1,2092 +1,2296 @@
/* command.c - SCdaemon command handler
* Copyright (C) 2001, 2002, 2003, 2004, 2005,
* 2007, 2008, 2009, 2011 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <signal.h>
#ifdef USE_NPTH
# include <npth.h>
#endif
#include "scdaemon.h"
#include <assuan.h>
#include <ksba.h>
-#include "app-common.h"
#include "iso7816.h"
#include "apdu.h" /* Required for apdu_*_reader (). */
#include "atr.h"
#ifdef HAVE_LIBUSB
#include "ccid-driver.h"
#endif
#include "../common/asshelp.h"
#include "../common/server-help.h"
/* Maximum length allowed as a PIN; used for INQUIRE NEEDPIN */
#define MAXLEN_PIN 100
/* Maximum allowed size of key data as used in inquiries. */
#define MAXLEN_KEYDATA 4096
/* Maximum allowed total data size for SETDATA. */
#define MAXLEN_SETDATA 4096
/* Maximum allowed size of certificate data as used in inquiries. */
#define MAXLEN_CERTDATA 16384
/* Maximum allowed size for "SETATTR --inquire". */
#define MAXLEN_SETATTRDATA 16384
#define set_error(e,t) assuan_set_error (ctx, gpg_error (e), (t))
#define IS_LOCKED(c) (locked_session && locked_session != (c)->server_local)
/* Data used to associate an Assuan context with local server data.
This object describes the local properties of one session. */
struct server_local_s
{
/* We keep a list of all active sessions with the anchor at
SESSION_LIST (see below). This field is used for linking. */
struct server_local_s *next_session;
/* This object is usually assigned to a CTRL object (which is
globally visible). While enumerating all sessions we sometimes
need to access data of the CTRL object; thus we keep a
backpointer here. */
ctrl_t ctrl_backlink;
/* The Assuan context used by this session/server. */
assuan_context_t assuan_ctx;
#ifdef HAVE_W32_SYSTEM
void *event_signal; /* Or NULL if not used. */
#else
int event_signal; /* Or 0 if not used. */
#endif
/* True if the card has been removed and a reset is required to
continue operation. */
int card_removed;
/* If set to true we will be terminate ourself at the end of the
this session. */
int stopme;
};
/* To keep track of all running sessions, we link all active server
contexts and the anchor in this variable. */
static struct server_local_s *session_list;
/* If a session has been locked we store a link to its server object
in this variable. */
static struct server_local_s *locked_session;
/* Convert the STRING into a newly allocated buffer while translating
the hex numbers. Stops at the first invalid character. Blanks and
colons are allowed to separate the hex digits. Returns NULL on
error or a newly malloced buffer and its length in LENGTH. */
static unsigned char *
hex_to_buffer (const char *string, size_t *r_length)
{
unsigned char *buffer;
const char *s;
size_t n;
buffer = xtrymalloc (strlen (string)+1);
if (!buffer)
return NULL;
for (s=string, n=0; *s; s++)
{
if (spacep (s) || *s == ':')
continue;
if (hexdigitp (s) && hexdigitp (s+1))
{
buffer[n++] = xtoi_2 (s);
s++;
}
else
break;
}
*r_length = n;
return buffer;
}
/* Reset the card and free the application context. With SEND_RESET
set to true actually send a RESET to the reader; this is the normal
way of calling the function. */
static void
do_reset (ctrl_t ctrl, int send_reset)
{
- app_t app = ctrl->app_ctx;
+ card_t card = ctrl->card_ctx;
- if (app)
- app_reset (app, ctrl, IS_LOCKED (ctrl)? 0: send_reset);
+ if (card)
+ card_reset (card, ctrl, IS_LOCKED (ctrl)? 0: send_reset);
/* If we hold a lock, unlock now. */
if (locked_session && ctrl->server_local == locked_session)
{
locked_session = NULL;
log_info ("implicitly unlocking due to RESET\n");
}
}
+
+
static gpg_error_t
reset_notify (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
(void) line;
do_reset (ctrl, 1);
return 0;
}
static gpg_error_t
option_handler (assuan_context_t ctx, const char *key, const char *value)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
if (!strcmp (key, "event-signal"))
{
/* A value of 0 is allowed to reset the event signal. */
#ifdef HAVE_W32_SYSTEM
if (!*value)
return gpg_error (GPG_ERR_ASS_PARAMETER);
#ifdef _WIN64
ctrl->server_local->event_signal = (void *)strtoull (value, NULL, 16);
#else
ctrl->server_local->event_signal = (void *)strtoul (value, NULL, 16);
#endif
#else
int i = *value? atoi (value) : -1;
if (i < 0)
return gpg_error (GPG_ERR_ASS_PARAMETER);
ctrl->server_local->event_signal = i;
#endif
}
return 0;
}
/* If the card has not yet been opened, do it. */
static gpg_error_t
open_card (ctrl_t ctrl)
{
/* If we ever got a card not present error code, return that. Only
the SERIALNO command and a reset are able to clear from that
state. */
if (ctrl->server_local->card_removed)
return gpg_error (GPG_ERR_CARD_REMOVED);
if ( IS_LOCKED (ctrl) )
return gpg_error (GPG_ERR_LOCKED);
- if (ctrl->app_ctx)
+ if (ctrl->card_ctx)
return 0;
- return select_application (ctrl, NULL, &ctrl->app_ctx, 0, NULL, 0);
+ return select_application (ctrl, NULL, &ctrl->card_ctx, 0, NULL, 0);
}
/* Explicitly open a card for a specific use of APPTYPE or SERIALNO. */
static gpg_error_t
-open_card_with_request (ctrl_t ctrl, const char *apptype, const char *serialno)
+open_card_with_request (ctrl_t ctrl,
+ const char *apptypestr, const char *serialno)
{
gpg_error_t err;
unsigned char *serialno_bin = NULL;
size_t serialno_bin_len = 0;
- app_t app = ctrl->app_ctx;
+ card_t card = ctrl->card_ctx;
+
+ if (serialno)
+ serialno_bin = hex_to_buffer (serialno, &serialno_bin_len);
/* If we are already initialized for one specific application we
need to check that the client didn't requested a specific
application different from the one in use before we continue. */
- if (apptype && ctrl->app_ctx)
- return check_application_conflict (apptype, ctrl->app_ctx);
-
- /* Re-scan USB devices. Release APP, before the scan. */
- ctrl->app_ctx = NULL;
- release_application (app, 0);
+ if (apptypestr && ctrl->card_ctx)
+ {
+ err = check_application_conflict (ctrl->card_ctx, apptypestr,
+ serialno_bin, serialno_bin_len);
+ if (gpg_err_code (err) == GPG_ERR_FALSE)
+ {
+ /* Different application but switching is supported. */
+ err = select_additional_application (ctrl, apptypestr);
+ }
+ goto leave;
+ }
- if (serialno)
- serialno_bin = hex_to_buffer (serialno, &serialno_bin_len);
+ /* Re-scan USB devices. Release CARD, before the scan. */
+ /* FIXME: Is a card_unref sufficient or do we need to deallocate? */
+ ctrl->card_ctx = NULL;
+ card_unref (card);
- err = select_application (ctrl, apptype, &ctrl->app_ctx, 1,
+ err = select_application (ctrl, apptypestr, &ctrl->card_ctx, 1,
serialno_bin, serialno_bin_len);
- xfree (serialno_bin);
+ leave:
+ xfree (serialno_bin);
return err;
}
static const char hlp_serialno[] =
"SERIALNO [--demand=<serialno>] [<apptype>]\n"
"\n"
"Return the serial number of the card using a status response. This\n"
"function should be used to check for the presence of a card.\n"
"\n"
"If --demand is given, an application on the card with SERIALNO is\n"
"selected and an error is returned if no such card available.\n"
"\n"
"If APPTYPE is given, an application of that type is selected and an\n"
"error is returned if the application is not supported or available.\n"
"The default is to auto-select the application using a hardwired\n"
"preference system. Note, that a future extension to this function\n"
"may enable specifying a list and order of applications to try.\n"
"\n"
"This function is special in that it can be used to reset the card.\n"
"Most other functions will return an error when a card change has\n"
"been detected and the use of this function is therefore required.\n"
"\n"
"Background: We want to keep the client clear of handling card\n"
"changes between operations; i.e. the client can assume that all\n"
"operations are done on the same card unless he calls this function.";
static gpg_error_t
cmd_serialno (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
struct server_local_s *sl;
int rc = 0;
char *serial;
const char *demand;
if ( IS_LOCKED (ctrl) )
return gpg_error (GPG_ERR_LOCKED);
if ((demand = has_option_name (line, "--demand")))
{
if (*demand != '=')
return set_error (GPG_ERR_ASS_PARAMETER, "missing value for option");
line = (char *)++demand;
for (; *line && !spacep (line); line++)
;
if (*line)
*line++ = 0;
}
else
demand = NULL;
/* Clear the remove flag so that the open_card is able to reread it. */
if (ctrl->server_local->card_removed)
ctrl->server_local->card_removed = 0;
if ((rc = open_card_with_request (ctrl, *line? line:NULL, demand)))
{
ctrl->server_local->card_removed = 1;
return rc;
}
/* Success, clear the card_removed flag for all sessions. */
for (sl=session_list; sl; sl = sl->next_session)
{
ctrl_t c = sl->ctrl_backlink;
if (c != ctrl)
c->server_local->card_removed = 0;
}
- serial = app_get_serialno (ctrl->app_ctx);
+ serial = card_get_serialno (ctrl->card_ctx);
if (!serial)
return gpg_error (GPG_ERR_INV_VALUE);
rc = assuan_write_status (ctx, "SERIALNO", serial);
xfree (serial);
return rc;
}
static const char hlp_learn[] =
"LEARN [--force] [--keypairinfo]\n"
"\n"
"Learn all useful information of the currently inserted card. When\n"
"used without the force options, the command might do an INQUIRE\n"
"like this:\n"
"\n"
" INQUIRE KNOWNCARDP <hexstring_with_serialNumber>\n"
"\n"
"The client should just send an \"END\" if the processing should go on\n"
"or a \"CANCEL\" to force the function to terminate with a Cancel\n"
"error message.\n"
"\n"
"With the option --keypairinfo only KEYPARIINFO status lines are\n"
"returned.\n"
"\n"
"The response of this command is a list of status lines formatted as\n"
"this:\n"
"\n"
" S APPTYPE <apptype>\n"
"\n"
"This returns the type of the application, currently the strings:\n"
"\n"
" P15 = PKCS-15 structure used\n"
" DINSIG = DIN SIG\n"
" OPENPGP = OpenPGP card\n"
" PIV = PIV card\n"
" NKS = NetKey card\n"
"\n"
"are implemented. These strings are aliases for the AID\n"
"\n"
" S KEYPAIRINFO <hexstring_with_keygrip> <hexstring_with_id> [<usage>]\n"
"\n"
"If there is no certificate yet stored on the card a single 'X' is\n"
"returned as the keygrip. In addition to the keypair info, information\n"
"about all certificates stored on the card is also returned:\n"
"\n"
" S CERTINFO <certtype> <hexstring_with_id>\n"
"\n"
"Where CERTTYPE is a number indicating the type of certificate:\n"
" 0 := Unknown\n"
" 100 := Regular X.509 cert\n"
" 101 := Trusted X.509 cert\n"
" 102 := Useful X.509 cert\n"
" 110 := Root CA cert in a special format (e.g. DINSIG)\n"
" 111 := Root CA cert as standard X509 cert.\n"
"\n"
"For certain cards, more information will be returned:\n"
"\n"
" S KEY-FPR <no> <hexstring>\n"
"\n"
"For OpenPGP cards this returns the stored fingerprints of the\n"
"keys. This can be used check whether a key is available on the\n"
"card. NO may be 1, 2 or 3.\n"
"\n"
" S CA-FPR <no> <hexstring>\n"
"\n"
"Similar to above, these are the fingerprints of keys assumed to be\n"
"ultimately trusted.\n"
"\n"
" S DISP-NAME <name_of_card_holder>\n"
"\n"
"The name of the card holder as stored on the card; percent\n"
"escaping takes place, spaces are encoded as '+'\n"
"\n"
" S PUBKEY-URL <url>\n"
"\n"
"The URL to be used for locating the entire public key.\n"
" \n"
"Note, that this function may even be used on a locked card.";
static gpg_error_t
cmd_learn (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc = 0;
int only_keypairinfo = has_option (line, "--keypairinfo");
if ((rc = open_card (ctrl)))
return rc;
/* Unless the force option is used we try a shortcut by identifying
the card using a serial number and inquiring the client with
that. The client may choose to cancel the operation if he already
knows about this card */
if (!only_keypairinfo)
{
const char *reader;
char *serial;
- app_t app = ctrl->app_ctx;
+ card_t card = ctrl->card_ctx;
- if (!app)
+ if (!card)
return gpg_error (GPG_ERR_CARD_NOT_PRESENT);
- reader = apdu_get_reader_name (app->slot);
+ reader = apdu_get_reader_name (card->slot);
if (!reader)
return out_of_core ();
send_status_direct (ctrl, "READER", reader);
/* No need to free the string of READER. */
- serial = app_get_serialno (ctrl->app_ctx);
+ serial = card_get_serialno (ctrl->card_ctx);
if (!serial)
- return gpg_error (GPG_ERR_INV_VALUE);
+ return gpg_error (GPG_ERR_INV_VALUE);
rc = assuan_write_status (ctx, "SERIALNO", serial);
if (rc < 0)
{
xfree (serial);
return out_of_core ();
}
if (!has_option (line, "--force"))
{
char *command;
rc = gpgrt_asprintf (&command, "KNOWNCARDP %s", serial);
if (rc < 0)
{
xfree (serial);
return out_of_core ();
}
rc = assuan_inquire (ctx, command, NULL, NULL, 0);
xfree (command);
if (rc)
{
if (gpg_err_code (rc) != GPG_ERR_ASS_CANCELED)
log_error ("inquire KNOWNCARDP failed: %s\n",
gpg_strerror (rc));
xfree (serial);
return rc;
}
/* Not canceled, so we have to proceed. */
}
xfree (serial);
}
/* Let the application print out its collection of useful status
information. */
if (!rc)
- rc = app_write_learn_status (ctrl->app_ctx, ctrl, only_keypairinfo);
+ rc = app_write_learn_status (ctrl->card_ctx, ctrl, only_keypairinfo);
return rc;
}
static const char hlp_readcert[] =
"READCERT <hexified_certid>|<keyid>|<oid>\n"
"\n"
"Note, that this function may even be used on a locked card.";
static gpg_error_t
cmd_readcert (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc;
unsigned char *cert;
size_t ncert;
if ((rc = open_card (ctrl)))
return rc;
line = xstrdup (line); /* Need a copy of the line. */
- rc = app_readcert (ctrl->app_ctx, ctrl, line, &cert, &ncert);
+ rc = app_readcert (ctrl->card_ctx, ctrl, line, &cert, &ncert);
if (rc)
log_error ("app_readcert failed: %s\n", gpg_strerror (rc));
xfree (line);
line = NULL;
if (!rc)
{
rc = assuan_send_data (ctx, cert, ncert);
xfree (cert);
if (rc)
return rc;
}
return rc;
}
static const char hlp_readkey[] =
- "READKEY [--advanced] <keyid>|<oid>\n"
+ "READKEY [--advanced] [--info[-only]] <keyid>|<oid>\n"
"\n"
"Return the public key for the given cert or key ID as a standard\n"
- "S-expression.\n"
- "In --advanced mode it returns the S-expression in advanced format.\n"
- "\n"
- "Note that this function may even be used on a locked card.";
+ "S-expression. With --advanced the S-expression is returned in\n"
+ "advanced format. With --info a KEYPAIRINFO status line is also\n"
+ "emitted; with --info-only the regular output is suppressed.";
static gpg_error_t
cmd_readkey (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc;
int advanced = 0;
+ int opt_info = 0;
+ int opt_nokey = 0;
unsigned char *cert = NULL;
unsigned char *pk = NULL;
size_t ncert, pklen;
if ((rc = open_card (ctrl)))
return rc;
if (has_option (line, "--advanced"))
advanced = 1;
+ if (has_option (line, "--info"))
+ opt_info = 1;
+ if (has_option (line, "--info-only"))
+ opt_info = opt_nokey = 1;
line = skip_options (line);
line = xstrdup (line); /* Need a copy of the line. */
/* If the application supports the READKEY function we use that.
Otherwise we use the old way by extracting it from the
certificate. */
- rc = app_readkey (ctrl->app_ctx, ctrl, line, &pk, &pklen);
+ rc = app_readkey (ctrl->card_ctx, ctrl, line,
+ opt_info? APP_READKEY_FLAG_INFO : 0,
+ opt_nokey? NULL : &pk, &pklen);
if (!rc)
; /* Okay, got that key. */
else if (gpg_err_code (rc) == GPG_ERR_UNSUPPORTED_OPERATION
|| gpg_err_code (rc) == GPG_ERR_NOT_FOUND)
{
/* Fall back to certificate reading. */
- rc = app_readcert (ctrl->app_ctx, ctrl, line, &cert, &ncert);
+ rc = app_readcert (ctrl->card_ctx, ctrl, line, &cert, &ncert);
if (rc)
{
log_error ("app_readcert failed: %s\n", gpg_strerror (rc));
goto leave;
}
rc = app_help_pubkey_from_cert (cert, ncert, &pk, &pklen);
if (rc)
{
log_error ("failed to parse the certificate: %s\n",
gpg_strerror (rc));
goto leave;
}
+
+ if (opt_info)
+ {
+ char keygripstr[KEYGRIP_LEN*2+1];
+
+ rc = app_help_get_keygrip_string_pk (pk, pklen, keygripstr);
+ if (rc)
+ {
+ log_error ("app_help_get_keygrip_string failed: %s\n",
+ gpg_strerror (rc));
+ goto leave;
+ }
+
+ /* FIXME: Using LINE is not correct because it might be an
+ * OID and has not been canonicalized (i.e. uppercased). */
+ send_status_info (ctrl, "KEYPAIRINFO",
+ keygripstr, strlen (keygripstr),
+ line, strlen (line),
+ NULL, (size_t)0);
+ }
}
else
{
log_error ("app_readkey failed: %s\n", gpg_strerror (rc));
goto leave;
}
- if (advanced)
+ if (opt_nokey)
+ ;
+ else if (advanced)
{
gcry_sexp_t s_key;
unsigned char *pkadv;
size_t pkadvlen;
rc = gcry_sexp_new (&s_key, pk, pklen, 0);
if (rc)
goto leave;
pkadvlen = gcry_sexp_sprint (s_key, GCRYSEXP_FMT_ADVANCED, NULL, 0);
pkadv = xtrymalloc (pkadvlen);
if (!pkadv)
{
rc = gpg_error_from_syserror ();
goto leave;
}
log_assert (pkadvlen);
gcry_sexp_sprint (s_key, GCRYSEXP_FMT_ADVANCED, pkadv, pkadvlen);
gcry_sexp_release (s_key);
/* (One less to adjust for the trailing '\0') */
rc = assuan_send_data (ctx, pkadv, pkadvlen-1);
xfree (pkadv);
}
else
rc = assuan_send_data (ctx, pk, pklen);
leave:
xfree (pk);
xfree (cert);
return rc;
}
static const char hlp_setdata[] =
"SETDATA [--append] <hexstring>\n"
"\n"
"The client should use this command to tell us the data he want to sign.\n"
"With the option --append, the data is appended to the data set by a\n"
"previous SETDATA command.";
static gpg_error_t
cmd_setdata (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int append;
int n, i, off;
char *p;
unsigned char *buf;
append = (ctrl->in_data.value && has_option (line, "--append"));
line = skip_options (line);
if (locked_session && locked_session != ctrl->server_local)
return gpg_error (GPG_ERR_LOCKED);
/* Parse the hexstring. */
for (p=line,n=0; hexdigitp (p); p++, n++)
;
if (*p)
return set_error (GPG_ERR_ASS_PARAMETER, "invalid hexstring");
if (!n)
return set_error (GPG_ERR_ASS_PARAMETER, "no data given");
if ((n&1))
return set_error (GPG_ERR_ASS_PARAMETER, "odd number of digits");
n /= 2;
if (append)
{
if (ctrl->in_data.valuelen + n > MAXLEN_SETDATA)
return set_error (GPG_ERR_TOO_LARGE,
"limit on total size of data reached");
buf = xtrymalloc (ctrl->in_data.valuelen + n);
}
else
buf = xtrymalloc (n);
if (!buf)
return out_of_core ();
if (append)
{
memcpy (buf, ctrl->in_data.value, ctrl->in_data.valuelen);
off = ctrl->in_data.valuelen;
}
else
off = 0;
for (p=line, i=0; i < n; p += 2, i++)
buf[off+i] = xtoi_2 (p);
xfree (ctrl->in_data.value);
ctrl->in_data.value = buf;
ctrl->in_data.valuelen = off+n;
return 0;
}
static gpg_error_t
pin_cb (void *opaque, const char *info, char **retstr)
{
assuan_context_t ctx = opaque;
char *command;
int rc;
unsigned char *value;
size_t valuelen;
if (!retstr)
{
/* We prompt for pinpad entry. To make sure that the popup has
been show we use an inquire and not just a status message.
We ignore any value returned. */
if (info)
{
log_debug ("prompting for pinpad entry '%s'\n", info);
rc = gpgrt_asprintf (&command, "POPUPPINPADPROMPT %s", info);
if (rc < 0)
return gpg_error (gpg_err_code_from_errno (errno));
rc = assuan_inquire (ctx, command, &value, &valuelen, MAXLEN_PIN);
xfree (command);
}
else
{
log_debug ("dismiss pinpad entry prompt\n");
rc = assuan_inquire (ctx, "DISMISSPINPADPROMPT",
&value, &valuelen, MAXLEN_PIN);
}
if (!rc)
xfree (value);
return rc;
}
*retstr = NULL;
log_debug ("asking for PIN '%s'\n", info);
rc = gpgrt_asprintf (&command, "NEEDPIN %s", info);
if (rc < 0)
return gpg_error (gpg_err_code_from_errno (errno));
/* Fixme: Write an inquire function which returns the result in
secure memory and check all further handling of the PIN. */
rc = assuan_inquire (ctx, command, &value, &valuelen, MAXLEN_PIN);
xfree (command);
if (rc)
return rc;
if (!valuelen || value[valuelen-1])
{
/* We require that the returned value is an UTF-8 string */
xfree (value);
return gpg_error (GPG_ERR_INV_RESPONSE);
}
*retstr = (char*)value;
return 0;
}
static const char hlp_pksign[] =
"PKSIGN [--hash=[rmd160|sha{1,224,256,384,512}|md5]] <hexified_id>\n"
"\n"
"The --hash option is optional; the default is SHA1.";
static gpg_error_t
cmd_pksign (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc;
unsigned char *outdata;
size_t outdatalen;
char *keyidstr;
int hash_algo;
+ card_t card;
+ int direct = 0;
if (has_option (line, "--hash=rmd160"))
hash_algo = GCRY_MD_RMD160;
else if (has_option (line, "--hash=sha1"))
hash_algo = GCRY_MD_SHA1;
else if (has_option (line, "--hash=sha224"))
hash_algo = GCRY_MD_SHA224;
else if (has_option (line, "--hash=sha256"))
hash_algo = GCRY_MD_SHA256;
else if (has_option (line, "--hash=sha384"))
hash_algo = GCRY_MD_SHA384;
else if (has_option (line, "--hash=sha512"))
hash_algo = GCRY_MD_SHA512;
else if (has_option (line, "--hash=md5"))
hash_algo = GCRY_MD_MD5;
else if (!strstr (line, "--"))
hash_algo = GCRY_MD_SHA1;
else
return set_error (GPG_ERR_ASS_PARAMETER, "invalid hash algorithm");
line = skip_options (line);
if ((rc = open_card (ctrl)))
return rc;
/* We have to use a copy of the key ID because the function may use
the pin_cb which in turn uses the assuan line buffer and thus
overwriting the original line with the keyid */
keyidstr = xtrystrdup (line);
if (!keyidstr)
return out_of_core ();
- rc = app_sign (ctrl->app_ctx, ctrl,
- keyidstr, hash_algo,
- pin_cb, ctx,
- ctrl->in_data.value, ctrl->in_data.valuelen,
- &outdata, &outdatalen);
+ /* When it's a keygrip, we directly use the card, with no change of
+ ctrl->card_ctx. */
+ if (strlen (keyidstr) == 40)
+ {
+ card = app_do_with_keygrip (ctrl, KEYGRIP_ACTION_LOOKUP, keyidstr);
+ direct = 1;
+ }
+ else
+ card = ctrl->card_ctx;
+
+ if (card)
+ {
+ if (direct)
+ card_ref (card);
+ rc = app_sign (card, ctrl,
+ keyidstr, hash_algo,
+ pin_cb, ctx,
+ ctrl->in_data.value, ctrl->in_data.valuelen,
+ &outdata, &outdatalen);
+ if (direct)
+ card_unref (card);
+ }
+ else
+ rc = gpg_error (GPG_ERR_NO_SECKEY);
xfree (keyidstr);
if (rc)
{
log_error ("app_sign failed: %s\n", gpg_strerror (rc));
}
else
{
rc = assuan_send_data (ctx, outdata, outdatalen);
xfree (outdata);
if (rc)
return rc; /* that is already an assuan error code */
}
return rc;
}
static const char hlp_pkauth[] =
"PKAUTH <hexified_id>";
static gpg_error_t
cmd_pkauth (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc;
unsigned char *outdata;
size_t outdatalen;
char *keyidstr;
+ card_t card;
+ int direct = 0;
if ((rc = open_card (ctrl)))
return rc;
- if (!ctrl->app_ctx)
+ if (!ctrl->card_ctx)
return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
/* We have to use a copy of the key ID because the function may use
the pin_cb which in turn uses the assuan line buffer and thus
overwriting the original line with the keyid */
keyidstr = xtrystrdup (line);
if (!keyidstr)
return out_of_core ();
- rc = app_auth (ctrl->app_ctx, ctrl, keyidstr, pin_cb, ctx,
- ctrl->in_data.value, ctrl->in_data.valuelen,
- &outdata, &outdatalen);
+ /* When it's a keygrip, we directly use CARD, with no change of
+ ctrl->card_ctx. */
+ if (strlen (keyidstr) == 40)
+ {
+ card = app_do_with_keygrip (ctrl, KEYGRIP_ACTION_LOOKUP, keyidstr);
+ direct = 1;
+ }
+ else
+ card = ctrl->card_ctx;
+
+ if (card)
+ {
+ if (direct)
+ card_ref (card);
+ rc = app_auth (card, ctrl, keyidstr, pin_cb, ctx,
+ ctrl->in_data.value, ctrl->in_data.valuelen,
+ &outdata, &outdatalen);
+ if (direct)
+ card_unref (card);
+ }
+ else
+ rc = gpg_error (GPG_ERR_NO_SECKEY);
+
xfree (keyidstr);
if (rc)
{
log_error ("app_auth failed: %s\n", gpg_strerror (rc));
}
else
{
rc = assuan_send_data (ctx, outdata, outdatalen);
xfree (outdata);
if (rc)
return rc; /* that is already an assuan error code */
}
return rc;
}
static const char hlp_pkdecrypt[] =
"PKDECRYPT <hexified_id>";
static gpg_error_t
cmd_pkdecrypt (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc;
unsigned char *outdata;
size_t outdatalen;
char *keyidstr;
unsigned int infoflags;
+ card_t card;
+ int direct = 0;
if ((rc = open_card (ctrl)))
return rc;
keyidstr = xtrystrdup (line);
if (!keyidstr)
return out_of_core ();
- rc = app_decipher (ctrl->app_ctx, ctrl, keyidstr, pin_cb, ctx,
- ctrl->in_data.value, ctrl->in_data.valuelen,
- &outdata, &outdatalen, &infoflags);
+
+ /* When it's a keygrip, we directly use CARD, with no change of
+ ctrl->card_ctx. */
+ if (strlen (keyidstr) == 40)
+ {
+ card = app_do_with_keygrip (ctrl, KEYGRIP_ACTION_LOOKUP, keyidstr);
+ direct = 1;
+ }
+ else
+ card = ctrl->card_ctx;
+
+ if (card)
+ {
+ if (direct)
+ card_ref (card);
+ rc = app_decipher (card, ctrl, keyidstr, pin_cb, ctx,
+ ctrl->in_data.value, ctrl->in_data.valuelen,
+ &outdata, &outdatalen, &infoflags);
+ if (direct)
+ card_unref (card);
+ }
+ else
+ rc = gpg_error (GPG_ERR_NO_SECKEY);
xfree (keyidstr);
if (rc)
{
log_error ("app_decipher failed: %s\n", gpg_strerror (rc));
}
else
{
/* If the card driver told us that there is no padding, send a
status line. If there is a padding it is assumed that the
caller knows what padding is used. It would have been better
to always send that information but for backward
compatibility we can't do that. */
if ((infoflags & APP_DECIPHER_INFO_NOPAD))
send_status_direct (ctrl, "PADDING", "0");
rc = assuan_send_data (ctx, outdata, outdatalen);
xfree (outdata);
if (rc)
return rc; /* that is already an assuan error code */
}
return rc;
}
static const char hlp_getattr[] =
"GETATTR <name>\n"
"\n"
"This command is used to retrieve data from a smartcard. The\n"
"allowed names depend on the currently selected smartcard\n"
"application. NAME must be percent and '+' escaped. The value is\n"
"returned through status message, see the LEARN command for details.\n"
"\n"
"However, the current implementation assumes that Name is not escaped;\n"
"this works as long as no one uses arbitrary escaping. \n"
"\n"
"Note, that this function may even be used on a locked card.";
static gpg_error_t
cmd_getattr (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc;
const char *keyword;
if ((rc = open_card (ctrl)))
return rc;
keyword = line;
for (; *line && !spacep (line); line++)
;
if (*line)
*line++ = 0;
/* (We ignore any garbage for now.) */
/* FIXME: Applications should not return sensitive data if the card
is locked. */
- rc = app_getattr (ctrl->app_ctx, ctrl, keyword);
+ rc = app_getattr (ctrl->card_ctx, ctrl, keyword);
return rc;
}
static const char hlp_setattr[] =
"SETATTR [--inquire] <name> <value> \n"
"\n"
"This command is used to store data on a smartcard. The allowed\n"
"names and values are depend on the currently selected smartcard\n"
"application. NAME and VALUE must be percent and '+' escaped.\n"
"\n"
"However, the current implementation assumes that NAME is not\n"
"escaped; this works as long as no one uses arbitrary escaping.\n"
"\n"
"If the option --inquire is used, VALUE shall not be given; instead\n"
"an inquiry using the keyword \"VALUE\" is used to retrieve it. The\n"
"value is in this case considered to be confidential and not logged.\n"
"\n"
"A PIN will be requested for most NAMEs. See the corresponding\n"
"setattr function of the actually used application (app-*.c) for\n"
"details.";
static gpg_error_t
cmd_setattr (assuan_context_t ctx, char *orig_line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err;
char *keyword;
int keywordlen;
size_t nbytes;
char *line, *linebuf;
int opt_inquire;
opt_inquire = has_option (orig_line, "--inquire");
orig_line = skip_options (orig_line);
if ((err = open_card (ctrl)))
return err;
/* We need to use a copy of LINE, because PIN_CB uses the same
context and thus reuses the Assuan provided LINE. */
line = linebuf = xtrystrdup (orig_line);
if (!line)
return out_of_core ();
keyword = line;
for (keywordlen=0; *line && !spacep (line); line++, keywordlen++)
;
if (*line)
*line++ = 0;
while (spacep (line))
line++;
if (opt_inquire)
{
unsigned char *value;
assuan_begin_confidential (ctx);
err = assuan_inquire (ctx, "VALUE", &value, &nbytes, MAXLEN_SETATTRDATA);
assuan_end_confidential (ctx);
if (!err)
{
- err = app_setattr (ctrl->app_ctx, ctrl, keyword, pin_cb, ctx,
+ err = app_setattr (ctrl->card_ctx, ctrl, keyword, pin_cb, ctx,
value, nbytes);
wipememory (value, nbytes);
xfree (value);
}
}
else
{
nbytes = percent_plus_unescape_inplace (line, 0);
- err = app_setattr (ctrl->app_ctx, ctrl, keyword, pin_cb, ctx,
+ err = app_setattr (ctrl->card_ctx, ctrl, keyword, pin_cb, ctx,
(const unsigned char*)line, nbytes);
}
xfree (linebuf);
return err;
}
static const char hlp_writecert[] =
"WRITECERT <hexified_certid>\n"
"\n"
"This command is used to store a certificate on a smartcard. The\n"
"allowed certids depend on the currently selected smartcard\n"
"application. The actual certifciate is requested using the inquiry\n"
"\"CERTDATA\" and needs to be provided in its raw (e.g. DER) form.\n"
"\n"
"In almost all cases a PIN will be requested. See the related\n"
"writecert function of the actually used application (app-*.c) for\n"
"details.";
static gpg_error_t
cmd_writecert (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc;
char *certid;
unsigned char *certdata;
size_t certdatalen;
line = skip_options (line);
if (!*line)
return set_error (GPG_ERR_ASS_PARAMETER, "no certid given");
certid = line;
while (*line && !spacep (line))
line++;
*line = 0;
if ((rc = open_card (ctrl)))
return rc;
- if (!ctrl->app_ctx)
+ if (!ctrl->card_ctx)
return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
certid = xtrystrdup (certid);
if (!certid)
return out_of_core ();
/* Now get the actual keydata. */
rc = assuan_inquire (ctx, "CERTDATA",
&certdata, &certdatalen, MAXLEN_CERTDATA);
if (rc)
{
xfree (certid);
return rc;
}
/* Write the certificate to the card. */
- rc = app_writecert (ctrl->app_ctx, ctrl, certid,
+ rc = app_writecert (ctrl->card_ctx, ctrl, certid,
pin_cb, ctx, certdata, certdatalen);
xfree (certid);
xfree (certdata);
return rc;
}
static const char hlp_writekey[] =
"WRITEKEY [--force] <keyid> \n"
"\n"
"This command is used to store a secret key on a smartcard. The\n"
"allowed keyids depend on the currently selected smartcard\n"
"application. The actual keydata is requested using the inquiry\n"
"\"KEYDATA\" and need to be provided without any protection. With\n"
"--force set an existing key under this KEYID will get overwritten.\n"
"The keydata is expected to be the usual canonical encoded\n"
"S-expression.\n"
"\n"
"A PIN will be requested for most NAMEs. See the corresponding\n"
"writekey function of the actually used application (app-*.c) for\n"
"details.";
static gpg_error_t
cmd_writekey (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc;
char *keyid;
int force = has_option (line, "--force");
unsigned char *keydata;
size_t keydatalen;
line = skip_options (line);
if (!*line)
return set_error (GPG_ERR_ASS_PARAMETER, "no keyid given");
keyid = line;
while (*line && !spacep (line))
line++;
*line = 0;
if ((rc = open_card (ctrl)))
return rc;
- if (!ctrl->app_ctx)
+ if (!ctrl->card_ctx)
return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
keyid = xtrystrdup (keyid);
if (!keyid)
return out_of_core ();
/* Now get the actual keydata. */
assuan_begin_confidential (ctx);
rc = assuan_inquire (ctx, "KEYDATA", &keydata, &keydatalen, MAXLEN_KEYDATA);
assuan_end_confidential (ctx);
if (rc)
{
xfree (keyid);
return rc;
}
/* Write the key to the card. */
- rc = app_writekey (ctrl->app_ctx, ctrl, keyid, force? 1:0,
+ rc = app_writekey (ctrl->card_ctx, ctrl, keyid, force? 1:0,
pin_cb, ctx, keydata, keydatalen);
xfree (keyid);
xfree (keydata);
return rc;
}
static const char hlp_genkey[] =
"GENKEY [--force] [--timestamp=<isodate>] <keyref>\n"
"\n"
"Generate a key on-card identified by <keyref>, which is application\n"
"specific. Return values are also application specific. For OpenPGP\n"
"cards 3 status lines are returned:\n"
"\n"
" S KEY-FPR <hexstring>\n"
" S KEY-CREATED-AT <seconds_since_epoch>\n"
" S KEY-DATA [-|p|n] <hexdata>\n"
"\n"
" 'p' and 'n' are the names of the RSA parameters; '-' is used to\n"
" indicate that HEXDATA is the first chunk of a parameter given\n"
" by the next KEY-DATA. Only used by GnuPG version < 2.1.\n"
"\n"
"--force is required to overwrite an already existing key. The\n"
"KEY-CREATED-AT is required for further processing because it is\n"
"part of the hashed key material for the fingerprint.\n"
"\n"
"If --timestamp is given an OpenPGP key will be created using this\n"
"value. The value needs to be in ISO Format; e.g.\n"
"\"--timestamp=20030316T120000\" and after 1970-01-01 00:00:00.\n"
"\n"
"The public part of the key can also later be retrieved using the\n"
"READKEY command.";
static gpg_error_t
cmd_genkey (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err;
char *keyref_buffer = NULL;
char *keyref;
int force;
const char *s;
char *opt_algo = NULL;
time_t timestamp;
force = has_option (line, "--force");
if ((s=has_option_name (line, "--timestamp")))
{
if (*s != '=')
return set_error (GPG_ERR_ASS_PARAMETER, "missing value for option");
timestamp = isotime2epoch (s+1);
if (timestamp < 1)
return set_error (GPG_ERR_ASS_PARAMETER, "invalid time value");
}
else
timestamp = 0;
err = get_option_value (line, "--algo", &opt_algo);
if (err)
goto leave;
line = skip_options (line);
if (!*line)
return set_error (GPG_ERR_ASS_PARAMETER, "no key number given");
keyref = line;
while (*line && !spacep (line))
line++;
*line = 0;
if ((err = open_card (ctrl)))
goto leave;
- if (!ctrl->app_ctx)
+ if (!ctrl->card_ctx)
return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
keyref = keyref_buffer = xtrystrdup (keyref);
if (!keyref)
{
err = gpg_error_from_syserror ();
goto leave;
}
- err = app_genkey (ctrl->app_ctx, ctrl, keyref, opt_algo,
+ err = app_genkey (ctrl->card_ctx, ctrl, keyref, opt_algo,
force? APP_GENKEY_FLAG_FORCE : 0,
timestamp, pin_cb, ctx);
leave:
xfree (keyref_buffer);
xfree (opt_algo);
return err;
}
static const char hlp_random[] =
"RANDOM <nbytes>\n"
"\n"
"Get NBYTES of random from the card and send them back as data.\n"
"This usually involves EEPROM write on the card and thus excessive\n"
"use of this command may destroy the card.\n"
"\n"
"Note, that this function may be even be used on a locked card.";
static gpg_error_t
cmd_random (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc;
size_t nbytes;
unsigned char *buffer;
if (!*line)
return set_error (GPG_ERR_ASS_PARAMETER,
"number of requested bytes missing");
nbytes = strtoul (line, NULL, 0);
if ((rc = open_card (ctrl)))
return rc;
- if (!ctrl->app_ctx)
+ if (!ctrl->card_ctx)
return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
buffer = xtrymalloc (nbytes);
if (!buffer)
return out_of_core ();
- rc = app_get_challenge (ctrl->app_ctx, ctrl, nbytes, buffer);
+ rc = app_get_challenge (ctrl->card_ctx, ctrl, nbytes, buffer);
if (!rc)
{
rc = assuan_send_data (ctx, buffer, nbytes);
xfree (buffer);
return rc; /* that is already an assuan error code */
}
xfree (buffer);
return rc;
}
static const char hlp_passwd[] =
"PASSWD [--reset] [--nullpin] [--clear] <chvno>\n"
"\n"
"Change the PIN or, if --reset is given, reset the retry counter of\n"
"the card holder verification vector CHVNO. The option --nullpin is\n"
"used for TCOS cards to set the initial PIN. The option --clear clears\n"
"the security status associated with the PIN so that the PIN needs to\n"
"be presented again. The format of CHVNO depends on the card application.";
static gpg_error_t
cmd_passwd (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc;
char *chvnostr;
unsigned int flags = 0;
if (has_option (line, "--reset"))
flags |= APP_CHANGE_FLAG_RESET;
if (has_option (line, "--nullpin"))
flags |= APP_CHANGE_FLAG_NULLPIN;
if (has_option (line, "--clear"))
flags |= APP_CHANGE_FLAG_CLEAR;
line = skip_options (line);
if (!*line)
return set_error (GPG_ERR_ASS_PARAMETER, "no CHV number given");
chvnostr = line;
while (*line && !spacep (line))
line++;
*line = 0;
/* Do not allow other flags aside of --clear. */
if ((flags & APP_CHANGE_FLAG_CLEAR) && (flags & ~APP_CHANGE_FLAG_CLEAR))
return set_error (GPG_ERR_UNSUPPORTED_OPERATION,
"--clear used with other options");
if ((rc = open_card (ctrl)))
return rc;
- if (!ctrl->app_ctx)
+ if (!ctrl->card_ctx)
return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
chvnostr = xtrystrdup (chvnostr);
if (!chvnostr)
return out_of_core ();
- rc = app_change_pin (ctrl->app_ctx, ctrl, chvnostr, flags, pin_cb, ctx);
+ rc = app_change_pin (ctrl->card_ctx, ctrl, chvnostr, flags, pin_cb, ctx);
if (rc)
log_error ("command passwd failed: %s\n", gpg_strerror (rc));
xfree (chvnostr);
return rc;
}
static const char hlp_checkpin[] =
"CHECKPIN <idstr>\n"
"\n"
"Perform a VERIFY operation without doing anything else. This may\n"
"be used to initialize a the PIN cache earlier to long lasting\n"
"operations. Its use is highly application dependent.\n"
"\n"
"For OpenPGP:\n"
"\n"
" Perform a simple verify operation for CHV1 and CHV2, so that\n"
" further operations won't ask for CHV2 and it is possible to do a\n"
" cheap check on the PIN: If there is something wrong with the PIN\n"
" entry system, only the regular CHV will get blocked and not the\n"
" dangerous CHV3. IDSTR is the usual card's serial number in hex\n"
" notation; an optional fingerprint part will get ignored. There\n"
" is however a special mode if the IDSTR is sffixed with the\n"
" literal string \"[CHV3]\": In this case the Admin PIN is checked\n"
" if and only if the retry counter is still at 3.\n"
"\n"
"For Netkey:\n"
"\n"
" Any of the valid PIN Ids may be used. These are the strings:\n"
"\n"
" PW1.CH - Global password 1\n"
" PW2.CH - Global password 2\n"
" PW1.CH.SIG - SigG password 1\n"
" PW2.CH.SIG - SigG password 2\n"
"\n"
" For a definitive list, see the implementation in app-nks.c.\n"
" Note that we call a PW2.* PIN a \"PUK\" despite that since TCOS\n"
" 3.0 they are technically alternative PINs used to mutally\n"
" unblock each other.";
static gpg_error_t
cmd_checkpin (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc;
char *idstr;
if ((rc = open_card (ctrl)))
return rc;
- if (!ctrl->app_ctx)
+ if (!ctrl->card_ctx)
return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
/* We have to use a copy of the key ID because the function may use
the pin_cb which in turn uses the assuan line buffer and thus
overwriting the original line with the keyid. */
idstr = xtrystrdup (line);
if (!idstr)
return out_of_core ();
- rc = app_check_pin (ctrl->app_ctx, ctrl, idstr, pin_cb, ctx);
+ rc = app_check_pin (ctrl->card_ctx, ctrl, idstr, pin_cb, ctx);
xfree (idstr);
if (rc)
log_error ("app_check_pin failed: %s\n", gpg_strerror (rc));
return rc;
}
static const char hlp_lock[] =
"LOCK [--wait]\n"
"\n"
"Grant exclusive card access to this session. Note that there is\n"
"no lock counter used and a second lock from the same session will\n"
"be ignored. A single unlock (or RESET) unlocks the session.\n"
"Return GPG_ERR_LOCKED if another session has locked the reader.\n"
"\n"
"If the option --wait is given the command will wait until a\n"
"lock has been released.";
static gpg_error_t
cmd_lock (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc = 0;
retry:
if (locked_session)
{
if (locked_session != ctrl->server_local)
rc = gpg_error (GPG_ERR_LOCKED);
}
else
locked_session = ctrl->server_local;
#ifdef USE_NPTH
if (rc && has_option (line, "--wait"))
{
rc = 0;
npth_sleep (1); /* Better implement an event mechanism. However,
for card operations this should be
sufficient. */
/* FIXME: Need to check that the connection is still alive.
This can be done by issuing status messages. */
goto retry;
}
#endif /*USE_NPTH*/
if (rc)
log_error ("cmd_lock failed: %s\n", gpg_strerror (rc));
return rc;
}
static const char hlp_unlock[] =
"UNLOCK\n"
"\n"
"Release exclusive card access.";
static gpg_error_t
cmd_unlock (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc = 0;
(void)line;
if (locked_session)
{
if (locked_session != ctrl->server_local)
rc = gpg_error (GPG_ERR_LOCKED);
else
locked_session = NULL;
}
else
rc = gpg_error (GPG_ERR_NOT_LOCKED);
if (rc)
log_error ("cmd_unlock failed: %s\n", gpg_strerror (rc));
return rc;
}
static const char hlp_getinfo[] =
"GETINFO <what>\n"
"\n"
"Multi purpose command to return certain information. \n"
"Supported values of WHAT are:\n"
"\n"
" version - Return the version of the program.\n"
" pid - Return the process id of the server.\n"
" socket_name - Return the name of the socket.\n"
" connections - Return number of active connections.\n"
" status - Return the status of the current reader (in the future,\n"
" may also return the status of all readers). The status\n"
" is a list of one-character flags. The following flags\n"
" are currently defined:\n"
" 'u' Usable card present.\n"
" 'r' Card removed. A reset is necessary.\n"
" These flags are exclusive.\n"
" reader_list - Return a list of detected card readers. Does\n"
" currently only work with the internal CCID driver.\n"
" deny_admin - Returns OK if admin commands are not allowed or\n"
" GPG_ERR_GENERAL if admin commands are allowed.\n"
" app_list - Return a list of supported applications. One\n"
" application per line, fields delimited by colons,\n"
" first field is the name.\n"
" card_list - Return a list of serial numbers of active cards,\n"
" using a status response.";
static gpg_error_t
cmd_getinfo (assuan_context_t ctx, char *line)
{
int rc = 0;
if (!strcmp (line, "version"))
{
const char *s = VERSION;
rc = assuan_send_data (ctx, s, strlen (s));
}
else if (!strcmp (line, "pid"))
{
char numbuf[50];
snprintf (numbuf, sizeof numbuf, "%lu", (unsigned long)getpid ());
rc = assuan_send_data (ctx, numbuf, strlen (numbuf));
}
else if (!strcmp (line, "socket_name"))
{
const char *s = scd_get_socket_name ();
if (s)
rc = assuan_send_data (ctx, s, strlen (s));
else
rc = gpg_error (GPG_ERR_NO_DATA);
}
else if (!strcmp (line, "connections"))
{
char numbuf[20];
snprintf (numbuf, sizeof numbuf, "%d", get_active_connection_count ());
rc = assuan_send_data (ctx, numbuf, strlen (numbuf));
}
else if (!strcmp (line, "status"))
{
ctrl_t ctrl = assuan_get_pointer (ctx);
char flag;
if (open_card (ctrl))
flag = 'r';
else
flag = 'u';
rc = assuan_send_data (ctx, &flag, 1);
}
else if (!strcmp (line, "reader_list"))
{
#ifdef HAVE_LIBUSB
char *s = ccid_get_reader_list ();
#else
char *s = NULL;
#endif
if (s)
rc = assuan_send_data (ctx, s, strlen (s));
else
rc = gpg_error (GPG_ERR_NO_DATA);
xfree (s);
}
else if (!strcmp (line, "deny_admin"))
rc = opt.allow_admin? gpg_error (GPG_ERR_GENERAL) : 0;
else if (!strcmp (line, "app_list"))
{
char *s = get_supported_applications ();
if (s)
rc = assuan_send_data (ctx, s, strlen (s));
else
rc = 0;
xfree (s);
}
else if (!strcmp (line, "card_list"))
{
ctrl_t ctrl = assuan_get_pointer (ctx);
- app_send_card_list (ctrl);
+ rc = app_send_card_list (ctrl);
}
else
rc = set_error (GPG_ERR_ASS_PARAMETER, "unknown value for WHAT");
return rc;
}
static const char hlp_restart[] =
"RESTART\n"
"\n"
"Restart the current connection; this is a kind of warm reset. It\n"
"deletes the context used by this connection but does not send a\n"
"RESET to the card. Thus the card itself won't get reset. \n"
"\n"
"This is used by gpg-agent to reuse a primary pipe connection and\n"
"may be used by clients to backup from a conflict in the serial\n"
"command; i.e. to select another application.";
static gpg_error_t
cmd_restart (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
- app_t app = ctrl->app_ctx;
+ card_t card = ctrl->card_ctx;
(void)line;
- if (app)
+ if (card)
{
- ctrl->app_ctx = NULL;
- release_application (app, 0);
+ ctrl->card_ctx = NULL;
+ card_unref (card);
}
if (locked_session && ctrl->server_local == locked_session)
{
locked_session = NULL;
log_info ("implicitly unlocking due to RESTART\n");
}
return 0;
}
static const char hlp_disconnect[] =
"DISCONNECT\n"
"\n"
"Disconnect the card if the backend supports a disconnect operation.";
static gpg_error_t
cmd_disconnect (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
(void)line;
- if (!ctrl->app_ctx)
+ if (!ctrl->card_ctx)
return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
- apdu_disconnect (ctrl->app_ctx->slot);
+ apdu_disconnect (ctrl->card_ctx->slot);
return 0;
}
static const char hlp_apdu[] =
"APDU [--[dump-]atr] [--more] [--exlen[=N]] [hexstring]\n"
"\n"
"Send an APDU to the current reader. This command bypasses the high\n"
"level functions and sends the data directly to the card. HEXSTRING\n"
"is expected to be a proper APDU. If HEXSTRING is not given no\n"
"commands are set to the card but the command will implictly check\n"
"whether the card is ready for use. \n"
"\n"
"Using the option \"--atr\" returns the ATR of the card as a status\n"
"message before any data like this:\n"
" S CARD-ATR 3BFA1300FF813180450031C173C00100009000B1\n"
"\n"
"Using the option --more handles the card status word MORE_DATA\n"
"(61xx) and concatenates all responses to one block.\n"
"\n"
"Using the option \"--exlen\" the returned APDU may use extended\n"
"length up to N bytes. If N is not given a default value is used\n"
"(currently 4096).";
static gpg_error_t
cmd_apdu (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
- app_t app;
+ card_t card;
int rc;
unsigned char *apdu;
size_t apdulen;
int with_atr;
int handle_more;
const char *s;
size_t exlen;
if (has_option (line, "--dump-atr"))
with_atr = 2;
else
with_atr = has_option (line, "--atr");
handle_more = has_option (line, "--more");
if ((s=has_option_name (line, "--exlen")))
{
if (*s == '=')
exlen = strtoul (s+1, NULL, 0);
else
exlen = 4096;
}
else
exlen = 0;
line = skip_options (line);
if ((rc = open_card (ctrl)))
return rc;
- app = ctrl->app_ctx;
- if (!app)
+ card = ctrl->card_ctx;
+ if (!card)
return gpg_error (GPG_ERR_CARD_NOT_PRESENT);
if (with_atr)
{
unsigned char *atr;
size_t atrlen;
char hexbuf[400];
- atr = apdu_get_atr (app->slot, &atrlen);
+ atr = apdu_get_atr (card->slot, &atrlen);
if (!atr || atrlen > sizeof hexbuf - 2 )
{
rc = gpg_error (GPG_ERR_INV_CARD);
goto leave;
}
if (with_atr == 2)
{
char *string, *p, *pend;
string = atr_dump (atr, atrlen);
if (string)
{
for (rc=0, p=string; !rc && (pend = strchr (p, '\n')); p = pend+1)
{
rc = assuan_send_data (ctx, p, pend - p + 1);
if (!rc)
rc = assuan_send_data (ctx, NULL, 0);
}
if (!rc && *p)
rc = assuan_send_data (ctx, p, strlen (p));
es_free (string);
if (rc)
goto leave;
}
}
else
{
bin2hex (atr, atrlen, hexbuf);
send_status_info (ctrl, "CARD-ATR", hexbuf, strlen (hexbuf), NULL, 0);
}
xfree (atr);
}
apdu = hex_to_buffer (line, &apdulen);
if (!apdu)
{
rc = gpg_error_from_syserror ();
goto leave;
}
if (apdulen)
{
unsigned char *result = NULL;
size_t resultlen;
- rc = apdu_send_direct (app->slot, exlen,
+ rc = apdu_send_direct (card->slot, exlen,
apdu, apdulen, handle_more,
NULL, &result, &resultlen);
if (rc)
log_error ("apdu_send_direct failed: %s\n", gpg_strerror (rc));
else
{
rc = assuan_send_data (ctx, result, resultlen);
xfree (result);
}
}
xfree (apdu);
leave:
return rc;
}
static const char hlp_killscd[] =
"KILLSCD\n"
"\n"
"Commit suicide.";
static gpg_error_t
cmd_killscd (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
(void)line;
ctrl->server_local->stopme = 1;
assuan_set_flag (ctx, ASSUAN_FORCE_CLOSE, 1);
return 0;
}
+static const char hlp_keyinfo[] =
+ "KEYINFO [--list] [--data] <keygrip>\n"
+ "\n"
+ "Return information about the key specified by the KEYGRIP. If the\n"
+ "key is not available GPG_ERR_NOT_FOUND is returned. If the option\n"
+ "--list is given the keygrip is ignored and information about all\n"
+ "available keys are returned. Unless --data is given, the\n"
+ "information is returned as a status line using the format:\n"
+ "\n"
+ " KEYINFO <keygrip> T <serialno> <idstr>\n"
+ "\n"
+ "KEYGRIP is the keygrip.\n"
+ "\n"
+ "SERIALNO is an ASCII string with the serial number of the\n"
+ " smartcard. If the serial number is not known a single\n"
+ " dash '-' is used instead.\n"
+ "\n"
+ "IDSTR is the IDSTR used to distinguish keys on a smartcard. If it\n"
+ " is not known a dash is used instead.\n"
+ "\n"
+ "More information may be added in the future.";
+static gpg_error_t
+cmd_keyinfo (assuan_context_t ctx, char *line)
+{
+ int list_mode;
+ int opt_data;
+ int action;
+ char *keygrip_str;
+ ctrl_t ctrl = assuan_get_pointer (ctx);
+ card_t card;
+
+ list_mode = has_option (line, "--list");
+ opt_data = has_option (line, "--data");
+ line = skip_options (line);
+
+ if (list_mode)
+ keygrip_str = NULL;
+ else
+ keygrip_str = line;
+
+ if (opt_data)
+ action = KEYGRIP_ACTION_SEND_DATA;
+ else
+ action = KEYGRIP_ACTION_WRITE_STATUS;
+
+ card = app_do_with_keygrip (ctrl, action, keygrip_str);
+
+ if (!list_mode && !card)
+ return gpg_error (GPG_ERR_NOT_FOUND);
+ return 0;
+}
+
+
+/* Send a keyinfo string as used by the KEYGRIP_ACTION_SEND_DATA. If
+ * DATA is true the string is emitted as a data line, else as a status
+ * line. */
+void
+send_keyinfo (ctrl_t ctrl, int data, const char *keygrip_str,
+ const char *serialno, const char *idstr)
+{
+ char *string;
+ assuan_context_t ctx = ctrl->server_local->assuan_ctx;
+
+ string = xtryasprintf ("%s T %s %s%s", keygrip_str,
+ serialno? serialno : "-",
+ idstr? idstr : "-",
+ data? "\n" : "");
+
+ if (!string)
+ return;
+
+ if (!data)
+ assuan_write_status (ctx, "KEYINFO", string);
+ else
+ assuan_send_data (ctx, string, strlen (string));
+
+ xfree (string);
+ return;
+}
+
+
/* Tell the assuan library about our commands */
static int
register_commands (assuan_context_t ctx)
{
static struct {
const char *name;
assuan_handler_t handler;
const char * const help;
} table[] = {
{ "SERIALNO", cmd_serialno, hlp_serialno },
{ "LEARN", cmd_learn, hlp_learn },
{ "READCERT", cmd_readcert, hlp_readcert },
{ "READKEY", cmd_readkey, hlp_readkey },
{ "SETDATA", cmd_setdata, hlp_setdata },
{ "PKSIGN", cmd_pksign, hlp_pksign },
{ "PKAUTH", cmd_pkauth, hlp_pkauth },
{ "PKDECRYPT", cmd_pkdecrypt,hlp_pkdecrypt },
{ "INPUT", NULL },
{ "OUTPUT", NULL },
{ "GETATTR", cmd_getattr, hlp_getattr },
{ "SETATTR", cmd_setattr, hlp_setattr },
{ "WRITECERT", cmd_writecert,hlp_writecert },
{ "WRITEKEY", cmd_writekey, hlp_writekey },
{ "GENKEY", cmd_genkey, hlp_genkey },
{ "RANDOM", cmd_random, hlp_random },
{ "PASSWD", cmd_passwd, hlp_passwd },
{ "CHECKPIN", cmd_checkpin, hlp_checkpin },
{ "LOCK", cmd_lock, hlp_lock },
{ "UNLOCK", cmd_unlock, hlp_unlock },
{ "GETINFO", cmd_getinfo, hlp_getinfo },
{ "RESTART", cmd_restart, hlp_restart },
{ "DISCONNECT", cmd_disconnect,hlp_disconnect },
{ "APDU", cmd_apdu, hlp_apdu },
{ "KILLSCD", cmd_killscd, hlp_killscd },
+ { "KEYINFO", cmd_keyinfo, hlp_keyinfo },
{ NULL }
};
int i, rc;
for (i=0; table[i].name; i++)
{
rc = assuan_register_command (ctx, table[i].name, table[i].handler,
table[i].help);
if (rc)
return rc;
}
assuan_set_hello_line (ctx, "GNU Privacy Guard's Smartcard server ready");
assuan_register_reset_notify (ctx, reset_notify);
assuan_register_option_handler (ctx, option_handler);
return 0;
}
/* Startup the server. If FD is given as -1 this is simple pipe
server, otherwise it is a regular server. Returns true if there
are no more active asessions. */
int
scd_command_handler (ctrl_t ctrl, int fd)
{
int rc;
assuan_context_t ctx = NULL;
int stopme;
rc = assuan_new (&ctx);
if (rc)
{
log_error ("failed to allocate assuan context: %s\n",
gpg_strerror (rc));
scd_exit (2);
}
if (fd == -1)
{
assuan_fd_t filedes[2];
filedes[0] = assuan_fdopen (0);
filedes[1] = assuan_fdopen (1);
rc = assuan_init_pipe_server (ctx, filedes);
}
else
{
rc = assuan_init_socket_server (ctx, INT2FD(fd),
ASSUAN_SOCKET_SERVER_ACCEPTED);
}
if (rc)
{
log_error ("failed to initialize the server: %s\n",
gpg_strerror(rc));
scd_exit (2);
}
rc = register_commands (ctx);
if (rc)
{
log_error ("failed to register commands with Assuan: %s\n",
gpg_strerror(rc));
scd_exit (2);
}
assuan_set_pointer (ctx, ctrl);
/* Allocate and initialize the server object. Put it into the list
of active sessions. */
ctrl->server_local = xcalloc (1, sizeof *ctrl->server_local);
ctrl->server_local->next_session = session_list;
session_list = ctrl->server_local;
ctrl->server_local->ctrl_backlink = ctrl;
ctrl->server_local->assuan_ctx = ctx;
/* Command processing loop. */
for (;;)
{
rc = assuan_accept (ctx);
if (rc == -1)
{
break;
}
else if (rc)
{
log_info ("Assuan accept problem: %s\n", gpg_strerror (rc));
break;
}
rc = assuan_process (ctx);
if (rc)
{
log_info ("Assuan processing failed: %s\n", gpg_strerror (rc));
continue;
}
}
/* Cleanup. We don't send an explicit reset to the card. */
do_reset (ctrl, 0);
/* Release the server object. */
if (session_list == ctrl->server_local)
session_list = ctrl->server_local->next_session;
else
{
struct server_local_s *sl;
for (sl=session_list; sl->next_session; sl = sl->next_session)
if (sl->next_session == ctrl->server_local)
break;
if (!sl->next_session)
BUG ();
sl->next_session = ctrl->server_local->next_session;
}
stopme = ctrl->server_local->stopme;
xfree (ctrl->server_local);
ctrl->server_local = NULL;
/* Release the Assuan context. */
assuan_release (ctx);
if (stopme)
scd_exit (0);
/* If there are no more sessions return true. */
return !session_list;
}
+/* Clear the current application info for CARD from all sessions.
+ * This is used while deallocating a card. */
+void
+scd_clear_current_app (card_t card)
+{
+ struct server_local_s *sl;
+
+ for (sl=session_list; sl; sl = sl->next_session)
+ {
+ if (sl->ctrl_backlink->card_ctx == card)
+ sl->ctrl_backlink->current_apptype = APPTYPE_NONE;
+ }
+}
+
/* Send a line with status information via assuan and escape all given
buffers. The variable elements are pairs of (char *, size_t),
terminated with a (NULL, 0). */
void
send_status_info (ctrl_t ctrl, const char *keyword, ...)
{
va_list arg_ptr;
const unsigned char *value;
size_t valuelen;
char buf[950], *p;
size_t n;
assuan_context_t ctx = ctrl->server_local->assuan_ctx;
va_start (arg_ptr, keyword);
p = buf;
n = 0;
while ( (value = va_arg (arg_ptr, const unsigned char *))
&& n < DIM (buf)-2 )
{
valuelen = va_arg (arg_ptr, size_t);
if (!valuelen)
continue; /* empty buffer */
if (n)
{
*p++ = ' ';
n++;
}
for ( ; valuelen && n < DIM (buf)-2; n++, valuelen--, value++)
{
if (*value == '+' || *value == '\"' || *value == '%'
|| *value < ' ')
{
sprintf (p, "%%%02X", *value);
p += 3;
n += 2;
}
else if (*value == ' ')
*p++ = '+';
else
*p++ = *value;
}
}
*p = 0;
assuan_write_status (ctx, keyword, buf);
va_end (arg_ptr);
}
/* Send a ready formatted status line via assuan. */
void
send_status_direct (ctrl_t ctrl, const char *keyword, const char *args)
{
assuan_context_t ctx = ctrl->server_local->assuan_ctx;
if (strchr (args, '\n'))
log_error ("error: LF detected in status line - not sending\n");
else
assuan_write_status (ctx, keyword, args);
}
/* This status functions expects a printf style format string. No
* filtering of the data is done instead the orintf formatted data is
* send using assuan_send_status. */
gpg_error_t
send_status_printf (ctrl_t ctrl, const char *keyword, const char *format, ...)
{
gpg_error_t err;
va_list arg_ptr;
assuan_context_t ctx;
if (!ctrl || !ctrl->server_local || !(ctx = ctrl->server_local->assuan_ctx))
return 0;
va_start (arg_ptr, format);
err = vprint_assuan_status (ctx, keyword, format, arg_ptr);
va_end (arg_ptr);
return err;
}
void
popup_prompt (void *opaque, int on)
{
ctrl_t ctrl = opaque;
if (ctrl)
{
assuan_context_t ctx = ctrl->server_local->assuan_ctx;
if (ctx)
{
const char *cmd;
gpg_error_t err;
unsigned char *value;
size_t valuelen;
if (on)
cmd = "POPUPPINPADPROMPT --ack";
else
cmd = "DISMISSPINPADPROMPT";
err = assuan_inquire (ctx, cmd, &value, &valuelen, 100);
if (!err)
xfree (value);
}
}
}
-/* Helper to send the clients a status change notification. */
+/* Helper to send the clients a status change notification. Note that
+ * this function assumes that APP is already locked. */
void
-send_client_notifications (app_t app, int removal)
+send_client_notifications (card_t card, int removal)
{
struct {
pid_t pid;
#ifdef HAVE_W32_SYSTEM
HANDLE handle;
#else
int signo;
#endif
} killed[50];
int killidx = 0;
int kidx;
struct server_local_s *sl;
for (sl=session_list; sl; sl = sl->next_session)
- if (sl->ctrl_backlink && sl->ctrl_backlink->app_ctx == app)
+ if (sl->ctrl_backlink && sl->ctrl_backlink->card_ctx == card)
{
pid_t pid;
#ifdef HAVE_W32_SYSTEM
HANDLE handle;
#else
int signo;
#endif
if (removal)
{
- sl->ctrl_backlink->app_ctx = NULL;
+ sl->ctrl_backlink->card_ctx = NULL;
sl->card_removed = 1;
- release_application (app, 1);
+ card_unref_locked (card);
}
if (!sl->event_signal || !sl->assuan_ctx)
continue;
pid = assuan_get_pid (sl->assuan_ctx);
#ifdef HAVE_W32_SYSTEM
handle = sl->event_signal;
for (kidx=0; kidx < killidx; kidx++)
if (killed[kidx].pid == pid
&& killed[kidx].handle == handle)
break;
if (kidx < killidx)
log_info ("event %p (%p) already triggered for client %d\n",
sl->event_signal, handle, (int)pid);
else
{
log_info ("triggering event %p (%p) for client %d\n",
sl->event_signal, handle, (int)pid);
if (!SetEvent (handle))
log_error ("SetEvent(%p) failed: %s\n",
sl->event_signal, w32_strerror (-1));
if (killidx < DIM (killed))
{
killed[killidx].pid = pid;
killed[killidx].handle = handle;
killidx++;
}
}
#else /*!HAVE_W32_SYSTEM*/
signo = sl->event_signal;
if (pid != (pid_t)(-1) && pid && signo > 0)
{
for (kidx=0; kidx < killidx; kidx++)
if (killed[kidx].pid == pid
&& killed[kidx].signo == signo)
break;
if (kidx < killidx)
log_info ("signal %d already sent to client %d\n",
signo, (int)pid);
else
{
log_info ("sending signal %d to client %d\n",
signo, (int)pid);
kill (pid, signo);
if (killidx < DIM (killed))
{
killed[killidx].pid = pid;
killed[killidx].signo = signo;
killidx++;
}
}
}
#endif /*!HAVE_W32_SYSTEM*/
}
}
diff --git a/scd/iso7816.c b/scd/iso7816.c
index d9f3336c7..954aa3d4a 100644
--- a/scd/iso7816.c
+++ b/scd/iso7816.c
@@ -1,974 +1,963 @@
/* iso7816.c - ISO 7816 commands
* Copyright (C) 2003, 2004, 2008, 2009 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
-#if defined(GNUPG_SCD_MAIN_HEADER)
-#include GNUPG_SCD_MAIN_HEADER
-#elif GNUPG_MAJOR_VERSION == 1
-/* This is used with GnuPG version < 1.9. The code has been source
- copied from the current GnuPG >= 1.9 and is maintained over
- there. */
-#include "options.h"
-#include "errors.h"
-#include "memory.h"
-#include "../common/util.h"
-#include "../common/i18n.h"
-#else /* GNUPG_MAJOR_VERSION != 1 */
-#include "scdaemon.h"
-#endif /* GNUPG_MAJOR_VERSION != 1 */
+#if defined(GNUPG_MAJOR_VERSION)
+# include "scdaemon.h"
+#endif /*GNUPG_MAJOR_VERSION*/
#include "iso7816.h"
#include "apdu.h"
#define CMD_SELECT_FILE 0xA4
#define CMD_VERIFY ISO7816_VERIFY
#define CMD_CHANGE_REFERENCE_DATA ISO7816_CHANGE_REFERENCE_DATA
#define CMD_RESET_RETRY_COUNTER ISO7816_RESET_RETRY_COUNTER
#define CMD_GET_DATA 0xCA
#define CMD_PUT_DATA 0xDA
#define CMD_MSE 0x22
#define CMD_PSO 0x2A
#define CMD_GENERAL_AUTHENTICATE 0x87
#define CMD_INTERNAL_AUTHENTICATE 0x88
#define CMD_GENERATE_KEYPAIR 0x47
#define CMD_GET_CHALLENGE 0x84
#define CMD_READ_BINARY 0xB0
#define CMD_READ_RECORD 0xB2
static gpg_error_t
map_sw (int sw)
{
gpg_err_code_t ec;
switch (sw)
{
case SW_EEPROM_FAILURE: ec = GPG_ERR_HARDWARE; break;
case SW_TERM_STATE: ec = GPG_ERR_OBJ_TERM_STATE; break;
case SW_WRONG_LENGTH: ec = GPG_ERR_INV_VALUE; break;
case SW_ACK_TIMEOUT: ec = GPG_ERR_TIMEOUT; break;
case SW_SM_NOT_SUP: ec = GPG_ERR_NOT_SUPPORTED; break;
case SW_CC_NOT_SUP: ec = GPG_ERR_NOT_SUPPORTED; break;
case SW_CHV_WRONG: ec = GPG_ERR_BAD_PIN; break;
case SW_CHV_BLOCKED: ec = GPG_ERR_PIN_BLOCKED; break;
case SW_USE_CONDITIONS: ec = GPG_ERR_USE_CONDITIONS; break;
case SW_NOT_SUPPORTED: ec = GPG_ERR_NOT_SUPPORTED; break;
case SW_BAD_PARAMETER: ec = GPG_ERR_INV_VALUE; break;
case SW_FILE_NOT_FOUND: ec = GPG_ERR_ENOENT; break;
case SW_RECORD_NOT_FOUND:ec= GPG_ERR_NOT_FOUND; break;
case SW_REF_NOT_FOUND: ec = GPG_ERR_NO_OBJ; break;
case SW_BAD_P0_P1: ec = GPG_ERR_INV_VALUE; break;
case SW_EXACT_LENGTH: ec = GPG_ERR_INV_VALUE; break;
case SW_INS_NOT_SUP: ec = GPG_ERR_CARD; break;
case SW_CLA_NOT_SUP: ec = GPG_ERR_CARD; break;
case SW_SUCCESS: ec = 0; break;
case SW_HOST_OUT_OF_CORE: ec = GPG_ERR_ENOMEM; break;
case SW_HOST_INV_VALUE: ec = GPG_ERR_INV_VALUE; break;
case SW_HOST_INCOMPLETE_CARD_RESPONSE: ec = GPG_ERR_CARD; break;
case SW_HOST_NOT_SUPPORTED: ec = GPG_ERR_NOT_SUPPORTED; break;
case SW_HOST_LOCKING_FAILED: ec = GPG_ERR_BUG; break;
case SW_HOST_BUSY: ec = GPG_ERR_EBUSY; break;
case SW_HOST_NO_CARD: ec = GPG_ERR_CARD_NOT_PRESENT; break;
case SW_HOST_CARD_INACTIVE: ec = GPG_ERR_CARD_RESET; break;
case SW_HOST_CARD_IO_ERROR: ec = GPG_ERR_EIO; break;
case SW_HOST_GENERAL_ERROR: ec = GPG_ERR_GENERAL; break;
case SW_HOST_NO_READER: ec = GPG_ERR_ENODEV; break;
case SW_HOST_ABORTED: ec = GPG_ERR_INV_RESPONSE; break;
case SW_HOST_NO_PINPAD: ec = GPG_ERR_NOT_SUPPORTED; break;
case SW_HOST_CANCELLED: ec = GPG_ERR_CANCELED; break;
default:
if ((sw & 0x010000))
ec = GPG_ERR_GENERAL; /* Should not happen. */
else if ((sw & 0xff00) == SW_MORE_DATA)
ec = 0; /* This should actually never been seen here. */
else
ec = GPG_ERR_CARD;
}
return gpg_error (ec);
}
/* Map a status word from the APDU layer to a gpg-error code. */
gpg_error_t
iso7816_map_sw (int sw)
{
/* All APDU functions should return 0x9000 on success but for
historical reasons of the implementation some return 0 to
indicate success. We allow for that here. */
return sw? map_sw (sw) : 0;
}
/* This function is specialized version of the SELECT FILE command.
SLOT is the card and reader as created for example by
apdu_open_reader (), AID is a buffer of size AIDLEN holding the
requested application ID. The function can't be used to enumerate
AIDs and won't return the AID on success. The return value is 0
for okay or a GPG error code. Note that ISO error codes are
internally mapped. Bit 0 of FLAGS should be set if the card does
not understand P2=0xC0. */
gpg_error_t
iso7816_select_application (int slot, const char *aid, size_t aidlen,
unsigned int flags)
{
int sw;
sw = apdu_send_simple (slot, 0, 0x00, CMD_SELECT_FILE, 4,
(flags&1)? 0 :0x0c, aidlen, aid);
return map_sw (sw);
}
/* This is the same as iso7816_select_application but may return data
* at RESULT,RESULTLEN). */
gpg_error_t
iso7816_select_application_ext (int slot, const char *aid, size_t aidlen,
unsigned int flags,
unsigned char **result, size_t *resultlen)
{
int sw;
sw = apdu_send (slot, 0, 0x00, CMD_SELECT_FILE, 4,
(flags&1)? 0:0x0c, aidlen, aid,
result, resultlen);
return map_sw (sw);
}
gpg_error_t
iso7816_select_file (int slot, int tag, int is_dir)
{
int sw, p0, p1;
unsigned char tagbuf[2];
tagbuf[0] = (tag >> 8) & 0xff;
tagbuf[1] = tag & 0xff;
p0 = (tag == 0x3F00)? 0: is_dir? 1:2;
p1 = 0x0c; /* No FC return. */
sw = apdu_send_simple (slot, 0, 0x00, CMD_SELECT_FILE,
p0, p1, 2, (char*)tagbuf );
return map_sw (sw);
}
/* Do a select file command with a direct path. */
gpg_error_t
iso7816_select_path (int slot, const unsigned short *path, size_t pathlen)
{
int sw, p0, p1;
unsigned char buffer[100];
int buflen;
if (pathlen/2 >= sizeof buffer)
return gpg_error (GPG_ERR_TOO_LARGE);
for (buflen = 0; pathlen; pathlen--, path++)
{
buffer[buflen++] = (*path >> 8);
buffer[buflen++] = *path;
}
p0 = 0x08;
p1 = 0x0c; /* No FC return. */
sw = apdu_send_simple (slot, 0, 0x00, CMD_SELECT_FILE,
p0, p1, buflen, (char*)buffer );
return map_sw (sw);
}
/* This is a private command currently only working for TCOS cards. */
gpg_error_t
iso7816_list_directory (int slot, int list_dirs,
unsigned char **result, size_t *resultlen)
{
int sw;
if (!result || !resultlen)
return gpg_error (GPG_ERR_INV_VALUE);
*result = NULL;
*resultlen = 0;
sw = apdu_send (slot, 0, 0x80, 0xAA, list_dirs? 1:2, 0, -1, NULL,
result, resultlen);
if (sw != SW_SUCCESS)
{
/* Make sure that pending buffers are released. */
xfree (*result);
*result = NULL;
*resultlen = 0;
}
return map_sw (sw);
}
/* Wrapper around apdu_send. RESULT can be NULL if no result is
* expected. In addition to an gpg-error return code the actual
* status word is stored at R_SW unless that is NULL. */
gpg_error_t
iso7816_send_apdu (int slot, int extended_mode,
int class, int ins, int p0, int p1,
int lc, const void *data,
unsigned int *r_sw,
unsigned char **result, size_t *resultlen)
{
int sw;
if (result)
{
*result = NULL;
*resultlen = 0;
}
sw = apdu_send (slot, extended_mode, class, ins, p0, p1, lc, data,
result, resultlen);
if (sw != SW_SUCCESS && result)
{
/* Make sure that pending buffers are released. */
xfree (*result);
*result = NULL;
*resultlen = 0;
}
if (r_sw)
*r_sw = sw;
return map_sw (sw);
}
/* This function sends an already formatted APDU to the card. With
HANDLE_MORE set to true a MORE DATA status will be handled
internally. The return value is a gpg error code (i.e. a mapped
status word). This is basically the same as apdu_send_direct but
it maps the status word and does not return it in the result
buffer. However, it R_SW is not NULL the status word is stored
R_SW for closer inspection. */
gpg_error_t
iso7816_apdu_direct (int slot, const void *apdudata, size_t apdudatalen,
int handle_more, unsigned int *r_sw,
unsigned char **result, size_t *resultlen)
{
int sw, sw2;
if (result)
{
*result = NULL;
*resultlen = 0;
}
sw = apdu_send_direct (slot, 0, apdudata, apdudatalen, handle_more,
&sw2, result, resultlen);
if (!sw)
{
if (!result)
sw = sw2;
else if (*resultlen < 2)
sw = SW_HOST_GENERAL_ERROR;
else
{
sw = ((*result)[*resultlen-2] << 8) | (*result)[*resultlen-1];
(*resultlen)--;
(*resultlen)--;
}
}
if (sw != SW_SUCCESS && result)
{
/* Make sure that pending buffers are released. */
xfree (*result);
*result = NULL;
*resultlen = 0;
}
if (r_sw)
*r_sw = sw;
return map_sw (sw);
}
/* Check whether the reader supports the ISO command code COMMAND on
the pinpad. Returns 0 on success. */
gpg_error_t
iso7816_check_pinpad (int slot, int command, pininfo_t *pininfo)
{
int sw;
sw = apdu_check_pinpad (slot, command, pininfo);
return iso7816_map_sw (sw);
}
/* Perform a VERIFY command on SLOT using the card holder verification
vector CHVNO. With PININFO non-NULL the pinpad of the reader will
be used. Returns 0 on success. */
gpg_error_t
iso7816_verify_kp (int slot, int chvno, pininfo_t *pininfo)
{
int sw;
sw = apdu_pinpad_verify (slot, 0x00, CMD_VERIFY, 0, chvno, pininfo);
return map_sw (sw);
}
/* Perform a VERIFY command on SLOT using the card holder verification
vector CHVNO with a CHV of length CHVLEN. Returns 0 on success. */
gpg_error_t
iso7816_verify (int slot, int chvno, const char *chv, size_t chvlen)
{
int sw;
sw = apdu_send_simple (slot, 0, 0x00, CMD_VERIFY, 0, chvno, chvlen, chv);
return map_sw (sw);
}
/* Perform a CHANGE_REFERENCE_DATA command on SLOT for the card holder
verification vector CHVNO. With PININFO non-NULL the pinpad of the
reader will be used. If IS_EXCHANGE is 0, a "change reference
data" is done, otherwise an "exchange reference data". */
gpg_error_t
iso7816_change_reference_data_kp (int slot, int chvno, int is_exchange,
pininfo_t *pininfo)
{
int sw;
sw = apdu_pinpad_modify (slot, 0x00, CMD_CHANGE_REFERENCE_DATA,
is_exchange ? 1 : 0, chvno, pininfo);
return map_sw (sw);
}
/* Perform a CHANGE_REFERENCE_DATA command on SLOT for the card holder
verification vector CHVNO. If the OLDCHV is NULL (and OLDCHVLEN
0), a "change reference data" is done, otherwise an "exchange
reference data". The new reference data is expected in NEWCHV of
length NEWCHVLEN. */
gpg_error_t
iso7816_change_reference_data (int slot, int chvno,
const char *oldchv, size_t oldchvlen,
const char *newchv, size_t newchvlen)
{
int sw;
char *buf;
if ((!oldchv && oldchvlen)
|| (oldchv && !oldchvlen)
|| !newchv || !newchvlen )
return gpg_error (GPG_ERR_INV_VALUE);
buf = xtrymalloc (oldchvlen + newchvlen);
if (!buf)
return gpg_error (gpg_err_code_from_errno (errno));
if (oldchvlen)
memcpy (buf, oldchv, oldchvlen);
memcpy (buf+oldchvlen, newchv, newchvlen);
sw = apdu_send_simple (slot, 0, 0x00, CMD_CHANGE_REFERENCE_DATA,
oldchvlen? 0 : 1, chvno, oldchvlen+newchvlen, buf);
wipememory (buf, oldchvlen+newchvlen);
xfree (buf);
return map_sw (sw);
}
gpg_error_t
iso7816_reset_retry_counter_with_rc (int slot, int chvno,
const char *data, size_t datalen)
{
int sw;
if (!data || !datalen )
return gpg_error (GPG_ERR_INV_VALUE);
sw = apdu_send_simple (slot, 0, 0x00, CMD_RESET_RETRY_COUNTER,
0, chvno, datalen, data);
return map_sw (sw);
}
gpg_error_t
iso7816_reset_retry_counter (int slot, int chvno,
const char *newchv, size_t newchvlen)
{
int sw;
sw = apdu_send_simple (slot, 0, 0x00, CMD_RESET_RETRY_COUNTER,
2, chvno, newchvlen, newchv);
return map_sw (sw);
}
/* Perform a GET DATA command requesting TAG and storing the result in
a newly allocated buffer at the address passed by RESULT. Return
the length of this data at the address of RESULTLEN. */
gpg_error_t
iso7816_get_data (int slot, int extended_mode, int tag,
unsigned char **result, size_t *resultlen)
{
int sw;
int le;
if (!result || !resultlen)
return gpg_error (GPG_ERR_INV_VALUE);
*result = NULL;
*resultlen = 0;
if (extended_mode > 0 && extended_mode < 256)
le = 65534; /* Not 65535 in case it is used as some special flag. */
else if (extended_mode > 0)
le = extended_mode;
else
le = 256;
sw = apdu_send_le (slot, extended_mode, 0x00, CMD_GET_DATA,
((tag >> 8) & 0xff), (tag & 0xff), -1, NULL, le,
result, resultlen);
if (sw != SW_SUCCESS)
{
/* Make sure that pending buffers are released. */
xfree (*result);
*result = NULL;
*resultlen = 0;
return map_sw (sw);
}
return 0;
}
/* Perform a GET DATA command requesting TAG and storing the result in
* a newly allocated buffer at the address passed by RESULT. Return
* the length of this data at the address of RESULTLEN. This variant
* is needed for long (3 octet) tags. */
gpg_error_t
iso7816_get_data_odd (int slot, int extended_mode, unsigned int tag,
unsigned char **result, size_t *resultlen)
{
int sw;
int le;
int datalen;
unsigned char data[5];
if (!result || !resultlen)
return gpg_error (GPG_ERR_INV_VALUE);
*result = NULL;
*resultlen = 0;
if (extended_mode > 0 && extended_mode < 256)
le = 65534; /* Not 65535 in case it is used as some special flag. */
else if (extended_mode > 0)
le = extended_mode;
else
le = 256;
data[0] = 0x5c;
if (tag <= 0xff)
{
data[1] = 1;
data[2] = tag;
datalen = 3;
}
else if (tag <= 0xffff)
{
data[1] = 2;
data[2] = (tag >> 8);
data[3] = tag;
datalen = 4;
}
else
{
data[1] = 3;
data[2] = (tag >> 16);
data[3] = (tag >> 8);
data[4] = tag;
datalen = 5;
}
sw = apdu_send_le (slot, extended_mode, 0x00, CMD_GET_DATA + 1,
0x3f, 0xff, datalen, data, le,
result, resultlen);
if (sw != SW_SUCCESS)
{
/* Make sure that pending buffers are released. */
xfree (*result);
*result = NULL;
*resultlen = 0;
return map_sw (sw);
}
return 0;
}
/* Perform a PUT DATA command on card in SLOT. Write DATA of length
DATALEN to TAG. EXTENDED_MODE controls whether extended length
headers or command chaining is used instead of single length
bytes. */
gpg_error_t
iso7816_put_data (int slot, int extended_mode, int tag,
const void *data, size_t datalen)
{
int sw;
sw = apdu_send_simple (slot, extended_mode, 0x00, CMD_PUT_DATA,
((tag >> 8) & 0xff), (tag & 0xff),
datalen, (const char*)data);
return map_sw (sw);
}
/* Same as iso7816_put_data but uses an odd instruction byte. */
gpg_error_t
iso7816_put_data_odd (int slot, int extended_mode, int tag,
const void *data, size_t datalen)
{
int sw;
sw = apdu_send_simple (slot, extended_mode, 0x00, CMD_PUT_DATA+1,
((tag >> 8) & 0xff), (tag & 0xff),
datalen, (const char*)data);
return map_sw (sw);
}
/* Manage Security Environment. This is a weird operation and there
is no easy abstraction for it. Furthermore, some card seem to have
a different interpretation of 7816-8 and thus we resort to let the
caller decide what to do. */
gpg_error_t
iso7816_manage_security_env (int slot, int p1, int p2,
const unsigned char *data, size_t datalen)
{
int sw;
if (p1 < 0 || p1 > 255 || p2 < 0 || p2 > 255 )
return gpg_error (GPG_ERR_INV_VALUE);
sw = apdu_send_simple (slot, 0, 0x00, CMD_MSE, p1, p2,
data? datalen : -1, (const char*)data);
return map_sw (sw);
}
/* Perform the security operation COMPUTE DIGITAL SIGANTURE. On
success 0 is returned and the data is available in a newly
allocated buffer stored at RESULT with its length stored at
RESULTLEN. For LE see do_generate_keypair. */
gpg_error_t
iso7816_compute_ds (int slot, int extended_mode,
const unsigned char *data, size_t datalen, int le,
unsigned char **result, size_t *resultlen)
{
int sw;
if (!data || !datalen || !result || !resultlen)
return gpg_error (GPG_ERR_INV_VALUE);
*result = NULL;
*resultlen = 0;
if (!extended_mode)
le = 256; /* Ignore provided Le and use what apdu_send uses. */
else if (le >= 0 && le < 256)
le = 256;
sw = apdu_send_le (slot, extended_mode,
0x00, CMD_PSO, 0x9E, 0x9A,
datalen, (const char*)data,
le,
result, resultlen);
if (sw != SW_SUCCESS)
{
/* Make sure that pending buffers are released. */
xfree (*result);
*result = NULL;
*resultlen = 0;
return map_sw (sw);
}
return 0;
}
/* Perform the security operation DECIPHER. PADIND is the padding
indicator to be used. It should be 0 if no padding is required, a
value of -1 suppresses the padding byte. On success 0 is returned
and the plaintext is available in a newly allocated buffer stored
at RESULT with its length stored at RESULTLEN. For LE see
do_generate_keypair. */
gpg_error_t
iso7816_decipher (int slot, int extended_mode,
const unsigned char *data, size_t datalen, int le,
int padind, unsigned char **result, size_t *resultlen)
{
int sw;
unsigned char *buf;
if (!data || !datalen || !result || !resultlen)
return gpg_error (GPG_ERR_INV_VALUE);
*result = NULL;
*resultlen = 0;
if (!extended_mode)
le = 256; /* Ignore provided Le and use what apdu_send uses. */
else if (le >= 0 && le < 256)
le = 256;
if (padind >= 0)
{
/* We need to prepend the padding indicator. */
buf = xtrymalloc (datalen + 1);
if (!buf)
return gpg_error (gpg_err_code_from_errno (errno));
*buf = padind; /* Padding indicator. */
memcpy (buf+1, data, datalen);
sw = apdu_send_le (slot, extended_mode,
0x00, CMD_PSO, 0x80, 0x86,
datalen+1, (char*)buf, le,
result, resultlen);
xfree (buf);
}
else
{
sw = apdu_send_le (slot, extended_mode,
0x00, CMD_PSO, 0x80, 0x86,
datalen, (const char *)data, le,
result, resultlen);
}
if (sw != SW_SUCCESS)
{
/* Make sure that pending buffers are released. */
xfree (*result);
*result = NULL;
*resultlen = 0;
return map_sw (sw);
}
return 0;
}
/* For LE see do_generate_keypair. */
gpg_error_t
iso7816_internal_authenticate (int slot, int extended_mode,
const unsigned char *data, size_t datalen,
int le,
unsigned char **result, size_t *resultlen)
{
int sw;
if (!data || !datalen || !result || !resultlen)
return gpg_error (GPG_ERR_INV_VALUE);
*result = NULL;
*resultlen = 0;
if (!extended_mode)
le = 256; /* Ignore provided Le and use what apdu_send uses. */
else if (le >= 0 && le < 256)
le = 256;
sw = apdu_send_le (slot, extended_mode,
0x00, CMD_INTERNAL_AUTHENTICATE, 0, 0,
datalen, (const char*)data,
le,
result, resultlen);
if (sw != SW_SUCCESS)
{
/* Make sure that pending buffers are released. */
xfree (*result);
*result = NULL;
*resultlen = 0;
return map_sw (sw);
}
return 0;
}
/* For LE see do_generate_keypair. */
gpg_error_t
iso7816_general_authenticate (int slot, int extended_mode,
int algoref, int keyref,
const unsigned char *data, size_t datalen,
int le,
unsigned char **result, size_t *resultlen)
{
int sw;
if (!data || !datalen || !result || !resultlen)
return gpg_error (GPG_ERR_INV_VALUE);
*result = NULL;
*resultlen = 0;
if (!extended_mode)
le = 256; /* Ignore provided Le and use what apdu_send uses. */
else if (le >= 0 && le < 256)
le = 256;
sw = apdu_send_le (slot, extended_mode,
0x00, CMD_GENERAL_AUTHENTICATE, algoref, keyref,
datalen, (const char*)data,
le,
result, resultlen);
if (sw != SW_SUCCESS)
{
/* Make sure that pending buffers are released. */
xfree (*result);
*result = NULL;
*resultlen = 0;
return map_sw (sw);
}
return 0;
}
/* LE is the expected return length. This is usually 0 except if
extended length mode is used and more than 256 byte will be
returned. In that case a value of -1 uses a large default
(e.g. 4096 bytes), a value larger 256 used that value. */
static gpg_error_t
do_generate_keypair (int slot, int extended_mode, int p1, int p2,
const char *data, size_t datalen, int le,
unsigned char **result, size_t *resultlen)
{
int sw;
if (!data || !datalen || !result || !resultlen)
return gpg_error (GPG_ERR_INV_VALUE);
*result = NULL;
*resultlen = 0;
sw = apdu_send_le (slot, extended_mode,
0x00, CMD_GENERATE_KEYPAIR, p1, p2,
datalen, data,
le >= 0 && le < 256? 256:le,
result, resultlen);
if (sw != SW_SUCCESS)
{
/* Make sure that pending buffers are released. */
xfree (*result);
*result = NULL;
*resultlen = 0;
return map_sw (sw);
}
return 0;
}
gpg_error_t
iso7816_generate_keypair (int slot, int extended_mode, int p1, int p2,
const char *data, size_t datalen,
int le,
unsigned char **result, size_t *resultlen)
{
return do_generate_keypair (slot, extended_mode, p1, p2,
data, datalen, le, result, resultlen);
}
gpg_error_t
iso7816_read_public_key (int slot, int extended_mode,
const char *data, size_t datalen,
int le,
unsigned char **result, size_t *resultlen)
{
return do_generate_keypair (slot, extended_mode, 0x81, 0,
data, datalen, le, result, resultlen);
}
gpg_error_t
iso7816_get_challenge (int slot, int length, unsigned char *buffer)
{
int sw;
unsigned char *result;
size_t resultlen, n;
if (!buffer || length < 1)
return gpg_error (GPG_ERR_INV_VALUE);
do
{
result = NULL;
n = length > 254? 254 : length;
sw = apdu_send_le (slot, 0,
0x00, CMD_GET_CHALLENGE, 0, 0, -1, NULL, n,
&result, &resultlen);
if (sw != SW_SUCCESS)
{
/* Make sure that pending buffers are released. */
xfree (result);
return map_sw (sw);
}
if (resultlen > n)
resultlen = n;
memcpy (buffer, result, resultlen);
buffer += resultlen;
length -= resultlen;
xfree (result);
}
while (length > 0);
return 0;
}
/* Perform a READ BINARY command requesting a maximum of NMAX bytes
from OFFSET. With NMAX = 0 the entire file is read. The result is
stored in a newly allocated buffer at the address passed by RESULT.
Returns the length of this data at the address of RESULTLEN. */
gpg_error_t
iso7816_read_binary (int slot, size_t offset, size_t nmax,
unsigned char **result, size_t *resultlen)
{
int sw;
unsigned char *buffer;
size_t bufferlen;
int read_all = !nmax;
size_t n;
if (!result || !resultlen)
return gpg_error (GPG_ERR_INV_VALUE);
*result = NULL;
*resultlen = 0;
/* We can only encode 15 bits in p0,p1 to indicate an offset. Thus
we check for this limit. */
if (offset > 32767)
return gpg_error (GPG_ERR_INV_VALUE);
do
{
buffer = NULL;
bufferlen = 0;
n = read_all? 0 : nmax;
sw = apdu_send_le (slot, 0, 0x00, CMD_READ_BINARY,
((offset>>8) & 0xff), (offset & 0xff) , -1, NULL,
n, &buffer, &bufferlen);
if ( SW_EXACT_LENGTH_P(sw) )
{
n = (sw & 0x00ff);
sw = apdu_send_le (slot, 0, 0x00, CMD_READ_BINARY,
((offset>>8) & 0xff), (offset & 0xff) , -1, NULL,
n, &buffer, &bufferlen);
}
if (*result && sw == SW_BAD_P0_P1)
{
/* Bad Parameter means that the offset is outside of the
EF. When reading all data we take this as an indication
for EOF. */
break;
}
if (sw != SW_SUCCESS && sw != SW_EOF_REACHED)
{
/* Make sure that pending buffers are released. */
xfree (buffer);
xfree (*result);
*result = NULL;
*resultlen = 0;
return map_sw (sw);
}
if (*result) /* Need to extend the buffer. */
{
unsigned char *p = xtryrealloc (*result, *resultlen + bufferlen);
if (!p)
{
gpg_error_t err = gpg_error_from_syserror ();
xfree (buffer);
xfree (*result);
*result = NULL;
*resultlen = 0;
return err;
}
*result = p;
memcpy (*result + *resultlen, buffer, bufferlen);
*resultlen += bufferlen;
xfree (buffer);
buffer = NULL;
}
else /* Transfer the buffer into our result. */
{
*result = buffer;
*resultlen = bufferlen;
}
offset += bufferlen;
if (offset > 32767)
break; /* We simply truncate the result for too large
files. */
if (nmax > bufferlen)
nmax -= bufferlen;
else
nmax = 0;
}
while ((read_all && sw != SW_EOF_REACHED) || (!read_all && nmax));
return 0;
}
/* Perform a READ RECORD command. RECNO gives the record number to
read with 0 indicating the current record. RECCOUNT must be 1 (not
all cards support reading of more than one record). SHORT_EF
should be 0 to read the current EF or contain a short EF. The
result is stored in a newly allocated buffer at the address passed
by RESULT. Returns the length of this data at the address of
RESULTLEN. */
gpg_error_t
iso7816_read_record (int slot, int recno, int reccount, int short_ef,
unsigned char **result, size_t *resultlen)
{
int sw;
unsigned char *buffer;
size_t bufferlen;
if (!result || !resultlen)
return gpg_error (GPG_ERR_INV_VALUE);
*result = NULL;
*resultlen = 0;
/* We can only encode 15 bits in p0,p1 to indicate an offset. Thus
we check for this limit. */
if (recno < 0 || recno > 255 || reccount != 1
|| short_ef < 0 || short_ef > 254 )
return gpg_error (GPG_ERR_INV_VALUE);
buffer = NULL;
bufferlen = 0;
sw = apdu_send_le (slot, 0, 0x00, CMD_READ_RECORD,
recno,
short_ef? short_ef : 0x04,
-1, NULL,
0, &buffer, &bufferlen);
if (sw != SW_SUCCESS && sw != SW_EOF_REACHED)
{
/* Make sure that pending buffers are released. */
xfree (buffer);
xfree (*result);
*result = NULL;
*resultlen = 0;
return map_sw (sw);
}
*result = buffer;
*resultlen = bufferlen;
return 0;
}
diff --git a/scd/scdaemon.c b/scd/scdaemon.c
index 507108db0..e89569e5d 100644
--- a/scd/scdaemon.c
+++ b/scd/scdaemon.c
@@ -1,1450 +1,1461 @@
/* scdaemon.c - The GnuPG Smartcard Daemon
* Copyright (C) 2001-2002, 2004-2005, 2007-2009 Free Software Foundation, Inc.
* Copyright (C) 2001-2002, 2004-2005, 2007-2014 Werner Koch
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>
#include <assert.h>
#include <time.h>
#include <fcntl.h>
#ifndef HAVE_W32_SYSTEM
#include <sys/socket.h>
#include <sys/un.h>
#endif /*HAVE_W32_SYSTEM*/
#include <unistd.h>
#include <signal.h>
#include <npth.h>
#define GNUPG_COMMON_NEED_AFLOCAL
#include "scdaemon.h"
#include <ksba.h>
#include <gcrypt.h>
#include <assuan.h> /* malloc hooks */
#include "../common/i18n.h"
#include "../common/sysutils.h"
-#include "app-common.h"
#include "iso7816.h"
#include "apdu.h"
#include "ccid-driver.h"
#include "../common/gc-opt-flags.h"
#include "../common/asshelp.h"
#include "../common/exechelp.h"
#include "../common/init.h"
#ifndef ENAMETOOLONG
# define ENAMETOOLONG EINVAL
#endif
enum cmd_and_opt_values
{ aNull = 0,
oCsh = 'c',
oQuiet = 'q',
oSh = 's',
oVerbose = 'v',
oNoVerbose = 500,
aGPGConfList,
aGPGConfTest,
oOptions,
oDebug,
oDebugAll,
oDebugLevel,
oDebugWait,
oDebugAllowCoreDump,
oDebugCCIDDriver,
oDebugLogTid,
oDebugAssuanLogCats,
oNoGreeting,
oNoOptions,
oHomedir,
oNoDetach,
oNoGrab,
oLogFile,
oServer,
oMultiServer,
oDaemon,
oBatch,
oReaderPort,
oCardTimeout,
octapiDriver,
opcscDriver,
oDisableCCID,
oDisableOpenSC,
oDisablePinpad,
oAllowAdmin,
oDenyAdmin,
oDisableApplication,
+ oApplicationPriority,
oEnablePinpadVarlen,
oListenBacklog
};
static ARGPARSE_OPTS opts[] = {
ARGPARSE_c (aGPGConfList, "gpgconf-list", "@"),
ARGPARSE_c (aGPGConfTest, "gpgconf-test", "@"),
ARGPARSE_group (301, N_("@Options:\n ")),
ARGPARSE_s_n (oServer,"server", N_("run in server mode (foreground)")),
ARGPARSE_s_n (oMultiServer, "multi-server",
N_("run in multi server mode (foreground)")),
ARGPARSE_s_n (oDaemon, "daemon", N_("run in daemon mode (background)")),
ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")),
ARGPARSE_s_n (oQuiet, "quiet", N_("be somewhat more quiet")),
ARGPARSE_s_n (oSh, "sh", N_("sh-style command output")),
ARGPARSE_s_n (oCsh, "csh", N_("csh-style command output")),
ARGPARSE_s_s (oOptions, "options", N_("|FILE|read options from FILE")),
ARGPARSE_s_s (oDebug, "debug", "@"),
ARGPARSE_s_n (oDebugAll, "debug-all", "@"),
ARGPARSE_s_s (oDebugLevel, "debug-level" ,
N_("|LEVEL|set the debugging level to LEVEL")),
ARGPARSE_s_i (oDebugWait, "debug-wait", "@"),
ARGPARSE_s_n (oDebugAllowCoreDump, "debug-allow-core-dump", "@"),
ARGPARSE_s_n (oDebugCCIDDriver, "debug-ccid-driver", "@"),
ARGPARSE_s_n (oDebugLogTid, "debug-log-tid", "@"),
ARGPARSE_p_u (oDebugAssuanLogCats, "debug-assuan-log-cats", "@"),
ARGPARSE_s_n (oNoDetach, "no-detach", N_("do not detach from the console")),
ARGPARSE_s_s (oLogFile, "log-file", N_("|FILE|write a log to FILE")),
ARGPARSE_s_s (oReaderPort, "reader-port",
N_("|N|connect to reader at port N")),
ARGPARSE_s_s (octapiDriver, "ctapi-driver",
N_("|NAME|use NAME as ct-API driver")),
ARGPARSE_s_s (opcscDriver, "pcsc-driver",
N_("|NAME|use NAME as PC/SC driver")),
ARGPARSE_s_n (oDisableCCID, "disable-ccid",
#ifdef HAVE_LIBUSB
N_("do not use the internal CCID driver")
#else
"@"
#endif
/* end --disable-ccid */),
ARGPARSE_s_u (oCardTimeout, "card-timeout",
N_("|N|disconnect the card after N seconds of inactivity")),
ARGPARSE_s_n (oDisablePinpad, "disable-pinpad",
N_("do not use a reader's pinpad")),
ARGPARSE_ignore (300, "disable-keypad"),
ARGPARSE_s_n (oAllowAdmin, "allow-admin", "@"),
ARGPARSE_s_n (oDenyAdmin, "deny-admin",
N_("deny the use of admin card commands")),
ARGPARSE_s_s (oDisableApplication, "disable-application", "@"),
+ ARGPARSE_s_s (oApplicationPriority, "application-priority",
+ N_("|LIST|Change the application priority to LIST")),
ARGPARSE_s_n (oEnablePinpadVarlen, "enable-pinpad-varlen",
N_("use variable length input for pinpad")),
ARGPARSE_s_s (oHomedir, "homedir", "@"),
ARGPARSE_s_i (oListenBacklog, "listen-backlog", "@"),
ARGPARSE_end ()
};
/* The list of supported debug flags. */
static struct debug_flags_s debug_flags [] =
{
{ DBG_MPI_VALUE , "mpi" },
{ DBG_CRYPTO_VALUE , "crypto" },
{ DBG_MEMORY_VALUE , "memory" },
{ DBG_CACHE_VALUE , "cache" },
{ DBG_MEMSTAT_VALUE, "memstat" },
{ DBG_HASHING_VALUE, "hashing" },
{ DBG_IPC_VALUE , "ipc" },
{ DBG_CARD_IO_VALUE, "cardio" },
{ DBG_READER_VALUE , "reader" },
{ 0, NULL }
};
/* The card driver we use by default for PC/SC. */
#if defined(HAVE_W32_SYSTEM) || defined(__CYGWIN__)
#define DEFAULT_PCSC_DRIVER "winscard.dll"
#elif defined(__APPLE__)
#define DEFAULT_PCSC_DRIVER "/System/Library/Frameworks/PCSC.framework/PCSC"
#elif defined(__GLIBC__)
#define DEFAULT_PCSC_DRIVER "libpcsclite.so.1"
#else
#define DEFAULT_PCSC_DRIVER "libpcsclite.so"
#endif
/* The timer tick used to check card removal.
We poll every 500ms to let the user immediately know a status
change.
For a card reader with an interrupt endpoint, this timer is not
used with the internal CCID driver.
This is not too good for power saving but given that there is no
easy way to block on card status changes it is the best we can do.
For PC/SC we could in theory use an extra thread to wait for status
changes but that requires a native thread because there is no way
to make the underlying PC/SC card change function block using a Npth
mechanism. Given that a native thread could only be used under W32
we don't do that at all. */
#define TIMERTICK_INTERVAL_SEC (0)
#define TIMERTICK_INTERVAL_USEC (500000)
/* Flag to indicate that a shutdown was requested. */
static int shutdown_pending;
/* It is possible that we are currently running under setuid permissions */
static int maybe_setuid = 1;
/* Flag telling whether we are running as a pipe server. */
static int pipe_server;
/* Name of the communication socket */
static char *socket_name;
/* Name of the redirected socket or NULL. */
static char *redir_socket_name;
/* We need to keep track of the server's nonces (these are dummies for
POSIX systems). */
static assuan_sock_nonce_t socket_nonce;
/* Value for the listen() backlog argument. Change at runtime with
* --listen-backlog. */
static int listen_backlog = 64;
#ifdef HAVE_W32_SYSTEM
static HANDLE the_event;
#else
/* PID to notify update of usb devices. */
static pid_t main_thread_pid;
#endif
#ifdef HAVE_PSELECT_NO_EINTR
/* FD to notify changes. */
static int notify_fd;
#endif
static char *create_socket_name (char *standard_name);
static gnupg_fd_t create_server_socket (const char *name,
char **r_redir_name,
assuan_sock_nonce_t *nonce);
static void *start_connection_thread (void *arg);
static void handle_connections (int listen_fd);
/* Pth wrapper function definitions. */
ASSUAN_SYSTEM_NPTH_IMPL;
static int active_connections;
static char *
make_libversion (const char *libname, const char *(*getfnc)(const char*))
{
const char *s;
char *result;
if (maybe_setuid)
{
gcry_control (GCRYCTL_INIT_SECMEM, 0, 0); /* Drop setuid. */
maybe_setuid = 0;
}
s = getfnc (NULL);
result = xmalloc (strlen (libname) + 1 + strlen (s) + 1);
strcpy (stpcpy (stpcpy (result, libname), " "), s);
return result;
}
static const char *
my_strusage (int level)
{
static char *ver_gcry, *ver_ksba;
const char *p;
switch (level)
{
case 11: p = "@SCDAEMON@ (@GNUPG@)";
break;
case 13: p = VERSION; break;
case 17: p = PRINTABLE_OS_NAME; break;
case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break;
case 20:
if (!ver_gcry)
ver_gcry = make_libversion ("libgcrypt", gcry_check_version);
p = ver_gcry;
break;
case 21:
if (!ver_ksba)
ver_ksba = make_libversion ("libksba", ksba_check_version);
p = ver_ksba;
break;
case 1:
case 40: p = _("Usage: @SCDAEMON@ [options] (-h for help)");
break;
case 41: p = _("Syntax: scdaemon [options] [command [args]]\n"
"Smartcard daemon for @GNUPG@\n");
break;
default: p = NULL;
}
return p;
}
static int
tid_log_callback (unsigned long *rvalue)
{
int len = sizeof (*rvalue);
npth_t thread;
thread = npth_self ();
if (sizeof (thread) < len)
len = sizeof (thread);
memcpy (rvalue, &thread, len);
return 2; /* Use use hex representation. */
}
/* Setup the debugging. With a LEVEL of NULL only the active debug
flags are propagated to the subsystems. With LEVEL set, a specific
set of debug flags is set; thus overriding all flags already
set. */
static void
set_debug (const char *level)
{
int numok = (level && digitp (level));
int numlvl = numok? atoi (level) : 0;
if (!level)
;
else if (!strcmp (level, "none") || (numok && numlvl < 1))
opt.debug = 0;
else if (!strcmp (level, "basic") || (numok && numlvl <= 2))
opt.debug = DBG_IPC_VALUE;
else if (!strcmp (level, "advanced") || (numok && numlvl <= 5))
opt.debug = DBG_IPC_VALUE;
else if (!strcmp (level, "expert") || (numok && numlvl <= 8))
opt.debug = (DBG_IPC_VALUE|DBG_CACHE_VALUE|DBG_CARD_IO_VALUE);
else if (!strcmp (level, "guru") || numok)
{
opt.debug = ~0;
/* Unless the "guru" string has been used we don't want to allow
hashing debugging. The rationale is that people tend to
select the highest debug value and would then clutter their
disk with debug files which may reveal confidential data. */
if (numok)
opt.debug &= ~(DBG_HASHING_VALUE);
}
else
{
log_error (_("invalid debug-level '%s' given\n"), level);
scd_exit(2);
}
if (opt.debug && !opt.verbose)
opt.verbose = 1;
if (opt.debug && opt.quiet)
opt.quiet = 0;
if (opt.debug & DBG_MPI_VALUE)
gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 2);
if (opt.debug & DBG_CRYPTO_VALUE )
gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 1);
gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose);
if (opt.debug)
parse_debug_flag (NULL, &opt.debug, debug_flags);
}
static void
cleanup (void)
{
if (socket_name && *socket_name)
{
char *name;
name = redir_socket_name? redir_socket_name : socket_name;
gnupg_remove (name);
*socket_name = 0;
}
}
static void
setup_signal_mask (void)
{
#ifndef HAVE_W32_SYSTEM
npth_sigev_init ();
npth_sigev_add (SIGHUP);
npth_sigev_add (SIGUSR1);
npth_sigev_add (SIGUSR2);
npth_sigev_add (SIGINT);
npth_sigev_add (SIGCONT);
npth_sigev_add (SIGTERM);
npth_sigev_fini ();
main_thread_pid = getpid ();
#endif
}
int
main (int argc, char **argv )
{
ARGPARSE_ARGS pargs;
int orig_argc;
char **orig_argv;
FILE *configfp = NULL;
char *configname = NULL;
const char *shell;
unsigned int configlineno;
int parse_debug = 0;
const char *debug_level = NULL;
int default_config =1;
int greeting = 0;
int nogreeting = 0;
int multi_server = 0;
int is_daemon = 0;
int nodetach = 0;
int csh_style = 0;
char *logfile = NULL;
int debug_wait = 0;
int gpgconf_list = 0;
const char *config_filename = NULL;
int allow_coredump = 0;
struct assuan_malloc_hooks malloc_hooks;
int res;
npth_t pipecon_handler;
+ const char *application_priority = NULL;
early_system_init ();
set_strusage (my_strusage);
gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN);
/* Please note that we may running SUID(ROOT), so be very CAREFUL
when adding any stuff between here and the call to INIT_SECMEM()
somewhere after the option parsing */
log_set_prefix ("scdaemon", GPGRT_LOG_WITH_PREFIX | GPGRT_LOG_WITH_PID);
/* Make sure that our subsystems are ready. */
i18n_init ();
init_common_subsystems (&argc, &argv);
ksba_set_malloc_hooks (gcry_malloc, gcry_realloc, gcry_free);
malloc_hooks.malloc = gcry_malloc;
malloc_hooks.realloc = gcry_realloc;
malloc_hooks.free = gcry_free;
assuan_set_malloc_hooks (&malloc_hooks);
assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT);
assuan_set_system_hooks (ASSUAN_SYSTEM_NPTH);
assuan_sock_init ();
setup_libassuan_logging (&opt.debug, NULL);
setup_libgcrypt_logging ();
gcry_control (GCRYCTL_USE_SECURE_RNDPOOL);
disable_core_dumps ();
/* Set default options. */
opt.allow_admin = 1;
opt.pcsc_driver = DEFAULT_PCSC_DRIVER;
shell = getenv ("SHELL");
if (shell && strlen (shell) >= 3 && !strcmp (shell+strlen (shell)-3, "csh") )
csh_style = 1;
/* 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 option 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)
gnupg_set_homedir (pargs.r.ret_str);
}
/* initialize the secure memory. */
gcry_control (GCRYCTL_INIT_SECMEM, 16384, 0);
maybe_setuid = 0;
/*
Now we are working under our real uid
*/
if (default_config)
configname = make_filename (gnupg_homedir (), SCDAEMON_NAME EXTSEP_S "conf",
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) );
exit(2);
}
xfree (configname);
configname = NULL;
}
if (parse_debug && configname )
log_info (_("reading options from '%s'\n"), configname );
default_config = 0;
}
while (optfile_parse( configfp, configname, &configlineno, &pargs, opts) )
{
switch (pargs.r_opt)
{
case aGPGConfList: gpgconf_list = 1; break;
case aGPGConfTest: gpgconf_list = 2; break;
case oQuiet: opt.quiet = 1; break;
case oVerbose: opt.verbose++; break;
case oBatch: opt.batch=1; break;
case oDebug:
if (parse_debug_flag (pargs.r.ret_str, &opt.debug, debug_flags))
{
pargs.r_opt = ARGPARSE_INVALID_ARG;
pargs.err = ARGPARSE_PRINT_ERROR;
}
break;
case oDebugAll: opt.debug = ~0; break;
case oDebugLevel: debug_level = pargs.r.ret_str; break;
case oDebugWait: debug_wait = pargs.r.ret_int; break;
case oDebugAllowCoreDump:
enable_core_dumps ();
allow_coredump = 1;
break;
case oDebugCCIDDriver:
#ifdef HAVE_LIBUSB
ccid_set_debug_level (ccid_set_debug_level (-1)+1);
#endif /*HAVE_LIBUSB*/
break;
case oDebugLogTid:
log_set_pid_suffix_cb (tid_log_callback);
break;
case oDebugAssuanLogCats:
set_libassuan_log_cats (pargs.r.ret_ulong);
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 oNoGreeting: nogreeting = 1; break;
case oNoVerbose: opt.verbose = 0; break;
case oNoOptions: break; /* no-options */
case oHomedir: gnupg_set_homedir (pargs.r.ret_str); break;
case oNoDetach: nodetach = 1; break;
case oLogFile: logfile = pargs.r.ret_str; break;
case oCsh: csh_style = 1; break;
case oSh: csh_style = 0; break;
case oServer: pipe_server = 1; break;
case oMultiServer: pipe_server = 1; multi_server = 1; break;
case oDaemon: is_daemon = 1; break;
case oReaderPort: opt.reader_port = pargs.r.ret_str; break;
case octapiDriver: opt.ctapi_driver = pargs.r.ret_str; break;
case opcscDriver: opt.pcsc_driver = pargs.r.ret_str; break;
case oDisableCCID: opt.disable_ccid = 1; break;
case oDisableOpenSC: break;
case oDisablePinpad: opt.disable_pinpad = 1; break;
case oAllowAdmin: /* Dummy because allow is now the default. */
break;
case oDenyAdmin: opt.allow_admin = 0; break;
case oCardTimeout: opt.card_timeout = pargs.r.ret_ulong; break;
case oDisableApplication:
add_to_strlist (&opt.disabled_applications, pargs.r.ret_str);
break;
+ case oApplicationPriority:
+ application_priority = pargs.r.ret_str;
+ break;
+
case oEnablePinpadVarlen: opt.enable_pinpad_varlen = 1; break;
case oListenBacklog:
listen_backlog = pargs.r.ret_int;
break;
default:
pargs.err = configfp? ARGPARSE_PRINT_WARNING:ARGPARSE_PRINT_ERROR;
break;
}
}
if (configfp)
{
fclose( configfp );
configfp = NULL;
/* Keep a copy of the config name for use by --gpgconf-list. */
config_filename = configname;
configname = NULL;
goto next_pass;
}
xfree (configname);
configname = NULL;
if (log_get_errorcount(0))
exit(2);
if (nogreeting )
greeting = 0;
if (greeting)
{
es_fprintf (es_stderr, "%s %s; %s\n",
strusage(11), strusage(13), strusage(14) );
es_fprintf (es_stderr, "%s\n", strusage(15) );
}
#ifdef IS_DEVELOPMENT_VERSION
log_info ("NOTE: this is a development version!\n");
#endif
/* Print a warning if an argument looks like an option. */
if (!opt.quiet && !(pargs.flags & ARGPARSE_FLAG_STOP_SEEN))
{
int i;
for (i=0; i < argc; i++)
if (argv[i][0] == '-' && argv[i][1] == '-')
log_info (_("Note: '%s' is not considered an option\n"), argv[i]);
}
if (atexit (cleanup))
{
log_error ("atexit failed\n");
cleanup ();
exit (1);
}
set_debug (debug_level);
if (initialize_module_command ())
{
log_error ("initialization failed\n");
cleanup ();
exit (1);
}
if (gpgconf_list == 2)
scd_exit (0);
if (gpgconf_list)
{
/* List options and default values in the GPG Conf format. */
char *filename = NULL;
char *filename_esc;
if (config_filename)
filename = xstrdup (config_filename);
else
filename = make_filename (gnupg_homedir (),
SCDAEMON_NAME EXTSEP_S "conf", NULL);
filename_esc = percent_escape (filename, NULL);
es_printf ("%s-%s.conf:%lu:\"%s\n",
GPGCONF_NAME, SCDAEMON_NAME,
GC_OPT_FLAG_DEFAULT, filename_esc);
xfree (filename_esc);
xfree (filename);
es_printf ("verbose:%lu:\n"
"quiet:%lu:\n"
"debug-level:%lu:\"none:\n"
"log-file:%lu:\n",
GC_OPT_FLAG_NONE,
GC_OPT_FLAG_NONE,
GC_OPT_FLAG_DEFAULT,
GC_OPT_FLAG_NONE );
es_printf ("reader-port:%lu:\n", GC_OPT_FLAG_NONE );
es_printf ("ctapi-driver:%lu:\n", GC_OPT_FLAG_NONE );
es_printf ("pcsc-driver:%lu:\"%s:\n",
GC_OPT_FLAG_DEFAULT, DEFAULT_PCSC_DRIVER );
#ifdef HAVE_LIBUSB
es_printf ("disable-ccid:%lu:\n", GC_OPT_FLAG_NONE );
#endif
es_printf ("deny-admin:%lu:\n", GC_OPT_FLAG_NONE );
es_printf ("disable-pinpad:%lu:\n", GC_OPT_FLAG_NONE );
es_printf ("card-timeout:%lu:%d:\n", GC_OPT_FLAG_DEFAULT, 0);
es_printf ("enable-pinpad-varlen:%lu:\n", GC_OPT_FLAG_NONE );
+ es_printf ("application-priority:%lu:\n", GC_OPT_FLAG_NONE );
scd_exit (0);
}
/* Now start with logging to a file if this is desired. */
if (logfile)
{
log_set_file (logfile);
log_set_prefix (NULL, GPGRT_LOG_WITH_PREFIX | GPGRT_LOG_WITH_TIME | GPGRT_LOG_WITH_PID);
}
if (debug_wait && pipe_server)
{
log_debug ("waiting for debugger - my pid is %u .....\n",
(unsigned int)getpid());
gnupg_sleep (debug_wait);
log_debug ("... okay\n");
}
+ if (application_priority)
+ app_update_priority_list (application_priority);
+
if (pipe_server)
{
/* This is the simple pipe based server */
ctrl_t ctrl;
npth_attr_t tattr;
int fd = -1;
#ifndef HAVE_W32_SYSTEM
{
struct sigaction sa;
sa.sa_handler = SIG_IGN;
sigemptyset (&sa.sa_mask);
sa.sa_flags = 0;
sigaction (SIGPIPE, &sa, NULL);
}
#endif
npth_init ();
setup_signal_mask ();
gpgrt_set_syscall_clamp (npth_unprotect, npth_protect);
/* If --debug-allow-core-dump has been given we also need to
switch the working directory to a place where we can actually
write. */
if (allow_coredump)
{
if (chdir("/tmp"))
log_debug ("chdir to '/tmp' failed: %s\n", strerror (errno));
else
log_debug ("changed working directory to '/tmp'\n");
}
/* In multi server mode we need to listen on an additional
socket. Create that socket now before starting the handler
for the pipe connection. This allows that handler to send
back the name of that socket. */
if (multi_server)
{
socket_name = create_socket_name (SCDAEMON_SOCK_NAME);
fd = FD2INT(create_server_socket (socket_name,
&redir_socket_name, &socket_nonce));
}
res = npth_attr_init (&tattr);
if (res)
{
log_error ("error allocating thread attributes: %s\n",
strerror (res));
scd_exit (2);
}
npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED);
ctrl = xtrycalloc (1, sizeof *ctrl);
if ( !ctrl )
{
log_error ("error allocating connection control data: %s\n",
strerror (errno) );
scd_exit (2);
}
ctrl->thread_startup.fd = GNUPG_INVALID_FD;
res = npth_create (&pipecon_handler, &tattr, start_connection_thread, ctrl);
if (res)
{
log_error ("error spawning pipe connection handler: %s\n",
strerror (res) );
xfree (ctrl);
scd_exit (2);
}
npth_setname_np (pipecon_handler, "pipe-connection");
npth_attr_destroy (&tattr);
/* We run handle_connection to wait for the shutdown signal and
to run the ticker stuff. */
handle_connections (fd);
if (fd != -1)
close (fd);
}
else if (!is_daemon)
{
log_info (_("please use the option '--daemon'"
" to run the program in the background\n"));
}
else
{ /* Regular server mode */
int fd;
#ifndef HAVE_W32_SYSTEM
pid_t pid;
int i;
#endif
/* Create the socket. */
socket_name = create_socket_name (SCDAEMON_SOCK_NAME);
fd = FD2INT (create_server_socket (socket_name,
&redir_socket_name, &socket_nonce));
fflush (NULL);
#ifdef HAVE_W32_SYSTEM
(void)csh_style;
(void)nodetach;
#else
pid = fork ();
if (pid == (pid_t)-1)
{
log_fatal ("fork failed: %s\n", strerror (errno) );
exit (1);
}
else if (pid)
{ /* we are the parent */
char *infostr;
close (fd);
/* create the info string: <name>:<pid>:<protocol_version> */
if (gpgrt_asprintf (&infostr, "SCDAEMON_INFO=%s:%lu:1",
socket_name, (ulong) pid) < 0)
{
log_error ("out of core\n");
kill (pid, SIGTERM);
exit (1);
}
*socket_name = 0; /* don't let cleanup() remove the socket -
the child should do this from now on */
if (argc)
{ /* run the program given on the commandline */
if (putenv (infostr))
{
log_error ("failed to set environment: %s\n",
strerror (errno) );
kill (pid, SIGTERM );
exit (1);
}
execvp (argv[0], argv);
log_error ("failed to run the command: %s\n", strerror (errno));
kill (pid, SIGTERM);
exit (1);
}
else
{
/* Print the environment string, so that the caller can use
shell's eval to set it */
if (csh_style)
{
*strchr (infostr, '=') = ' ';
es_printf ( "setenv %s;\n", infostr);
}
else
{
es_printf ( "%s; export SCDAEMON_INFO;\n", infostr);
}
xfree (infostr);
exit (0);
}
/* NOTREACHED */
} /* end parent */
/* This is the child. */
npth_init ();
setup_signal_mask ();
gpgrt_set_syscall_clamp (npth_unprotect, npth_protect);
/* Detach from tty and put process into a new session. */
if (!nodetach )
{
/* Close stdin, stdout and stderr unless it is the log stream. */
for (i=0; i <= 2; i++)
{
if (!log_test_fd (i) && i != fd )
{
if ( !close (i)
&& open ("/dev/null", i? O_WRONLY : O_RDONLY) == -1)
{
log_error ("failed to open '%s': %s\n",
"/dev/null", strerror (errno));
cleanup ();
exit (1);
}
}
}
if (setsid() == -1)
{
log_error ("setsid() failed: %s\n", strerror(errno) );
cleanup ();
exit (1);
}
}
{
struct sigaction sa;
sa.sa_handler = SIG_IGN;
sigemptyset (&sa.sa_mask);
sa.sa_flags = 0;
sigaction (SIGPIPE, &sa, NULL);
}
#endif /*!HAVE_W32_SYSTEM*/
if (gnupg_chdir (gnupg_daemon_rootdir ()))
{
log_error ("chdir to '%s' failed: %s\n",
gnupg_daemon_rootdir (), strerror (errno));
exit (1);
}
handle_connections (fd);
close (fd);
}
return 0;
}
void
scd_exit (int rc)
{
apdu_prepare_exit ();
#if 0
#warning no update_random_seed_file
update_random_seed_file();
#endif
#if 0
/* at this time a bit annoying */
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 );
#endif
gcry_control (GCRYCTL_TERM_SECMEM );
rc = rc? rc : log_get_errorcount(0)? 2 : 0;
exit (rc);
}
static void
scd_init_default_ctrl (ctrl_t ctrl)
{
(void)ctrl;
}
static void
scd_deinit_default_ctrl (ctrl_t ctrl)
{
if (!ctrl)
return;
xfree (ctrl->in_data.value);
ctrl->in_data.value = NULL;
ctrl->in_data.valuelen = 0;
}
/* Return the name of the socket to be used to connect to this
process. If no socket is available, return NULL. */
const char *
scd_get_socket_name ()
{
if (socket_name && *socket_name)
return socket_name;
return NULL;
}
#ifndef HAVE_W32_SYSTEM
static void
handle_signal (int signo)
{
switch (signo)
{
case SIGHUP:
log_info ("SIGHUP received - "
"re-reading configuration and resetting cards\n");
/* reread_configuration (); */
break;
case SIGUSR1:
log_info ("SIGUSR1 received - printing internal information:\n");
/* Fixme: We need to see how to integrate pth dumping into our
logging system. */
/* pth_ctrl (PTH_CTRL_DUMPSTATE, log_get_stream ()); */
app_dump_state ();
break;
case SIGUSR2:
log_info ("SIGUSR2 received - no action defined\n");
break;
case SIGCONT:
/* Nothing. */
log_debug ("SIGCONT received - breaking select\n");
break;
case SIGTERM:
if (!shutdown_pending)
log_info ("SIGTERM received - shutting down ...\n");
else
log_info ("SIGTERM received - still %i running threads\n",
active_connections);
shutdown_pending++;
if (shutdown_pending > 2)
{
log_info ("shutdown forced\n");
log_info ("%s %s stopped\n", strusage(11), strusage(13) );
cleanup ();
scd_exit (0);
}
break;
case SIGINT:
log_info ("SIGINT received - immediate shutdown\n");
log_info( "%s %s stopped\n", strusage(11), strusage(13));
cleanup ();
scd_exit (0);
break;
default:
log_info ("signal %d received - no action defined\n", signo);
}
}
#endif /*!HAVE_W32_SYSTEM*/
/* Create a name for the socket. We check for valid characters as
well as against a maximum allowed length for a unix domain socket
is done. The function terminates the process in case of an error.
Returns: Pointer to an allcoated string with the absolute name of
the socket used. */
static char *
create_socket_name (char *standard_name)
{
char *name;
name = make_filename (gnupg_socketdir (), standard_name, NULL);
if (strchr (name, PATHSEP_C))
{
log_error (("'%s' are not allowed in the socket name\n"), PATHSEP_S);
scd_exit (2);
}
return name;
}
/* Create a Unix domain socket with NAME. Returns the file descriptor
or terminates the process in case of an error. If the socket has
been redirected the name of the real socket is stored as a malloced
string at R_REDIR_NAME. */
static gnupg_fd_t
create_server_socket (const char *name, char **r_redir_name,
assuan_sock_nonce_t *nonce)
{
struct sockaddr *addr;
struct sockaddr_un *unaddr;
socklen_t len;
gnupg_fd_t fd;
int rc;
xfree (*r_redir_name);
*r_redir_name = NULL;
fd = assuan_sock_new (AF_UNIX, SOCK_STREAM, 0);
if (fd == GNUPG_INVALID_FD)
{
log_error (_("can't create socket: %s\n"), strerror (errno));
scd_exit (2);
}
unaddr = xmalloc (sizeof (*unaddr));
addr = (struct sockaddr*)unaddr;
{
int redirected;
if (assuan_sock_set_sockaddr_un (name, addr, &redirected))
{
if (errno == ENAMETOOLONG)
log_error (_("socket name '%s' is too long\n"), name);
else
log_error ("error preparing socket '%s': %s\n",
name, gpg_strerror (gpg_error_from_syserror ()));
scd_exit (2);
}
if (redirected)
{
*r_redir_name = xstrdup (unaddr->sun_path);
if (opt.verbose)
log_info ("redirecting socket '%s' to '%s'\n", name, *r_redir_name);
}
}
len = SUN_LEN (unaddr);
rc = assuan_sock_bind (fd, addr, len);
if (rc == -1 && errno == EADDRINUSE)
{
gnupg_remove (unaddr->sun_path);
rc = assuan_sock_bind (fd, addr, len);
}
if (rc != -1
&& (rc=assuan_sock_get_nonce (addr, len, nonce)))
log_error (_("error getting nonce for the socket\n"));
if (rc == -1)
{
log_error (_("error binding socket to '%s': %s\n"),
unaddr->sun_path,
gpg_strerror (gpg_error_from_syserror ()));
assuan_sock_close (fd);
scd_exit (2);
}
if (gnupg_chmod (unaddr->sun_path, "-rwx"))
log_error (_("can't set permissions of '%s': %s\n"),
unaddr->sun_path, strerror (errno));
if (listen (FD2INT(fd), listen_backlog) == -1)
{
log_error ("listen(fd, %d) failed: %s\n",
listen_backlog, gpg_strerror (gpg_error_from_syserror ()));
assuan_sock_close (fd);
scd_exit (2);
}
if (opt.verbose)
log_info (_("listening on socket '%s'\n"), unaddr->sun_path);
return fd;
}
/* This is the standard connection thread's main function. */
static void *
start_connection_thread (void *arg)
{
ctrl_t ctrl = arg;
if (ctrl->thread_startup.fd != GNUPG_INVALID_FD
&& assuan_sock_check_nonce (ctrl->thread_startup.fd, &socket_nonce))
{
log_info (_("error reading nonce on fd %d: %s\n"),
FD2INT(ctrl->thread_startup.fd), strerror (errno));
assuan_sock_close (ctrl->thread_startup.fd);
xfree (ctrl);
return NULL;
}
active_connections++;
scd_init_default_ctrl (ctrl);
if (opt.verbose)
log_info (_("handler for fd %d started\n"),
FD2INT(ctrl->thread_startup.fd));
/* If this is a pipe server, we request a shutdown if the command
handler asked for it. With the next ticker event and given that
no other connections are running the shutdown will then
happen. */
if (scd_command_handler (ctrl, FD2INT(ctrl->thread_startup.fd))
&& pipe_server)
shutdown_pending = 1;
if (opt.verbose)
log_info (_("handler for fd %d terminated\n"),
FD2INT (ctrl->thread_startup.fd));
scd_deinit_default_ctrl (ctrl);
xfree (ctrl);
if (--active_connections == 0)
scd_kick_the_loop ();
return NULL;
}
void
scd_kick_the_loop (void)
{
/* Kick the select loop. */
#ifdef HAVE_W32_SYSTEM
int ret = SetEvent (the_event);
if (ret == 0)
log_error ("SetEvent for scd_kick_the_loop failed: %s\n",
w32_strerror (-1));
#elif defined(HAVE_PSELECT_NO_EINTR)
write (notify_fd, "", 1);
#else
int ret = kill (main_thread_pid, SIGCONT);
if (ret < 0)
log_error ("SetEvent for scd_kick_the_loop failed: %s\n",
gpg_strerror (gpg_error_from_syserror ()));
#endif
}
/* Connection handler loop. Wait for connection requests and spawn a
thread after accepting a connection. LISTEN_FD is allowed to be -1
in which case this code will only do regular timeouts and handle
signals. */
static void
handle_connections (int listen_fd)
{
npth_attr_t tattr;
struct sockaddr_un paddr;
socklen_t plen;
fd_set fdset, read_fdset;
int nfd;
int ret;
int fd;
struct timespec timeout;
struct timespec *t;
int saved_errno;
#ifdef HAVE_W32_SYSTEM
HANDLE events[2];
unsigned int events_set;
#else
int signo;
#endif
#ifdef HAVE_PSELECT_NO_EINTR
int pipe_fd[2];
ret = gnupg_create_pipe (pipe_fd);
if (ret)
{
log_error ("pipe creation failed: %s\n", gpg_strerror (ret));
return;
}
notify_fd = pipe_fd[1];
#endif
ret = npth_attr_init(&tattr);
if (ret)
{
log_error ("npth_attr_init failed: %s\n", strerror (ret));
return;
}
npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED);
#ifdef HAVE_W32_SYSTEM
{
HANDLE h, h2;
SECURITY_ATTRIBUTES sa = { sizeof (SECURITY_ATTRIBUTES), NULL, TRUE};
events[0] = the_event = INVALID_HANDLE_VALUE;
events[1] = INVALID_HANDLE_VALUE;
h = CreateEvent (&sa, TRUE, FALSE, NULL);
if (!h)
log_error ("can't create scd event: %s\n", w32_strerror (-1) );
else if (!DuplicateHandle (GetCurrentProcess(), h,
GetCurrentProcess(), &h2,
EVENT_MODIFY_STATE|SYNCHRONIZE, TRUE, 0))
{
log_error ("setting synchronize for scd_kick_the_loop failed: %s\n",
w32_strerror (-1) );
CloseHandle (h);
}
else
{
CloseHandle (h);
events[0] = the_event = h2;
}
}
#endif
FD_ZERO (&fdset);
nfd = 0;
if (listen_fd != -1)
{
FD_SET (listen_fd, &fdset);
nfd = listen_fd;
}
for (;;)
{
int periodical_check;
int max_fd = nfd;
if (shutdown_pending)
{
if (active_connections == 0)
break; /* ready */
/* Do not accept anymore connections but wait for existing
connections to terminate. We do this by clearing out all
file descriptors to wait for, so that the select will be
used to just wait on a signal or timeout event. */
FD_ZERO (&fdset);
listen_fd = -1;
}
periodical_check = scd_update_reader_status_file ();
timeout.tv_sec = TIMERTICK_INTERVAL_SEC;
timeout.tv_nsec = TIMERTICK_INTERVAL_USEC * 1000;
if (shutdown_pending || periodical_check)
t = &timeout;
else
t = NULL;
/* POSIX says that fd_set should be implemented as a structure,
thus a simple assignment is fine to copy the entire set. */
read_fdset = fdset;
#ifdef HAVE_PSELECT_NO_EINTR
FD_SET (pipe_fd[0], &read_fdset);
if (max_fd < pipe_fd[0])
max_fd = pipe_fd[0];
#else
(void)max_fd;
#endif
#ifndef HAVE_W32_SYSTEM
ret = npth_pselect (max_fd+1, &read_fdset, NULL, NULL, t,
npth_sigev_sigmask ());
saved_errno = errno;
while (npth_sigev_get_pending(&signo))
handle_signal (signo);
#else
ret = npth_eselect (nfd+1, &read_fdset, NULL, NULL, t,
events, &events_set);
saved_errno = errno;
if (events_set & 1)
continue;
#endif
if (ret == -1 && saved_errno != EINTR)
{
log_error (_("npth_pselect failed: %s - waiting 1s\n"),
strerror (saved_errno));
npth_sleep (1);
continue;
}
if (ret <= 0)
/* Timeout. Will be handled when calculating the next timeout. */
continue;
#ifdef HAVE_PSELECT_NO_EINTR
if (FD_ISSET (pipe_fd[0], &read_fdset))
{
char buf[256];
read (pipe_fd[0], buf, sizeof buf);
}
#endif
if (listen_fd != -1 && FD_ISSET (listen_fd, &read_fdset))
{
ctrl_t ctrl;
plen = sizeof paddr;
fd = npth_accept (listen_fd, (struct sockaddr *)&paddr, &plen);
if (fd == -1)
{
log_error ("accept failed: %s\n", strerror (errno));
}
else if ( !(ctrl = xtrycalloc (1, sizeof *ctrl)) )
{
log_error ("error allocating connection control data: %s\n",
strerror (errno) );
close (fd);
}
else
{
char threadname[50];
npth_t thread;
snprintf (threadname, sizeof threadname, "conn fd=%d", fd);
ctrl->thread_startup.fd = INT2FD (fd);
ret = npth_create (&thread, &tattr, start_connection_thread, ctrl);
if (ret)
{
log_error ("error spawning connection handler: %s\n",
strerror (ret));
xfree (ctrl);
close (fd);
}
else
npth_setname_np (thread, threadname);
}
}
}
#ifdef HAVE_W32_SYSTEM
if (the_event != INVALID_HANDLE_VALUE)
CloseHandle (the_event);
#endif
#ifdef HAVE_PSELECT_NO_EINTR
close (pipe_fd[0]);
close (pipe_fd[1]);
#endif
cleanup ();
log_info (_("%s %s stopped\n"), strusage(11), strusage(13));
npth_attr_destroy (&tattr);
}
/* Return the number of active connections. */
int
get_active_connection_count (void)
{
return active_connections;
}
diff --git a/scd/scdaemon.h b/scd/scdaemon.h
index 73589ade8..b709b1cbc 100644
--- a/scd/scdaemon.h
+++ b/scd/scdaemon.h
@@ -1,137 +1,150 @@
/* scdaemon.h - Global definitions for the SCdaemon
* 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 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#ifndef SCDAEMON_H
#define SCDAEMON_H
#ifdef GPG_ERR_SOURCE_DEFAULT
#error GPG_ERR_SOURCE_DEFAULT already defined
#endif
#define GPG_ERR_SOURCE_DEFAULT GPG_ERR_SOURCE_SCD
#include <gpg-error.h>
#include <time.h>
#include <gcrypt.h>
#include "../common/util.h"
#include "../common/sysutils.h"
+#include "app-common.h"
+
/* To convey some special hash algorithms we use algorithm numbers
reserved for application use. */
#ifndef GCRY_MODULE_ID_USER
#define GCRY_MODULE_ID_USER 1024
#endif
#define MD_USER_TLS_MD5SHA1 (GCRY_MODULE_ID_USER+1)
/* Maximum length of a digest. */
#define MAX_DIGEST_LEN 64
/* 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 dry_run; /* Don't change any persistent data. */
int batch; /* Batch mode. */
const char *ctapi_driver; /* Library to access the ctAPI. */
const char *pcsc_driver; /* Library to access the PC/SC system. */
const char *reader_port; /* NULL or reder port to use. */
int disable_ccid; /* Disable the use of the internal CCID driver. */
int disable_pinpad; /* Do not use a pinpad. */
int enable_pinpad_varlen; /* Use variable length input for pinpad. */
int allow_admin; /* Allow the use of admin commands for certain
cards. */
strlist_t disabled_applications; /* Card applications we do not
want to use. */
unsigned long card_timeout; /* Disconnect after N seconds of inactivity. */
} opt;
#define DBG_MPI_VALUE 2 /* debug mpi details */
#define DBG_CRYPTO_VALUE 4 /* debug low level crypto */
#define DBG_MEMORY_VALUE 32 /* debug memory allocation stuff */
#define DBG_CACHE_VALUE 64 /* debug the caching */
#define DBG_MEMSTAT_VALUE 128 /* show memory statistics */
#define DBG_HASHING_VALUE 512 /* debug hashing operations */
#define DBG_IPC_VALUE 1024
#define DBG_CARD_IO_VALUE 2048
#define DBG_READER_VALUE 4096 /* Trace reader related functions. */
#define DBG_CRYPTO (opt.debug & DBG_CRYPTO_VALUE)
#define DBG_MEMORY (opt.debug & DBG_MEMORY_VALUE)
#define DBG_CACHE (opt.debug & DBG_CACHE_VALUE)
#define DBG_HASHING (opt.debug & DBG_HASHING_VALUE)
#define DBG_IPC (opt.debug & DBG_IPC_VALUE)
#define DBG_CARD_IO (opt.debug & DBG_CARD_IO_VALUE)
#define DBG_READER (opt.debug & DBG_READER_VALUE)
struct server_local_s;
+struct card_ctx_s;
struct app_ctx_s;
struct server_control_s
{
/* Private data used to fire up the connection thread. We use this
structure do avoid an extra allocation for just a few bytes. */
struct {
gnupg_fd_t fd;
} thread_startup;
/* Local data of the server; used only in command.c. */
struct server_local_s *server_local;
/* The application context used with this connection or NULL if none
associated. Note that this is shared with the other connections:
All connections accessing the same reader are using the same
application context. */
- struct app_ctx_s *app_ctx;
+ struct card_ctx_s *card_ctx;
+
+ /* The currently active application for this context. We need to
+ * know this for cards which are able to switch on the fly between
+ * apps. */
+ apptype_t current_apptype;
/* Helper to store the value we are going to sign */
struct
{
unsigned char *value;
int valuelen;
} in_data;
};
-typedef struct app_ctx_s *app_t;
/*-- scdaemon.c --*/
void scd_exit (int rc);
const char *scd_get_socket_name (void);
/*-- command.c --*/
gpg_error_t initialize_module_command (void);
int scd_command_handler (ctrl_t, int);
+void scd_clear_current_app (card_t card);
void send_status_info (ctrl_t ctrl, const char *keyword, ...)
GPGRT_ATTR_SENTINEL(1);
void send_status_direct (ctrl_t ctrl, const char *keyword, const char *args);
gpg_error_t send_status_printf (ctrl_t ctrl, const char *keyword,
const char *format, ...) GPGRT_ATTR_PRINTF(3,4);
+void send_keyinfo (ctrl_t ctrl, int data, const char *keygrip_str,
+ const char *serialno, const char *idstr);
void popup_prompt (void *opaque, int on);
-void send_client_notifications (app_t app, int removal);
+
+/* Take care: this function assumes that CARD is locked. */
+void send_client_notifications (card_t card, int removal);
+
void scd_kick_the_loop (void);
int get_active_connection_count (void);
/*-- app.c --*/
int scd_update_reader_status_file (void);
#endif /*SCDAEMON_H*/
diff --git a/sm/call-agent.c b/sm/call-agent.c
index 4f2b83f56..b37c2e53d 100644
--- a/sm/call-agent.c
+++ b/sm/call-agent.c
@@ -1,1423 +1,1438 @@
/* call-agent.c - Divert GPGSM operations to the agent
* Copyright (C) 2001, 2002, 2003, 2005, 2007,
* 2008, 2009, 2010 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#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 "../common/i18n.h"
#include "../common/asshelp.h"
#include "keydb.h" /* fixme: Move this to import.c */
#include "../common/membuf.h"
#include "../common/shareddefs.h"
#include "passphrase.h"
static assuan_context_t agent_ctx = NULL;
struct cipher_parm_s
{
ctrl_t ctrl;
assuan_context_t ctx;
const unsigned char *ciphertext;
size_t ciphertextlen;
};
struct genkey_parm_s
{
ctrl_t ctrl;
assuan_context_t ctx;
const unsigned char *sexp;
size_t sexplen;
};
struct learn_parm_s
{
int error;
ctrl_t ctrl;
assuan_context_t ctx;
membuf_t *data;
};
struct import_key_parm_s
{
ctrl_t ctrl;
assuan_context_t ctx;
const void *key;
size_t keylen;
};
struct default_inq_parm_s
{
ctrl_t ctrl;
assuan_context_t ctx;
};
/* Print a warning if the server's version number is less than our
version number. Returns an error code on a connection problem. */
static gpg_error_t
warn_version_mismatch (ctrl_t ctrl, assuan_context_t ctx,
const char *servername, int mode)
{
gpg_error_t err;
char *serverversion;
const char *myversion = strusage (13);
err = get_assuan_server_version (ctx, mode, &serverversion);
if (err)
log_error (_("error getting version from '%s': %s\n"),
servername, gpg_strerror (err));
else if (compare_version_strings (serverversion, myversion) < 0)
{
char *warn;
warn = xtryasprintf (_("server '%s' is older than us (%s < %s)"),
servername, serverversion, myversion);
if (!warn)
err = gpg_error_from_syserror ();
else
{
log_info (_("WARNING: %s\n"), warn);
if (!opt.quiet)
{
log_info (_("Note: Outdated servers may lack important"
" security fixes.\n"));
log_info (_("Note: Use the command \"%s\" to restart them.\n"),
"gpgconf --kill all");
}
gpgsm_status2 (ctrl, STATUS_WARNING, "server_version_mismatch 0",
warn, NULL);
xfree (warn);
}
}
xfree (serverversion);
return err;
}
/* 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 (ctrl_t ctrl)
{
int rc;
if (agent_ctx)
rc = 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. */
else
{
rc = start_new_gpg_agent (&agent_ctx,
GPG_ERR_SOURCE_DEFAULT,
opt.agent_program,
opt.lc_ctype, opt.lc_messages,
opt.session_env,
opt.autostart, opt.verbose, DBG_IPC,
gpgsm_status2, ctrl);
if (!opt.autostart && gpg_err_code (rc) == GPG_ERR_NO_AGENT)
{
static int shown;
if (!shown)
{
shown = 1;
log_info (_("no gpg-agent running in this session\n"));
}
}
else if (!rc && !(rc = warn_version_mismatch (ctrl, agent_ctx,
GPG_AGENT_NAME, 0)))
{
/* Tell the agent that we support Pinentry notifications. No
error checking so that it will work also with older
agents. */
assuan_transact (agent_ctx, "OPTION allow-pinentry-notify",
NULL, NULL, NULL, NULL, NULL, NULL);
/* Pass on the pinentry mode. */
if (opt.pinentry_mode)
{
char *tmp = xasprintf ("OPTION pinentry-mode=%s",
str_pinentry_mode (opt.pinentry_mode));
rc = assuan_transact (agent_ctx, tmp,
NULL, NULL, NULL, NULL, NULL, NULL);
xfree (tmp);
if (rc)
log_error ("setting pinentry mode '%s' failed: %s\n",
str_pinentry_mode (opt.pinentry_mode),
gpg_strerror (rc));
}
/* Pass on the request origin. */
if (opt.request_origin)
{
char *tmp = xasprintf ("OPTION pretend-request-origin=%s",
str_request_origin (opt.request_origin));
rc = assuan_transact (agent_ctx, tmp,
NULL, NULL, NULL, NULL, NULL, NULL);
xfree (tmp);
if (rc)
log_error ("setting request origin '%s' failed: %s\n",
str_request_origin (opt.request_origin),
gpg_strerror (rc));
}
/* In DE_VS mode under Windows we require that the JENT RNG
* is active. */
#ifdef HAVE_W32_SYSTEM
if (!rc && opt.compliance == CO_DE_VS)
{
if (assuan_transact (agent_ctx, "GETINFO jent_active",
NULL, NULL, NULL, NULL, NULL, NULL))
{
rc = gpg_error (GPG_ERR_FORBIDDEN);
log_error (_("%s is not compliant with %s mode\n"),
GPG_AGENT_NAME,
gnupg_compliance_option_string (opt.compliance));
gpgsm_status_with_error (ctrl, STATUS_ERROR,
"random-compliance", rc);
}
}
#endif /*HAVE_W32_SYSTEM*/
}
}
if (!ctrl->agent_seen)
{
ctrl->agent_seen = 1;
audit_log_ok (ctrl->audit, AUDIT_AGENT_READY, rc);
}
return rc;
}
/* This is the default inquiry callback. It mainly handles the
Pinentry notifications. */
static gpg_error_t
default_inq_cb (void *opaque, const char *line)
{
gpg_error_t err = 0;
struct default_inq_parm_s *parm = opaque;
ctrl_t ctrl = parm->ctrl;
if (has_leading_keyword (line, "PINENTRY_LAUNCHED"))
{
err = gpgsm_proxy_pinentry_notify (ctrl, line);
if (err)
log_error (_("failed to proxy %s inquiry to client\n"),
"PINENTRY_LAUNCHED");
/* We do not pass errors to avoid breaking other code. */
}
else if ((has_leading_keyword (line, "PASSPHRASE")
|| has_leading_keyword (line, "NEW_PASSPHRASE"))
&& opt.pinentry_mode == PINENTRY_MODE_LOOPBACK
&& have_static_passphrase ())
{
const char *s = get_static_passphrase ();
err = assuan_send_data (parm->ctx, s, strlen (s));
}
else
log_error ("ignoring gpg-agent inquiry '%s'\n", line);
return err;
}
/* Call the agent to do a sign operation using the key identified by
the hex string KEYGRIP. */
int
gpgsm_agent_pksign (ctrl_t ctrl, const char *keygrip, const char *desc,
unsigned char *digest, size_t digestlen, int digestalgo,
unsigned char **r_buf, size_t *r_buflen )
{
int rc, i;
char *p, line[ASSUAN_LINELENGTH];
membuf_t data;
size_t len;
struct default_inq_parm_s inq_parm;
*r_buf = NULL;
rc = start_agent (ctrl);
if (rc)
return rc;
inq_parm.ctrl = ctrl;
inq_parm.ctx = agent_ctx;
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 rc;
snprintf (line, DIM(line), "SIGKEY %s", keygrip);
rc = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
if (rc)
return rc;
if (desc)
{
snprintf (line, DIM(line), "SETKEYDESC %s", desc);
rc = assuan_transact (agent_ctx, line,
NULL, NULL, NULL, NULL, NULL, NULL);
if (rc)
return 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 rc;
init_membuf (&data, 1024);
rc = assuan_transact (agent_ctx, "PKSIGN",
put_membuf_cb, &data, default_inq_cb, &inq_parm,
NULL, NULL);
if (rc)
{
xfree (get_membuf (&data, &len));
return 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 ();
}
/* Call the scdaemon to do a sign operation using the key identified by
the hex string KEYID. */
int
gpgsm_scd_pksign (ctrl_t ctrl, const char *keyid, const char *desc,
unsigned char *digest, size_t digestlen, int digestalgo,
unsigned char **r_buf, size_t *r_buflen )
{
int rc, i, pkalgo;
char *p, line[ASSUAN_LINELENGTH];
membuf_t data;
size_t len;
const char *hashopt;
unsigned char *sigbuf;
size_t sigbuflen;
struct default_inq_parm_s inq_parm;
gcry_sexp_t sig;
(void)desc;
*r_buf = NULL;
switch(digestalgo)
{
case GCRY_MD_SHA1: hashopt = "--hash=sha1"; break;
case GCRY_MD_RMD160:hashopt = "--hash=rmd160"; break;
case GCRY_MD_MD5: hashopt = "--hash=md5"; break;
case GCRY_MD_SHA256:hashopt = "--hash=sha256"; break;
case GCRY_MD_SHA512:hashopt = "--hash=sha512"; break;
default:
return gpg_error (GPG_ERR_DIGEST_ALGO);
}
rc = start_agent (ctrl);
if (rc)
return rc;
inq_parm.ctrl = ctrl;
inq_parm.ctx = agent_ctx;
if (digestlen*2 + 50 > DIM(line))
return gpg_error (GPG_ERR_GENERAL);
/* Get the key type from the scdaemon. */
snprintf (line, DIM(line), "SCD READKEY %s", keyid);
init_membuf (&data, 1024);
rc = assuan_transact (agent_ctx, line,
put_membuf_cb, &data, NULL, NULL, NULL, NULL);
if (rc)
{
xfree (get_membuf (&data, &len));
return rc;
}
p = get_membuf (&data, &len);
pkalgo = get_pk_algo_from_canon_sexp (p, len);
xfree (p);
if (!pkalgo)
return gpg_error (GPG_ERR_WRONG_PUBKEY_ALGO);
p = stpcpy (line, "SCD SETDATA " );
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 rc;
init_membuf (&data, 1024);
snprintf (line, DIM(line), "SCD PKSIGN %s %s", hashopt, keyid);
rc = assuan_transact (agent_ctx, line,
put_membuf_cb, &data, default_inq_cb, &inq_parm,
NULL, NULL);
if (rc)
{
xfree (get_membuf (&data, &len));
return rc;
}
sigbuf = get_membuf (&data, &sigbuflen);
switch(pkalgo)
{
case GCRY_PK_RSA:
rc = gcry_sexp_build (&sig, NULL, "(sig-val(rsa(s%b)))",
- sigbuflen, sigbuf);
+ (int)sigbuflen, sigbuf);
break;
case GCRY_PK_ECC:
rc = gcry_sexp_build (&sig, NULL, "(sig-val(ecdsa(r%b)(s%b)))",
- sigbuflen/2, sigbuf,
- sigbuflen/2, sigbuf + sigbuflen/2);
+ (int)sigbuflen/2, sigbuf,
+ (int)sigbuflen/2, sigbuf + sigbuflen/2);
break;
case GCRY_PK_EDDSA:
rc = gcry_sexp_build (&sig, NULL, "(sig-val(eddsa(r%b)(s%b)))",
- sigbuflen/2, sigbuf,
- sigbuflen/2, sigbuf + sigbuflen/2);
+ (int)sigbuflen/2, sigbuf,
+ (int)sigbuflen/2, sigbuf + sigbuflen/2);
break;
default:
rc = gpg_error (GPG_ERR_WRONG_PUBKEY_ALGO);
break;
}
xfree (sigbuf);
if (rc)
return rc;
rc = make_canon_sexp (sig, r_buf, r_buflen);
gcry_sexp_release (sig);
if (rc)
return rc;
assert (gcry_sexp_canon_len (*r_buf, *r_buflen, NULL, NULL));
return 0;
}
/* Handle a CIPHERTEXT inquiry. Note, we only send the data,
assuan_transact takes care of flushing and writing the end */
static gpg_error_t
inq_ciphertext_cb (void *opaque, const char *line)
{
struct cipher_parm_s *parm = opaque;
int rc;
if (has_leading_keyword (line, "CIPHERTEXT"))
{
assuan_begin_confidential (parm->ctx);
rc = assuan_send_data (parm->ctx, parm->ciphertext, parm->ciphertextlen);
assuan_end_confidential (parm->ctx);
}
else
{
struct default_inq_parm_s inq_parm = { parm->ctrl, parm->ctx };
rc = default_inq_cb (&inq_parm, line);
}
return rc;
}
/* Call the agent to do a decrypt operation using the key identified by
the hex string KEYGRIP. */
int
gpgsm_agent_pkdecrypt (ctrl_t ctrl, const char *keygrip, const char *desc,
ksba_const_sexp_t ciphertext,
char **r_buf, size_t *r_buflen )
{
int rc;
char line[ASSUAN_LINELENGTH];
membuf_t data;
struct cipher_parm_s cipher_parm;
size_t n, len;
char *p, *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 (ctrl);
if (rc)
return rc;
rc = assuan_transact (agent_ctx, "RESET", NULL, NULL, NULL, NULL, NULL, NULL);
if (rc)
return rc;
assert ( DIM(line) >= 50 );
snprintf (line, DIM(line), "SETKEY %s", keygrip);
rc = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
if (rc)
return rc;
if (desc)
{
snprintf (line, DIM(line), "SETKEYDESC %s", desc);
rc = assuan_transact (agent_ctx, line,
NULL, NULL, NULL, NULL, NULL, NULL);
if (rc)
return rc;
}
init_membuf (&data, 1024);
cipher_parm.ctrl = ctrl;
cipher_parm.ctx = agent_ctx;
cipher_parm.ciphertext = ciphertext;
cipher_parm.ciphertextlen = ciphertextlen;
rc = assuan_transact (agent_ctx, "PKDECRYPT",
put_membuf_cb, &data,
inq_ciphertext_cb, &cipher_parm, NULL, NULL);
if (rc)
{
xfree (get_membuf (&data, &len));
return rc;
}
put_membuf (&data, "", 1); /* Make sure it is 0 terminated. */
buf = get_membuf (&data, &len);
if (!buf)
return gpg_error (GPG_ERR_ENOMEM);
assert (len); /* (we forced Nul termination.) */
if (*buf == '(')
{
if (len < 13 || memcmp (buf, "(5:value", 8) ) /* "(5:valueN:D)\0" */
return gpg_error (GPG_ERR_INV_SEXP);
len -= 11; /* Count only the data of the second part. */
p = buf + 8; /* Skip leading parenthesis and the value tag. */
}
else
{
/* For compatibility with older gpg-agents handle the old style
incomplete S-exps. */
len--; /* Do not count the Nul. */
p = buf;
}
n = strtoul (p, &endp, 10);
if (!n || *endp != ':')
return gpg_error (GPG_ERR_INV_SEXP);
endp++;
if (endp-p+n > len)
return gpg_error (GPG_ERR_INV_SEXP); /* Oops: Inconsistent S-Exp. */
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 gpg_error_t
inq_genkey_parms (void *opaque, const char *line)
{
struct genkey_parm_s *parm = opaque;
int rc;
if (has_leading_keyword (line, "KEYPARAM"))
{
rc = assuan_send_data (parm->ctx, parm->sexp, parm->sexplen);
}
else
{
struct default_inq_parm_s inq_parm = { parm->ctrl, parm->ctx };
rc = default_inq_cb (&inq_parm, line);
}
return rc;
}
/* Call the agent to generate a newkey */
int
gpgsm_agent_genkey (ctrl_t ctrl,
ksba_const_sexp_t keyparms, ksba_sexp_t *r_pubkey)
{
int rc;
struct genkey_parm_s gk_parm;
membuf_t data;
size_t len;
unsigned char *buf;
*r_pubkey = NULL;
rc = start_agent (ctrl);
if (rc)
return rc;
rc = assuan_transact (agent_ctx, "RESET", NULL, NULL, NULL, NULL, NULL, NULL);
if (rc)
return rc;
init_membuf (&data, 1024);
gk_parm.ctrl = ctrl;
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",
put_membuf_cb, &data,
inq_genkey_parms, &gk_parm, NULL, NULL);
if (rc)
{
xfree (get_membuf (&data, &len));
return 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;
}
/* Call the agent to read the public key part for a given keygrip. If
FROMCARD is true, the key is directly read from the current
smartcard. In this case HEXKEYGRIP should be the keyID
(e.g. OPENPGP.3). */
int
gpgsm_agent_readkey (ctrl_t ctrl, int fromcard, const char *hexkeygrip,
ksba_sexp_t *r_pubkey)
{
int rc;
membuf_t data;
size_t len;
unsigned char *buf;
char line[ASSUAN_LINELENGTH];
struct default_inq_parm_s inq_parm;
*r_pubkey = NULL;
rc = start_agent (ctrl);
if (rc)
return rc;
inq_parm.ctrl = ctrl;
inq_parm.ctx = agent_ctx;
rc = assuan_transact (agent_ctx, "RESET",NULL, NULL, NULL, NULL, NULL, NULL);
if (rc)
return rc;
snprintf (line, DIM(line), "%sREADKEY %s",
fromcard? "SCD ":"", hexkeygrip);
init_membuf (&data, 1024);
rc = assuan_transact (agent_ctx, line,
put_membuf_cb, &data,
default_inq_cb, &inq_parm, NULL, NULL);
if (rc)
{
xfree (get_membuf (&data, &len));
return 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;
}
/* Take the serial number from LINE and return it verbatim in a newly
allocated string. We make sure that only hex characters are
returned. */
static char *
store_serialno (const char *line)
{
const char *s;
char *p;
for (s=line; hexdigitp (s); s++)
;
p = xtrymalloc (s + 1 - line);
if (p)
{
memcpy (p, line, s-line);
p[s-line] = 0;
}
return p;
}
/* Callback for the gpgsm_agent_serialno function. */
static gpg_error_t
scd_serialno_status_cb (void *opaque, const char *line)
{
char **r_serialno = opaque;
const char *keyword = line;
int keywordlen;
for (keywordlen=0; *line && !spacep (line); line++, keywordlen++)
;
while (spacep (line))
line++;
if (keywordlen == 8 && !memcmp (keyword, "SERIALNO", keywordlen))
{
xfree (*r_serialno);
*r_serialno = store_serialno (line);
}
return 0;
}
/* Call the agent to read the serial number of the current card. */
int
gpgsm_agent_scd_serialno (ctrl_t ctrl, char **r_serialno)
{
int rc;
char *serialno = NULL;
struct default_inq_parm_s inq_parm;
*r_serialno = NULL;
rc = start_agent (ctrl);
if (rc)
return rc;
inq_parm.ctrl = ctrl;
inq_parm.ctx = agent_ctx;
rc = assuan_transact (agent_ctx, "SCD SERIALNO",
NULL, NULL,
default_inq_cb, &inq_parm,
scd_serialno_status_cb, &serialno);
if (!rc && !serialno)
rc = gpg_error (GPG_ERR_INTERNAL);
if (rc)
{
xfree (serialno);
return rc;
}
*r_serialno = serialno;
return 0;
}
/* Callback for the gpgsm_agent_serialno function. */
static gpg_error_t
scd_keypairinfo_status_cb (void *opaque, const char *line)
{
strlist_t *listaddr = opaque;
const char *keyword = line;
int keywordlen;
strlist_t sl;
char *p;
for (keywordlen=0; *line && !spacep (line); line++, keywordlen++)
;
while (spacep (line))
line++;
if (keywordlen == 11 && !memcmp (keyword, "KEYPAIRINFO", keywordlen))
{
sl = append_to_strlist (listaddr, line);
p = sl->d;
- /* Make sure that we only have two tokes so that future
- extensions of the format won't change the format expected by
- the caller. */
+ /* Make sure that we only have two tokens so that future
+ * extensions of the format won't change the format expected by
+ * the caller. */
while (*p && !spacep (p))
p++;
if (*p)
{
while (spacep (p))
p++;
while (*p && !spacep (p))
p++;
- *p = 0;
+ if (*p)
+ {
+ *p++ = 0;
+ while (spacep (p))
+ p++;
+ while (*p && !spacep (p))
+ {
+ switch (*p++)
+ {
+ case 'c': sl->flags |= GCRY_PK_USAGE_CERT; break;
+ case 's': sl->flags |= GCRY_PK_USAGE_SIGN; break;
+ case 'e': sl->flags |= GCRY_PK_USAGE_ENCR; break;
+ case 'a': sl->flags |= GCRY_PK_USAGE_AUTH; break;
+ }
+ }
+ }
}
}
return 0;
}
/* Call the agent to read the keypairinfo lines of the current card.
The list is returned as a string made up of the keygrip, a space
- and the keyid. */
+ and the keyid. The flags of the string carry the usage bits. */
int
gpgsm_agent_scd_keypairinfo (ctrl_t ctrl, strlist_t *r_list)
{
int rc;
strlist_t list = NULL;
struct default_inq_parm_s inq_parm;
*r_list = NULL;
rc = start_agent (ctrl);
if (rc)
return rc;
inq_parm.ctrl = ctrl;
inq_parm.ctx = agent_ctx;
- rc = assuan_transact (agent_ctx, "SCD LEARN --force",
+ rc = assuan_transact (agent_ctx, "SCD LEARN --keypairinfo",
NULL, NULL,
default_inq_cb, &inq_parm,
scd_keypairinfo_status_cb, &list);
if (!rc && !list)
rc = gpg_error (GPG_ERR_NO_DATA);
if (rc)
{
free_strlist (list);
return rc;
}
*r_list = list;
return 0;
}
static gpg_error_t
istrusted_status_cb (void *opaque, const char *line)
{
struct rootca_flags_s *flags = opaque;
const char *s;
if ((s = has_leading_keyword (line, "TRUSTLISTFLAG")))
{
line = s;
if (has_leading_keyword (line, "relax"))
flags->relax = 1;
else if (has_leading_keyword (line, "cm"))
flags->chain_model = 1;
}
return 0;
}
/* Ask the agent whether the certificate is in the list of trusted
keys. The certificate is either specified by the CERT object or by
the fingerprint HEXFPR. ROOTCA_FLAGS is guaranteed to be cleared
on error. */
int
gpgsm_agent_istrusted (ctrl_t ctrl, ksba_cert_t cert, const char *hexfpr,
struct rootca_flags_s *rootca_flags)
{
int rc;
char line[ASSUAN_LINELENGTH];
memset (rootca_flags, 0, sizeof *rootca_flags);
if (cert && hexfpr)
return gpg_error (GPG_ERR_INV_ARG);
rc = start_agent (ctrl);
if (rc)
return rc;
if (hexfpr)
{
snprintf (line, DIM(line), "ISTRUSTED %s", hexfpr);
}
else
{
char *fpr;
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), "ISTRUSTED %s", fpr);
xfree (fpr);
}
rc = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL,
istrusted_status_cb, rootca_flags);
if (!rc)
rootca_flags->valid = 1;
return rc;
}
/* Ask the agent to mark CERT as a trusted Root-CA one */
int
gpgsm_agent_marktrusted (ctrl_t ctrl, ksba_cert_t cert)
{
int rc;
char *fpr, *dn, *dnfmt;
char line[ASSUAN_LINELENGTH];
struct default_inq_parm_s inq_parm;
rc = start_agent (ctrl);
if (rc)
return rc;
inq_parm.ctrl = ctrl;
inq_parm.ctx = agent_ctx;
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);
}
dnfmt = gpgsm_format_name2 (dn, 0);
xfree (dn);
if (!dnfmt)
return gpg_error_from_syserror ();
snprintf (line, DIM(line), "MARKTRUSTED %s S %s", fpr, dnfmt);
ksba_free (dnfmt);
xfree (fpr);
rc = assuan_transact (agent_ctx, line, NULL, NULL,
default_inq_cb, &inq_parm, NULL, NULL);
return rc;
}
/* Ask the agent whether the a corresponding secret key is available
for the given keygrip */
int
gpgsm_agent_havekey (ctrl_t ctrl, const char *hexkeygrip)
{
int rc;
char line[ASSUAN_LINELENGTH];
rc = start_agent (ctrl);
if (rc)
return rc;
if (!hexkeygrip || strlen (hexkeygrip) != 40)
return gpg_error (GPG_ERR_INV_VALUE);
snprintf (line, DIM(line), "HAVEKEY %s", hexkeygrip);
rc = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
return rc;
}
static gpg_error_t
learn_status_cb (void *opaque, const char *line)
{
struct learn_parm_s *parm = opaque;
const char *s;
/* Pass progress data to the caller. */
if ((s = has_leading_keyword (line, "PROGRESS")))
{
line = s;
if (parm->ctrl)
{
if (gpgsm_status (parm->ctrl, STATUS_PROGRESS, line))
return gpg_error (GPG_ERR_ASS_CANCELED);
}
}
return 0;
}
static gpg_error_t
learn_cb (void *opaque, const void *buffer, size_t length)
{
struct learn_parm_s *parm = opaque;
size_t len;
char *buf;
ksba_cert_t cert;
int rc;
if (parm->error)
return 0;
if (buffer)
{
put_membuf (parm->data, buffer, length);
return 0;
}
/* END encountered - process what we have */
buf = get_membuf (parm->data, &len);
if (!buf)
{
parm->error = gpg_error (GPG_ERR_ENOMEM);
return 0;
}
if (gpgsm_status (parm->ctrl, STATUS_PROGRESS, "learncard C 0 0"))
return gpg_error (GPG_ERR_ASS_CANCELED);
/* FIXME: this should go into import.c */
rc = ksba_cert_new (&cert);
if (rc)
{
parm->error = rc;
return 0;
}
rc = ksba_cert_init_from_mem (cert, buf, len);
if (rc)
{
log_error ("failed to parse a certificate: %s\n", gpg_strerror (rc));
ksba_cert_release (cert);
parm->error = rc;
return 0;
}
/* We do not store a certifciate with missing issuers as ephemeral
because we can assume that the --learn-card command has been used
on purpose. */
rc = gpgsm_basic_cert_check (parm->ctrl, cert);
if (rc && gpg_err_code (rc) != GPG_ERR_MISSING_CERT
&& gpg_err_code (rc) != GPG_ERR_MISSING_ISSUER_CERT)
log_error ("invalid certificate: %s\n", gpg_strerror (rc));
else
{
int existed;
if (!keydb_store_cert (parm->ctrl, 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 (ctrl_t ctrl)
{
int rc;
struct learn_parm_s learn_parm;
membuf_t data;
size_t len;
rc = start_agent (ctrl);
if (rc)
return rc;
rc = warn_version_mismatch (ctrl, agent_ctx, SCDAEMON_NAME, 2);
if (rc)
return rc;
init_membuf (&data, 4096);
learn_parm.error = 0;
learn_parm.ctrl = ctrl;
learn_parm.ctx = agent_ctx;
learn_parm.data = &data;
rc = assuan_transact (agent_ctx, "LEARN --send",
learn_cb, &learn_parm,
NULL, NULL,
learn_status_cb, &learn_parm);
xfree (get_membuf (&data, &len));
if (rc)
return rc;
return learn_parm.error;
}
/* Ask the agent to change the passphrase of the key identified by
HEXKEYGRIP. If DESC is not NULL, display instead of the default
description message. */
int
gpgsm_agent_passwd (ctrl_t ctrl, const char *hexkeygrip, const char *desc)
{
int rc;
char line[ASSUAN_LINELENGTH];
struct default_inq_parm_s inq_parm;
rc = start_agent (ctrl);
if (rc)
return rc;
inq_parm.ctrl = ctrl;
inq_parm.ctx = agent_ctx;
if (!hexkeygrip || strlen (hexkeygrip) != 40)
return gpg_error (GPG_ERR_INV_VALUE);
if (desc)
{
snprintf (line, DIM(line), "SETKEYDESC %s", desc);
rc = assuan_transact (agent_ctx, line,
NULL, NULL, NULL, NULL, NULL, NULL);
if (rc)
return rc;
}
snprintf (line, DIM(line), "PASSWD %s", hexkeygrip);
rc = assuan_transact (agent_ctx, line, NULL, NULL,
default_inq_cb, &inq_parm, NULL, NULL);
return rc;
}
/* Ask the agent to pop up a confirmation dialog with the text DESC
and an okay and cancel button. */
gpg_error_t
gpgsm_agent_get_confirmation (ctrl_t ctrl, const char *desc)
{
int rc;
char line[ASSUAN_LINELENGTH];
struct default_inq_parm_s inq_parm;
rc = start_agent (ctrl);
if (rc)
return rc;
inq_parm.ctrl = ctrl;
inq_parm.ctx = agent_ctx;
snprintf (line, DIM(line), "GET_CONFIRMATION %s", desc);
rc = assuan_transact (agent_ctx, line, NULL, NULL,
default_inq_cb, &inq_parm, NULL, NULL);
return rc;
}
/* Return 0 if the agent is alive. This is useful to make sure that
an agent has been started. */
gpg_error_t
gpgsm_agent_send_nop (ctrl_t ctrl)
{
int rc;
rc = start_agent (ctrl);
if (!rc)
rc = assuan_transact (agent_ctx, "NOP",
NULL, NULL, NULL, NULL, NULL, NULL);
return rc;
}
static gpg_error_t
keyinfo_status_cb (void *opaque, const char *line)
{
char **serialno = opaque;
const char *s, *s2;
if ((s = has_leading_keyword (line, "KEYINFO")) && !*serialno)
{
s = strchr (s, ' ');
if (s && s[1] == 'T' && s[2] == ' ' && s[3])
{
s += 3;
s2 = strchr (s, ' ');
if ( s2 > s )
{
*serialno = xtrymalloc ((s2 - s)+1);
if (*serialno)
{
memcpy (*serialno, s, s2 - s);
(*serialno)[s2 - s] = 0;
}
}
}
}
return 0;
}
/* Return the serial number for a secret key. If the returned serial
number is NULL, the key is not stored on a smartcard. Caller needs
to free R_SERIALNO. */
gpg_error_t
gpgsm_agent_keyinfo (ctrl_t ctrl, const char *hexkeygrip, char **r_serialno)
{
gpg_error_t err;
char line[ASSUAN_LINELENGTH];
char *serialno = NULL;
*r_serialno = NULL;
err = start_agent (ctrl);
if (err)
return err;
if (!hexkeygrip || strlen (hexkeygrip) != 40)
return gpg_error (GPG_ERR_INV_VALUE);
snprintf (line, DIM(line), "KEYINFO %s", hexkeygrip);
err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL,
keyinfo_status_cb, &serialno);
if (!err && serialno)
{
/* Sanity check for bad characters. */
if (strpbrk (serialno, ":\n\r"))
err = GPG_ERR_INV_VALUE;
}
if (err)
xfree (serialno);
else
*r_serialno = serialno;
return err;
}
/* Ask for the passphrase (this is used for pkcs#12 import/export. On
success the caller needs to free the string stored at R_PASSPHRASE.
On error NULL will be stored at R_PASSPHRASE and an appropriate
error code returned. If REPEAT is true the agent tries to get a
new passphrase (i.e. asks the user to confirm it). */
gpg_error_t
gpgsm_agent_ask_passphrase (ctrl_t ctrl, const char *desc_msg, int repeat,
char **r_passphrase)
{
gpg_error_t err;
char line[ASSUAN_LINELENGTH];
char *arg4 = NULL;
membuf_t data;
struct default_inq_parm_s inq_parm;
*r_passphrase = NULL;
err = start_agent (ctrl);
if (err)
return err;
inq_parm.ctrl = ctrl;
inq_parm.ctx = agent_ctx;
if (desc_msg && *desc_msg && !(arg4 = percent_plus_escape (desc_msg)))
return gpg_error_from_syserror ();
snprintf (line, DIM(line), "GET_PASSPHRASE --data%s -- X X X %s",
repeat? " --repeat=1 --check --qualitybar":"",
arg4);
xfree (arg4);
init_membuf_secure (&data, 64);
err = assuan_transact (agent_ctx, line,
put_membuf_cb, &data,
default_inq_cb, &inq_parm, NULL, NULL);
if (err)
xfree (get_membuf (&data, NULL));
else
{
put_membuf (&data, "", 1);
*r_passphrase = get_membuf (&data, NULL);
if (!*r_passphrase)
err = gpg_error_from_syserror ();
}
return err;
}
/* Retrieve a key encryption key from the agent. With FOREXPORT true
the key shall be use for export, with false for import. On success
the new key is stored at R_KEY and its length at R_KEKLEN. */
gpg_error_t
gpgsm_agent_keywrap_key (ctrl_t ctrl, int forexport,
void **r_kek, size_t *r_keklen)
{
gpg_error_t err;
membuf_t data;
size_t len;
unsigned char *buf;
char line[ASSUAN_LINELENGTH];
struct default_inq_parm_s inq_parm;
*r_kek = NULL;
err = start_agent (ctrl);
if (err)
return err;
inq_parm.ctrl = ctrl;
inq_parm.ctx = agent_ctx;
snprintf (line, DIM(line), "KEYWRAP_KEY %s",
forexport? "--export":"--import");
init_membuf_secure (&data, 64);
err = assuan_transact (agent_ctx, line,
put_membuf_cb, &data,
default_inq_cb, &inq_parm, NULL, NULL);
if (err)
{
xfree (get_membuf (&data, &len));
return err;
}
buf = get_membuf (&data, &len);
if (!buf)
return gpg_error_from_syserror ();
*r_kek = buf;
*r_keklen = len;
return 0;
}
/* Handle the inquiry for an IMPORT_KEY command. */
static gpg_error_t
inq_import_key_parms (void *opaque, const char *line)
{
struct import_key_parm_s *parm = opaque;
gpg_error_t err;
if (has_leading_keyword (line, "KEYDATA"))
{
assuan_begin_confidential (parm->ctx);
err = assuan_send_data (parm->ctx, parm->key, parm->keylen);
assuan_end_confidential (parm->ctx);
}
else
{
struct default_inq_parm_s inq_parm = { parm->ctrl, parm->ctx };
err = default_inq_cb (&inq_parm, line);
}
return err;
}
/* Call the agent to import a key into the agent. */
gpg_error_t
gpgsm_agent_import_key (ctrl_t ctrl, const void *key, size_t keylen)
{
gpg_error_t err;
struct import_key_parm_s parm;
err = start_agent (ctrl);
if (err)
return err;
parm.ctrl = ctrl;
parm.ctx = agent_ctx;
parm.key = key;
parm.keylen = keylen;
err = assuan_transact (agent_ctx, "IMPORT_KEY",
NULL, NULL, inq_import_key_parms, &parm, NULL, NULL);
return err;
}
/* Receive a secret key from the agent. KEYGRIP is the hexified
keygrip, DESC a prompt to be displayed with the agent's passphrase
question (needs to be plus+percent escaped). On success the key is
stored as a canonical S-expression at R_RESULT and R_RESULTLEN. */
gpg_error_t
gpgsm_agent_export_key (ctrl_t ctrl, const char *keygrip, const char *desc,
unsigned char **r_result, size_t *r_resultlen)
{
gpg_error_t err;
membuf_t data;
size_t len;
unsigned char *buf;
char line[ASSUAN_LINELENGTH];
struct default_inq_parm_s inq_parm;
*r_result = NULL;
err = start_agent (ctrl);
if (err)
return err;
inq_parm.ctrl = ctrl;
inq_parm.ctx = agent_ctx;
if (desc)
{
snprintf (line, DIM(line), "SETKEYDESC %s", desc);
err = assuan_transact (agent_ctx, line,
NULL, NULL, NULL, NULL, NULL, NULL);
if (err)
return err;
}
snprintf (line, DIM(line), "EXPORT_KEY %s", keygrip);
init_membuf_secure (&data, 1024);
err = assuan_transact (agent_ctx, line,
put_membuf_cb, &data,
default_inq_cb, &inq_parm, NULL, NULL);
if (err)
{
xfree (get_membuf (&data, &len));
return err;
}
buf = get_membuf (&data, &len);
if (!buf)
return gpg_error_from_syserror ();
*r_result = buf;
*r_resultlen = len;
return 0;
}
diff --git a/sm/certlist.c b/sm/certlist.c
index 12a492518..b3d113bfd 100644
--- a/sm/certlist.c
+++ b/sm/certlist.c
@@ -1,608 +1,608 @@
/* certlist.c - build list of certificates
* Copyright (C) 2001, 2003, 2004, 2005, 2007,
* 2008, 2011 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#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 "../common/i18n.h"
static const char oid_kp_serverAuth[] = "1.3.6.1.5.5.7.3.1";
static const char oid_kp_clientAuth[] = "1.3.6.1.5.5.7.3.2";
static const char oid_kp_codeSigning[] = "1.3.6.1.5.5.7.3.3";
static const char oid_kp_emailProtection[]= "1.3.6.1.5.5.7.3.4";
static const char oid_kp_timeStamping[] = "1.3.6.1.5.5.7.3.8";
static const char oid_kp_ocspSigning[] = "1.3.6.1.5.5.7.3.9";
/* 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). MODE 4 is for certificate signing, MODE for COSP
response signing. */
static int
-cert_usage_p (ksba_cert_t cert, int mode)
+cert_usage_p (ksba_cert_t cert, int mode, int silent)
{
gpg_error_t err;
unsigned int use;
char *extkeyusages;
int have_ocsp_signing = 0;
err = ksba_cert_get_ext_key_usages (cert, &extkeyusages);
if (gpg_err_code (err) == GPG_ERR_NO_DATA)
err = 0; /* no policy given */
if (!err)
{
unsigned int extusemask = ~0; /* Allow all. */
if (extkeyusages)
{
char *p, *pend;
int any_critical = 0;
extusemask = 0;
p = extkeyusages;
while (p && (pend=strchr (p, ':')))
{
*pend++ = 0;
/* Only care about critical flagged usages. */
if ( *pend == 'C' )
{
any_critical = 1;
if ( !strcmp (p, oid_kp_serverAuth))
extusemask |= (KSBA_KEYUSAGE_DIGITAL_SIGNATURE
| KSBA_KEYUSAGE_KEY_ENCIPHERMENT
| KSBA_KEYUSAGE_KEY_AGREEMENT);
else if ( !strcmp (p, oid_kp_clientAuth))
extusemask |= (KSBA_KEYUSAGE_DIGITAL_SIGNATURE
| KSBA_KEYUSAGE_KEY_AGREEMENT);
else if ( !strcmp (p, oid_kp_codeSigning))
extusemask |= (KSBA_KEYUSAGE_DIGITAL_SIGNATURE);
else if ( !strcmp (p, oid_kp_emailProtection))
extusemask |= (KSBA_KEYUSAGE_DIGITAL_SIGNATURE
| KSBA_KEYUSAGE_NON_REPUDIATION
| KSBA_KEYUSAGE_KEY_ENCIPHERMENT
| KSBA_KEYUSAGE_KEY_AGREEMENT);
else if ( !strcmp (p, oid_kp_timeStamping))
extusemask |= (KSBA_KEYUSAGE_DIGITAL_SIGNATURE
| KSBA_KEYUSAGE_NON_REPUDIATION);
}
/* This is a hack to cope with OCSP. Note that we do
not yet fully comply with the requirements and that
the entire CRL/OCSP checking thing should undergo a
thorough review and probably redesign. */
if ( !strcmp (p, oid_kp_ocspSigning))
have_ocsp_signing = 1;
if ((p = strchr (pend, '\n')))
p++;
}
xfree (extkeyusages);
extkeyusages = NULL;
if (!any_critical)
extusemask = ~0; /* Reset to the don't care mask. */
}
err = ksba_cert_get_key_usage (cert, &use);
if (gpg_err_code (err) == GPG_ERR_NO_DATA)
{
err = 0;
- if (opt.verbose && mode < 2)
+ if (opt.verbose && mode < 2 && !silent)
log_info (_("no key usage specified - assuming all usages\n"));
use = ~0;
}
/* Apply extKeyUsage. */
use &= extusemask;
}
if (err)
{
log_error (_("error getting key usage information: %s\n"),
gpg_strerror (err));
xfree (extkeyusages);
return err;
}
if (mode == 4)
{
if ((use & (KSBA_KEYUSAGE_KEY_CERT_SIGN)))
return 0;
- log_info (_("certificate should not have "
- "been used for certification\n"));
+ if (!silent)
+ log_info (_("certificate should not have "
+ "been used for certification\n"));
return gpg_error (GPG_ERR_WRONG_KEY_USAGE);
}
if (mode == 5)
{
if (use != ~0
&& (have_ocsp_signing
|| (use & (KSBA_KEYUSAGE_KEY_CERT_SIGN
|KSBA_KEYUSAGE_CRL_SIGN))))
return 0;
- log_info (_("certificate should not have "
- "been used for OCSP response signing\n"));
+ if (!silent)
+ log_info (_("certificate should not have "
+ "been used for OCSP response signing\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 not have been used for encryption\n"):
- mode==2? _("certificate should not have been used for signing\n"):
- mode==1? _("certificate is not usable for encryption\n"):
- _("certificate is not usable for signing\n"));
+ if (!silent)
+ log_info
+ (mode==3? _("certificate should not have been used for encryption\n"):
+ mode==2? _("certificate should not have 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 (ksba_cert_t cert)
+gpgsm_cert_use_sign_p (ksba_cert_t cert, int silent)
{
- return cert_usage_p (cert, 0);
+ return cert_usage_p (cert, 0, silent);
}
/* Return 0 if the cert is usable for encryption */
int
gpgsm_cert_use_encrypt_p (ksba_cert_t cert)
{
- return cert_usage_p (cert, 1);
+ return cert_usage_p (cert, 1, 0);
}
int
gpgsm_cert_use_verify_p (ksba_cert_t cert)
{
- return cert_usage_p (cert, 2);
+ return cert_usage_p (cert, 2, 0);
}
int
gpgsm_cert_use_decrypt_p (ksba_cert_t cert)
{
- return cert_usage_p (cert, 3);
+ return cert_usage_p (cert, 3, 0);
}
int
gpgsm_cert_use_cert_p (ksba_cert_t cert)
{
- return cert_usage_p (cert, 4);
+ return cert_usage_p (cert, 4, 0);
}
int
gpgsm_cert_use_ocsp_p (ksba_cert_t cert)
{
- return cert_usage_p (cert, 5);
+ return cert_usage_p (cert, 5, 0);
}
/* Return true if CERT has the well known private key extension. */
int
gpgsm_cert_has_well_known_private_key (ksba_cert_t cert)
{
int idx;
const char *oid;
for (idx=0; !ksba_cert_get_extension (cert, idx,
&oid, NULL, NULL, NULL);idx++)
if (!strcmp (oid, "1.3.6.1.4.1.11591.2.2.2") )
return 1; /* Yes. */
return 0; /* No. */
}
static int
same_subject_issuer (const char *subject, const char *issuer, ksba_cert_t cert)
{
char *subject2 = ksba_cert_get_subject (cert, 0);
char *issuer2 = ksba_cert_get_issuer (cert, 0);
int tmp;
tmp = (subject && subject2
&& !strcmp (subject, subject2)
&& issuer && issuer2
&& !strcmp (issuer, issuer2));
xfree (subject2);
xfree (issuer2);
return tmp;
}
/* Return true if CERT_A is the same as CERT_B. */
int
gpgsm_certs_identical_p (ksba_cert_t cert_a, ksba_cert_t cert_b)
{
const unsigned char *img_a, *img_b;
size_t len_a, len_b;
img_a = ksba_cert_get_image (cert_a, &len_a);
if (img_a)
{
img_b = ksba_cert_get_image (cert_b, &len_b);
if (img_b && len_a == len_b && !memcmp (img_a, img_b, len_a))
return 1; /* Identical. */
}
return 0;
}
/* Return true if CERT is already contained in CERTLIST. */
static int
is_cert_in_certlist (ksba_cert_t cert, certlist_t certlist)
{
const unsigned char *img_a, *img_b;
size_t len_a, len_b;
img_a = ksba_cert_get_image (cert, &len_a);
if (img_a)
{
for ( ; certlist; certlist = certlist->next)
{
img_b = ksba_cert_get_image (certlist->cert, &len_b);
if (img_b && len_a == len_b && !memcmp (img_a, img_b, len_a))
return 1; /* Already contained. */
}
}
return 0;
}
/* Add CERT to the list of certificates at CERTADDR but avoid
duplicates. */
int
gpgsm_add_cert_to_certlist (ctrl_t ctrl, ksba_cert_t cert,
certlist_t *listaddr, int is_encrypt_to)
{
(void)ctrl;
if (!is_cert_in_certlist (cert, *listaddr))
{
certlist_t cl = xtrycalloc (1, sizeof *cl);
if (!cl)
return out_of_core ();
cl->cert = cert;
ksba_cert_ref (cert);
cl->next = *listaddr;
cl->is_encrypt_to = is_encrypt_to;
*listaddr = cl;
}
return 0;
}
/* 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
available for the certificate. IS_ENCRYPT_TO sets the corresponding
flag in the new create LISTADDR item. */
int
gpgsm_add_to_certlist (ctrl_t ctrl, const char *name, int secret,
certlist_t *listaddr, int is_encrypt_to)
{
int rc;
KEYDB_SEARCH_DESC desc;
KEYDB_HANDLE kh = NULL;
ksba_cert_t cert = NULL;
rc = classify_user_id (name, &desc, 0);
if (!rc)
{
kh = keydb_new ();
if (!kh)
rc = gpg_error (GPG_ERR_ENOMEM);
else
{
int wrong_usage = 0;
char *first_subject = NULL;
char *first_issuer = NULL;
get_next:
rc = keydb_search (ctrl, kh, &desc, 1);
if (!rc)
rc = keydb_get_cert (kh, &cert);
if (!rc)
{
if (!first_subject)
{
/* Save the subject and the issuer for key usage
and ambiguous name tests. */
first_subject = ksba_cert_get_subject (cert, 0);
first_issuer = ksba_cert_get_issuer (cert, 0);
}
- rc = secret? gpgsm_cert_use_sign_p (cert)
+ rc = secret? gpgsm_cert_use_sign_p (cert, 0)
: 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;
- ksba_cert_release (cert);
- cert = NULL;
- goto get_next;
- }
- else if (same_subject_issuer (first_subject, first_issuer,
- cert))
+ if (!wrong_usage
+ || same_subject_issuer (first_subject, first_issuer,cert))
{
- wrong_usage = rc;
+ if (!wrong_usage)
+ wrong_usage = rc; /* save error of the first match */
ksba_cert_release (cert);
cert = NULL;
+ log_info (_("looking for another certificate\n"));
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)
{
certlist_t dup_certs = NULL;
next_ambigious:
rc = keydb_search (ctrl, kh, &desc, 1);
if (rc == -1)
rc = 0;
else if (!rc)
{
ksba_cert_t cert2 = NULL;
/* If this is the first possible duplicate, add the original
certificate to our list of duplicates. */
if (!dup_certs)
gpgsm_add_cert_to_certlist (ctrl, cert, &dup_certs, 0);
/* We have to ignore ambiguous names as long as
there only fault is a bad key usage. This is
required to support encryption and signing
certificates of the same subject.
Further we ignore them if they are due to an
identical certificate (which may happen if a
certificate is accidentally duplicated in the
keybox). */
if (!keydb_get_cert (kh, &cert2))
{
int tmp = (same_subject_issuer (first_subject,
first_issuer,
cert2)
&& ((gpg_err_code (
- secret? gpgsm_cert_use_sign_p (cert2)
- : gpgsm_cert_use_encrypt_p (cert2)
+ secret? gpgsm_cert_use_sign_p (cert2,0)
+ : gpgsm_cert_use_encrypt_p (cert2)
)
) == GPG_ERR_WRONG_KEY_USAGE));
if (tmp)
gpgsm_add_cert_to_certlist (ctrl, cert2,
&dup_certs, 0);
else
{
if (is_cert_in_certlist (cert2, dup_certs))
tmp = 1;
}
ksba_cert_release (cert2);
if (tmp)
goto next_ambigious;
}
rc = gpg_error (GPG_ERR_AMBIGUOUS_NAME);
}
gpgsm_release_certlist (dup_certs);
}
xfree (first_subject);
xfree (first_issuer);
first_subject = NULL;
first_issuer = NULL;
if (!rc && !is_cert_in_certlist (cert, *listaddr))
{
if (!rc && secret)
{
char *p;
rc = gpg_error (GPG_ERR_NO_SECKEY);
p = gpgsm_get_keygrip_hexstring (cert);
if (p)
{
if (!gpgsm_agent_havekey (ctrl, p))
rc = 0;
xfree (p);
}
}
if (!rc)
rc = gpgsm_validate_chain (ctrl, cert, "", NULL,
0, NULL, 0, NULL);
if (!rc)
{
certlist_t cl = xtrycalloc (1, sizeof *cl);
if (!cl)
rc = out_of_core ();
else
{
cl->cert = cert; cert = NULL;
cl->next = *listaddr;
cl->is_encrypt_to = is_encrypt_to;
*listaddr = cl;
}
}
}
}
}
keydb_release (kh);
ksba_cert_release (cert);
return rc == -1? gpg_error (GPG_ERR_NO_PUBKEY): rc;
}
void
gpgsm_release_certlist (certlist_t list)
{
while (list)
{
certlist_t 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. If KEYID is not NULL it is taken as an
additional filter value which must match the
subjectKeyIdentifier. */
int
gpgsm_find_cert (ctrl_t ctrl,
const char *name, ksba_sexp_t keyid, ksba_cert_t *r_cert,
int allow_ambiguous)
{
int rc;
KEYDB_SEARCH_DESC desc;
KEYDB_HANDLE kh = NULL;
*r_cert = NULL;
rc = classify_user_id (name, &desc, 0);
if (!rc)
{
kh = keydb_new ();
if (!kh)
rc = gpg_error (GPG_ERR_ENOMEM);
else
{
nextone:
rc = keydb_search (ctrl, kh, &desc, 1);
if (!rc)
{
rc = keydb_get_cert (kh, r_cert);
if (!rc && keyid)
{
ksba_sexp_t subj;
rc = ksba_cert_get_subj_key_id (*r_cert, NULL, &subj);
if (!rc)
{
if (cmp_simple_canon_sexp (keyid, subj))
{
xfree (subj);
goto nextone;
}
xfree (subj);
/* Okay: Here we know that the certificate's
subjectKeyIdentifier matches the requested
one. */
}
else if (gpg_err_code (rc) == GPG_ERR_NO_DATA)
goto nextone;
}
}
/* If we don't have the KEYID filter we need to check for
ambiguous search results. Note, that it is somewhat
reasonable to assume that a specification of a KEYID
won't lead to ambiguous names. */
if (!rc && !keyid)
{
ksba_isotime_t notbefore = "";
const unsigned char *image = NULL;
size_t length = 0;
if (allow_ambiguous)
{
/* We want to return the newest certificate */
if (ksba_cert_get_validity (*r_cert, 0, notbefore))
*notbefore = '\0';
image = ksba_cert_get_image (*r_cert, &length);
}
next_ambiguous:
rc = keydb_search (ctrl, kh, &desc, 1);
if (rc == -1)
rc = 0;
else
{
if (!rc)
{
ksba_cert_t cert2 = NULL;
ksba_isotime_t notbefore2 = "";
const unsigned char *image2 = NULL;
size_t length2 = 0;
int cmp = 0;
if (!keydb_get_cert (kh, &cert2))
{
if (gpgsm_certs_identical_p (*r_cert, cert2))
{
ksba_cert_release (cert2);
goto next_ambiguous;
}
if (allow_ambiguous)
{
if (ksba_cert_get_validity (cert2, 0, notbefore2))
*notbefore2 = '\0';
image2 = ksba_cert_get_image (cert2, &length2);
cmp = strcmp (notbefore, notbefore2);
/* use certificate image bits as last resort for stable ordering */
if (!cmp)
cmp = memcmp (image, image2, length < length2 ? length : length2);
if (!cmp)
cmp = length < length2 ? -1 : length > length2 ? 1 : 0;
if (cmp < 0)
{
ksba_cert_release (*r_cert);
*r_cert = cert2;
strcpy (notbefore, notbefore2);
image = image2;
length = length2;
}
else
ksba_cert_release (cert2);
goto next_ambiguous;
}
ksba_cert_release (cert2);
}
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-ui.c b/sm/certreqgen-ui.c
index 70e5739e8..ae9ec35d0 100644
--- a/sm/certreqgen-ui.c
+++ b/sm/certreqgen-ui.c
@@ -1,451 +1,473 @@
/* certreqgen-ui.c - Simple user interface for certreqgen.c
* Copyright (C) 2007, 2010, 2011 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#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 "../common/i18n.h"
#include "../common/ttyio.h"
#include "../common/membuf.h"
/* Prompt for lines and append them to MB. */
static void
ask_mb_lines (membuf_t *mb, const char *prefix)
{
char *answer = NULL;
do
{
xfree (answer);
answer = tty_get ("> ");
tty_kill_prompt ();
trim_spaces (answer);
if (*answer)
{
put_membuf_str (mb, prefix);
put_membuf_str (mb, answer);
put_membuf (mb, "\n", 1);
}
}
while (*answer);
xfree (answer);
}
/* Helper to store stuff in a membuf. */
void
store_key_value_lf (membuf_t *mb, const char *key, const char *value)
{
put_membuf_str (mb, key);
put_membuf_str (mb, value);
put_membuf (mb, "\n", 1);
}
/* Helper tp store a membuf create by mb_ask_lines into MB. Returns
-1 on error. */
int
store_mb_lines (membuf_t *mb, membuf_t *lines)
{
char *p;
if (get_membuf_len (lines))
{
put_membuf (lines, "", 1);
p = get_membuf (lines, NULL);
if (!p)
return -1;
put_membuf_str (mb, p);
xfree (p);
}
return 0;
}
/* Check whether we have a key for the key with HEXGRIP. Returns NULL
if not or a string describing the type of the key (RSA, ELG, DSA,
etc..). */
static const char *
check_keygrip (ctrl_t ctrl, const char *hexgrip)
{
gpg_error_t err;
ksba_sexp_t public;
size_t publiclen;
int algo;
if (hexgrip[0] == '&')
hexgrip++;
err = gpgsm_agent_readkey (ctrl, 0, hexgrip, &public);
if (err)
return NULL;
publiclen = gcry_sexp_canon_len (public, 0, NULL, NULL);
algo = get_pk_algo_from_canon_sexp (public, publiclen);
xfree (public);
switch (algo)
{
case GCRY_PK_RSA: return "RSA";
case GCRY_PK_DSA: return "DSA";
case GCRY_PK_ELG: return "ELG";
case GCRY_PK_EDDSA: return "ECDSA";
default: return NULL;
}
}
/* This function is used to create a certificate request from the
command line. In the past the similar gpgsm-gencert.sh script has
been used for it; however that scripts requires a full Unix shell
and thus is not suitable for the Windows port. So here is the
re-implementation. */
void
gpgsm_gencertreq_tty (ctrl_t ctrl, estream_t output_stream)
{
gpg_error_t err;
char *answer;
int selection;
estream_t fp = NULL;
int method;
char *keytype_buffer = NULL;
const char *keytype;
char *keygrip = NULL;
unsigned int nbits;
int minbits = 1024;
int maxbits = 4096;
int defbits = 3072;
const char *keyusage;
char *subject_name;
membuf_t mb_email, mb_dns, mb_uri, mb_result;
char *result = NULL;
const char *s, *s2;
int selfsigned;
answer = NULL;
init_membuf (&mb_email, 100);
init_membuf (&mb_dns, 100);
init_membuf (&mb_uri, 100);
init_membuf (&mb_result, 512);
again:
/* Get the type of the key. */
tty_printf (_("Please select what kind of key you want:\n"));
tty_printf (_(" (%d) RSA\n"), 1 );
tty_printf (_(" (%d) Existing key\n"), 2 );
tty_printf (_(" (%d) Existing key from card\n"), 3 );
do
{
xfree (answer);
answer = tty_get (_("Your selection? "));
tty_kill_prompt ();
selection = *answer? atoi (answer): 1;
}
while (!(selection >= 1 && selection <= 3));
method = selection;
/* Get size of the key. */
if (method == 1)
{
keytype = "RSA";
for (;;)
{
xfree (answer);
answer = tty_getf (_("What keysize do you want? (%u) "), defbits);
tty_kill_prompt ();
trim_spaces (answer);
nbits = *answer? atoi (answer): defbits;
if (nbits < minbits || nbits > maxbits)
tty_printf(_("%s keysizes must be in the range %u-%u\n"),
"RSA", minbits, maxbits);
else
break; /* Okay. */
}
tty_printf (_("Requested keysize is %u bits\n"), nbits);
/* We round it up so that it better matches the word size. */
if (( nbits % 64))
{
nbits = ((nbits + 63) / 64) * 64;
tty_printf (_("rounded up to %u bits\n"), nbits);
}
}
else if (method == 2)
{
for (;;)
{
xfree (answer);
answer = tty_get (_("Enter the keygrip: "));
tty_kill_prompt ();
trim_spaces (answer);
if (!*answer)
goto again;
else if (strlen (answer) != 40 &&
!(answer[0] == '&' && strlen (answer+1) == 40))
tty_printf (_("Not a valid keygrip (expecting 40 hex digits)\n"));
else if (!(keytype = check_keygrip (ctrl, answer)) )
tty_printf (_("No key with this keygrip\n"));
else
break; /* Okay. */
}
xfree (keygrip);
keygrip = answer;
answer = NULL;
nbits = 1024; /* A dummy value is sufficient. */
}
else /* method == 3 */
{
char *serialno;
strlist_t keypairlist, sl;
int count;
err = gpgsm_agent_scd_serialno (ctrl, &serialno);
if (err)
{
tty_printf (_("error reading the card: %s\n"), gpg_strerror (err));
goto again;
}
tty_printf (_("Serial number of the card: %s\n"), serialno);
xfree (serialno);
err = gpgsm_agent_scd_keypairinfo (ctrl, &keypairlist);
if (err)
{
tty_printf (_("error reading the card: %s\n"), gpg_strerror (err));
goto again;
}
do
{
tty_printf (_("Available keys:\n"));
for (count=1,sl=keypairlist; sl; sl = sl->next, count++)
{
ksba_sexp_t pkey;
gcry_sexp_t s_pkey;
char *algostr = NULL;
const char *keyref;
+ int any = 0;
keyref = strchr (sl->d, ' ');
if (keyref)
{
keyref++;
if (!gpgsm_agent_readkey (ctrl, 1, keyref, &pkey))
{
if (!gcry_sexp_new (&s_pkey, pkey, 0, 0))
- algostr = pubkey_algo_string (s_pkey);
+ algostr = pubkey_algo_string (s_pkey, NULL);
gcry_sexp_release (s_pkey);
}
xfree (pkey);
}
- tty_printf (" (%d) %s %s\n", count, sl->d, algostr);
+ tty_printf (" (%d) %s %s", count, sl->d, algostr);
+ if ((sl->flags & GCRY_PK_USAGE_CERT))
+ {
+ tty_printf ("%scert", any?",":" (");
+ any = 1;
+ }
+ if ((sl->flags & GCRY_PK_USAGE_SIGN))
+ {
+ tty_printf ("%ssign", any?",":" (");
+ any = 1;
+ }
+ if ((sl->flags & GCRY_PK_USAGE_AUTH))
+ {
+ tty_printf ("%sauth", any?",":" (");
+ any = 1;
+ }
+ if ((sl->flags & GCRY_PK_USAGE_ENCR))
+ {
+ tty_printf ("%sencr", any?",":" (");
+ any = 1;
+ }
+ tty_printf ("%s\n", any?")":"");
xfree (algostr);
}
xfree (answer);
answer = tty_get (_("Your selection? "));
tty_kill_prompt ();
trim_spaces (answer);
selection = atoi (answer);
}
while (!(selection > 0 && selection < count));
for (count=1,sl=keypairlist; sl; sl = sl->next, count++)
if (count == selection)
break;
s = sl->d;
while (*s && !spacep (s))
s++;
while (spacep (s))
s++;
xfree (keygrip);
keygrip = NULL;
xfree (keytype_buffer);
keytype_buffer = xasprintf ("card:%s", s);
free_strlist (keypairlist);
keytype = keytype_buffer;
nbits = 1024; /* A dummy value is sufficient. */
}
/* Ask for the key usage. */
tty_printf (_("Possible actions for a %s key:\n"), "RSA");
tty_printf (_(" (%d) sign, encrypt\n"), 1 );
tty_printf (_(" (%d) sign\n"), 2 );
tty_printf (_(" (%d) encrypt\n"), 3 );
do
{
xfree (answer);
answer = tty_get (_("Your selection? "));
tty_kill_prompt ();
trim_spaces (answer);
selection = *answer? atoi (answer): 1;
switch (selection)
{
case 1: keyusage = "sign, encrypt"; break;
case 2: keyusage = "sign"; break;
case 3: keyusage = "encrypt"; break;
default: keyusage = NULL; break;
}
}
while (!keyusage);
/* Get the subject name. */
do
{
size_t erroff, errlen;
xfree (answer);
answer = tty_get (_("Enter the X.509 subject name: "));
tty_kill_prompt ();
trim_spaces (answer);
if (!*answer)
tty_printf (_("No subject name given\n"));
else if ( (err = ksba_dn_teststr (answer, 0, &erroff, &errlen)) )
{
if (gpg_err_code (err) == GPG_ERR_UNKNOWN_NAME)
tty_printf (_("Invalid subject name label '%.*s'\n"),
(int)errlen, answer+erroff);
else
{
/* TRANSLATORS: The 22 in the second string is the
length of the first string up to the "%s". Please
adjust it do the length of your translation. The
second string is merely passed to atoi so you can
drop everything after the number. */
tty_printf (_("Invalid subject name '%s'\n"), answer);
tty_printf ("%*s^\n",
atoi (_("22 translator: see "
"certreg-ui.c:gpgsm_gencertreq_tty"))
+ (int)erroff, "");
}
*answer = 0;
}
}
while (!*answer);
subject_name = answer;
answer = NULL;
/* Get the email addresses. */
tty_printf (_("Enter email addresses"));
tty_printf (_(" (end with an empty line):\n"));
ask_mb_lines (&mb_email, "Name-Email: ");
/* DNS names. */
tty_printf (_("Enter DNS names"));
tty_printf (_(" (optional; end with an empty line):\n"));
ask_mb_lines (&mb_dns, "Name-DNS: ");
/* URIs. */
tty_printf (_("Enter URIs"));
tty_printf (_(" (optional; end with an empty line):\n"));
ask_mb_lines (&mb_uri, "Name-URI: ");
/* Want a self-signed certificate? */
selfsigned = tty_get_answer_is_yes
(_("Create self-signed certificate? (y/N) "));
/* Put it all together. */
store_key_value_lf (&mb_result, "Key-Type: ", keytype);
{
char numbuf[30];
snprintf (numbuf, sizeof numbuf, "%u", nbits);
store_key_value_lf (&mb_result, "Key-Length: ", numbuf);
}
if (keygrip)
store_key_value_lf (&mb_result, "Key-Grip: ", keygrip);
store_key_value_lf (&mb_result, "Key-Usage: ", keyusage);
if (selfsigned)
store_key_value_lf (&mb_result, "Serial: ", "random");
store_key_value_lf (&mb_result, "Name-DN: ", subject_name);
if (store_mb_lines (&mb_result, &mb_email))
goto mem_error;
if (store_mb_lines (&mb_result, &mb_dns))
goto mem_error;
if (store_mb_lines (&mb_result, &mb_uri))
goto mem_error;
put_membuf (&mb_result, "", 1);
result = get_membuf (&mb_result, NULL);
if (!result)
goto mem_error;
tty_printf (_("These parameters are used:\n"));
for (s=result; (s2 = strchr (s, '\n')); s = s2+1)
tty_printf (" %.*s\n", (int)(s2-s), s);
tty_printf ("\n");
if (!tty_get_answer_is_yes ("Proceed with creation? (y/N) "))
goto leave;
/* Now create a parameter file and generate the key. */
fp = es_fopenmem (0, "w+");
if (!fp)
{
log_error (_("error creating temporary file: %s\n"), strerror (errno));
goto leave;
}
es_fputs (result, fp);
es_rewind (fp);
if (selfsigned)
tty_printf ("%s", _("Now creating self-signed certificate. "));
else
tty_printf ("%s", _("Now creating certificate request. "));
tty_printf ("%s", _("This may take a while ...\n"));
{
int save_pem = ctrl->create_pem;
ctrl->create_pem = 1; /* Force creation of PEM. */
err = gpgsm_genkey (ctrl, fp, output_stream);
ctrl->create_pem = save_pem;
}
if (!err)
{
if (selfsigned)
tty_printf (_("Ready.\n"));
else
tty_printf
(_("Ready. You should now send this request to your CA.\n"));
}
goto leave;
mem_error:
log_error (_("resource problem: out of core\n"));
leave:
es_fclose (fp);
xfree (answer);
xfree (subject_name);
xfree (keytype_buffer);
xfree (keygrip);
xfree (get_membuf (&mb_email, NULL));
xfree (get_membuf (&mb_dns, NULL));
xfree (get_membuf (&mb_uri, NULL));
xfree (get_membuf (&mb_result, NULL));
xfree (result);
}
diff --git a/sm/decrypt.c b/sm/decrypt.c
index b0ab63f00..ec9800840 100644
--- a/sm/decrypt.c
+++ b/sm/decrypt.c
@@ -1,648 +1,650 @@
/* decrypt.c - Decrypt a message
* Copyright (C) 2001, 2003, 2010 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#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 "../common/i18n.h"
#include "../common/compliance.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; /* did 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 (ctrl_t ctrl, const char *hexkeygrip, const char *desc,
ksba_const_sexp_t enc_val,
struct decrypt_filter_parm_s *parm)
{
char *seskey = NULL;
size_t n, seskeylen;
int rc;
rc = gpgsm_agent_pkdecrypt (ctrl, hexkeygrip, desc, enc_val,
&seskey, &seskeylen);
if (rc)
{
log_error ("error decrypting session key: %s\n", gpg_strerror (rc));
goto leave;
}
if (DBG_CRYPTO)
log_printhex (seskey, seskeylen, "pkcs1 encoded session key:");
n=0;
if (seskeylen == 24 || seskeylen == 16)
{
/* Smells like a 3-DES or AES-128 key. This might happen
* because a SC has already done the unpacking. A better
* solution would be to test for this only after we triggered
* the GPG_ERR_INV_SESSION_KEY. */
}
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 right way 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 (seskey+n, seskeylen-n, "session key:");
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 processed or fewer bytes
are written. */
static gpg_error_t
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 gpg_error (GPG_ERR_BUG);
if (maxoutlen < 2*parm->blklen)
return gpg_error (GPG_ERR_BUG);
/* Make some space because 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 gpg_error (GPG_ERR_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_t ctrl, int in_fd, estream_t out_fp)
{
int rc;
gnupg_ksba_io_t b64reader = NULL;
gnupg_ksba_io_t b64writer = NULL;
ksba_reader_t reader;
ksba_writer_t writer;
ksba_cms_t cms = NULL;
ksba_stop_reason_t stopreason;
KEYDB_HANDLE kh;
int recp;
estream_t in_fp = NULL;
struct decrypt_filter_parm_s dfparm;
memset (&dfparm, 0, sizeof dfparm);
audit_set_type (ctrl->audit, AUDIT_TYPE_DECRYPT);
kh = keydb_new ();
if (!kh)
{
log_error (_("failed to allocate keyDB handle\n"));
rc = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
in_fp = es_fdopen_nc (in_fd, "rb");
if (!in_fp)
{
rc = gpg_error_from_syserror ();
log_error ("fdopen() failed: %s\n", strerror (errno));
goto leave;
}
rc = gnupg_ksba_create_reader
(&b64reader, ((ctrl->is_pem? GNUPG_KSBA_IO_PEM : 0)
| (ctrl->is_base64? GNUPG_KSBA_IO_BASE64 : 0)
| (ctrl->autodetect_encoding? GNUPG_KSBA_IO_AUTODETECT : 0)),
in_fp, &reader);
if (rc)
{
log_error ("can't create reader: %s\n", gpg_strerror (rc));
goto leave;
}
rc = gnupg_ksba_create_writer
(&b64writer, ((ctrl->create_pem? GNUPG_KSBA_IO_PEM : 0)
| (ctrl->create_base64? GNUPG_KSBA_IO_BASE64 : 0)),
ctrl->pem_name, out_fp, &writer);
if (rc)
{
log_error ("can't create writer: %s\n", gpg_strerror (rc));
goto leave;
}
rc = ksba_cms_new (&cms);
if (rc)
goto leave;
rc = ksba_cms_set_reader_writer (cms, reader, writer);
if (rc)
{
log_debug ("ksba_cms_set_reader_writer failed: %s\n",
gpg_strerror (rc));
goto leave;
}
audit_log (ctrl->audit, AUDIT_SETUP_READY);
/* Parser loop. */
do
{
rc = ksba_cms_parse (cms, &stopreason);
if (rc)
{
log_debug ("ksba_cms_parse failed: %s\n", gpg_strerror (rc));
goto leave;
}
if (stopreason == KSBA_SR_BEGIN_DATA
|| stopreason == KSBA_SR_DETACHED_DATA)
{
int algo, mode;
const char *algoid;
int any_key = 0;
int is_de_vs; /* Computed compliance with CO_DE_VS. */
audit_log (ctrl->audit, AUDIT_GOT_DATA);
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);
audit_log_s (ctrl->audit, AUDIT_BAD_DATA_CIPHER_ALGO, algoid);
}
/* If it seems that this is not an encrypted message we
return a more sensible error code. */
if (!algoid)
rc = gpg_error (GPG_ERR_NO_DATA);
goto leave;
}
/* Check compliance. */
if (! gnupg_cipher_is_allowed (opt.compliance, 0, algo, mode))
{
log_error (_("cipher algorithm '%s'"
" may not be used in %s mode\n"),
gcry_cipher_algo_name (algo),
gnupg_compliance_option_string (opt.compliance));
rc = gpg_error (GPG_ERR_CIPHER_ALGO);
goto leave;
}
/* For CMS, CO_DE_VS demands CBC mode. */
is_de_vs = gnupg_cipher_is_compliant (CO_DE_VS, algo, mode);
audit_log_i (ctrl->audit, AUDIT_DATA_CIPHER_ALGO, algo);
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", gpg_strerror (rc));
goto leave;
}
for (recp=0; !any_key; recp++)
{
char *issuer;
ksba_sexp_t serial;
ksba_sexp_t enc_val;
char *hexkeygrip = NULL;
char *desc = NULL;
char kidbuf[16+1];
+ int tmp_rc;
*kidbuf = 0;
- rc = ksba_cms_get_issuer_serial (cms, recp, &issuer, &serial);
- if (rc == -1 && recp)
+ tmp_rc = ksba_cms_get_issuer_serial (cms, recp, &issuer, &serial);
+ if (tmp_rc == -1 && recp)
break; /* no more recipients */
audit_log_i (ctrl->audit, AUDIT_NEW_RECP, recp);
- if (rc)
+ if (tmp_rc)
log_error ("recp %d - error getting info: %s\n",
- recp, gpg_strerror (rc));
+ recp, gpg_strerror (tmp_rc));
else
{
ksba_cert_t 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");
if (ctrl->audit)
{
char *tmpstr = gpgsm_format_sn_issuer (serial, issuer);
audit_log_s (ctrl->audit, AUDIT_RECP_NAME, tmpstr);
xfree (tmpstr);
}
keydb_search_reset (kh);
rc = keydb_search_issuer_sn (ctrl, 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;
}
/* Print the ENC_TO status line. Note that we can
do so only if we have the certificate. This is
in contrast to gpg where the keyID is commonly
included in the encrypted messages. It is too
cumbersome to retrieve the used algorithm, thus
we don't print it for now. We also record the
keyid for later use. */
{
unsigned long kid[2];
kid[0] = gpgsm_get_short_fingerprint (cert, kid+1);
snprintf (kidbuf, sizeof kidbuf, "%08lX%08lX",
kid[1], kid[0]);
gpgsm_status2 (ctrl, STATUS_ENC_TO,
kidbuf, "0", "0", NULL);
}
/* Put the certificate into the audit log. */
audit_log_cert (ctrl->audit, AUDIT_SAVE_CERT, cert, 0);
/* 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);
desc = gpgsm_format_keydesc (cert);
{
unsigned int nbits;
int pk_algo = gpgsm_get_key_algo_info (cert, &nbits);
/* Check compliance. */
if (!gnupg_pk_is_allowed (opt.compliance,
PK_USE_DECRYPTION,
pk_algo, NULL, nbits, NULL))
{
char kidstr[10+1];
snprintf (kidstr, sizeof kidstr, "0x%08lX",
gpgsm_get_short_fingerprint (cert, NULL));
log_info
(_("key %s is not suitable for decryption"
" in %s mode\n"),
kidstr,
gnupg_compliance_option_string (opt.compliance));
rc = gpg_error (GPG_ERR_PUBKEY_ALGO);
goto oops;
}
/* Check that all certs are compliant with CO_DE_VS. */
is_de_vs =
(is_de_vs
&& gnupg_pk_is_compliant (CO_DE_VS, pk_algo, NULL,
nbits, NULL));
}
oops:
if (rc)
{
/* We cannot check compliance of certs that we
* don't have. */
is_de_vs = 0;
}
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 (ctrl,
hexkeygrip, desc, enc_val, &dfparm);
xfree (enc_val);
if (rc)
{
log_info ("decrypting session key failed: %s\n",
gpg_strerror (rc));
if (gpg_err_code (rc) == GPG_ERR_NO_SECKEY && *kidbuf)
gpgsm_status2 (ctrl, STATUS_NO_SECKEY, kidbuf, NULL);
}
else
{ /* setup the bulk decrypter */
any_key = 1;
ksba_writer_set_filter (writer,
decrypt_filter,
&dfparm);
if (is_de_vs)
gpgsm_status (ctrl, STATUS_DECRYPTION_COMPLIANCE_MODE,
gnupg_status_compliance_flag (CO_DE_VS));
}
audit_log_ok (ctrl->audit, AUDIT_RECP_RESULT, rc);
}
xfree (hexkeygrip);
xfree (desc);
}
/* If we write an audit log add the unused recipients to the
log as well. */
if (ctrl->audit && any_key)
{
for (;; recp++)
{
char *issuer;
ksba_sexp_t serial;
int tmp_rc;
tmp_rc = ksba_cms_get_issuer_serial (cms, recp,
&issuer, &serial);
if (tmp_rc == -1)
break; /* no more recipients */
audit_log_i (ctrl->audit, AUDIT_NEW_RECP, recp);
if (tmp_rc)
log_error ("recp %d - error getting info: %s\n",
- recp, gpg_strerror (rc));
+ recp, gpg_strerror (tmp_rc));
else
{
char *tmpstr = gpgsm_format_sn_issuer (serial, issuer);
audit_log_s (ctrl->audit, AUDIT_RECP_NAME, tmpstr);
xfree (tmpstr);
xfree (issuer);
xfree (serial);
}
}
}
if (!any_key)
{
- rc = gpg_error (GPG_ERR_NO_SECKEY);
+ if (!rc)
+ 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)
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 = gnupg_ksba_finish_writer (b64writer);
if (rc)
{
log_error ("write failed: %s\n", gpg_strerror (rc));
goto leave;
}
gpgsm_status (ctrl, STATUS_DECRYPTION_OKAY, NULL);
leave:
audit_log_ok (ctrl->audit, AUDIT_DECRYPTION_RESULT, rc);
if (rc)
{
gpgsm_status (ctrl, STATUS_DECRYPTION_FAILED, NULL);
log_error ("message decryption failed: %s <%s>\n",
gpg_strerror (rc), gpg_strsource (rc));
}
ksba_cms_release (cms);
gnupg_ksba_destroy_reader (b64reader);
gnupg_ksba_destroy_writer (b64writer);
keydb_release (kh);
es_fclose (in_fp);
if (dfparm.hd)
gcry_cipher_close (dfparm.hd);
return rc;
}
diff --git a/sm/delete.c b/sm/delete.c
index f359cc595..b370406de 100644
--- a/sm/delete.c
+++ b/sm/delete.c
@@ -1,180 +1,181 @@
/* delete.c - Delete certificates from the keybox.
* Copyright (C) 2002, 2009 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#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 "../common/i18n.h"
/* Delete a certificate or an secret key from a key database. */
static int
delete_one (ctrl_t ctrl, const char *username)
{
int rc = 0;
KEYDB_SEARCH_DESC desc;
KEYDB_HANDLE kh = NULL;
ksba_cert_t cert = NULL;
int duplicates = 0;
int is_ephem = 0;
rc = classify_user_id (username, &desc, 0);
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 ();
if (!kh)
{
log_error ("keydb_new failed\n");
goto leave;
}
/* If the key is specified in a unique way, include ephemeral keys
in the search. */
if ( desc.mode == KEYDB_SEARCH_MODE_FPR
|| desc.mode == KEYDB_SEARCH_MODE_KEYGRIP )
{
is_ephem = 1;
keydb_set_ephemeral (kh, 1);
}
rc = keydb_search (ctrl, kh, &desc, 1);
if (!rc)
rc = keydb_get_cert (kh, &cert);
if (!rc && !is_ephem)
{
unsigned char fpr[20];
gpgsm_get_fingerprint (cert, 0, fpr, NULL);
next_ambigious:
rc = keydb_search (ctrl, kh, &desc, 1);
if (rc == -1)
rc = 0;
else if (!rc)
{
ksba_cert_t cert2 = NULL;
unsigned 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. */
+ /* We need to search again to get back to the right position. Neo
+ * that the lock is kept until the KH is released. */
rc = keydb_lock (kh);
if (rc)
{
log_error (_("error locking keybox: %s\n"), gpg_strerror (rc));
goto leave;
}
do
{
keydb_search_reset (kh);
rc = keydb_search (ctrl, kh, &desc, 1);
if (rc)
{
log_error ("problem re-searching certificate: %s\n",
gpg_strerror (rc));
goto leave;
}
- rc = keydb_delete (kh, duplicates ? 0 : 1);
+ 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_t ctrl, strlist_t 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/gpgsm.c b/sm/gpgsm.c
index 2f9e5bfd2..f5837079d 100644
--- a/sm/gpgsm.c
+++ b/sm/gpgsm.c
@@ -1,2302 +1,2329 @@
/* gpgsm.c - GnuPG for S/MIME
* Copyright (C) 2001-2008, 2010 Free Software Foundation, Inc.
* Copyright (C) 2001-2008, 2010 Werner Koch
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#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 <mcheck.h>*/
#include "gpgsm.h"
#include <gcrypt.h>
#include <assuan.h> /* malloc hooks */
#include "passphrase.h"
#include "../common/shareddefs.h"
#include "../kbx/keybox.h" /* malloc hooks */
#include "../common/i18n.h"
#include "keydb.h"
#include "../common/sysutils.h"
#include "../common/gc-opt-flags.h"
#include "../common/asshelp.h"
#include "../common/init.h"
#include "../common/compliance.h"
#ifndef O_BINARY
#define O_BINARY 0
#endif
enum cmd_and_opt_values {
aNull = 0,
oArmor = 'a',
aDetachedSign = 'b',
aSym = 'c',
aDecrypt = 'd',
aEncr = 'e',
aListKeys = 'k',
aListSecretKeys = 'K',
oDryRun = 'n',
oOutput = 'o',
oQuiet = 'q',
oRecipient = 'r',
aSign = 's',
oUser = 'u',
oVerbose = 'v',
oBatch = 500,
aClearsign,
aKeygen,
aSignEncr,
aDeleteKey,
aImport,
aVerify,
aListExternalKeys,
aListChain,
aSendKeys,
aRecvKeys,
aExport,
aExportSecretKeyP12,
aExportSecretKeyP8,
aExportSecretKeyRaw,
aServer,
aLearnCard,
aCallDirmngr,
aCallProtectTool,
aPasswd,
aGPGConfList,
aGPGConfTest,
aDumpKeys,
aDumpChain,
aDumpSecretKeys,
aDumpExternalKeys,
aKeydbClearSomeCertFlags,
aFingerprint,
oOptions,
oDebug,
oDebugLevel,
oDebugAll,
oDebugNone,
oDebugWait,
oDebugAllowCoreDump,
oDebugNoChainValidation,
oDebugIgnoreExpiration,
oLogFile,
oNoLogFile,
oAuditLog,
oHtmlAuditLog,
oEnableSpecialFilenames,
oAgentProgram,
oDisplay,
oTTYname,
oTTYtype,
oLCctype,
oLCmessages,
oXauthority,
oPreferSystemDirmngr,
oDirmngrProgram,
oDisableDirmngr,
oProtectToolProgram,
oFakedSystemTime,
oPassphraseFD,
oPinentryMode,
oRequestOrigin,
oAssumeArmor,
oAssumeBase64,
oAssumeBinary,
oBase64,
oNoArmor,
oP12Charset,
oCompliance,
oDisableCRLChecks,
oEnableCRLChecks,
oDisableTrustedCertCRLCheck,
oEnableTrustedCertCRLCheck,
oForceCRLRefresh,
oDisableOCSP,
oEnableOCSP,
oIncludeCerts,
oPolicyFile,
oDisablePolicyChecks,
oEnablePolicyChecks,
oAutoIssuerKeyRetrieve,
oWithFingerprint,
oWithMD5Fingerprint,
oWithKeygrip,
oWithSecret,
oWithKeyScreening,
oAnswerYes,
oAnswerNo,
oKeyring,
oDefaultKey,
oDefRecipient,
oDefRecipientSelf,
oNoDefRecipient,
oStatusFD,
oCipherAlgo,
oDigestAlgo,
oExtraDigestAlgo,
oNoVerbose,
oNoSecmemWarn,
oNoDefKeyring,
oNoGreeting,
oNoTTY,
oNoOptions,
oNoBatch,
oHomedir,
oWithColons,
oWithKeyData,
oWithValidation,
oWithEphemeralKeys,
oSkipVerify,
oValidationModel,
oKeyServer,
oEncryptTo,
oNoEncryptTo,
oLoggerFD,
oDisableCipherAlgo,
oDisablePubkeyAlgo,
oIgnoreTimeConflict,
oNoRandomSeedFile,
oNoCommonCertsImport,
oIgnoreCertExtension,
+ oAuthenticode,
+ oAttribute,
oNoAutostart
};
static ARGPARSE_OPTS opts[] = {
ARGPARSE_group (300, N_("@Commands:\n ")),
ARGPARSE_c (aSign, "sign", N_("make a signature")),
/*ARGPARSE_c (aClearsign, "clearsign", N_("make a clear text signature") ),*/
ARGPARSE_c (aDetachedSign, "detach-sign", N_("make a detached signature")),
ARGPARSE_c (aEncr, "encrypt", N_("encrypt data")),
/*ARGPARSE_c (aSym, "symmetric", N_("encryption only with symmetric cipher")),*/
ARGPARSE_c (aDecrypt, "decrypt", N_("decrypt data (default)")),
ARGPARSE_c (aVerify, "verify", N_("verify a signature")),
ARGPARSE_c (aListKeys, "list-keys", N_("list keys")),
ARGPARSE_c (aListExternalKeys, "list-external-keys",
N_("list external keys")),
ARGPARSE_c (aListSecretKeys, "list-secret-keys", N_("list secret keys")),
ARGPARSE_c (aListChain, "list-chain", N_("list certificate chain")),
ARGPARSE_c (aFingerprint, "fingerprint", N_("list keys and fingerprints")),
ARGPARSE_c (aKeygen, "generate-key", N_("generate a new key pair")),
ARGPARSE_c (aKeygen, "gen-key", "@"),
ARGPARSE_c (aDeleteKey, "delete-keys",
N_("remove keys from the public keyring")),
/*ARGPARSE_c (aSendKeys, "send-keys", N_("export keys to a keyserver")),*/
/*ARGPARSE_c (aRecvKeys, "recv-keys", N_("import keys from a keyserver")),*/
ARGPARSE_c (aImport, "import", N_("import certificates")),
ARGPARSE_c (aExport, "export", N_("export certificates")),
/* We use -raw and not -p1 for pkcs#1 secret key export so that it
won't accidentally be used in case -p12 was intended. */
ARGPARSE_c (aExportSecretKeyP12, "export-secret-key-p12", "@"),
ARGPARSE_c (aExportSecretKeyP8, "export-secret-key-p8", "@"),
ARGPARSE_c (aExportSecretKeyRaw, "export-secret-key-raw", "@"),
ARGPARSE_c (aLearnCard, "learn-card", N_("register a smartcard")),
ARGPARSE_c (aServer, "server", N_("run in server mode")),
ARGPARSE_c (aCallDirmngr, "call-dirmngr",
N_("pass a command to the dirmngr")),
ARGPARSE_c (aCallProtectTool, "call-protect-tool",
N_("invoke gpg-protect-tool")),
ARGPARSE_c (aPasswd, "change-passphrase", N_("change a passphrase")),
ARGPARSE_c (aPasswd, "passwd", "@"),
ARGPARSE_c (aGPGConfList, "gpgconf-list", "@"),
ARGPARSE_c (aGPGConfTest, "gpgconf-test", "@"),
ARGPARSE_c (aDumpKeys, "dump-cert", "@"),
ARGPARSE_c (aDumpKeys, "dump-keys", "@"),
ARGPARSE_c (aDumpChain, "dump-chain", "@"),
ARGPARSE_c (aDumpExternalKeys, "dump-external-keys", "@"),
ARGPARSE_c (aDumpSecretKeys, "dump-secret-keys", "@"),
ARGPARSE_c (aKeydbClearSomeCertFlags, "keydb-clear-some-cert-flags", "@"),
ARGPARSE_group (301, N_("@\nOptions:\n ")),
ARGPARSE_s_n (oArmor, "armor", N_("create ascii armored output")),
ARGPARSE_s_n (oArmor, "armour", "@"),
ARGPARSE_s_n (oBase64, "base64", N_("create base-64 encoded output")),
ARGPARSE_s_s (oP12Charset, "p12-charset", "@"),
ARGPARSE_s_i (oPassphraseFD, "passphrase-fd", "@"),
ARGPARSE_s_s (oPinentryMode, "pinentry-mode", "@"),
ARGPARSE_s_s (oRequestOrigin, "request-origin", "@"),
ARGPARSE_s_n (oAssumeArmor, "assume-armor",
N_("assume input is in PEM format")),
ARGPARSE_s_n (oAssumeBase64, "assume-base64",
N_("assume input is in base-64 format")),
ARGPARSE_s_n (oAssumeBinary, "assume-binary",
N_("assume input is in binary format")),
ARGPARSE_s_s (oRecipient, "recipient", N_("|USER-ID|encrypt for USER-ID")),
ARGPARSE_s_n (oPreferSystemDirmngr,"prefer-system-dirmngr", "@"),
ARGPARSE_s_n (oDisableCRLChecks, "disable-crl-checks",
N_("never consult a CRL")),
ARGPARSE_s_n (oEnableCRLChecks, "enable-crl-checks", "@"),
ARGPARSE_s_n (oDisableTrustedCertCRLCheck,
"disable-trusted-cert-crl-check", "@"),
ARGPARSE_s_n (oEnableTrustedCertCRLCheck,
"enable-trusted-cert-crl-check", "@"),
ARGPARSE_s_n (oForceCRLRefresh, "force-crl-refresh", "@"),
ARGPARSE_s_n (oDisableOCSP, "disable-ocsp", "@"),
ARGPARSE_s_n (oEnableOCSP, "enable-ocsp", N_("check validity using OCSP")),
ARGPARSE_s_s (oValidationModel, "validation-model", "@"),
ARGPARSE_s_i (oIncludeCerts, "include-certs",
N_("|N|number of certificates to include") ),
ARGPARSE_s_s (oPolicyFile, "policy-file",
N_("|FILE|take policy information from FILE")),
ARGPARSE_s_n (oDisablePolicyChecks, "disable-policy-checks",
N_("do not check certificate policies")),
ARGPARSE_s_n (oEnablePolicyChecks, "enable-policy-checks", "@"),
ARGPARSE_s_n (oAutoIssuerKeyRetrieve, "auto-issuer-key-retrieve",
N_("fetch missing issuer certificates")),
ARGPARSE_s_s (oEncryptTo, "encrypt-to", "@"),
ARGPARSE_s_n (oNoEncryptTo, "no-encrypt-to", "@"),
ARGPARSE_s_s (oUser, "local-user",
N_("|USER-ID|use USER-ID to sign or decrypt")),
ARGPARSE_s_s (oOutput, "output", N_("|FILE|write output to FILE")),
ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")),
ARGPARSE_s_n (oQuiet, "quiet", N_("be somewhat more quiet")),
ARGPARSE_s_n (oNoTTY, "no-tty", N_("don't use the terminal at all")),
ARGPARSE_s_s (oLogFile, "log-file",
N_("|FILE|write a server mode log to FILE")),
ARGPARSE_s_n (oNoLogFile, "no-log-file", "@"),
ARGPARSE_s_i (oLoggerFD, "logger-fd", "@"),
ARGPARSE_s_s (oAuditLog, "audit-log",
N_("|FILE|write an audit log to FILE")),
ARGPARSE_s_s (oHtmlAuditLog, "html-audit-log", "@"),
ARGPARSE_s_n (oDryRun, "dry-run", N_("do not make any changes")),
ARGPARSE_s_n (oBatch, "batch", N_("batch mode: never ask")),
ARGPARSE_s_n (oAnswerYes, "yes", N_("assume yes on most questions")),
ARGPARSE_s_n (oAnswerNo, "no", N_("assume no on most questions")),
ARGPARSE_s_s (oKeyring, "keyring",
N_("|FILE|add keyring to the list of keyrings")),
ARGPARSE_s_s (oDefaultKey, "default-key",
N_("|USER-ID|use USER-ID as default secret key")),
/* Not yet used: */
/* ARGPARSE_s_s (oDefRecipient, "default-recipient", */
/* N_("|NAME|use NAME as default recipient")), */
/* ARGPARSE_s_n (oDefRecipientSelf, "default-recipient-self", */
/* N_("use the default key as default recipient")), */
/* ARGPARSE_s_n (oNoDefRecipient, "no-default-recipient", "@"), */
ARGPARSE_s_s (oKeyServer, "keyserver",
N_("|SPEC|use this keyserver to lookup keys")),
ARGPARSE_s_s (oOptions, "options", N_("|FILE|read options from FILE")),
ARGPARSE_s_s (oDebug, "debug", "@"),
ARGPARSE_s_s (oDebugLevel, "debug-level",
N_("|LEVEL|set the debugging level to LEVEL")),
ARGPARSE_s_n (oDebugAll, "debug-all", "@"),
ARGPARSE_s_n (oDebugNone, "debug-none", "@"),
ARGPARSE_s_i (oDebugWait, "debug-wait", "@"),
ARGPARSE_s_n (oDebugAllowCoreDump, "debug-allow-core-dump", "@"),
ARGPARSE_s_n (oDebugNoChainValidation, "debug-no-chain-validation", "@"),
ARGPARSE_s_n (oDebugIgnoreExpiration, "debug-ignore-expiration", "@"),
ARGPARSE_s_i (oStatusFD, "status-fd",
N_("|FD|write status info to this FD")),
ARGPARSE_s_s (oCipherAlgo, "cipher-algo",
N_("|NAME|use cipher algorithm NAME")),
ARGPARSE_s_s (oDigestAlgo, "digest-algo",
N_("|NAME|use message digest algorithm NAME")),
ARGPARSE_s_s (oExtraDigestAlgo, "extra-digest-algo", "@"),
ARGPARSE_group (302, N_(
"@\n(See the man page for a complete listing of all commands and options)\n"
)),
/* Hidden options. */
ARGPARSE_s_s (oCompliance, "compliance", "@"),
ARGPARSE_s_n (oNoVerbose, "no-verbose", "@"),
ARGPARSE_s_n (oEnableSpecialFilenames, "enable-special-filenames", "@"),
ARGPARSE_s_n (oNoSecmemWarn, "no-secmem-warning", "@"),
ARGPARSE_s_n (oNoArmor, "no-armor", "@"),
ARGPARSE_s_n (oNoArmor, "no-armour", "@"),
ARGPARSE_s_n (oNoDefKeyring, "no-default-keyring", "@"),
ARGPARSE_s_n (oNoGreeting, "no-greeting", "@"),
ARGPARSE_s_n (oNoOptions, "no-options", "@"),
ARGPARSE_s_s (oHomedir, "homedir", "@"),
ARGPARSE_s_s (oAgentProgram, "agent-program", "@"),
ARGPARSE_s_s (oDisplay, "display", "@"),
ARGPARSE_s_s (oTTYname, "ttyname", "@"),
ARGPARSE_s_s (oTTYtype, "ttytype", "@"),
ARGPARSE_s_s (oLCctype, "lc-ctype", "@"),
ARGPARSE_s_s (oLCmessages, "lc-messages", "@"),
ARGPARSE_s_s (oXauthority, "xauthority", "@"),
ARGPARSE_s_s (oDirmngrProgram, "dirmngr-program", "@"),
ARGPARSE_s_n (oDisableDirmngr, "disable-dirmngr", "@"),
ARGPARSE_s_s (oProtectToolProgram, "protect-tool-program", "@"),
ARGPARSE_s_s (oFakedSystemTime, "faked-system-time", "@"),
ARGPARSE_s_n (oNoBatch, "no-batch", "@"),
ARGPARSE_s_n (oWithColons, "with-colons", "@"),
ARGPARSE_s_n (oWithKeyData,"with-key-data", "@"),
ARGPARSE_s_n (oWithValidation, "with-validation", "@"),
ARGPARSE_s_n (oWithMD5Fingerprint, "with-md5-fingerprint", "@"),
ARGPARSE_s_n (oWithEphemeralKeys, "with-ephemeral-keys", "@"),
ARGPARSE_s_n (oSkipVerify, "skip-verify", "@"),
ARGPARSE_s_n (oWithFingerprint, "with-fingerprint", "@"),
ARGPARSE_s_n (oWithKeygrip, "with-keygrip", "@"),
ARGPARSE_s_n (oWithSecret, "with-secret", "@"),
ARGPARSE_s_n (oWithKeyScreening,"with-key-screening", "@"),
ARGPARSE_s_s (oDisableCipherAlgo, "disable-cipher-algo", "@"),
ARGPARSE_s_s (oDisablePubkeyAlgo, "disable-pubkey-algo", "@"),
ARGPARSE_s_n (oIgnoreTimeConflict, "ignore-time-conflict", "@"),
ARGPARSE_s_n (oNoRandomSeedFile, "no-random-seed-file", "@"),
ARGPARSE_s_n (oNoCommonCertsImport, "no-common-certs-import", "@"),
ARGPARSE_s_s (oIgnoreCertExtension, "ignore-cert-extension", "@"),
ARGPARSE_s_n (oNoAutostart, "no-autostart", "@"),
+ ARGPARSE_s_n (oAuthenticode, "authenticode", "@"),
+ ARGPARSE_s_s (oAttribute, "attribute", "@"),
/* Command aliases. */
ARGPARSE_c (aListKeys, "list-key", "@"),
ARGPARSE_c (aListChain, "list-signatures", "@"),
ARGPARSE_c (aListChain, "list-sigs", "@"),
ARGPARSE_c (aListChain, "check-signatures", "@"),
ARGPARSE_c (aListChain, "check-sigs", "@"),
ARGPARSE_c (aDeleteKey, "delete-key", "@"),
ARGPARSE_end ()
};
/* The list of supported debug flags. */
static struct debug_flags_s debug_flags [] =
{
{ DBG_X509_VALUE , "x509" },
{ DBG_MPI_VALUE , "mpi" },
{ DBG_CRYPTO_VALUE , "crypto" },
{ DBG_MEMORY_VALUE , "memory" },
{ DBG_CACHE_VALUE , "cache" },
{ DBG_MEMSTAT_VALUE, "memstat" },
{ DBG_HASHING_VALUE, "hashing" },
{ DBG_IPC_VALUE , "ipc" },
+ { DBG_CLOCK_VALUE , "clock" },
+ { DBG_LOOKUP_VALUE , "lookup" },
{ 0, NULL }
};
/* Global variable to keep an error count. */
int gpgsm_errors_seen = 0;
/* It is possible that we are currentlu running under setuid permissions */
static int maybe_setuid = 1;
/* Helper to implement --debug-level and --debug*/
static const char *debug_level;
static unsigned int debug_value;
/* Default value for include-certs. We need an extra macro for
gpgconf-list because the variable will be changed by the command
line option.
It is often cumbersome to locate intermediate certificates, thus by
default we include all certificates in the chain. However we leave
out the root certificate because that would make it too easy for
the recipient to import that root certificate. A root certificate
should be installed only after due checks and thus it won't help to
send it along with each message. */
#define DEFAULT_INCLUDE_CERTS -2 /* Include all certs but root. */
static int default_include_certs = DEFAULT_INCLUDE_CERTS;
/* Whether the chain mode shall be used for validation. */
static int default_validation_model;
/* The default cipher algo. */
#define DEFAULT_CIPHER_ALGO "AES"
static char *build_list (const char *text,
const char *(*mapf)(int), int (*chkf)(int));
static void set_cmd (enum cmd_and_opt_values *ret_cmd,
enum cmd_and_opt_values new_cmd );
static void emergency_cleanup (void);
static int open_read (const char *filename);
static estream_t open_es_fread (const char *filename, const char *mode);
static estream_t open_es_fwrite (const char *filename);
static void run_protect_tool (int argc, char **argv);
static int
our_pk_test_algo (int algo)
{
switch (algo)
{
case GCRY_PK_RSA:
case GCRY_PK_ECDSA:
return gcry_pk_test_algo (algo);
default:
return 1;
}
}
static int
our_cipher_test_algo (int algo)
{
switch (algo)
{
case GCRY_CIPHER_3DES:
case GCRY_CIPHER_AES128:
case GCRY_CIPHER_AES192:
case GCRY_CIPHER_AES256:
case GCRY_CIPHER_SERPENT128:
case GCRY_CIPHER_SERPENT192:
case GCRY_CIPHER_SERPENT256:
case GCRY_CIPHER_SEED:
case GCRY_CIPHER_CAMELLIA128:
case GCRY_CIPHER_CAMELLIA192:
case GCRY_CIPHER_CAMELLIA256:
return gcry_cipher_test_algo (algo);
default:
return 1;
}
}
static int
our_md_test_algo (int algo)
{
switch (algo)
{
case GCRY_MD_MD5:
case GCRY_MD_SHA1:
case GCRY_MD_RMD160:
case GCRY_MD_SHA224:
case GCRY_MD_SHA256:
case GCRY_MD_SHA384:
case GCRY_MD_SHA512:
case GCRY_MD_WHIRLPOOL:
return gcry_md_test_algo (algo);
default:
return 1;
}
}
static char *
make_libversion (const char *libname, const char *(*getfnc)(const char*))
{
const char *s;
char *result;
if (maybe_setuid)
{
gcry_control (GCRYCTL_INIT_SECMEM, 0, 0); /* Drop setuid. */
maybe_setuid = 0;
}
s = getfnc (NULL);
result = xmalloc (strlen (libname) + 1 + strlen (s) + 1);
strcpy (stpcpy (stpcpy (result, libname), " "), s);
return result;
}
static const char *
my_strusage( int level )
{
static char *digests, *pubkeys, *ciphers;
static char *ver_gcry, *ver_ksba;
const char *p;
switch (level)
{
case 11: p = "@GPGSM@ (@GNUPG@)";
break;
case 13: p = VERSION; break;
case 17: p = PRINTABLE_OS_NAME; break;
case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break;
case 1:
case 40: p = _("Usage: @GPGSM@ [options] [files] (-h for help)");
break;
case 41:
p = _("Syntax: @GPGSM@ [options] [files]\n"
"Sign, check, encrypt or decrypt using the S/MIME protocol\n"
"Default operation depends on the input data\n");
break;
case 20:
if (!ver_gcry)
ver_gcry = make_libversion ("libgcrypt", gcry_check_version);
p = ver_gcry;
break;
case 21:
if (!ver_ksba)
ver_ksba = make_libversion ("libksba", ksba_check_version);
p = ver_ksba;
break;
case 31: p = "\nHome: "; break;
case 32: p = gnupg_homedir (); break;
case 33: p = _("\nSupported algorithms:\n"); break;
case 34:
if (!ciphers)
ciphers = build_list ("Cipher: ", gnupg_cipher_algo_name,
our_cipher_test_algo );
p = ciphers;
break;
case 35:
if (!pubkeys)
pubkeys = build_list ("Pubkey: ", gcry_pk_algo_name,
our_pk_test_algo );
p = pubkeys;
break;
case 36:
if (!digests)
digests = build_list("Hash: ", gcry_md_algo_name, our_md_test_algo );
p = digests;
break;
default: p = NULL; break;
}
return p;
}
static char *
build_list (const char *text, const char * (*mapf)(int), int (*chkf)(int))
{
int i;
size_t n=strlen(text)+2;
char *list, *p;
if (maybe_setuid) {
gcry_control (GCRYCTL_DROP_PRIVS); /* drop setuid */
}
for (i=1; i < 400; i++ )
if (!chkf(i))
n += strlen(mapf(i)) + 2;
list = xmalloc (21 + n);
*list = 0;
for (p=NULL, i=1; i < 400; i++)
{
if (!chkf(i))
{
if( !p )
p = stpcpy (list, text );
else
p = stpcpy (p, ", ");
p = stpcpy (p, mapf(i) );
}
}
if (p)
strcpy (p, "\n" );
return list;
}
/* Set the file pointer into binary mode if required. */
static void
set_binary (FILE *fp)
{
#ifdef HAVE_DOSISH_SYSTEM
setmode (fileno (fp), O_BINARY);
#else
(void)fp;
#endif
}
static void
wrong_args (const char *text)
{
fprintf (stderr, _("usage: %s [options] %s\n"), GPGSM_NAME, text);
gpgsm_exit (2);
}
static void
set_opt_session_env (const char *name, const char *value)
{
gpg_error_t err;
err = session_env_setenv (opt.session_env, name, value);
if (err)
log_fatal ("error setting session environment: %s\n",
gpg_strerror (err));
}
/* Setup the debugging. With a DEBUG_LEVEL of NULL only the active
debug flags are propagated to the subsystems. With DEBUG_LEVEL
set, a specific set of debug flags is set; and individual debugging
flags will be added on top. */
static void
set_debug (void)
{
int numok = (debug_level && digitp (debug_level));
int numlvl = numok? atoi (debug_level) : 0;
if (!debug_level)
;
else if (!strcmp (debug_level, "none") || (numok && numlvl < 1))
opt.debug = 0;
else if (!strcmp (debug_level, "basic") || (numok && numlvl <= 2))
opt.debug = DBG_IPC_VALUE;
else if (!strcmp (debug_level, "advanced") || (numok && numlvl <= 5))
opt.debug = DBG_IPC_VALUE|DBG_X509_VALUE;
else if (!strcmp (debug_level, "expert") || (numok && numlvl <= 8))
opt.debug = (DBG_IPC_VALUE|DBG_X509_VALUE
|DBG_CACHE_VALUE|DBG_CRYPTO_VALUE);
else if (!strcmp (debug_level, "guru") || numok)
{
opt.debug = ~0;
/* Unless the "guru" string has been used we don't want to allow
hashing debugging. The rationale is that people tend to
select the highest debug value and would then clutter their
disk with debug files which may reveal confidential data. */
if (numok)
opt.debug &= ~(DBG_HASHING_VALUE);
}
else
{
log_error (_("invalid debug-level '%s' given\n"), debug_level);
gpgsm_exit (2);
}
opt.debug |= debug_value;
if (opt.debug && !opt.verbose)
opt.verbose = 1;
if (opt.debug)
opt.quiet = 0;
if (opt.debug & DBG_MPI_VALUE)
gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 2);
if (opt.debug & DBG_CRYPTO_VALUE )
gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 1);
gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose);
if (opt.debug)
parse_debug_flag (NULL, &opt.debug, debug_flags);
}
static void
set_cmd (enum cmd_and_opt_values *ret_cmd, enum cmd_and_opt_values new_cmd)
{
enum cmd_and_opt_values cmd = *ret_cmd;
if (!cmd || cmd == new_cmd)
cmd = new_cmd;
else if ( cmd == aSign && new_cmd == aEncr )
cmd = aSignEncr;
else if ( cmd == aEncr && new_cmd == aSign )
cmd = aSignEncr;
else if ( (cmd == aSign && new_cmd == aClearsign)
|| (cmd == aClearsign && new_cmd == aSign) )
cmd = aClearsign;
else
{
log_error(_("conflicting commands\n"));
gpgsm_exit(2);
}
*ret_cmd = cmd;
}
/* Helper to add recipients to a list. */
static void
do_add_recipient (ctrl_t ctrl, const char *name,
certlist_t *recplist, int is_encrypt_to, int recp_required)
{
int rc = gpgsm_add_to_certlist (ctrl, name, 0, recplist, is_encrypt_to);
if (rc)
{
if (recp_required)
{
log_error ("can't encrypt to '%s': %s\n", name, gpg_strerror (rc));
gpgsm_status2 (ctrl, STATUS_INV_RECP,
get_inv_recpsgnr_code (rc), name, NULL);
}
else
log_info (_("Note: won't be able to encrypt to '%s': %s\n"),
name, gpg_strerror (rc));
}
}
static void
parse_validation_model (const char *model)
{
int i = gpgsm_parse_validation_model (model);
if (i == -1)
log_error (_("unknown validation model '%s'\n"), model);
else
default_validation_model = i;
}
/* Release the list of SERVERS. As usual it is okay to call this
function with SERVERS passed as NULL. */
void
keyserver_list_free (struct keyserver_spec *servers)
{
while (servers)
{
struct keyserver_spec *tmp = servers->next;
xfree (servers->host);
xfree (servers->user);
if (servers->pass)
memset (servers->pass, 0, strlen (servers->pass));
xfree (servers->pass);
xfree (servers->base);
xfree (servers);
servers = tmp;
}
}
/* See also dirmngr ldapserver_parse_one(). */
struct keyserver_spec *
parse_keyserver_line (char *line,
const char *filename, unsigned int lineno)
{
char *p;
char *endp;
struct keyserver_spec *server;
int fieldno;
int fail = 0;
/* Parse the colon separated fields. */
server = xcalloc (1, sizeof *server);
for (fieldno = 1, p = line; p; p = endp, fieldno++ )
{
endp = strchr (p, ':');
if (endp)
*endp++ = '\0';
trim_spaces (p);
switch (fieldno)
{
case 1:
if (*p)
server->host = xstrdup (p);
else
{
log_error (_("%s:%u: no hostname given\n"),
filename, lineno);
fail = 1;
}
break;
case 2:
if (*p)
server->port = atoi (p);
break;
case 3:
if (*p)
server->user = xstrdup (p);
break;
case 4:
if (*p && !server->user)
{
log_error (_("%s:%u: password given without user\n"),
filename, lineno);
fail = 1;
}
else if (*p)
server->pass = xstrdup (p);
break;
case 5:
if (*p)
server->base = xstrdup (p);
break;
default:
/* (We silently ignore extra fields.) */
break;
}
}
if (fail)
{
log_info (_("%s:%u: skipping this line\n"), filename, lineno);
keyserver_list_free (server);
server = NULL;
}
return server;
}
int
main ( int argc, char **argv)
{
ARGPARSE_ARGS pargs;
int orig_argc;
char **orig_argv;
/* char *username;*/
int may_coredump;
strlist_t sl, remusr= NULL, locusr=NULL;
strlist_t nrings=NULL;
int detached_sig = 0;
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;
char *auditlog = NULL;
char *htmlauditlog = NULL;
int greeting = 0;
int nogreeting = 0;
int debug_wait = 0;
int use_random_seed = 1;
int no_common_certs_import = 0;
int with_fpr = 0;
const char *forced_digest_algo = NULL;
const char *extra_digest_algo = NULL;
enum cmd_and_opt_values cmd = 0;
struct server_control_s ctrl;
certlist_t recplist = NULL;
certlist_t signerlist = NULL;
int do_not_setup_keys = 0;
int recp_required = 0;
estream_t auditfp = NULL;
estream_t htmlauditfp = NULL;
struct assuan_malloc_hooks malloc_hooks;
int pwfd = -1;
/*mtrace();*/
early_system_init ();
gnupg_reopen_std (GPGSM_NAME);
/* trap_unaligned ();*/
gnupg_rl_initialize ();
set_strusage (my_strusage);
gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN);
/* Please note that we may running SUID(ROOT), so be very CAREFUL
when adding any stuff between here and the call to secmem_init()
somewhere after the option parsing */
log_set_prefix (GPGSM_NAME, GPGRT_LOG_WITH_PREFIX);
/* Make sure that our subsystems are ready. */
i18n_init ();
init_common_subsystems (&argc, &argv);
/* Check that the libraries are suitable. Do it here because the
option parse may need services of the library */
if (!ksba_check_version (NEED_KSBA_VERSION) )
log_fatal (_("%s is too old (need %s, have %s)\n"), "libksba",
NEED_KSBA_VERSION, ksba_check_version (NULL) );
gcry_control (GCRYCTL_USE_SECURE_RNDPOOL);
may_coredump = disable_core_dumps ();
gnupg_init_signals (0, emergency_cleanup);
dotlock_create (NULL, 0); /* Register lockfile cleanup. */
/* Tell the compliance module who we are. */
gnupg_initialize_compliance (GNUPG_MODULE_NAME_GPGSM);
opt.autostart = 1;
opt.session_env = session_env_new ();
if (!opt.session_env)
log_fatal ("error allocating session environment block: %s\n",
strerror (errno));
/* Note: If you change this default cipher algorithm , please
remember to update the Gpgconflist entry as well. */
opt.def_cipher_algoid = DEFAULT_CIPHER_ALGO;
/* First check whether we have a config file on the commandline */
orig_argc = argc;
orig_argv = argv;
pargs.argc = &argc;
pargs.argv = &argv;
pargs.flags= 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 */
opt.no_homedir_creation = 1;
}
else if (pargs.r_opt == oHomedir)
gnupg_set_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 );
malloc_hooks.malloc = gcry_malloc;
malloc_hooks.realloc = gcry_realloc;
malloc_hooks.free = gcry_free;
assuan_set_malloc_hooks (&malloc_hooks);
assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT);
setup_libassuan_logging (&opt.debug, NULL);
/* Setup a default control structure for command line mode */
memset (&ctrl, 0, sizeof ctrl);
gpgsm_init_default_ctrl (&ctrl);
ctrl.no_server = 1;
ctrl.status_fd = -1; /* No status output. */
ctrl.autodetect_encoding = 1;
/* Set the default option file */
if (default_config )
configname = make_filename (gnupg_homedir (),
GPGSM_NAME EXTSEP_S "conf", NULL);
/* Set the default policy file */
opt.policy_file = make_filename (gnupg_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 aGPGConfList:
case aGPGConfTest:
set_cmd (&cmd, pargs.r_opt);
do_not_setup_keys = 1;
nogreeting = 1;
break;
case aServer:
opt.batch = 1;
set_cmd (&cmd, aServer);
break;
case aCallDirmngr:
opt.batch = 1;
set_cmd (&cmd, aCallDirmngr);
do_not_setup_keys = 1;
break;
case aCallProtectTool:
opt.batch = 1;
set_cmd (&cmd, aCallProtectTool);
no_more_options = 1; /* Stop parsing. */
do_not_setup_keys = 1;
break;
case aDeleteKey:
set_cmd (&cmd, aDeleteKey);
/*greeting=1;*/
do_not_setup_keys = 1;
break;
case aDetachedSign:
detached_sig = 1;
set_cmd (&cmd, aSign );
break;
case aKeygen:
set_cmd (&cmd, aKeygen);
greeting=1;
do_not_setup_keys = 1;
break;
case aImport:
case aSendKeys:
case aRecvKeys:
case aExport:
case aExportSecretKeyP12:
case aExportSecretKeyP8:
case aExportSecretKeyRaw:
case aDumpKeys:
case aDumpChain:
case aDumpExternalKeys:
case aDumpSecretKeys:
case aListKeys:
case aListExternalKeys:
case aListSecretKeys:
case aListChain:
case aLearnCard:
case aPasswd:
case aKeydbClearSomeCertFlags:
do_not_setup_keys = 1;
set_cmd (&cmd, pargs.r_opt);
break;
case aEncr:
recp_required = 1;
set_cmd (&cmd, pargs.r_opt);
break;
case aSym:
case aDecrypt:
case aSign:
case aClearsign:
case aVerify:
set_cmd (&cmd, pargs.r_opt);
break;
/* Output encoding selection. */
case oArmor:
ctrl.create_pem = 1;
break;
case oBase64:
ctrl.create_pem = 0;
ctrl.create_base64 = 1;
break;
case oNoArmor:
ctrl.create_pem = 0;
ctrl.create_base64 = 0;
break;
case oP12Charset:
opt.p12_charset = pargs.r.ret_str;
break;
case oPassphraseFD:
pwfd = translate_sys2libc_fd_int (pargs.r.ret_int, 0);
break;
case oPinentryMode:
opt.pinentry_mode = parse_pinentry_mode (pargs.r.ret_str);
if (opt.pinentry_mode == -1)
log_error (_("invalid pinentry mode '%s'\n"), pargs.r.ret_str);
break;
case oRequestOrigin:
opt.request_origin = parse_request_origin (pargs.r.ret_str);
if (opt.request_origin == -1)
log_error (_("invalid request origin '%s'\n"), pargs.r.ret_str);
break;
/* Input encoding selection. */
case oAssumeArmor:
ctrl.autodetect_encoding = 0;
ctrl.is_pem = 1;
ctrl.is_base64 = 0;
break;
case oAssumeBase64:
ctrl.autodetect_encoding = 0;
ctrl.is_pem = 0;
ctrl.is_base64 = 1;
break;
case oAssumeBinary:
ctrl.autodetect_encoding = 0;
ctrl.is_pem = 0;
ctrl.is_base64 = 0;
break;
case oDisableCRLChecks:
opt.no_crl_check = 1;
break;
case oEnableCRLChecks:
opt.no_crl_check = 0;
break;
case oDisableTrustedCertCRLCheck:
opt.no_trusted_cert_crl_check = 1;
break;
case oEnableTrustedCertCRLCheck:
opt.no_trusted_cert_crl_check = 0;
break;
case oForceCRLRefresh:
opt.force_crl_refresh = 1;
break;
case oDisableOCSP:
ctrl.use_ocsp = opt.enable_ocsp = 0;
break;
case oEnableOCSP:
ctrl.use_ocsp = opt.enable_ocsp = 1;
break;
case oIncludeCerts:
ctrl.include_certs = default_include_certs = pargs.r.ret_int;
break;
case oPolicyFile:
xfree (opt.policy_file);
if (*pargs.r.ret_str)
opt.policy_file = xstrdup (pargs.r.ret_str);
else
opt.policy_file = NULL;
break;
case oDisablePolicyChecks:
opt.no_policy_check = 1;
break;
case oEnablePolicyChecks:
opt.no_policy_check = 0;
break;
case oAutoIssuerKeyRetrieve:
opt.auto_issuer_key_retrieve = 1;
break;
case oOutput: opt.outfile = pargs.r.ret_str; break;
case oQuiet: opt.quiet = 1; break;
case oNoTTY: /* fixme:tty_no_terminal(1);*/ break;
case oDryRun: opt.dry_run = 1; break;
case oVerbose:
opt.verbose++;
gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose);
break;
case oNoVerbose:
opt.verbose = 0;
gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose);
break;
case oLogFile: logfile = pargs.r.ret_str; break;
case oNoLogFile: logfile = NULL; break;
case oAuditLog: auditlog = pargs.r.ret_str; break;
case oHtmlAuditLog: htmlauditlog = pargs.r.ret_str; break;
case oBatch:
opt.batch = 1;
greeting = 0;
break;
case oNoBatch: opt.batch = 0; break;
case oAnswerYes: opt.answer_yes = 1; break;
case oAnswerNo: opt.answer_no = 1; break;
case oKeyring: append_to_strlist (&nrings, pargs.r.ret_str); break;
case oDebug:
if (parse_debug_flag (pargs.r.ret_str, &debug_value, debug_flags))
{
pargs.r_opt = ARGPARSE_INVALID_ARG;
pargs.err = ARGPARSE_PRINT_ERROR;
}
break;
case oDebugAll: debug_value = ~0; break;
case oDebugNone: debug_value = 0; break;
case oDebugLevel: debug_level = pargs.r.ret_str; break;
case oDebugWait: debug_wait = pargs.r.ret_int; break;
case oDebugAllowCoreDump:
may_coredump = enable_core_dumps ();
break;
case oDebugNoChainValidation: opt.no_chain_validation = 1; break;
case oDebugIgnoreExpiration: opt.ignore_expiration = 1; break;
- case oStatusFD: ctrl.status_fd = pargs.r.ret_int; break;
- case oLoggerFD: log_set_fd (pargs.r.ret_int ); break;
+ case oStatusFD:
+ ctrl.status_fd = translate_sys2libc_fd_int (pargs.r.ret_int, 1);
+ break;
+ case oLoggerFD:
+ log_set_fd (translate_sys2libc_fd_int (pargs.r.ret_int, 1));
+ break;
case oWithMD5Fingerprint:
opt.with_md5_fingerprint=1; /*fall through*/
case oWithFingerprint:
with_fpr=1; /*fall through*/
case aFingerprint:
opt.fingerprint++;
break;
case oWithKeygrip:
opt.with_keygrip = 1;
break;
case oWithKeyScreening:
opt.with_key_screening = 1;
break;
case 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: opt.no_homedir_creation = 1; break; /* no-options */
case oHomedir: gnupg_set_homedir (pargs.r.ret_str); break;
case oAgentProgram: opt.agent_program = pargs.r.ret_str; break;
case oDisplay:
set_opt_session_env ("DISPLAY", pargs.r.ret_str);
break;
case oTTYname:
set_opt_session_env ("GPG_TTY", pargs.r.ret_str);
break;
case oTTYtype:
set_opt_session_env ("TERM", pargs.r.ret_str);
break;
case oXauthority:
set_opt_session_env ("XAUTHORITY", pargs.r.ret_str);
break;
case oLCctype: opt.lc_ctype = xstrdup (pargs.r.ret_str); break;
case oLCmessages: opt.lc_messages = xstrdup (pargs.r.ret_str); break;
case oDirmngrProgram: opt.dirmngr_program = pargs.r.ret_str; break;
case oDisableDirmngr: opt.disable_dirmngr = 1; break;
case oPreferSystemDirmngr: /* Obsolete */; break;
case oProtectToolProgram:
opt.protect_tool_program = pargs.r.ret_str;
break;
case oFakedSystemTime:
{
time_t faked_time = isotime2epoch (pargs.r.ret_str);
if (faked_time == (time_t)(-1))
faked_time = (time_t)strtoul (pargs.r.ret_str, NULL, 10);
gnupg_set_time (faked_time, 0);
}
break;
case oNoDefKeyring: default_keyring = 0; break;
case oNoGreeting: nogreeting = 1; break;
case oDefaultKey:
if (*pargs.r.ret_str)
{
xfree (opt.local_user);
opt.local_user = xstrdup (pargs.r.ret_str);
}
break;
case oDefRecipient:
if (*pargs.r.ret_str)
opt.def_recipient = xstrdup (pargs.r.ret_str);
break;
case oDefRecipientSelf:
xfree (opt.def_recipient);
opt.def_recipient = NULL;
opt.def_recipient_self = 1;
break;
case oNoDefRecipient:
xfree (opt.def_recipient);
opt.def_recipient = NULL;
opt.def_recipient_self = 0;
break;
case oWithKeyData: opt.with_key_data=1; /* fall through */
case oWithColons: ctrl.with_colons = 1; break;
case oWithSecret: ctrl.with_secret = 1; break;
case oWithValidation: ctrl.with_validation=1; break;
case oWithEphemeralKeys: ctrl.with_ephemeral_keys=1; break;
case oSkipVerify: opt.skip_verify=1; break;
case oNoEncryptTo: opt.no_encrypt_to = 1; break;
case oEncryptTo: /* Store the recipient in the second list */
sl = add_to_strlist (&remusr, pargs.r.ret_str);
sl->flags = 1;
break;
case oRecipient: /* store the recipient */
add_to_strlist ( &remusr, pargs.r.ret_str);
break;
case oUser: /* Store the local users, the first one is the default */
if (!opt.local_user)
opt.local_user = xstrdup (pargs.r.ret_str);
add_to_strlist (&locusr, pargs.r.ret_str);
break;
case oNoSecmemWarn:
gcry_control (GCRYCTL_DISABLE_SECMEM_WARN);
break;
case oCipherAlgo:
opt.def_cipher_algoid = pargs.r.ret_str;
break;
case oDisableCipherAlgo:
{
int algo = gcry_cipher_map_name (pargs.r.ret_str);
gcry_cipher_ctl (NULL, GCRYCTL_DISABLE_ALGO, &algo, sizeof algo);
}
break;
case oDisablePubkeyAlgo:
{
int algo = gcry_pk_map_name (pargs.r.ret_str);
gcry_pk_ctl (GCRYCTL_DISABLE_ALGO,&algo, sizeof algo );
}
break;
case oDigestAlgo:
forced_digest_algo = pargs.r.ret_str;
break;
case oExtraDigestAlgo:
extra_digest_algo = pargs.r.ret_str;
break;
case oIgnoreTimeConflict: opt.ignore_time_conflict = 1; break;
case oNoRandomSeedFile: use_random_seed = 0; break;
case oNoCommonCertsImport: no_common_certs_import = 1; break;
case oEnableSpecialFilenames:
enable_special_filenames ();
break;
case oValidationModel: parse_validation_model (pargs.r.ret_str); break;
case oKeyServer:
{
struct keyserver_spec *keyserver;
keyserver = parse_keyserver_line (pargs.r.ret_str,
configname, configlineno);
if (! keyserver)
log_error (_("could not parse keyserver\n"));
else
{
/* FIXME: Keep last next pointer. */
struct keyserver_spec **next_p = &opt.keyserver;
while (*next_p)
next_p = &(*next_p)->next;
*next_p = keyserver;
}
}
break;
case oIgnoreCertExtension:
add_to_strlist (&opt.ignored_cert_extensions, pargs.r.ret_str);
break;
+ case oAuthenticode: opt.authenticode = 1; break;
+
+ case oAttribute:
+ add_to_strlist (&opt.attributes, pargs.r.ret_str);
+ break;
+
case oNoAutostart: opt.autostart = 0; break;
case oCompliance:
{
struct gnupg_compliance_option compliance_options[] =
{
{ "gnupg", CO_GNUPG },
{ "de-vs", CO_DE_VS }
};
int compliance = gnupg_parse_compliance_option (pargs.r.ret_str,
compliance_options,
DIM (compliance_options),
opt.quiet);
if (compliance < 0)
log_inc_errorcount (); /* Force later termination. */
opt.compliance = compliance;
}
break;
default:
pargs.err = configfp? ARGPARSE_PRINT_WARNING:ARGPARSE_PRINT_ERROR;
break;
}
}
if (configfp)
{
fclose (configfp);
configfp = NULL;
/* Keep a copy of the config filename. */
opt.config_filename = configname;
configname = NULL;
goto next_pass;
}
xfree (configname);
configname = NULL;
if (!opt.config_filename)
opt.config_filename = make_filename (gnupg_homedir (),
GPGSM_NAME EXTSEP_S "conf",
NULL);
if (log_get_errorcount(0))
{
gpgsm_status_with_error (&ctrl, STATUS_FAILURE,
"option-parser", gpg_error (GPG_ERR_GENERAL));
gpgsm_exit(2);
}
if (pwfd != -1) /* Read the passphrase now. */
read_passphrase_from_fd (pwfd);
/* Now that we have the options parsed we need to update the default
control structure. */
gpgsm_init_default_ctrl (&ctrl);
if (nogreeting)
greeting = 0;
if (greeting)
{
es_fprintf (es_stderr, "%s %s; %s\n",
strusage(11), strusage(13), strusage(14) );
es_fprintf (es_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 (opt.qualsig_approval && !opt.quiet) */
/* log_info (_("This software has officially been approved to " */
/* "create and verify\n" */
/* "qualified signatures according to German law.\n")); */
if (logfile && cmd == aServer)
{
log_set_file (logfile);
log_set_prefix (NULL, GPGRT_LOG_WITH_PREFIX | GPGRT_LOG_WITH_TIME | GPGRT_LOG_WITH_PID);
}
if (gnupg_faked_time_p ())
{
gnupg_isotime_t tbuf;
log_info (_("WARNING: running with faked system time: "));
gnupg_get_isotime (tbuf);
dump_isotime (tbuf);
log_printf ("\n");
}
/* Print a warning if an argument looks like an option. */
if (!opt.quiet && !(pargs.flags & ARGPARSE_FLAG_STOP_SEEN))
{
int i;
for (i=0; i < argc; i++)
if (argv[i][0] == '-' && argv[i][1] == '-')
log_info (_("Note: '%s' is not considered an option\n"), argv[i]);
}
/*FIXME if (opt.batch) */
/* tty_batchmode (1); */
gcry_control (GCRYCTL_RESUME_SECMEM_WARN);
set_debug ();
/* Although we always use gpgsm_exit, we better install a regular
exit handler so that at least the secure memory gets wiped
out. */
if (atexit (emergency_cleanup))
{
log_error ("atexit failed\n");
gpgsm_exit (2);
}
/* Must do this after dropping setuid, because the mapping functions
may try to load an module and we may have disabled an algorithm.
We remap the commonly used algorithms to the OIDs for
convenience. We need to work with the OIDs because they are used
to check whether the encryption mode is actually available. */
if (!strcmp (opt.def_cipher_algoid, "3DES") )
opt.def_cipher_algoid = "1.2.840.113549.3.7";
else if (!strcmp (opt.def_cipher_algoid, "AES")
|| !strcmp (opt.def_cipher_algoid, "AES128"))
opt.def_cipher_algoid = "2.16.840.1.101.3.4.1.2";
else if (!strcmp (opt.def_cipher_algoid, "AES192") )
opt.def_cipher_algoid = "2.16.840.1.101.3.4.1.22";
else if (!strcmp (opt.def_cipher_algoid, "AES256") )
opt.def_cipher_algoid = "2.16.840.1.101.3.4.1.42";
else if (!strcmp (opt.def_cipher_algoid, "SERPENT")
|| !strcmp (opt.def_cipher_algoid, "SERPENT128") )
opt.def_cipher_algoid = "1.3.6.1.4.1.11591.13.2.2";
else if (!strcmp (opt.def_cipher_algoid, "SERPENT192") )
opt.def_cipher_algoid = "1.3.6.1.4.1.11591.13.2.22";
else if (!strcmp (opt.def_cipher_algoid, "SERPENT256") )
opt.def_cipher_algoid = "1.3.6.1.4.1.11591.13.2.42";
else if (!strcmp (opt.def_cipher_algoid, "SEED") )
opt.def_cipher_algoid = "1.2.410.200004.1.4";
else if (!strcmp (opt.def_cipher_algoid, "CAMELLIA")
|| !strcmp (opt.def_cipher_algoid, "CAMELLIA128") )
opt.def_cipher_algoid = "1.2.392.200011.61.1.1.1.2";
else if (!strcmp (opt.def_cipher_algoid, "CAMELLIA192") )
opt.def_cipher_algoid = "1.2.392.200011.61.1.1.1.3";
else if (!strcmp (opt.def_cipher_algoid, "CAMELLIA256") )
opt.def_cipher_algoid = "1.2.392.200011.61.1.1.1.4";
if (cmd != aGPGConfList)
{
if ( !gcry_cipher_map_name (opt.def_cipher_algoid)
|| !gcry_cipher_mode_from_oid (opt.def_cipher_algoid))
log_error (_("selected cipher algorithm is invalid\n"));
if (forced_digest_algo)
{
opt.forced_digest_algo = gcry_md_map_name (forced_digest_algo);
if (our_md_test_algo(opt.forced_digest_algo) )
log_error (_("selected digest algorithm is invalid\n"));
}
if (extra_digest_algo)
{
opt.extra_digest_algo = gcry_md_map_name (extra_digest_algo);
if (our_md_test_algo (opt.extra_digest_algo) )
log_error (_("selected digest algorithm is invalid\n"));
}
}
/* Check our chosen algorithms against the list of allowed
* algorithms in the current compliance mode, and fail hard if it is
* not. This is us being nice to the user informing her early that
* the chosen algorithms are not available. We also check and
* enforce this right before the actual operation. */
if (! gnupg_cipher_is_allowed (opt.compliance,
cmd == aEncr || cmd == aSignEncr,
gcry_cipher_map_name (opt.def_cipher_algoid),
GCRY_CIPHER_MODE_NONE)
&& ! gnupg_cipher_is_allowed (opt.compliance,
cmd == aEncr || cmd == aSignEncr,
gcry_cipher_mode_from_oid
(opt.def_cipher_algoid),
GCRY_CIPHER_MODE_NONE))
log_error (_("cipher algorithm '%s' may not be used in %s mode\n"),
opt.def_cipher_algoid,
gnupg_compliance_option_string (opt.compliance));
if (forced_digest_algo
&& ! gnupg_digest_is_allowed (opt.compliance,
cmd == aSign
|| cmd == aSignEncr
|| cmd == aClearsign,
opt.forced_digest_algo))
log_error (_("digest algorithm '%s' may not be used in %s mode\n"),
forced_digest_algo,
gnupg_compliance_option_string (opt.compliance));
if (extra_digest_algo
&& ! gnupg_digest_is_allowed (opt.compliance,
cmd == aSign
|| cmd == aSignEncr
|| cmd == aClearsign,
opt.extra_digest_algo))
log_error (_("digest algorithm '%s' may not be used in %s mode\n"),
extra_digest_algo,
gnupg_compliance_option_string (opt.compliance));
if (log_get_errorcount(0))
{
gpgsm_status_with_error (&ctrl, STATUS_FAILURE, "option-postprocessing",
gpg_error (GPG_ERR_GENERAL));
gpgsm_exit (2);
}
/* Set the random seed file. */
if (use_random_seed)
{
char *p = make_filename (gnupg_homedir (), "random_seed", NULL);
gcry_control (GCRYCTL_SET_RANDOM_SEED_FILE, p);
xfree(p);
}
if (!cmd && opt.fingerprint && !with_fpr)
set_cmd (&cmd, aListKeys);
/* Add default keybox. */
if (!nrings && default_keyring)
{
int created;
keydb_add_resource (&ctrl, "pubring.kbx", 0, &created);
if (created && !no_common_certs_import)
{
/* Import the standard certificates for a new default keybox. */
char *filelist[2];
filelist[0] = make_filename (gnupg_datadir (),"com-certs.pem", NULL);
filelist[1] = NULL;
if (!access (filelist[0], F_OK))
{
log_info (_("importing common certificates '%s'\n"),
filelist[0]);
gpgsm_import_files (&ctrl, 1, filelist, open_read);
}
xfree (filelist[0]);
}
}
for (sl = nrings; sl; sl = sl->next)
keydb_add_resource (&ctrl, sl->d, 0, NULL);
FREE_STRLIST(nrings);
/* Prepare the audit log feature for certain commands. */
if (auditlog || htmlauditlog)
{
switch (cmd)
{
case aEncr:
case aSign:
case aDecrypt:
case aVerify:
audit_release (ctrl.audit);
ctrl.audit = audit_new ();
if (auditlog)
auditfp = open_es_fwrite (auditlog);
if (htmlauditlog)
htmlauditfp = open_es_fwrite (htmlauditlog);
break;
default:
break;
}
}
if (!do_not_setup_keys)
{
+ int errcount = log_get_errorcount (0);
+
for (sl = locusr; sl ; sl = sl->next)
{
int rc = gpgsm_add_to_certlist (&ctrl, sl->d, 1, &signerlist, 0);
if (rc)
{
log_error (_("can't sign using '%s': %s\n"),
sl->d, gpg_strerror (rc));
gpgsm_status2 (&ctrl, STATUS_INV_SGNR,
get_inv_recpsgnr_code (rc), sl->d, NULL);
gpgsm_status2 (&ctrl, STATUS_INV_RECP,
get_inv_recpsgnr_code (rc), sl->d, NULL);
}
}
/* Build the recipient list. We first add the regular ones and then
the encrypt-to ones because the underlying function will silently
ignore duplicates and we can't allow keeping a duplicate which is
flagged as encrypt-to as the actually encrypt function would then
complain about no (regular) recipients. */
for (sl = remusr; sl; sl = sl->next)
if (!(sl->flags & 1))
do_add_recipient (&ctrl, sl->d, &recplist, 0, recp_required);
if (!opt.no_encrypt_to)
{
for (sl = remusr; sl; sl = sl->next)
if ((sl->flags & 1))
do_add_recipient (&ctrl, sl->d, &recplist, 1, recp_required);
}
+
+ /* We do not require a recipient for decryption but because
+ * recipients and signers are always checked and log_error is
+ * sometimes used (for failed signing keys or due to a failed
+ * CRL checking) that would have bumbed up the error counter.
+ * We clear the counter in the decryption case because there is
+ * no reason to force decryption to fail. */
+ if (cmd == aDecrypt && !errcount)
+ log_get_errorcount (1); /* clear counter */
}
if (log_get_errorcount(0))
gpgsm_exit(1); /* Must stop for invalid recipients. */
/* Dispatch command. */
switch (cmd)
{
case aGPGConfList:
{ /* List options and default values in the GPG Conf format. */
char *config_filename_esc = percent_escape (opt.config_filename, NULL);
es_printf ("%s-%s.conf:%lu:\"%s\n",
GPGCONF_NAME, GPGSM_NAME,
GC_OPT_FLAG_DEFAULT, config_filename_esc);
xfree (config_filename_esc);
es_printf ("verbose:%lu:\n", GC_OPT_FLAG_NONE);
es_printf ("quiet:%lu:\n", GC_OPT_FLAG_NONE);
es_printf ("debug-level:%lu:\"none:\n", GC_OPT_FLAG_DEFAULT);
es_printf ("log-file:%lu:\n", GC_OPT_FLAG_NONE);
es_printf ("disable-crl-checks:%lu:\n", GC_OPT_FLAG_NONE);
es_printf ("enable-crl-checks:%lu:\n", GC_OPT_FLAG_NONE);
es_printf ("disable-trusted-cert-crl-check:%lu:\n", GC_OPT_FLAG_NONE);
es_printf ("enable-ocsp:%lu:\n", GC_OPT_FLAG_NONE);
es_printf ("include-certs:%lu:%d:\n", GC_OPT_FLAG_DEFAULT,
DEFAULT_INCLUDE_CERTS);
es_printf ("disable-policy-checks:%lu:\n", GC_OPT_FLAG_NONE);
es_printf ("auto-issuer-key-retrieve:%lu:\n", GC_OPT_FLAG_NONE);
es_printf ("disable-dirmngr:%lu:\n", GC_OPT_FLAG_NONE);
es_printf ("cipher-algo:%lu:\"%s:\n", GC_OPT_FLAG_DEFAULT,
DEFAULT_CIPHER_ALGO);
es_printf ("p12-charset:%lu:\n", GC_OPT_FLAG_DEFAULT);
es_printf ("default-key:%lu:\n", GC_OPT_FLAG_DEFAULT);
es_printf ("encrypt-to:%lu:\n", GC_OPT_FLAG_DEFAULT);
es_printf ("keyserver:%lu:\n", GC_OPT_FLAG_NONE);
/* The next one is an info only item and should match what
proc_parameters actually implements. */
es_printf ("default_pubkey_algo:%lu:\"%s:\n", GC_OPT_FLAG_DEFAULT,
"RSA-3072");
}
break;
case aGPGConfTest:
/* This is merely a dummy command to test whether the
configuration file is valid. */
break;
case aServer:
if (debug_wait)
{
log_debug ("waiting for debugger - my pid is %u .....\n",
(unsigned int)getpid());
gnupg_sleep (debug_wait);
log_debug ("... okay\n");
}
gpgsm_server (recplist);
break;
case aCallDirmngr:
if (!argc)
wrong_args ("--call-dirmngr <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. */
{
estream_t fp = open_es_fwrite (opt.outfile?opt.outfile:"-");
set_binary (stdin);
if (!argc) /* Source is stdin. */
gpgsm_encrypt (&ctrl, recplist, 0, fp);
else if (argc == 1) /* Source is the given file. */
gpgsm_encrypt (&ctrl, recplist, open_read (*argv), fp);
else
wrong_args ("--encrypt [datafile]");
es_fclose (fp);
}
break;
case aSign: /* Sign the given file. */
{
estream_t fp = open_es_fwrite (opt.outfile?opt.outfile:"-");
/* Fixme: We should also allow concatenation of multiple files for
signing because that is what gpg does.*/
set_binary (stdin);
if (!argc) /* Create from stdin. */
gpgsm_sign (&ctrl, signerlist, 0, detached_sig, fp);
else if (argc == 1) /* From file. */
gpgsm_sign (&ctrl, signerlist,
open_read (*argv), detached_sig, fp);
else
wrong_args ("--sign [datafile]");
es_fclose (fp);
}
break;
case aSignEncr: /* sign and encrypt the given file */
log_error ("this command has not yet been implemented\n");
break;
case aClearsign: /* make a clearsig */
log_error ("this command has not yet been implemented\n");
break;
case aVerify:
{
estream_t fp = NULL;
set_binary (stdin);
if (argc == 2 && opt.outfile)
log_info ("option --output ignored for a detached signature\n");
else if (opt.outfile)
fp = open_es_fwrite (opt.outfile);
if (!argc)
gpgsm_verify (&ctrl, 0, -1, fp); /* normal signature from stdin */
else if (argc == 1)
gpgsm_verify (&ctrl, open_read (*argv), -1, fp); /* std signature */
else if (argc == 2) /* detached signature (sig, detached) */
gpgsm_verify (&ctrl, open_read (*argv), open_read (argv[1]), NULL);
else
wrong_args ("--verify [signature [detached_data]]");
es_fclose (fp);
}
break;
case aDecrypt:
{
estream_t fp = open_es_fwrite (opt.outfile?opt.outfile:"-");
set_binary (stdin);
if (!argc)
gpgsm_decrypt (&ctrl, 0, fp); /* from stdin */
else if (argc == 1)
gpgsm_decrypt (&ctrl, open_read (*argv), fp); /* from file */
else
wrong_args ("--decrypt [filename]");
es_fclose (fp);
}
break;
case aDeleteKey:
for (sl=NULL; argc; argc--, argv++)
add_to_strlist (&sl, *argv);
gpgsm_delete (&ctrl, sl);
free_strlist(sl);
break;
case aListChain:
case aDumpChain:
ctrl.with_chain = 1; /* fall through */
case aListKeys:
case aDumpKeys:
case aListExternalKeys:
case aDumpExternalKeys:
case aListSecretKeys:
case aDumpSecretKeys:
{
unsigned int mode;
estream_t fp;
switch (cmd)
{
case aListChain:
case aListKeys: mode = (0 | 0 | (1<<6)); break;
case aDumpChain:
case aDumpKeys: mode = (256 | 0 | (1<<6)); break;
case aListExternalKeys: mode = (0 | 0 | (1<<7)); break;
case aDumpExternalKeys: mode = (256 | 0 | (1<<7)); break;
case aListSecretKeys: mode = (0 | 2 | (1<<6)); break;
case aDumpSecretKeys: mode = (256 | 2 | (1<<6)); break;
default: BUG();
}
fp = open_es_fwrite (opt.outfile?opt.outfile:"-");
for (sl=NULL; argc; argc--, argv++)
add_to_strlist (&sl, *argv);
gpgsm_list_keys (&ctrl, sl, fp, mode);
free_strlist(sl);
es_fclose (fp);
}
break;
case aKeygen: /* Generate a key; well kind of. */
{
estream_t fpin = NULL;
estream_t fpout;
if (opt.batch)
{
if (!argc) /* Create from stdin. */
fpin = open_es_fread ("-", "r");
else if (argc == 1) /* From file. */
fpin = open_es_fread (*argv, "r");
else
wrong_args ("--generate-key --batch [parmfile]");
}
fpout = open_es_fwrite (opt.outfile?opt.outfile:"-");
if (fpin)
gpgsm_genkey (&ctrl, fpin, fpout);
else
gpgsm_gencertreq_tty (&ctrl, fpout);
es_fclose (fpout);
}
break;
case aImport:
gpgsm_import_files (&ctrl, argc, argv, open_read);
break;
case aExport:
{
estream_t fp;
fp = open_es_fwrite (opt.outfile?opt.outfile:"-");
for (sl=NULL; argc; argc--, argv++)
add_to_strlist (&sl, *argv);
gpgsm_export (&ctrl, sl, fp);
free_strlist(sl);
es_fclose (fp);
}
break;
case aExportSecretKeyP12:
{
estream_t fp = open_es_fwrite (opt.outfile?opt.outfile:"-");
if (argc == 1)
gpgsm_p12_export (&ctrl, *argv, fp, 0);
else
wrong_args ("--export-secret-key-p12 KEY-ID");
if (fp != es_stdout)
es_fclose (fp);
}
break;
case aExportSecretKeyP8:
{
estream_t fp = open_es_fwrite (opt.outfile?opt.outfile:"-");
if (argc == 1)
gpgsm_p12_export (&ctrl, *argv, fp, 1);
else
wrong_args ("--export-secret-key-p8 KEY-ID");
if (fp != es_stdout)
es_fclose (fp);
}
break;
case aExportSecretKeyRaw:
{
estream_t fp = open_es_fwrite (opt.outfile?opt.outfile:"-");
if (argc == 1)
gpgsm_p12_export (&ctrl, *argv, fp, 2);
else
wrong_args ("--export-secret-key-raw KEY-ID");
if (fp != es_stdout)
es_fclose (fp);
}
break;
case aSendKeys:
case aRecvKeys:
log_error ("this command has not yet been implemented\n");
break;
case aLearnCard:
if (argc)
wrong_args ("--learn-card");
else
{
int rc = gpgsm_agent_learn (&ctrl);
if (rc)
log_error ("error learning card: %s\n", gpg_strerror (rc));
}
break;
case aPasswd:
if (argc != 1)
wrong_args ("--change-passphrase <key-Id>");
else
{
int rc;
ksba_cert_t cert = NULL;
char *grip = NULL;
rc = gpgsm_find_cert (&ctrl, *argv, NULL, &cert, 0);
if (rc)
;
else if (!(grip = gpgsm_get_keygrip_hexstring (cert)))
rc = gpg_error (GPG_ERR_BUG);
else
{
char *desc = gpgsm_format_keydesc (cert);
rc = gpgsm_agent_passwd (&ctrl, grip, desc);
xfree (desc);
}
if (rc)
log_error ("error changing passphrase: %s\n", gpg_strerror (rc));
xfree (grip);
ksba_cert_release (cert);
}
break;
case aKeydbClearSomeCertFlags:
for (sl=NULL; argc; argc--, argv++)
add_to_strlist (&sl, *argv);
keydb_clear_some_cert_flags (&ctrl, sl);
free_strlist(sl);
break;
default:
log_error (_("invalid command (there is no implicit command)\n"));
break;
}
/* Print the audit result if needed. */
if ((auditlog && auditfp) || (htmlauditlog && htmlauditfp))
{
if (auditlog && auditfp)
audit_print_result (ctrl.audit, auditfp, 0);
if (htmlauditlog && htmlauditfp)
audit_print_result (ctrl.audit, htmlauditfp, 1);
audit_release (ctrl.audit);
ctrl.audit = NULL;
es_fclose (auditfp);
es_fclose (htmlauditfp);
}
/* cleanup */
keyserver_list_free (opt.keyserver);
opt.keyserver = NULL;
gpgsm_release_certlist (recplist);
gpgsm_release_certlist (signerlist);
FREE_STRLIST (remusr);
FREE_STRLIST (locusr);
gpgsm_exit(0);
return 8; /*NOTREACHED*/
}
/* Note: This function is used by signal handlers!. */
static void
emergency_cleanup (void)
{
gcry_control (GCRYCTL_TERM_SECMEM );
}
void
gpgsm_exit (int rc)
{
gcry_control (GCRYCTL_UPDATE_RANDOM_SEED_FILE);
if (opt.debug & DBG_MEMSTAT_VALUE)
{
gcry_control( GCRYCTL_DUMP_MEMORY_STATS );
gcry_control( GCRYCTL_DUMP_RANDOM_STATS );
}
if (opt.debug)
gcry_control (GCRYCTL_DUMP_SECMEM_STATS );
emergency_cleanup ();
rc = rc? rc : log_get_errorcount(0)? 2 : gpgsm_errors_seen? 1 : 0;
exit (rc);
}
void
gpgsm_init_default_ctrl (struct server_control_s *ctrl)
{
ctrl->include_certs = default_include_certs;
ctrl->use_ocsp = opt.enable_ocsp;
ctrl->validation_model = default_validation_model;
ctrl->offline = opt.disable_dirmngr;
}
int
gpgsm_parse_validation_model (const char *model)
{
if (!ascii_strcasecmp (model, "shell") )
return 0;
else if ( !ascii_strcasecmp (model, "chain") )
return 1;
else if ( !ascii_strcasecmp (model, "steed") )
return 2;
else
return -1;
}
/* Open the FILENAME for read and return the file descriptor. Stop
with an error message in case of problems. "-" denotes stdin and
if special filenames are allowed the given fd is opened instead. */
static int
open_read (const char *filename)
{
int fd;
if (filename[0] == '-' && !filename[1])
{
set_binary (stdin);
return 0; /* stdin */
}
fd = check_special_filename (filename, 0, 0);
if (fd != -1)
return fd;
fd = open (filename, O_RDONLY | O_BINARY);
if (fd == -1)
{
log_error (_("can't open '%s': %s\n"), filename, strerror (errno));
gpgsm_exit (2);
}
return fd;
}
/* Same as open_read but return an estream_t. */
static estream_t
open_es_fread (const char *filename, const char *mode)
{
int fd;
estream_t fp;
if (filename[0] == '-' && !filename[1])
fd = fileno (stdin);
else
fd = check_special_filename (filename, 0, 0);
if (fd != -1)
{
fp = es_fdopen_nc (fd, mode);
if (!fp)
{
log_error ("es_fdopen(%d) failed: %s\n", fd, strerror (errno));
gpgsm_exit (2);
}
return fp;
}
fp = es_fopen (filename, mode);
if (!fp)
{
log_error (_("can't open '%s': %s\n"), filename, strerror (errno));
gpgsm_exit (2);
}
return fp;
}
/* Open FILENAME for fwrite and return an extended stream. Stop with
an error message in case of problems. "-" denotes stdout and if
special filenames are allowed the given fd is opened instead.
Caller must close the returned stream. */
static estream_t
open_es_fwrite (const char *filename)
{
int fd;
estream_t fp;
if (filename[0] == '-' && !filename[1])
{
fflush (stdout);
fp = es_fdopen_nc (fileno(stdout), "wb");
return fp;
}
fd = check_special_filename (filename, 1, 0);
if (fd != -1)
{
fp = es_fdopen_nc (fd, "wb");
if (!fp)
{
log_error ("es_fdopen(%d) failed: %s\n", fd, strerror (errno));
gpgsm_exit (2);
}
return fp;
}
fp = es_fopen (filename, "wb");
if (!fp)
{
log_error (_("can't open '%s': %s\n"), filename, strerror (errno));
gpgsm_exit (2);
}
return fp;
}
static void
run_protect_tool (int argc, char **argv)
{
#ifdef HAVE_W32_SYSTEM
(void)argc;
(void)argv;
#else
const char *pgm;
char **av;
int i;
if (!opt.protect_tool_program || !*opt.protect_tool_program)
pgm = gnupg_module_name (GNUPG_MODULE_NAME_PROTECT_TOOL);
else
pgm = opt.protect_tool_program;
av = xcalloc (argc+2, sizeof *av);
av[0] = strrchr (pgm, '/');
if (!av[0])
av[0] = xstrdup (pgm);
for (i=1; argc; i++, argc--, argv++)
av[i] = *argv;
av[i] = NULL;
execv (pgm, av);
log_error ("error executing '%s': %s\n", pgm, strerror (errno));
#endif /*!HAVE_W32_SYSTEM*/
gpgsm_exit (2);
}
diff --git a/sm/gpgsm.h b/sm/gpgsm.h
index 7a5e4917d..65fff853a 100644
--- a/sm/gpgsm.h
+++ b/sm/gpgsm.h
@@ -1,440 +1,459 @@
/* gpgsm.h - Global definitions for GpgSM
* Copyright (C) 2001, 2003, 2004, 2007, 2009,
* 2010 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#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/status.h"
#include "../common/audit.h"
#include "../common/session-env.h"
#include "../common/ksba-io-support.h"
#include "../common/compliance.h"
#define MAX_DIGEST_LEN 64
struct keyserver_spec
{
struct keyserver_spec *next;
char *host;
int port;
char *user;
char *pass;
char *base;
};
/* A large struct named "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 */
int no_homedir_creation;
const char *config_filename; /* Name of the used config file. */
const char *agent_program;
session_env_t session_env;
char *lc_ctype;
char *lc_messages;
int autostart;
const char *dirmngr_program;
int disable_dirmngr; /* Do not do any dirmngr calls. */
const char *protect_tool_program;
char *outfile; /* name of output file */
int with_key_data;/* include raw key in the column delimited output */
int fingerprint; /* list fingerprints in all key listings */
int with_md5_fingerprint; /* Also print an MD5 fingerprint for
standard key listings. */
int with_keygrip; /* Option --with-keygrip active. */
int with_key_screening; /* Option --with-key-screening active. */
int pinentry_mode;
int request_origin;
int armor; /* force base64 armoring (see also ctrl.with_base64) */
int no_armor; /* don't try to figure out whether data is base64 armored*/
const char *p12_charset; /* Use this charset for encoding the
pkcs#12 passphrase. */
const char *def_cipher_algoid; /* cipher algorithm to use if
nothing else is specified */
int def_compress_algo; /* Ditto for compress algorithm */
int forced_digest_algo; /* User forced hash algorithm. */
char *def_recipient; /* userID of the default recipient */
int def_recipient_self; /* The default recipient is the default key */
int no_encrypt_to; /* Ignore all as encrypt to marked recipients. */
char *local_user; /* NULL or argument to -u */
int extra_digest_algo; /* A digest algorithm also used for
verification of signatures. */
int always_trust; /* Trust the given keys even if there is no
valid certification chain */
int skip_verify; /* do not check signatures on data */
int lock_once; /* Keep lock once they are set */
int ignore_time_conflict; /* Ignore certain time conflicts */
int no_crl_check; /* Don't do a CRL check */
int no_trusted_cert_crl_check; /* Don't run a CRL check for trusted certs. */
int force_crl_refresh; /* Force refreshing the CRL. */
int enable_ocsp; /* Default to use OCSP checks. */
char *policy_file; /* full pathname of policy file */
int no_policy_check; /* ignore certificate policies */
int no_chain_validation; /* Bypass all cert chain validity tests */
int ignore_expiration; /* Ignore the notAfter validity checks. */
int auto_issuer_key_retrieve; /* try to retrieve a missing issuer key. */
int qualsig_approval; /* Set to true if this software has
officially been approved to create an
verify qualified signatures. This is a
runtime option in case we want to check
the integrity of the software at
runtime. */
struct keyserver_spec *keyserver;
/* A list of certificate extension OIDs which are ignored so that
one can claim that a critical extension has been handled. One
OID per string. */
strlist_t ignored_cert_extensions;
enum gnupg_compliance_mode compliance;
+
+ /* Enable creation of authenticode signatures. */
+ int authenticode;
+
+ /* A list of extra attributes put into a signed data object. For a
+ * signed each attribute each string has the format:
+ * <oid>:s:<hex_or_filename>
+ * and for an unsigned attribute
+ * <oid>:u:<hex_or_filename>
+ * The OID is in the usual dotted decimal for. The HEX_OR_FILENAME
+ * is either a list of hex digits or a filename with the DER encoded
+ * value. A filename is detected by the presence of a slash in the
+ * HEX_OR_FILENAME. The actual value needs to be encoded as a SET OF
+ * attribute values. */
+ strlist_t attributes;
} opt;
/* Debug values and macros. */
#define DBG_X509_VALUE 1 /* debug x.509 data reading/writing */
#define DBG_MPI_VALUE 2 /* debug mpi details */
#define DBG_CRYPTO_VALUE 4 /* debug low level crypto */
#define DBG_MEMORY_VALUE 32 /* debug memory allocation stuff */
#define DBG_CACHE_VALUE 64 /* debug the caching */
#define DBG_MEMSTAT_VALUE 128 /* show memory statistics */
#define DBG_HASHING_VALUE 512 /* debug hashing operations */
#define DBG_IPC_VALUE 1024 /* debug assuan communication */
+#define DBG_CLOCK_VALUE 4096
+#define DBG_LOOKUP_VALUE 8192 /* debug the key lookup */
#define DBG_X509 (opt.debug & DBG_X509_VALUE)
#define DBG_CRYPTO (opt.debug & DBG_CRYPTO_VALUE)
#define DBG_MEMORY (opt.debug & DBG_MEMORY_VALUE)
#define DBG_CACHE (opt.debug & DBG_CACHE_VALUE)
#define DBG_HASHING (opt.debug & DBG_HASHING_VALUE)
#define DBG_IPC (opt.debug & DBG_IPC_VALUE)
+#define DBG_CLOCK (opt.debug & DBG_CLOCK_VALUE)
+#define DBG_LOOKUP (opt.debug & DBG_LOOKUP_VALUE)
/* Forward declaration for an object defined in server.c */
struct server_local_s;
/* Session control object. This object is passed down to most
functions. Note that the default values for it are set by
gpgsm_init_default_ctrl(). */
struct server_control_s
{
int no_server; /* We are not running under server control */
int status_fd; /* Only for non-server mode */
struct server_local_s *server_local;
audit_ctx_t audit; /* NULL or a context for the audit subsystem. */
int agent_seen; /* Flag indicating that the gpg-agent has been
accessed. */
int with_colons; /* Use column delimited output format */
int with_secret; /* Mark secret keys in a public key listing. */
int with_chain; /* Include the certifying certs in a listing */
int with_validation;/* Validate each key while listing. */
int with_ephemeral_keys; /* Include ephemeral flagged keys in the
keylisting. */
int autodetect_encoding; /* Try to detect the input encoding */
int is_pem; /* Is in PEM format */
int is_base64; /* is in plain base-64 format */
int create_base64; /* Create base64 encoded output */
int create_pem; /* create PEM output */
const char *pem_name; /* PEM name to use */
int include_certs; /* -1 to send all certificates in the chain
along with a signature or the number of
certificates up the chain (0 = none, 1 = only
signer) */
int use_ocsp; /* Set to true if OCSP should be used. */
int validation_model; /* 0 := standard model (shell),
1 := chain model,
2 := STEED model. */
int offline; /* If true gpgsm won't do any network access. */
};
/* An object to keep a list of certificates. */
struct certlist_s
{
struct certlist_s *next;
ksba_cert_t cert;
int is_encrypt_to; /* True if the certificate has been set through
the --encrypto-to option. */
int hash_algo; /* Used to track the hash algorithm to use. */
const char *hash_algo_oid; /* And the corresponding OID. */
};
typedef struct certlist_s *certlist_t;
/* A structure carrying information about trusted root certificates. */
struct rootca_flags_s
{
unsigned int valid:1; /* The rest of the structure has valid
information. */
unsigned int relax:1; /* Relax checking of root certificates. */
unsigned int chain_model:1; /* Root requires the use of the chain model. */
};
/*-- gpgsm.c --*/
void gpgsm_exit (int rc);
void gpgsm_init_default_ctrl (struct server_control_s *ctrl);
int gpgsm_parse_validation_model (const char *model);
/*-- server.c --*/
void gpgsm_server (certlist_t default_recplist);
gpg_error_t gpgsm_status (ctrl_t ctrl, int no, const char *text);
gpg_error_t gpgsm_status2 (ctrl_t ctrl, int no, ...) GPGRT_ATTR_SENTINEL(0);
gpg_error_t gpgsm_status_with_err_code (ctrl_t ctrl, int no, const char *text,
gpg_err_code_t ec);
gpg_error_t gpgsm_status_with_error (ctrl_t ctrl, int no, const char *text,
gpg_error_t err);
gpg_error_t gpgsm_proxy_pinentry_notify (ctrl_t ctrl,
const unsigned char *line);
/*-- fingerprint --*/
unsigned char *gpgsm_get_fingerprint (ksba_cert_t cert, int algo,
unsigned char *array, int *r_len);
char *gpgsm_get_fingerprint_string (ksba_cert_t cert, int algo);
char *gpgsm_get_fingerprint_hexstring (ksba_cert_t cert, int algo);
unsigned long gpgsm_get_short_fingerprint (ksba_cert_t cert,
unsigned long *r_high);
unsigned char *gpgsm_get_keygrip (ksba_cert_t cert, unsigned char *array);
char *gpgsm_get_keygrip_hexstring (ksba_cert_t cert);
int gpgsm_get_key_algo_info (ksba_cert_t cert, unsigned int *nbits);
gcry_mpi_t gpgsm_get_rsa_modulus (ksba_cert_t cert);
char *gpgsm_get_certid (ksba_cert_t cert);
/*-- certdump.c --*/
void gpgsm_print_serial (estream_t fp, ksba_const_sexp_t p);
void gpgsm_print_time (estream_t fp, ksba_isotime_t t);
void gpgsm_print_name2 (FILE *fp, const char *string, int translate);
void gpgsm_print_name (FILE *fp, const char *string);
void gpgsm_es_print_name (estream_t fp, const char *string);
void gpgsm_es_print_name2 (estream_t fp, const char *string, int translate);
void gpgsm_cert_log_name (const char *text, ksba_cert_t cert);
void gpgsm_dump_cert (const char *text, ksba_cert_t cert);
void gpgsm_dump_serial (ksba_const_sexp_t p);
void gpgsm_dump_time (ksba_isotime_t t);
void gpgsm_dump_string (const char *string);
char *gpgsm_format_serial (ksba_const_sexp_t p);
char *gpgsm_format_name2 (const char *name, int translate);
char *gpgsm_format_name (const char *name);
char *gpgsm_format_sn_issuer (ksba_sexp_t sn, const char *issuer);
char *gpgsm_fpr_and_name_for_status (ksba_cert_t cert);
char *gpgsm_format_keydesc (ksba_cert_t cert);
/*-- certcheck.c --*/
int gpgsm_check_cert_sig (ksba_cert_t issuer_cert, ksba_cert_t cert);
int gpgsm_check_cms_signature (ksba_cert_t cert, ksba_const_sexp_t sigval,
gcry_md_hd_t md, int hash_algo, int *r_pkalgo);
/* fixme: move create functions to another file */
int gpgsm_create_cms_signature (ctrl_t ctrl,
ksba_cert_t cert, gcry_md_hd_t md, int mdalgo,
unsigned char **r_sigval);
/*-- certchain.c --*/
/* Flags used with gpgsm_validate_chain. */
#define VALIDATE_FLAG_NO_DIRMNGR 1
#define VALIDATE_FLAG_CHAIN_MODEL 2
#define VALIDATE_FLAG_STEED 4
int gpgsm_walk_cert_chain (ctrl_t ctrl,
ksba_cert_t start, ksba_cert_t *r_next);
int gpgsm_is_root_cert (ksba_cert_t cert);
int gpgsm_validate_chain (ctrl_t ctrl, ksba_cert_t cert,
ksba_isotime_t checktime,
ksba_isotime_t r_exptime,
int listmode, estream_t listfp,
unsigned int flags, unsigned int *retflags);
int gpgsm_basic_cert_check (ctrl_t ctrl, ksba_cert_t cert);
/*-- certlist.c --*/
-int gpgsm_cert_use_sign_p (ksba_cert_t cert);
+int gpgsm_cert_use_sign_p (ksba_cert_t cert, int silent);
int gpgsm_cert_use_encrypt_p (ksba_cert_t cert);
int gpgsm_cert_use_verify_p (ksba_cert_t cert);
int gpgsm_cert_use_decrypt_p (ksba_cert_t cert);
int gpgsm_cert_use_cert_p (ksba_cert_t cert);
int gpgsm_cert_use_ocsp_p (ksba_cert_t cert);
int gpgsm_cert_has_well_known_private_key (ksba_cert_t cert);
int gpgsm_certs_identical_p (ksba_cert_t cert_a, ksba_cert_t cert_b);
int gpgsm_add_cert_to_certlist (ctrl_t ctrl, ksba_cert_t cert,
certlist_t *listaddr, int is_encrypt_to);
int gpgsm_add_to_certlist (ctrl_t ctrl, const char *name, int secret,
certlist_t *listaddr, int is_encrypt_to);
void gpgsm_release_certlist (certlist_t list);
int gpgsm_find_cert (ctrl_t ctrl, const char *name, ksba_sexp_t keyid,
ksba_cert_t *r_cert, int allow_ambiguous);
/*-- keylist.c --*/
gpg_error_t gpgsm_list_keys (ctrl_t ctrl, strlist_t names,
estream_t fp, unsigned int mode);
/*-- import.c --*/
int gpgsm_import (ctrl_t ctrl, int in_fd, int reimport_mode);
int gpgsm_import_files (ctrl_t ctrl, int nfiles, char **files,
int (*of)(const char *fname));
/*-- export.c --*/
void gpgsm_export (ctrl_t ctrl, strlist_t names, estream_t stream);
void gpgsm_p12_export (ctrl_t ctrl, const char *name, estream_t stream,
int rawmode);
/*-- delete.c --*/
int gpgsm_delete (ctrl_t ctrl, strlist_t names);
/*-- verify.c --*/
int gpgsm_verify (ctrl_t ctrl, int in_fd, int data_fd, estream_t out_fp);
/*-- sign.c --*/
int gpgsm_get_default_cert (ctrl_t ctrl, ksba_cert_t *r_cert);
int gpgsm_sign (ctrl_t ctrl, certlist_t signerlist,
int data_fd, int detached, estream_t out_fp);
/*-- encrypt.c --*/
int gpgsm_encrypt (ctrl_t ctrl, certlist_t recplist,
int in_fd, estream_t out_fp);
/*-- decrypt.c --*/
int gpgsm_decrypt (ctrl_t ctrl, int in_fd, estream_t out_fp);
/*-- certreqgen.c --*/
int gpgsm_genkey (ctrl_t ctrl, estream_t in_stream, estream_t out_stream);
/*-- certreqgen-ui.c --*/
void gpgsm_gencertreq_tty (ctrl_t ctrl, estream_t out_stream);
/*-- qualified.c --*/
gpg_error_t gpgsm_is_in_qualified_list (ctrl_t ctrl, ksba_cert_t cert,
char *country);
gpg_error_t gpgsm_qualified_consent (ctrl_t ctrl, ksba_cert_t cert);
gpg_error_t gpgsm_not_qualified_warning (ctrl_t ctrl, ksba_cert_t cert);
/*-- call-agent.c --*/
int gpgsm_agent_pksign (ctrl_t ctrl, const char *keygrip, const char *desc,
unsigned char *digest,
size_t digestlen,
int digestalgo,
unsigned char **r_buf, size_t *r_buflen);
int gpgsm_scd_pksign (ctrl_t ctrl, const char *keyid, const char *desc,
unsigned char *digest, size_t digestlen, int digestalgo,
unsigned char **r_buf, size_t *r_buflen);
int gpgsm_agent_pkdecrypt (ctrl_t ctrl, const char *keygrip, const char *desc,
ksba_const_sexp_t ciphertext,
char **r_buf, size_t *r_buflen);
int gpgsm_agent_genkey (ctrl_t ctrl,
ksba_const_sexp_t keyparms, ksba_sexp_t *r_pubkey);
int gpgsm_agent_readkey (ctrl_t ctrl, int fromcard, const char *hexkeygrip,
ksba_sexp_t *r_pubkey);
int gpgsm_agent_scd_serialno (ctrl_t ctrl, char **r_serialno);
int gpgsm_agent_scd_keypairinfo (ctrl_t ctrl, strlist_t *r_list);
int gpgsm_agent_istrusted (ctrl_t ctrl, ksba_cert_t cert, const char *hexfpr,
struct rootca_flags_s *rootca_flags);
int gpgsm_agent_havekey (ctrl_t ctrl, const char *hexkeygrip);
int gpgsm_agent_marktrusted (ctrl_t ctrl, ksba_cert_t cert);
int gpgsm_agent_learn (ctrl_t ctrl);
int gpgsm_agent_passwd (ctrl_t ctrl, const char *hexkeygrip, const char *desc);
gpg_error_t gpgsm_agent_get_confirmation (ctrl_t ctrl, const char *desc);
gpg_error_t gpgsm_agent_send_nop (ctrl_t ctrl);
gpg_error_t gpgsm_agent_keyinfo (ctrl_t ctrl, const char *hexkeygrip,
char **r_serialno);
gpg_error_t gpgsm_agent_ask_passphrase (ctrl_t ctrl, const char *desc_msg,
int repeat, char **r_passphrase);
gpg_error_t gpgsm_agent_keywrap_key (ctrl_t ctrl, int forexport,
void **r_kek, size_t *r_keklen);
gpg_error_t gpgsm_agent_import_key (ctrl_t ctrl,
const void *key, size_t keylen);
gpg_error_t gpgsm_agent_export_key (ctrl_t ctrl, const char *keygrip,
const char *desc,
unsigned char **r_result,
size_t *r_resultlen);
/*-- call-dirmngr.c --*/
int gpgsm_dirmngr_isvalid (ctrl_t ctrl,
ksba_cert_t cert, ksba_cert_t issuer_cert,
int use_ocsp);
int gpgsm_dirmngr_lookup (ctrl_t ctrl, strlist_t names, int cache_only,
void (*cb)(void*, ksba_cert_t), void *cb_value);
int gpgsm_dirmngr_run_command (ctrl_t ctrl, const char *command,
int argc, char **argv);
/*-- misc.c --*/
void setup_pinentry_env (void);
gpg_error_t transform_sigval (const unsigned char *sigval, size_t sigvallen,
int mdalgo,
unsigned char **r_newsigval,
size_t *r_newsigvallen);
#endif /*GPGSM_H*/
diff --git a/sm/keydb.c b/sm/keydb.c
index f66a5766d..53e3cf887 100644
--- a/sm/keydb.c
+++ b/sm/keydb.c
@@ -1,1362 +1,1536 @@
/* keydb.c - key database dispatcher
* Copyright (C) 2001, 2003, 2004 Free Software Foundation, Inc.
* Copyright (C) 2014 g10 Code GmbH
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#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 "../common/i18n.h"
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;
- dotlock_t lockhandle;
};
static struct resource_item all_resources[MAX_KEYDB_RESOURCES];
static int used_resources;
/* Whether we have successfully registered any resource. */
static int any_registered;
struct keydb_handle {
+
+ /* If this flag is set the resources is locked. */
int locked;
+
+ /* If this flag is set a lock will only be released by
+ * keydb_release. */
+ int keep_lock;
+
int found;
int saved_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);
static void
try_make_homedir (const char *fname)
{
const char *defhome = standard_homedir ();
/* Create the directory only if the supplied directory name is the
same as the default one. This way we avoid to create arbitrary
directories when a non-default home directory is used. To cope
with HOME, we do compare only the suffix if we see that the
default homedir does start with a tilde. */
if ( opt.dry_run || opt.no_homedir_creation )
return;
if (
#ifdef HAVE_W32_SYSTEM
( !compare_filenames (fname, defhome) )
#else
( *defhome == '~'
&& (strlen(fname) >= strlen (defhome+1)
&& !strcmp(fname+strlen(fname)-strlen(defhome+1), defhome+1 ) ))
|| (*defhome != '~' && !compare_filenames( fname, defhome ) )
#endif
)
{
if (gnupg_mkdir (fname, "-rwx"))
log_info (_("can't create directory '%s': %s\n"),
fname, strerror(errno) );
else if (!opt.quiet )
log_info (_("directory '%s' created\n"), fname);
}
}
/* Handle the creation of a keybox if it does not yet exist. Take
into account that other processes might have the keybox already
locked. This lock check does not work if the directory itself is
not yet available. If R_CREATED is not NULL it will be set to true
if the function created a new keybox. */
static gpg_error_t
maybe_create_keybox (char *filename, int force, int *r_created)
{
dotlock_t lockhd = NULL;
FILE *fp;
int rc;
mode_t oldmask;
char *last_slash_in_filename;
int save_slash;
if (r_created)
*r_created = 0;
/* A quick test whether the filename already exists. */
if (!access (filename, F_OK))
return !access (filename, R_OK)? 0 : gpg_error (GPG_ERR_EACCES);
/* If we don't want to create a new file at all, there is no need to
go any further - bail out right here. */
if (!force)
return gpg_error (GPG_ERR_ENOENT);
/* First of all we try to create the home directory. Note, that we
don't do any locking here because any sane application of gpg
would create the home directory by itself and not rely on gpg's
tricky auto-creation which is anyway only done for some home
directory name patterns. */
last_slash_in_filename = strrchr (filename, DIRSEP_C);
#if HAVE_W32_SYSTEM
{
/* Windows may either have a slash or a backslash. Take care of it. */
char *p = strrchr (filename, '/');
if (!last_slash_in_filename || p > last_slash_in_filename)
last_slash_in_filename = p;
}
#endif /*HAVE_W32_SYSTEM*/
if (!last_slash_in_filename)
return gpg_error (GPG_ERR_ENOENT); /* No slash at all - should
not happen though. */
save_slash = *last_slash_in_filename;
*last_slash_in_filename = 0;
if (access(filename, F_OK))
{
static int tried;
if (!tried)
{
tried = 1;
try_make_homedir (filename);
}
if (access (filename, F_OK))
{
rc = gpg_error_from_syserror ();
*last_slash_in_filename = save_slash;
goto leave;
}
}
*last_slash_in_filename = save_slash;
/* To avoid races with other instances of gpg trying to create or
update the keybox (it is removed during an update for a short
time), we do the next stuff in a locked state. */
lockhd = dotlock_create (filename, 0);
if (!lockhd)
{
/* A reason for this to fail is that the directory is not
writable. However, this whole locking stuff does not make
sense if this is the case. An empty non-writable directory
with no keyring is not really useful at all. */
if (opt.verbose)
log_info ("can't allocate lock for '%s'\n", filename );
if (!force)
return gpg_error (GPG_ERR_ENOENT);
else
return gpg_error (GPG_ERR_GENERAL);
}
if ( dotlock_take (lockhd, -1) )
{
/* This is something bad. Probably a stale lockfile. */
log_info ("can't lock '%s'\n", filename);
rc = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
/* Now the real test while we are locked. */
if (!access(filename, F_OK))
{
rc = 0; /* Okay, we may access the file now. */
goto leave;
}
/* The file does not yet exist, create it now. */
oldmask = umask (077);
fp = fopen (filename, "wb");
if (!fp)
{
rc = gpg_error_from_syserror ();
umask (oldmask);
log_error (_("error creating keybox '%s': %s\n"),
filename, gpg_strerror (rc));
goto leave;
}
umask (oldmask);
/* Make sure that at least one record is in a new keybox file, so
that the detection magic for OpenPGP keyboxes works the next time
it is used. */
rc = _keybox_write_header_blob (fp, 0);
if (rc)
{
fclose (fp);
log_error (_("error creating keybox '%s': %s\n"),
filename, gpg_strerror (rc));
goto leave;
}
if (!opt.quiet)
log_info (_("keybox '%s' created\n"), filename);
if (r_created)
*r_created = 1;
fclose (fp);
rc = 0;
leave:
if (lockhd)
{
dotlock_release (lockhd);
dotlock_destroy (lockhd);
}
return rc;
}
/*
* 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. If AUTO_CREATED is not NULL it will be set to true
* if the function has created a new keybox.
*/
gpg_error_t
keydb_add_resource (ctrl_t ctrl, const char *url, int force, int *auto_created)
{
const char *resname = url;
char *filename = NULL;
gpg_error_t err = 0;
KeydbResourceType rt = KEYDB_RESOURCE_TYPE_NONE;
if (auto_created)
*auto_created = 0;
/* Do we have an URL?
gnupg-kbx:filename := this is a plain keybox
filename := See what it 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 );
err = 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 (gnupg_homedir (), resname, NULL);
}
else
filename = xstrdup (resname);
if (!force)
force = !any_registered;
/* see whether we can determine the filetype */
if (rt == KEYDB_RESOURCE_TYPE_NONE)
{
FILE *fp = fopen( filename, "rb" );
if (fp)
{
u32 magic;
/* FIXME: check for the keybox magic */
if (fread (&magic, 4, 1, fp) == 1 )
{
if (magic == 0x13579ace || magic == 0xce9a5713)
; /* GDBM magic - no more support */
else
rt = KEYDB_RESOURCE_TYPE_KEYBOX;
}
else /* maybe empty: assume keybox */
rt = KEYDB_RESOURCE_TYPE_KEYBOX;
fclose (fp);
}
else /* no file yet: create keybox */
rt = KEYDB_RESOURCE_TYPE_KEYBOX;
}
switch (rt)
{
case KEYDB_RESOURCE_TYPE_NONE:
log_error ("unknown type of key resource '%s'\n", url );
err = gpg_error (GPG_ERR_GENERAL);
goto leave;
case KEYDB_RESOURCE_TYPE_KEYBOX:
err = maybe_create_keybox (filename, force, auto_created);
if (err)
goto leave;
/* Now register the file */
{
void *token;
err = keybox_register_file (filename, 0, &token);
if (gpg_err_code (err) == GPG_ERR_EEXIST)
; /* Already registered - ignore. */
else if (err)
; /* Other error. */
else if (used_resources >= MAX_KEYDB_RESOURCES)
err = gpg_error (GPG_ERR_RESOURCE_LIMIT);
else
{
+ KEYBOX_HANDLE kbxhd;
+
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].lockhandle
- = dotlock_create (filename, 0);
- if (!all_resources[used_resources].lockhandle)
- log_fatal ( _("can't create lock for '%s'\n"), filename);
-
- /* Do a compress run if needed and the file is not locked. */
- if (!dotlock_take (all_resources[used_resources].lockhandle, 0))
+ /* Do a compress run if needed and the keybox is not locked. */
+ kbxhd = keybox_new_x509 (token, 0);
+ if (kbxhd)
{
- KEYBOX_HANDLE kbxhd = keybox_new_x509 (token, 0);
-
- if (kbxhd)
- {
- keybox_compress (kbxhd);
- keybox_release (kbxhd);
- }
- dotlock_release (all_resources[used_resources].lockhandle);
+ if (!keybox_lock (kbxhd, 1, 0))
+ keybox_compress (kbxhd);
+
+ keybox_release (kbxhd);
}
used_resources++;
}
}
break;
default:
log_error ("resource type of '%s' not supported\n", url);
err = gpg_error (GPG_ERR_NOT_SUPPORTED);
goto leave;
}
/* fixme: check directory permissions and print a warning */
leave:
if (err)
{
log_error ("keyblock resource '%s': %s\n", filename, gpg_strerror (err));
gpgsm_status_with_error (ctrl, STATUS_ERROR,
"add_keyblock_resource", err);
}
else
any_registered = 1;
xfree (filename);
return err;
}
KEYDB_HANDLE
keydb_new (void)
{
KEYDB_HANDLE hd;
int i, j;
+ if (DBG_CLOCK)
+ log_clock ("%s: enter\n", __func__);
+
hd = xcalloc (1, sizeof *hd);
hd->found = -1;
hd->saved_found = -1;
- assert (used_resources <= MAX_KEYDB_RESOURCES);
+ log_assert (used_resources <= MAX_KEYDB_RESOURCES);
for (i=j=0; i < used_resources; i++)
{
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].lockhandle = all_resources[i].lockhandle;
hd->active[j].u.kr = keybox_new_x509 (all_resources[i].token, 0);
if (!hd->active[j].u.kr)
{
xfree (hd);
return NULL; /* fixme: release all previously allocated handles*/
}
j++;
break;
}
}
hd->used = j;
active_handles++;
+ if (DBG_CLOCK)
+ log_clock ("%s: leave (hd=%p)\n", __func__, hd);
return hd;
}
void
keydb_release (KEYDB_HANDLE hd)
{
int i;
if (!hd)
return;
- assert (active_handles > 0);
+
+ if (DBG_CLOCK)
+ log_clock ("%s: enter (hd=%p)\n", __func__, hd);
+
+ log_assert (active_handles > 0);
active_handles--;
+ hd->keep_lock = 0;
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);
+ xfree (hd);
+ if (DBG_CLOCK)
+ log_clock ("%s: leave\n", __func__);
}
/* 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 original 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;
}
/* If the keyring has not yet been locked, lock it now. This
- operation is required before any update operation; it is optional
- for an insert operation. The lock is released with
- keydb_released. */
+ * operation is required before any update operation; it is optional
+ * for an insert operation. The lock is kept until a keydb_release so
+ * that internal unlock_all calls have no effect. */
gpg_error_t
keydb_lock (KEYDB_HANDLE hd)
{
+ gpg_error_t err;
+
if (!hd)
return gpg_error (GPG_ERR_INV_HANDLE);
- if (hd->locked)
- return 0; /* Already locked. */
- return lock_all (hd);
+
+ if (DBG_CLOCK)
+ log_clock ("%s: enter (hd=%p)\n", __func__, hd);
+ err = lock_all (hd);
+ if (!err)
+ hd->keep_lock = 1;
+
+ if (DBG_CLOCK)
+ log_clock ("%s: leave (err=%s)\n", __func__, gpg_strerror (err));
+ return err;
}
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 order by all processes. We are
currently 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 = dotlock_take (hd->active[i].lockhandle, -1);
+ rc = keybox_lock (hd->active[i].u.kr, 1, -1);
break;
}
if (rc)
break;
}
if (rc)
{
- /* revert the already set locks */
+ /* 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)
- dotlock_release (hd->active[i].lockhandle);
+ keybox_lock (hd->active[i].u.kr, 0, 0);
break;
}
}
}
else
hd->locked = 1;
- /* make_dotlock () does not yet guarantee that errno is set, thus
- we can't rely on the error reason and will simply use
- EACCES. */
- return rc? gpg_error (GPG_ERR_EACCES) : 0;
+ return rc;
}
static void
unlock_all (KEYDB_HANDLE hd)
{
int i;
- if (!hd->locked)
+ if (!hd->locked || hd->keep_lock)
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)
- dotlock_release (hd->active[i].lockhandle);
+ keybox_lock (hd->active[i].u.kr, 0, 0);
break;
}
}
hd->locked = 0;
}
/* Push the last found state if any. */
void
keydb_push_found_state (KEYDB_HANDLE hd)
{
if (!hd)
return;
if (hd->found < 0 || hd->found >= hd->used)
{
hd->saved_found = -1;
return;
}
switch (hd->active[hd->found].type)
{
case KEYDB_RESOURCE_TYPE_NONE:
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
keybox_push_found_state (hd->active[hd->found].u.kr);
break;
}
hd->saved_found = hd->found;
hd->found = -1;
+ if (DBG_CLOCK)
+ log_clock ("%s: done (hd=%p)\n", __func__, hd);
}
/* Pop the last found state. */
void
keydb_pop_found_state (KEYDB_HANDLE hd)
{
if (!hd)
return;
hd->found = hd->saved_found;
hd->saved_found = -1;
if (hd->found < 0 || hd->found >= hd->used)
return;
switch (hd->active[hd->found].type)
{
case KEYDB_RESOURCE_TYPE_NONE:
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
keybox_pop_found_state (hd->active[hd->found].u.kr);
break;
}
+ if (DBG_CLOCK)
+ log_clock ("%s: done (hd=%p)\n", __func__, hd);
}
/*
Return the last found object. 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, ksba_cert_t *r_cert)
{
int rc = 0;
if (!hd)
return gpg_error (GPG_ERR_INV_VALUE);
+ if (DBG_CLOCK)
+ log_clock ("%s: enter (hd=%p)\n", __func__, hd);
+
if ( hd->found < 0 || hd->found >= hd->used)
- return -1; /* nothing found */
+ {
+ rc = -1; /* nothing found */
+ goto leave;
+ }
+ rc = GPG_ERR_BUG;
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;
}
+ leave:
+ if (DBG_CLOCK)
+ log_clock ("%s: leave (rc=%d)\n", __func__, rc);
return rc;
}
+
/* Return a flag of the last found object. WHICH is the flag requested;
it should be one of the KEYBOX_FLAG_ values. If the operation is
successful, the flag value will be stored at the address given by
VALUE. Return 0 on success or an error code. */
gpg_error_t
keydb_get_flags (KEYDB_HANDLE hd, int which, int idx, unsigned int *value)
{
- int err = 0;
+ gpg_error_t err;
if (!hd)
return gpg_error (GPG_ERR_INV_VALUE);
+ if (DBG_CLOCK)
+ log_clock ("%s: enter (hd=%p)\n", __func__, hd);
+
if ( hd->found < 0 || hd->found >= hd->used)
- return gpg_error (GPG_ERR_NOTHING_FOUND);
+ {
+ err = gpg_error (GPG_ERR_NOTHING_FOUND);
+ goto leave;
+ }
+ err = gpg_error (GPG_ERR_BUG);
switch (hd->active[hd->found].type)
{
case KEYDB_RESOURCE_TYPE_NONE:
err = gpg_error (GPG_ERR_GENERAL); /* oops */
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
err = keybox_get_flags (hd->active[hd->found].u.kr, which, idx, value);
break;
}
+ leave:
+ if (DBG_CLOCK)
+ log_clock ("%s: leave (err=%s)\n", __func__, gpg_strerror (err));
return err;
}
+
/* Set a flag of the last found object. WHICH is the flag to be set; it
should be one of the KEYBOX_FLAG_ values. If the operation is
successful, the flag value will be stored in the keybox. Note,
that some flag values can't be updated and thus may return an
error, some other flag values may be masked out before an update.
Returns 0 on success or an error code. */
gpg_error_t
keydb_set_flags (KEYDB_HANDLE hd, int which, int idx, unsigned int value)
{
- int err = 0;
+ gpg_error_t err = 0;
if (!hd)
return gpg_error (GPG_ERR_INV_VALUE);
+ if (DBG_CLOCK)
+ log_clock ("%s: enter (hd=%p)\n", __func__, hd);
+
if ( hd->found < 0 || hd->found >= hd->used)
- return gpg_error (GPG_ERR_NOTHING_FOUND);
+ {
+ err = gpg_error (GPG_ERR_NOTHING_FOUND);
+ goto leave;
+ }
if (!hd->locked)
- return gpg_error (GPG_ERR_NOT_LOCKED);
+ {
+ err = gpg_error (GPG_ERR_NOT_LOCKED);
+ goto leave;
+ }
switch (hd->active[hd->found].type)
{
case KEYDB_RESOURCE_TYPE_NONE:
err = gpg_error (GPG_ERR_GENERAL); /* oops */
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
err = keybox_set_flags (hd->active[hd->found].u.kr, which, idx, value);
break;
}
+ leave:
+ if (DBG_CLOCK)
+ log_clock ("%s: leave (err=%s)\n", __func__, gpg_strerror (err));
return err;
}
/*
* Insert a new Certificate into one of the resources.
*/
-int
+gpg_error_t
keydb_insert_cert (KEYDB_HANDLE hd, ksba_cert_t cert)
{
- int rc = -1;
+ gpg_error_t err;
int idx;
unsigned char digest[20];
if (!hd)
return gpg_error (GPG_ERR_INV_VALUE);
if (opt.dry_run)
return 0;
+ if (DBG_CLOCK)
+ log_clock ("%s: enter (hd=%p)\n", __func__, hd);
+
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);
+ {
+ err = gpg_error (GPG_ERR_GENERAL);
+ goto leave;
+ }
if (!hd->locked)
- return gpg_error (GPG_ERR_NOT_LOCKED);
+ {
+ err = gpg_error (GPG_ERR_NOT_LOCKED);
+ goto leave;
+ }
gpgsm_get_fingerprint (cert, GCRY_MD_SHA1, digest, NULL); /* kludge*/
+ err = gpg_error (GPG_ERR_BUG);
switch (hd->active[idx].type)
{
case KEYDB_RESOURCE_TYPE_NONE:
- rc = gpg_error (GPG_ERR_GENERAL);
+ err = gpg_error (GPG_ERR_GENERAL);
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
- rc = keybox_insert_cert (hd->active[idx].u.kr, cert, digest);
+ err = keybox_insert_cert (hd->active[idx].u.kr, cert, digest);
break;
}
unlock_all (hd);
- return rc;
+
+ leave:
+ if (DBG_CLOCK)
+ log_clock ("%s: leave (err=%s)\n", __func__, gpg_strerror (err));
+ return err;
}
/* Update the current keyblock with KB. */
-int
+/* Note: This function is currently not called. */
+gpg_error_t
keydb_update_cert (KEYDB_HANDLE hd, ksba_cert_t cert)
{
- int rc = 0;
+ gpg_error_t err;
unsigned char digest[20];
if (!hd)
return gpg_error (GPG_ERR_INV_VALUE);
if ( hd->found < 0 || hd->found >= hd->used)
- return -1; /* nothing found */
+ return gpg_error (GPG_ERR_NOT_FOUND);
if (opt.dry_run)
return 0;
- rc = lock_all (hd);
- if (rc)
- return rc;
+ if (DBG_CLOCK)
+ log_clock ("%s: enter (hd=%p)\n", __func__, hd);
+
+ err = lock_all (hd);
+ if (err)
+ goto leave;
gpgsm_get_fingerprint (cert, GCRY_MD_SHA1, digest, NULL); /* kludge*/
+ err = gpg_error (GPG_ERR_BUG);
switch (hd->active[hd->found].type)
{
case KEYDB_RESOURCE_TYPE_NONE:
- rc = gpg_error (GPG_ERR_GENERAL); /* oops */
+ err = gpg_error (GPG_ERR_GENERAL); /* oops */
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
- rc = keybox_update_cert (hd->active[hd->found].u.kr, cert, digest);
+ err = keybox_update_cert (hd->active[hd->found].u.kr, cert, digest);
break;
}
unlock_all (hd);
- return rc;
+ leave:
+ if (DBG_CLOCK)
+ log_clock ("%s: leave (err=%s)\n", __func__, gpg_strerror (err));
+ return err;
}
/*
* The current keyblock or cert will be deleted.
*/
-int
-keydb_delete (KEYDB_HANDLE hd, int unlock)
+gpg_error_t
+keydb_delete (KEYDB_HANDLE hd)
{
- int rc = -1;
+ gpg_error_t err;
if (!hd)
return gpg_error (GPG_ERR_INV_VALUE);
if ( hd->found < 0 || hd->found >= hd->used)
- return -1; /* nothing found */
+ return gpg_error (GPG_ERR_NOT_FOUND);
- if( opt.dry_run )
+ if (opt.dry_run)
return 0;
+ if (DBG_CLOCK)
+ log_clock ("%s: enter (hd=%p)\n", __func__, hd);
+
if (!hd->locked)
- return gpg_error (GPG_ERR_NOT_LOCKED);
+ {
+ err = gpg_error (GPG_ERR_NOT_LOCKED);
+ goto leave;
+ }
+ err = gpg_error (GPG_ERR_BUG);
switch (hd->active[hd->found].type)
{
case KEYDB_RESOURCE_TYPE_NONE:
- rc = gpg_error (GPG_ERR_GENERAL);
+ err = gpg_error (GPG_ERR_GENERAL);
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
- rc = keybox_delete (hd->active[hd->found].u.kr);
+ err = keybox_delete (hd->active[hd->found].u.kr);
break;
}
- if (unlock)
- unlock_all (hd);
- return rc;
+ unlock_all (hd);
+
+ leave:
+ if (DBG_CLOCK)
+ log_clock ("%s: leave (err=%s)\n", __func__, gpg_strerror (err));
+ return err;
}
/*
* 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;
(void)reserved;
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++)
{
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
*/
gpg_error_t
keydb_search_reset (KEYDB_HANDLE hd)
{
+ gpg_error_t err = 0;
int i;
- gpg_error_t rc = 0;
if (!hd)
return gpg_error (GPG_ERR_INV_VALUE);
+ if (DBG_CLOCK)
+ log_clock ("%s: enter (hd=%p)\n", __func__, hd);
+
hd->current = 0;
hd->found = -1;
/* and reset all resources */
- for (i=0; !rc && i < hd->used; i++)
+ for (i=0; !err && 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);
+ err = keybox_search_reset (hd->active[i].u.kr);
break;
}
}
- return rc;
+
+ if (DBG_CLOCK)
+ log_clock ("%s: leave (err=%s)\n", __func__, gpg_strerror (err));
+ return err;
}
+
+char *
+keydb_search_desc_dump (struct keydb_search_desc *desc)
+{
+ char *fpr;
+ char *result;
+
+ switch (desc->mode)
+ {
+ case KEYDB_SEARCH_MODE_EXACT:
+ return xasprintf ("EXACT: '%s'", desc->u.name);
+ case KEYDB_SEARCH_MODE_SUBSTR:
+ return xasprintf ("SUBSTR: '%s'", desc->u.name);
+ case KEYDB_SEARCH_MODE_MAIL:
+ return xasprintf ("MAIL: '%s'", desc->u.name);
+ case KEYDB_SEARCH_MODE_MAILSUB:
+ return xasprintf ("MAILSUB: '%s'", desc->u.name);
+ case KEYDB_SEARCH_MODE_MAILEND:
+ return xasprintf ("MAILEND: '%s'", desc->u.name);
+ case KEYDB_SEARCH_MODE_WORDS:
+ return xasprintf ("WORDS: '%s'", desc->u.name);
+ case KEYDB_SEARCH_MODE_SHORT_KID:
+ return xasprintf ("SHORT_KID: '%08lX'", (ulong)desc->u.kid[1]);
+ case KEYDB_SEARCH_MODE_LONG_KID:
+ return xasprintf ("LONG_KID: '%08lX%08lX'",
+ (ulong)desc->u.kid[0], (ulong)desc->u.kid[1]);
+ case KEYDB_SEARCH_MODE_FPR:
+ fpr = bin2hexcolon (desc->u.fpr, desc->fprlen, NULL);
+ result = xasprintf ("FPR%02d: '%s'", desc->fprlen, fpr);
+ xfree (fpr);
+ return result;
+ case KEYDB_SEARCH_MODE_ISSUER:
+ return xasprintf ("ISSUER: '%s'", desc->u.name);
+ case KEYDB_SEARCH_MODE_ISSUER_SN:
+ return xasprintf ("ISSUER_SN: '%*s'",
+ (int) (desc->snlen == -1
+ ? strlen (desc->sn) : desc->snlen),
+ desc->sn);
+ case KEYDB_SEARCH_MODE_SN:
+ return xasprintf ("SN: '%*s'",
+ (int) (desc->snlen == -1
+ ? strlen (desc->sn) : desc->snlen),
+ desc->sn);
+ case KEYDB_SEARCH_MODE_SUBJECT:
+ return xasprintf ("SUBJECT: '%s'", desc->u.name);
+ case KEYDB_SEARCH_MODE_KEYGRIP:
+ return xasprintf ("KEYGRIP: %s", desc->u.grip);
+ case KEYDB_SEARCH_MODE_FIRST:
+ return xasprintf ("FIRST");
+ case KEYDB_SEARCH_MODE_NEXT:
+ return xasprintf ("NEXT");
+ default:
+ return xasprintf ("Bad search mode (%d)", desc->mode);
+ }
+}
+
+
/*
* 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 (ctrl_t ctrl, KEYDB_HANDLE hd,
KEYDB_SEARCH_DESC *desc, size_t ndesc)
{
int rc = -1;
unsigned long skipped;
+ int i;
if (!hd)
return gpg_error (GPG_ERR_INV_VALUE);
if (!any_registered)
{
gpgsm_status_with_error (ctrl, STATUS_ERROR, "keydb_search",
gpg_error (GPG_ERR_KEYRING_OPEN));
return gpg_error (GPG_ERR_NOT_FOUND);
}
- while (rc == -1 && hd->current >= 0 && hd->current < hd->used)
+ if (DBG_CLOCK)
+ log_clock ("%s: enter (hd=%p)\n", __func__, hd);
+
+ if (DBG_LOOKUP)
+ {
+ log_debug ("%s: %zd search description(s):\n", __func__, ndesc);
+ for (i = 0; i < ndesc; i ++)
+ {
+ char *t = keydb_search_desc_dump (&desc[i]);
+ log_debug ("%s: %d: %s\n", __func__, i, t);
+ xfree (t);
+ }
+ }
+
+ while ((rc == -1 || gpg_err_code (rc) == GPG_ERR_EOF)
+ && 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,
KEYBOX_BLOBTYPE_X509,
NULL, &skipped);
break;
}
+
+ if (DBG_LOOKUP)
+ log_debug ("%s: searched %s (resource %d of %d) => %s\n",
+ __func__,
+ hd->active[hd->current].type == KEYDB_RESOURCE_TYPE_KEYBOX
+ ? "keybox" : "unknown type",
+ hd->current, hd->used,
+ rc == -1 ? "EOF" : gpg_strerror (rc));
+
if (rc == -1 || gpg_err_code (rc) == GPG_ERR_EOF)
{ /* EOF -> switch to next resource */
hd->current++;
}
else if (!rc)
hd->found = hd->current;
}
+
+ if (DBG_CLOCK)
+ log_clock ("%s: leave (rc=%d)\n", __func__, rc);
return rc;
}
int
keydb_search_first (ctrl_t ctrl, KEYDB_HANDLE hd)
{
KEYDB_SEARCH_DESC desc;
memset (&desc, 0, sizeof desc);
desc.mode = KEYDB_SEARCH_MODE_FIRST;
return keydb_search (ctrl, hd, &desc, 1);
}
int
keydb_search_next (ctrl_t ctrl, KEYDB_HANDLE hd)
{
KEYDB_SEARCH_DESC desc;
memset (&desc, 0, sizeof desc);
desc.mode = KEYDB_SEARCH_MODE_NEXT;
return keydb_search (ctrl, hd, &desc, 1);
}
int
keydb_search_kid (ctrl_t ctrl, KEYDB_HANDLE hd, u32 *kid)
{
KEYDB_SEARCH_DESC desc;
(void)kid;
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 (ctrl, hd, &desc, 1);
}
int
keydb_search_fpr (ctrl_t ctrl, 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);
desc.fprlen = 20;
return keydb_search (ctrl, hd, &desc, 1);
}
int
keydb_search_issuer (ctrl_t ctrl, 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 (ctrl, hd, &desc, 1);
return rc;
}
int
keydb_search_issuer_sn (ctrl_t ctrl, KEYDB_HANDLE hd,
const char *issuer, ksba_const_sexp_t 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 (ctrl, hd, &desc, 1);
return rc;
}
int
keydb_search_subject (ctrl_t ctrl, 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 (ctrl, hd, &desc, 1);
return rc;
}
/* 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 (ctrl_t ctrl, ksba_cert_t 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 ();
if (!kh)
{
log_error (_("failed to allocate keyDB handle\n"));
return gpg_error (GPG_ERR_ENOMEM);;
}
/* Set the ephemeral flag so that the search looks at all
records. */
keydb_set_ephemeral (kh, 1);
rc = lock_all (kh);
if (rc)
return rc;
rc = keydb_search_fpr (ctrl, kh, fpr);
if (rc != -1)
{
keydb_release (kh);
if (!rc)
{
if (existed)
*existed = 1;
if (!ephemeral)
{
/* Remove ephemeral flags from existing certificate to "store"
it permanently. */
rc = keydb_set_cert_flags (ctrl, cert, 1, KEYBOX_FLAG_BLOB, 0,
KEYBOX_FLAG_BLOB_EPHEMERAL, 0);
if (rc)
{
log_error ("clearing ephemeral flag failed: %s\n",
gpg_strerror (rc));
return rc;
}
}
return 0; /* okay */
}
log_error (_("problem looking for existing certificate: %s\n"),
gpg_strerror (rc));
return rc;
}
/* Reset the ephemeral flag if not requested. */
if (!ephemeral)
keydb_set_ephemeral (kh, 0);
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;
}
/* This is basically keydb_set_flags but it implements a complete
transaction by locating the certificate in the DB and updating the
flags. */
gpg_error_t
keydb_set_cert_flags (ctrl_t ctrl, ksba_cert_t cert, int ephemeral,
int which, int idx,
unsigned int mask, unsigned int value)
{
KEYDB_HANDLE kh;
gpg_error_t err;
unsigned char fpr[20];
unsigned int old_value;
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 ();
if (!kh)
{
log_error (_("failed to allocate keyDB handle\n"));
return gpg_error (GPG_ERR_ENOMEM);;
}
if (ephemeral)
keydb_set_ephemeral (kh, 1);
err = keydb_lock (kh);
if (err)
{
log_error (_("error locking keybox: %s\n"), gpg_strerror (err));
keydb_release (kh);
return err;
}
err = keydb_search_fpr (ctrl, kh, fpr);
if (err)
{
if (err == -1)
err = gpg_error (GPG_ERR_NOT_FOUND);
else
log_error (_("problem re-searching certificate: %s\n"),
gpg_strerror (err));
keydb_release (kh);
return err;
}
err = keydb_get_flags (kh, which, idx, &old_value);
if (err)
{
log_error (_("error getting stored flags: %s\n"), gpg_strerror (err));
keydb_release (kh);
return err;
}
value = ((old_value & ~mask) | (value & mask));
if (value != old_value)
{
err = keydb_set_flags (kh, which, idx, value);
if (err)
{
log_error (_("error storing flags: %s\n"), gpg_strerror (err));
keydb_release (kh);
return err;
}
}
keydb_release (kh);
return 0;
}
/* Reset all the certificate flags we have stored with the certificates
for performance reasons. */
void
keydb_clear_some_cert_flags (ctrl_t ctrl, strlist_t names)
{
gpg_error_t err;
KEYDB_HANDLE hd = NULL;
KEYDB_SEARCH_DESC *desc = NULL;
int ndesc;
strlist_t sl;
int rc=0;
unsigned int old_value, value;
(void)ctrl;
hd = keydb_new ();
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 failed: %s\n",
gpg_strerror (out_of_core ()));
goto leave;
}
if (!names)
desc[0].mode = KEYDB_SEARCH_MODE_FIRST;
else
{
for (ndesc=0, sl=names; sl; sl = sl->next)
{
rc = classify_user_id (sl->d, desc+ndesc, 0);
if (rc)
log_error ("key '%s' not found: %s\n", sl->d, gpg_strerror (rc));
else
ndesc++;
}
}
err = keydb_lock (hd);
if (err)
{
log_error (_("error locking keybox: %s\n"), gpg_strerror (err));
goto leave;
}
while (!(rc = keydb_search (ctrl, hd, desc, ndesc)))
{
if (!names)
desc[0].mode = KEYDB_SEARCH_MODE_NEXT;
err = keydb_get_flags (hd, KEYBOX_FLAG_VALIDITY, 0, &old_value);
if (err)
{
log_error (_("error getting stored flags: %s\n"),
gpg_strerror (err));
goto leave;
}
value = (old_value & ~VALIDITY_REVOKED);
if (value != old_value)
{
err = keydb_set_flags (hd, KEYBOX_FLAG_VALIDITY, 0, value);
if (err)
{
log_error (_("error storing flags: %s\n"), gpg_strerror (err));
goto leave;
}
}
}
if (rc && rc != -1)
log_error ("keydb_search failed: %s\n", gpg_strerror (rc));
leave:
xfree (desc);
keydb_release (hd);
}
diff --git a/sm/keydb.h b/sm/keydb.h
index 623462553..1ab94a2c2 100644
--- a/sm/keydb.h
+++ b/sm/keydb.h
@@ -1,78 +1,78 @@
/* keydb.h - Key database
* Copyright (C) 1998, 1999, 2000, 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 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#ifndef GNUPG_KEYDB_H
#define GNUPG_KEYDB_H
#include <ksba.h>
#include "../common/userids.h"
typedef struct keydb_handle *KEYDB_HANDLE;
/* Flag value used with KEYBOX_FLAG_VALIDITY. */
#define VALIDITY_REVOKED (1<<5)
/*-- keydb.c --*/
gpg_error_t keydb_add_resource (ctrl_t ctrl, const char *url,
int force, int *auto_created);
KEYDB_HANDLE keydb_new (void);
void keydb_release (KEYDB_HANDLE hd);
int keydb_set_ephemeral (KEYDB_HANDLE hd, int yes);
const char *keydb_get_resource_name (KEYDB_HANDLE hd);
gpg_error_t keydb_lock (KEYDB_HANDLE hd);
gpg_error_t keydb_get_flags (KEYDB_HANDLE hd, int which, int idx,
unsigned int *value);
gpg_error_t keydb_set_flags (KEYDB_HANDLE hd, int which, int idx,
unsigned int value);
void keydb_push_found_state (KEYDB_HANDLE hd);
void keydb_pop_found_state (KEYDB_HANDLE hd);
int keydb_get_cert (KEYDB_HANDLE hd, ksba_cert_t *r_cert);
-int keydb_insert_cert (KEYDB_HANDLE hd, ksba_cert_t cert);
-int keydb_update_cert (KEYDB_HANDLE hd, ksba_cert_t cert);
+gpg_error_t keydb_insert_cert (KEYDB_HANDLE hd, ksba_cert_t cert);
+gpg_error_t keydb_update_cert (KEYDB_HANDLE hd, ksba_cert_t cert);
-int keydb_delete (KEYDB_HANDLE hd, int unlock);
+gpg_error_t keydb_delete (KEYDB_HANDLE hd);
int keydb_locate_writable (KEYDB_HANDLE hd, const char *reserved);
void keydb_rebuild_caches (void);
gpg_error_t keydb_search_reset (KEYDB_HANDLE hd);
int keydb_search (ctrl_t ctrl, KEYDB_HANDLE hd,
KEYDB_SEARCH_DESC *desc, size_t ndesc);
int keydb_search_first (ctrl_t ctrl, KEYDB_HANDLE hd);
int keydb_search_next (ctrl_t ctrl, KEYDB_HANDLE hd);
int keydb_search_kid (ctrl_t ctrl, KEYDB_HANDLE hd, u32 *kid);
int keydb_search_fpr (ctrl_t ctrl, KEYDB_HANDLE hd, const byte *fpr);
int keydb_search_issuer (ctrl_t ctrl, KEYDB_HANDLE hd, const char *issuer);
int keydb_search_issuer_sn (ctrl_t ctrl, KEYDB_HANDLE hd,
const char *issuer, const unsigned char *serial);
int keydb_search_subject (ctrl_t ctrl, KEYDB_HANDLE hd, const char *issuer);
int keydb_store_cert (ctrl_t ctrl, ksba_cert_t cert, int ephemeral,
int *existed);
gpg_error_t keydb_set_cert_flags (ctrl_t ctrl, ksba_cert_t cert, int ephemeral,
int which, int idx,
unsigned int mask, unsigned int value);
void keydb_clear_some_cert_flags (ctrl_t ctrl, strlist_t names);
#endif /*GNUPG_KEYDB_H*/
diff --git a/sm/keylist.c b/sm/keylist.c
index e242310be..4eecb8720 100644
--- a/sm/keylist.c
+++ b/sm/keylist.c
@@ -1,1680 +1,1682 @@
/* keylist.c - Print certificates in various formats.
* Copyright (C) 1998, 1999, 2000, 2001, 2003, 2004, 2005, 2008, 2009,
* 2010, 2011 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#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 "../kbx/keybox.h" /* for KEYBOX_FLAG_* */
#include "../common/i18n.h"
#include "../common/tlv.h"
#include "../common/compliance.h"
#include "../common/pkscreening.h"
struct list_external_parm_s
{
ctrl_t ctrl;
estream_t fp;
int print_header;
int with_colons;
int with_chain;
int raw_mode;
};
/* This table is to map Extended Key Usage OIDs to human readable
names. */
struct
{
const char *oid;
const char *name;
} key_purpose_map[] = {
{ "1.3.6.1.5.5.7.3.1", "serverAuth" },
{ "1.3.6.1.5.5.7.3.2", "clientAuth" },
{ "1.3.6.1.5.5.7.3.3", "codeSigning" },
{ "1.3.6.1.5.5.7.3.4", "emailProtection" },
{ "1.3.6.1.5.5.7.3.5", "ipsecEndSystem" },
{ "1.3.6.1.5.5.7.3.6", "ipsecTunnel" },
{ "1.3.6.1.5.5.7.3.7", "ipsecUser" },
{ "1.3.6.1.5.5.7.3.8", "timeStamping" },
{ "1.3.6.1.5.5.7.3.9", "ocspSigning" },
{ "1.3.6.1.5.5.7.3.10", "dvcs" },
{ "1.3.6.1.5.5.7.3.11", "sbgpCertAAServerAuth" },
{ "1.3.6.1.5.5.7.3.13", "eapOverPPP" },
{ "1.3.6.1.5.5.7.3.14", "wlanSSID" },
{ "2.16.840.1.113730.4.1", "serverGatedCrypto.ns" }, /* Netscape. */
{ "1.3.6.1.4.1.311.10.3.3", "serverGatedCrypto.ms"}, /* Microsoft. */
{ "1.3.6.1.5.5.7.48.1.5", "ocspNoCheck" },
{ NULL, NULL }
};
/* Do not print this extension in the list of extensions. This is set
for oids which are already available via ksba functions. */
#define OID_FLAG_SKIP 1
/* The extension is a simple UTF8String and should be printed. */
#define OID_FLAG_UTF8 2
/* The extension can be trnted as a hex string. */
#define OID_FLAG_HEX 4
/* A table mapping OIDs to a descriptive string. */
static struct
{
char *oid;
char *name;
unsigned int flag; /* A flag as described above. */
} oidtranstbl[] = {
/* Algorithms. */
{ "1.2.840.10040.4.1", "dsa" },
{ "1.2.840.10040.4.3", "dsaWithSha1" },
{ "1.2.840.113549.1.1.1", "rsaEncryption" },
{ "1.2.840.113549.1.1.2", "md2WithRSAEncryption" },
{ "1.2.840.113549.1.1.3", "md4WithRSAEncryption" },
{ "1.2.840.113549.1.1.4", "md5WithRSAEncryption" },
{ "1.2.840.113549.1.1.5", "sha1WithRSAEncryption" },
{ "1.2.840.113549.1.1.7", "rsaOAEP" },
{ "1.2.840.113549.1.1.8", "rsaOAEP-MGF" },
{ "1.2.840.113549.1.1.9", "rsaOAEP-pSpecified" },
{ "1.2.840.113549.1.1.10", "rsaPSS" },
{ "1.2.840.113549.1.1.11", "sha256WithRSAEncryption" },
{ "1.2.840.113549.1.1.12", "sha384WithRSAEncryption" },
{ "1.2.840.113549.1.1.13", "sha512WithRSAEncryption" },
{ "1.3.14.3.2.26", "sha1" },
{ "1.3.14.3.2.29", "sha-1WithRSAEncryption" },
{ "1.3.36.3.3.1.2", "rsaSignatureWithripemd160" },
/* Telesec extensions. */
{ "0.2.262.1.10.12.0", "certExtensionLiabilityLimitationExt" },
{ "0.2.262.1.10.12.1", "telesecCertIdExt" },
{ "0.2.262.1.10.12.2", "telesecPolicyIdentifier" },
{ "0.2.262.1.10.12.3", "telesecPolicyQualifierID" },
{ "0.2.262.1.10.12.4", "telesecCRLFilteredExt" },
{ "0.2.262.1.10.12.5", "telesecCRLFilterExt"},
{ "0.2.262.1.10.12.6", "telesecNamingAuthorityExt" },
#define OIDSTR_restriction \
"1.3.36.8.3.8"
{ OIDSTR_restriction, "restriction", OID_FLAG_UTF8 },
/* PKIX private extensions. */
{ "1.3.6.1.5.5.7.1.1", "authorityInfoAccess" },
{ "1.3.6.1.5.5.7.1.2", "biometricInfo" },
{ "1.3.6.1.5.5.7.1.3", "qcStatements" },
{ "1.3.6.1.5.5.7.1.4", "acAuditIdentity" },
{ "1.3.6.1.5.5.7.1.5", "acTargeting" },
{ "1.3.6.1.5.5.7.1.6", "acAaControls" },
{ "1.3.6.1.5.5.7.1.7", "sbgp-ipAddrBlock" },
{ "1.3.6.1.5.5.7.1.8", "sbgp-autonomousSysNum" },
{ "1.3.6.1.5.5.7.1.9", "sbgp-routerIdentifier" },
{ "1.3.6.1.5.5.7.1.10", "acProxying" },
{ "1.3.6.1.5.5.7.1.11", "subjectInfoAccess" },
{ "1.3.6.1.5.5.7.48.1", "ocsp" },
{ "1.3.6.1.5.5.7.48.2", "caIssuers" },
{ "1.3.6.1.5.5.7.48.3", "timeStamping" },
{ "1.3.6.1.5.5.7.48.5", "caRepository" },
/* X.509 id-ce */
{ "2.5.29.14", "subjectKeyIdentifier", OID_FLAG_SKIP},
{ "2.5.29.15", "keyUsage", OID_FLAG_SKIP},
{ "2.5.29.16", "privateKeyUsagePeriod" },
{ "2.5.29.17", "subjectAltName", OID_FLAG_SKIP},
{ "2.5.29.18", "issuerAltName", OID_FLAG_SKIP},
{ "2.5.29.19", "basicConstraints", OID_FLAG_SKIP},
{ "2.5.29.20", "cRLNumber" },
{ "2.5.29.21", "cRLReason" },
{ "2.5.29.22", "expirationDate" },
{ "2.5.29.23", "instructionCode" },
{ "2.5.29.24", "invalidityDate" },
{ "2.5.29.27", "deltaCRLIndicator" },
{ "2.5.29.28", "issuingDistributionPoint" },
{ "2.5.29.29", "certificateIssuer" },
{ "2.5.29.30", "nameConstraints" },
{ "2.5.29.31", "cRLDistributionPoints", OID_FLAG_SKIP},
{ "2.5.29.32", "certificatePolicies", OID_FLAG_SKIP},
{ "2.5.29.32.0", "anyPolicy" },
{ "2.5.29.33", "policyMappings" },
{ "2.5.29.35", "authorityKeyIdentifier", OID_FLAG_SKIP},
{ "2.5.29.36", "policyConstraints" },
{ "2.5.29.37", "extKeyUsage", OID_FLAG_SKIP},
{ "2.5.29.46", "freshestCRL" },
{ "2.5.29.54", "inhibitAnyPolicy" },
/* Netscape certificate extensions. */
{ "2.16.840.1.113730.1.1", "netscape-cert-type" },
{ "2.16.840.1.113730.1.2", "netscape-base-url" },
{ "2.16.840.1.113730.1.3", "netscape-revocation-url" },
{ "2.16.840.1.113730.1.4", "netscape-ca-revocation-url" },
{ "2.16.840.1.113730.1.7", "netscape-cert-renewal-url" },
{ "2.16.840.1.113730.1.8", "netscape-ca-policy-url" },
{ "2.16.840.1.113730.1.9", "netscape-homePage-url" },
{ "2.16.840.1.113730.1.10", "netscape-entitylogo" },
{ "2.16.840.1.113730.1.11", "netscape-userPicture" },
{ "2.16.840.1.113730.1.12", "netscape-ssl-server-name" },
{ "2.16.840.1.113730.1.13", "netscape-comment" },
/* GnuPG extensions */
{ "1.3.6.1.4.1.11591.2.1.1", "pkaAddress" },
{ "1.3.6.1.4.1.11591.2.2.1", "standaloneCertificate" },
{ "1.3.6.1.4.1.11591.2.2.2", "wellKnownPrivateKey" },
/* Extensions used by the Bundesnetzagentur. */
{ "1.3.6.1.4.1.8301.3.5", "validityModel" },
/* Yubikey extensions for attestation certificates. */
{ "1.3.6.1.4.1.41482.3.3", "yubikey-firmware-version", OID_FLAG_HEX },
{ "1.3.6.1.4.1.41482.3.7", "yubikey-serial-number", OID_FLAG_HEX },
{ "1.3.6.1.4.1.41482.3.8", "yubikey-pin-touch-policy", OID_FLAG_HEX },
{ "1.3.6.1.4.1.41482.3.9", "yubikey-formfactor", OID_FLAG_HEX },
{ NULL }
};
/* Return the description for OID; if no description is available
NULL is returned. */
static const char *
get_oid_desc (const char *oid, unsigned int *flag)
{
int i;
if (oid)
for (i=0; oidtranstbl[i].oid; i++)
if (!strcmp (oidtranstbl[i].oid, oid))
{
if (flag)
*flag = oidtranstbl[i].flag;
return oidtranstbl[i].name;
}
if (flag)
*flag = 0;
return NULL;
}
static void
print_key_data (ksba_cert_t cert, estream_t fp)
{
#if 0
int n = pk ? pubkey_get_npkey( pk->pubkey_algo ) : 0;
int i;
for(i=0; i < n; i++ )
{
es_fprintf (fp, "pkd:%d:%u:", i, mpi_get_nbits( pk->pkey[i] ) );
mpi_print(stdout, pk->pkey[i], 1 );
putchar(':');
putchar('\n');
}
#else
(void)cert;
(void)fp;
#endif
}
/* Various public key screenings. (Right now just ROCA). With
* COLON_MODE set the output is formatted for use in the compliance
* field of a colon listing. */
static void
print_pk_screening (ksba_cert_t cert, int colon_mode, estream_t fp)
{
gpg_error_t err;
gcry_mpi_t modulus;
modulus = gpgsm_get_rsa_modulus (cert);
if (modulus)
{
err = screen_key_for_roca (modulus);
if (!err)
;
else if (gpg_err_code (err) == GPG_ERR_TRUE)
{
if (colon_mode)
es_fprintf (fp, colon_mode > 1? " %d":"%d", 6001);
else
es_fprintf (fp, " screening: ROCA vulnerability detected\n");
}
else if (!colon_mode)
es_fprintf (fp, " screening: [ROCA check failed: %s]\n",
gpg_strerror (err));
gcry_mpi_release (modulus);
}
}
static void
print_capabilities (ksba_cert_t cert, estream_t fp)
{
gpg_error_t err;
unsigned int use;
size_t buflen;
char buffer[1];
err = ksba_cert_get_user_data (cert, "is_qualified",
&buffer, sizeof (buffer), &buflen);
if (!err && buflen)
{
if (*buffer)
es_putc ('q', fp);
}
else if (gpg_err_code (err) == GPG_ERR_NOT_FOUND)
; /* Don't know - will not get marked as 'q' */
else
log_debug ("get_user_data(is_qualified) failed: %s\n",
gpg_strerror (err));
err = ksba_cert_get_key_usage (cert, &use);
if (gpg_err_code (err) == GPG_ERR_NO_DATA)
{
es_putc ('e', fp);
es_putc ('s', fp);
es_putc ('c', fp);
es_putc ('E', fp);
es_putc ('S', fp);
es_putc ('C', fp);
return;
}
if (err)
{
log_error (_("error getting key usage information: %s\n"),
gpg_strerror (err));
return;
}
if ((use & (KSBA_KEYUSAGE_KEY_ENCIPHERMENT|KSBA_KEYUSAGE_DATA_ENCIPHERMENT)))
es_putc ('e', fp);
if ((use & (KSBA_KEYUSAGE_DIGITAL_SIGNATURE|KSBA_KEYUSAGE_NON_REPUDIATION)))
es_putc ('s', fp);
if ((use & KSBA_KEYUSAGE_KEY_CERT_SIGN))
es_putc ('c', fp);
if ((use & (KSBA_KEYUSAGE_KEY_ENCIPHERMENT|KSBA_KEYUSAGE_DATA_ENCIPHERMENT)))
es_putc ('E', fp);
if ((use & (KSBA_KEYUSAGE_DIGITAL_SIGNATURE|KSBA_KEYUSAGE_NON_REPUDIATION)))
es_putc ('S', fp);
if ((use & KSBA_KEYUSAGE_KEY_CERT_SIGN))
es_putc ('C', fp);
}
static void
print_time (gnupg_isotime_t t, estream_t fp)
{
if (!t || !*t)
;
else
es_fputs (t, fp);
}
/* Return an allocated string with the email address extracted from a
DN. Note hat we use this code also in ../kbx/keybox-blob.c. */
static char *
email_kludge (const char *name)
{
const char *p, *string;
unsigned char *buf;
int n;
string = name;
for (;;)
{
p = strstr (string, "1.2.840.113549.1.9.1=#");
if (!p)
return NULL;
if (p == name || (p > string+1 && p[-1] == ',' && p[-2] != '\\'))
{
name = p + 22;
break;
}
string = p + 22;
}
/* This looks pretty much like an email address in the subject's DN
we use this to add an additional user ID entry. This way,
OpenSSL generated keys get a nicer and usable listing. */
for (n=0, p=name; hexdigitp (p) && hexdigitp (p+1); p +=2, n++)
;
if (!n)
return NULL;
buf = xtrymalloc (n+3);
if (!buf)
return NULL; /* oops, out of core */
*buf = '<';
for (n=1, p=name; hexdigitp (p); p +=2, n++)
buf[n] = xtoi_2 (p);
buf[n++] = '>';
buf[n] = 0;
return (char*)buf;
}
/* Print the compliance flags to field 18. ALGO is the gcrypt algo
* number. NBITS is the length of the key in bits. */
static void
print_compliance_flags (ksba_cert_t cert, int algo, unsigned int nbits,
estream_t fp)
{
int indent = 0;
int hashalgo;
if (gnupg_pk_is_compliant (CO_DE_VS, algo, NULL, nbits, NULL))
{
hashalgo = gcry_md_map_name (ksba_cert_get_digest_algo (cert));
if (gnupg_digest_is_compliant (CO_DE_VS, hashalgo))
{
es_fputs (gnupg_status_compliance_flag (CO_DE_VS), fp);
indent = 1;
}
}
if (opt.with_key_screening)
print_pk_screening (cert, 1+indent, fp);
}
/* List one certificate in colon mode */
static void
list_cert_colon (ctrl_t ctrl, ksba_cert_t cert, unsigned int validity,
estream_t fp, int have_secret)
{
int rc;
int idx;
char truststring[2];
char *p;
ksba_sexp_t sexp;
char *fpr;
ksba_isotime_t t;
gpg_error_t valerr;
int algo;
unsigned int nbits;
const char *chain_id;
char *chain_id_buffer = NULL;
int is_root = 0;
char *kludge_uid;
if (ctrl->with_validation)
valerr = gpgsm_validate_chain (ctrl, cert, "", NULL, 1, NULL, 0, NULL);
else
valerr = 0;
/* We need to get the fingerprint and the chaining ID in advance. */
fpr = gpgsm_get_fingerprint_hexstring (cert, GCRY_MD_SHA1);
{
ksba_cert_t next;
rc = gpgsm_walk_cert_chain (ctrl, cert, &next);
if (!rc) /* We known the issuer's certificate. */
{
p = gpgsm_get_fingerprint_hexstring (next, GCRY_MD_SHA1);
chain_id_buffer = p;
chain_id = chain_id_buffer;
ksba_cert_release (next);
}
else if (rc == -1) /* We have reached the root certificate. */
{
chain_id = fpr;
is_root = 1;
}
else
chain_id = NULL;
}
es_fputs (have_secret? "crs:":"crt:", fp);
/* Note: We can't use multiple flags, like "ei", because the
validation check does only return one error. */
truststring[0] = 0;
truststring[1] = 0;
if ((validity & VALIDITY_REVOKED)
|| gpg_err_code (valerr) == GPG_ERR_CERT_REVOKED)
*truststring = 'r';
else if (gpg_err_code (valerr) == GPG_ERR_CERT_EXPIRED)
*truststring = 'e';
else
{
/* Lets also check whether the certificate under question
expired. This is merely a hack until we found a proper way
to store the expiration flag in the keybox. */
ksba_isotime_t current_time, not_after;
gnupg_get_isotime (current_time);
if (!opt.ignore_expiration
&& !ksba_cert_get_validity (cert, 1, not_after)
&& *not_after && strcmp (current_time, not_after) > 0 )
*truststring = 'e';
else if (valerr)
{
if (gpgsm_cert_has_well_known_private_key (cert))
*truststring = 'w'; /* Well, this is dummy CA. */
else
*truststring = 'i';
}
else if (ctrl->with_validation && !is_root)
*truststring = 'f';
}
/* If we have no truststring yet (i.e. the certificate might be
good) and this is a root certificate, we ask the agent whether
this is a trusted root certificate. */
if (!*truststring && is_root)
{
struct rootca_flags_s dummy_flags;
if (gpgsm_cert_has_well_known_private_key (cert))
*truststring = 'w'; /* Well, this is dummy CA. */
else
{
rc = gpgsm_agent_istrusted (ctrl, cert, NULL, &dummy_flags);
if (!rc)
*truststring = 'u'; /* Yes, we trust this one (ultimately). */
else if (gpg_err_code (rc) == GPG_ERR_NOT_TRUSTED)
*truststring = 'n'; /* No, we do not trust this one. */
/* (in case of an error we can't tell anything.) */
}
}
if (*truststring)
es_fputs (truststring, fp);
algo = gpgsm_get_key_algo_info (cert, &nbits);
es_fprintf (fp, ":%u:%d:%s:", nbits, algo, fpr+24);
ksba_cert_get_validity (cert, 0, t);
print_time (t, fp);
es_putc (':', fp);
ksba_cert_get_validity (cert, 1, t);
print_time ( t, fp);
es_putc (':', fp);
/* Field 8, serial number: */
if ((sexp = ksba_cert_get_serial (cert)))
{
int len;
const unsigned char *s = sexp;
if (*s == '(')
{
s++;
for (len=0; *s && *s != ':' && digitp (s); s++)
len = len*10 + atoi_1 (s);
if (*s == ':')
for (s++; len; len--, s++)
es_fprintf (fp,"%02X", *s);
}
xfree (sexp);
}
es_putc (':', fp);
/* Field 9, ownertrust - not used here */
es_putc (':', fp);
/* field 10, old user ID - we use it here for the issuer DN */
if ((p = ksba_cert_get_issuer (cert,0)))
{
es_write_sanitized (fp, p, strlen (p), ":", NULL);
xfree (p);
}
es_putc (':', fp);
/* Field 11, signature class - not used */
es_putc (':', fp);
/* Field 12, capabilities: */
print_capabilities (cert, fp);
es_putc (':', fp);
/* Field 13, not used: */
es_putc (':', fp);
/* Field 14, not used: */
es_putc (':', fp);
if (have_secret || ctrl->with_secret)
{
char *cardsn;
p = gpgsm_get_keygrip_hexstring (cert);
if (!gpgsm_agent_keyinfo (ctrl, p, &cardsn)
&& (cardsn || ctrl->with_secret))
{
/* Field 15: Token serial number or secret key indicator. */
if (cardsn)
es_fputs (cardsn, fp);
else if (ctrl->with_secret)
es_putc ('+', fp);
}
xfree (cardsn);
xfree (p);
}
es_putc (':', fp); /* End of field 15. */
es_putc (':', fp); /* End of field 16. */
es_putc (':', fp); /* End of field 17. */
print_compliance_flags (cert, algo, nbits, fp);
es_putc (':', fp); /* End of field 18. */
es_putc ('\n', fp);
/* FPR record */
es_fprintf (fp, "fpr:::::::::%s:::", fpr);
/* Print chaining ID (field 13)*/
if (chain_id)
es_fputs (chain_id, fp);
es_putc (':', fp);
es_putc ('\n', fp);
xfree (fpr); fpr = NULL; chain_id = NULL;
xfree (chain_id_buffer); chain_id_buffer = NULL;
/* Always print the keygrip. */
if ( (p = gpgsm_get_keygrip_hexstring (cert)))
{
es_fprintf (fp, "grp:::::::::%s:\n", p);
xfree (p);
}
if (opt.with_key_data)
print_key_data (cert, fp);
kludge_uid = NULL;
for (idx=0; (p = ksba_cert_get_subject (cert,idx)); idx++)
{
/* In the case that the same email address is in the subject DN
as well as in an alternate subject name we avoid printing it
a second time. */
if (kludge_uid && !strcmp (kludge_uid, p))
continue;
es_fprintf (fp, "uid:%s::::::::", truststring);
es_write_sanitized (fp, p, strlen (p), ":", NULL);
es_putc (':', fp);
es_putc (':', fp);
es_putc ('\n', fp);
if (!idx)
{
/* It would be better to get the faked email address from
the keydb. But as long as we don't have a way to pass
the meta data back, we just check it the same way as the
code used to create the keybox meta data does */
kludge_uid = email_kludge (p);
if (kludge_uid)
{
es_fprintf (fp, "uid:%s::::::::", truststring);
es_write_sanitized (fp, kludge_uid, strlen (kludge_uid),
":", NULL);
es_putc (':', fp);
es_putc (':', fp);
es_putc ('\n', fp);
}
}
xfree (p);
}
xfree (kludge_uid);
}
static void
print_name_raw (estream_t fp, const char *string)
{
if (!string)
es_fputs ("[error]", fp);
else
es_write_sanitized (fp, string, strlen (string), NULL, NULL);
}
static void
print_names_raw (estream_t fp, int indent, ksba_name_t name)
{
int idx;
const char *s;
int indent_all;
if ((indent_all = (indent < 0)))
indent = - indent;
if (!name)
{
es_fputs ("none\n", fp);
return;
}
for (idx=0; (s = ksba_name_enum (name, idx)); idx++)
{
char *p = ksba_name_get_uri (name, idx);
es_fprintf (fp, "%*s", idx||indent_all?indent:0, "");
es_write_sanitized (fp, p?p:s, strlen (p?p:s), NULL, NULL);
es_putc ('\n', fp);
xfree (p);
}
}
static void
print_utf8_extn_raw (estream_t fp, int indent,
const unsigned char *der, size_t derlen)
{
gpg_error_t err;
int class, tag, constructed, ndef;
size_t objlen, hdrlen;
if (indent < 0)
indent = - indent;
err = parse_ber_header (&der, &derlen, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > derlen || tag != TAG_UTF8_STRING))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
{
es_fprintf (fp, "%*s[%s]\n", indent, "", gpg_strerror (err));
return;
}
es_fprintf (fp, "%*s(%.*s)\n", indent, "", (int)objlen, der);
}
static void
print_utf8_extn (estream_t fp, int indent,
const unsigned char *der, size_t derlen)
{
gpg_error_t err;
int class, tag, constructed, ndef;
size_t objlen, hdrlen;
int indent_all;
if ((indent_all = (indent < 0)))
indent = - indent;
err = parse_ber_header (&der, &derlen, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > derlen || tag != TAG_UTF8_STRING))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
{
es_fprintf (fp, "%*s[%s%s]\n",
indent_all? indent:0, "", _("Error - "), gpg_strerror (err));
return;
}
es_fprintf (fp, "%*s\"", indent_all? indent:0, "");
/* Fixme: we should implement word wrapping */
es_write_sanitized (fp, der, objlen, "\"", NULL);
es_fputs ("\"\n", fp);
}
/* Print the extension described by (DER,DERLEN) in hex. */
static void
print_hex_extn (estream_t fp, int indent,
const unsigned char *der, size_t derlen)
{
if (indent < 0)
indent = - indent;
es_fprintf (fp, "%*s(", indent, "");
for (; derlen; der++, derlen--)
es_fprintf (fp, "%02X%s", *der, derlen > 1? " ":"");
es_fprintf (fp, ")\n");
}
/* List one certificate in raw mode useful to have a closer look at
the certificate. This one does no beautification and only minimal
output sanitation. It is mainly useful for debugging. */
static void
list_cert_raw (ctrl_t ctrl, KEYDB_HANDLE hd,
ksba_cert_t cert, estream_t fp, int have_secret,
int with_validation)
{
gpg_error_t err;
size_t off, len;
ksba_sexp_t sexp, keyid;
char *dn;
ksba_isotime_t t;
int idx, i;
int is_ca, chainlen;
unsigned int kusage;
char *string, *p, *pend;
const char *oid, *s;
ksba_name_t name, name2;
unsigned int reason;
const unsigned char *cert_der = NULL;
(void)have_secret;
es_fprintf (fp, " ID: 0x%08lX\n",
gpgsm_get_short_fingerprint (cert, NULL));
sexp = ksba_cert_get_serial (cert);
es_fputs (" S/N: ", fp);
gpgsm_print_serial (fp, sexp);
ksba_free (sexp);
es_putc ('\n', fp);
dn = ksba_cert_get_issuer (cert, 0);
es_fputs (" Issuer: ", fp);
print_name_raw (fp, dn);
ksba_free (dn);
es_putc ('\n', fp);
for (idx=1; (dn = ksba_cert_get_issuer (cert, idx)); idx++)
{
es_fputs (" aka: ", fp);
print_name_raw (fp, dn);
ksba_free (dn);
es_putc ('\n', fp);
}
dn = ksba_cert_get_subject (cert, 0);
es_fputs (" Subject: ", fp);
print_name_raw (fp, dn);
ksba_free (dn);
es_putc ('\n', fp);
for (idx=1; (dn = ksba_cert_get_subject (cert, idx)); idx++)
{
es_fputs (" aka: ", fp);
print_name_raw (fp, dn);
ksba_free (dn);
es_putc ('\n', fp);
}
dn = gpgsm_get_fingerprint_string (cert, 0);
es_fprintf (fp, " sha1_fpr: %s\n", dn?dn:"error");
xfree (dn);
dn = gpgsm_get_fingerprint_string (cert, GCRY_MD_MD5);
es_fprintf (fp, " md5_fpr: %s\n", dn?dn:"error");
xfree (dn);
dn = gpgsm_get_certid (cert);
es_fprintf (fp, " certid: %s\n", dn?dn:"error");
xfree (dn);
dn = gpgsm_get_keygrip_hexstring (cert);
es_fprintf (fp, " keygrip: %s\n", dn?dn:"error");
xfree (dn);
ksba_cert_get_validity (cert, 0, t);
es_fputs (" notBefore: ", fp);
gpgsm_print_time (fp, t);
es_putc ('\n', fp);
es_fputs (" notAfter: ", fp);
ksba_cert_get_validity (cert, 1, t);
gpgsm_print_time (fp, t);
es_putc ('\n', fp);
oid = ksba_cert_get_digest_algo (cert);
s = get_oid_desc (oid, NULL);
es_fprintf (fp, " hashAlgo: %s%s%s%s\n", oid, s?" (":"",s?s:"",s?")":"");
{
const char *algoname;
unsigned int nbits;
algoname = gcry_pk_algo_name (gpgsm_get_key_algo_info (cert, &nbits));
es_fprintf (fp, " keyType: %u bit %s\n",
nbits, algoname? algoname:"?");
}
/* subjectKeyIdentifier */
es_fputs (" subjKeyId: ", fp);
err = ksba_cert_get_subj_key_id (cert, NULL, &keyid);
if (!err || gpg_err_code (err) == GPG_ERR_NO_DATA)
{
if (gpg_err_code (err) == GPG_ERR_NO_DATA)
es_fputs ("[none]\n", fp);
else
{
gpgsm_print_serial (fp, keyid);
ksba_free (keyid);
es_putc ('\n', fp);
}
}
else
es_fputs ("[?]\n", fp);
/* authorityKeyIdentifier */
es_fputs (" authKeyId: ", fp);
err = ksba_cert_get_auth_key_id (cert, &keyid, &name, &sexp);
if (!err || gpg_err_code (err) == GPG_ERR_NO_DATA)
{
if (gpg_err_code (err) == GPG_ERR_NO_DATA || !name)
es_fputs ("[none]\n", fp);
else
{
gpgsm_print_serial (fp, sexp);
ksba_free (sexp);
es_putc ('\n', fp);
print_names_raw (fp, -15, name);
ksba_name_release (name);
}
if (keyid)
{
es_fputs (" authKeyId.ki: ", fp);
gpgsm_print_serial (fp, keyid);
ksba_free (keyid);
es_putc ('\n', fp);
}
}
else
es_fputs ("[?]\n", fp);
es_fputs (" keyUsage:", fp);
err = ksba_cert_get_key_usage (cert, &kusage);
if (gpg_err_code (err) != GPG_ERR_NO_DATA)
{
if (err)
es_fprintf (fp, " [error: %s]", gpg_strerror (err));
else
{
if ( (kusage & KSBA_KEYUSAGE_DIGITAL_SIGNATURE))
es_fputs (" digitalSignature", fp);
if ( (kusage & KSBA_KEYUSAGE_NON_REPUDIATION))
es_fputs (" nonRepudiation", fp);
if ( (kusage & KSBA_KEYUSAGE_KEY_ENCIPHERMENT))
es_fputs (" keyEncipherment", fp);
if ( (kusage & KSBA_KEYUSAGE_DATA_ENCIPHERMENT))
es_fputs (" dataEncipherment", fp);
if ( (kusage & KSBA_KEYUSAGE_KEY_AGREEMENT))
es_fputs (" keyAgreement", fp);
if ( (kusage & KSBA_KEYUSAGE_KEY_CERT_SIGN))
es_fputs (" certSign", fp);
if ( (kusage & KSBA_KEYUSAGE_CRL_SIGN))
es_fputs (" crlSign", fp);
if ( (kusage & KSBA_KEYUSAGE_ENCIPHER_ONLY))
es_fputs (" encipherOnly", fp);
if ( (kusage & KSBA_KEYUSAGE_DECIPHER_ONLY))
es_fputs (" decipherOnly", fp);
}
es_putc ('\n', fp);
}
else
es_fputs (" [none]\n", fp);
es_fputs (" extKeyUsage: ", fp);
err = ksba_cert_get_ext_key_usages (cert, &string);
if (gpg_err_code (err) != GPG_ERR_NO_DATA)
{
if (err)
es_fprintf (fp, "[error: %s]", gpg_strerror (err));
else
{
p = string;
while (p && (pend=strchr (p, ':')))
{
*pend++ = 0;
for (i=0; key_purpose_map[i].oid; i++)
if ( !strcmp (key_purpose_map[i].oid, p) )
break;
es_fputs (key_purpose_map[i].oid?key_purpose_map[i].name:p, fp);
p = pend;
if (*p != 'C')
es_fputs (" (suggested)", fp);
if ((p = strchr (p, '\n')))
{
p++;
es_fputs ("\n ", fp);
}
}
xfree (string);
}
es_putc ('\n', fp);
}
else
es_fputs ("[none]\n", fp);
es_fputs (" policies: ", fp);
err = ksba_cert_get_cert_policies (cert, &string);
if (gpg_err_code (err) != GPG_ERR_NO_DATA)
{
if (err)
es_fprintf (fp, "[error: %s]", gpg_strerror (err));
else
{
p = string;
while (p && (pend=strchr (p, ':')))
{
*pend++ = 0;
for (i=0; key_purpose_map[i].oid; i++)
if ( !strcmp (key_purpose_map[i].oid, p) )
break;
es_fputs (p, fp);
p = pend;
if (*p == 'C')
es_fputs (" (critical)", fp);
if ((p = strchr (p, '\n')))
{
p++;
es_fputs ("\n ", fp);
}
}
xfree (string);
}
es_putc ('\n', fp);
}
else
es_fputs ("[none]\n", fp);
es_fputs (" chainLength: ", fp);
err = ksba_cert_is_ca (cert, &is_ca, &chainlen);
if (err || is_ca)
{
if (gpg_err_code (err) == GPG_ERR_NO_VALUE )
es_fprintf (fp, "[none]");
else if (err)
es_fprintf (fp, "[error: %s]", gpg_strerror (err));
else if (chainlen == -1)
es_fputs ("unlimited", fp);
else
es_fprintf (fp, "%d", chainlen);
es_putc ('\n', fp);
}
else
es_fputs ("not a CA\n", fp);
/* CRL distribution point */
for (idx=0; !(err=ksba_cert_get_crl_dist_point (cert, idx, &name, &name2,
&reason)) ;idx++)
{
es_fputs (" crlDP: ", fp);
print_names_raw (fp, 15, name);
if (reason)
{
es_fputs (" reason: ", fp);
if ( (reason & KSBA_CRLREASON_UNSPECIFIED))
es_fputs (" unused", fp);
if ( (reason & KSBA_CRLREASON_KEY_COMPROMISE))
es_fputs (" keyCompromise", fp);
if ( (reason & KSBA_CRLREASON_CA_COMPROMISE))
es_fputs (" caCompromise", fp);
if ( (reason & KSBA_CRLREASON_AFFILIATION_CHANGED))
es_fputs (" affiliationChanged", fp);
if ( (reason & KSBA_CRLREASON_SUPERSEDED))
es_fputs (" superseded", fp);
if ( (reason & KSBA_CRLREASON_CESSATION_OF_OPERATION))
es_fputs (" cessationOfOperation", fp);
if ( (reason & KSBA_CRLREASON_CERTIFICATE_HOLD))
es_fputs (" certificateHold", fp);
es_putc ('\n', fp);
}
es_fputs (" issuer: ", fp);
print_names_raw (fp, 23, name2);
ksba_name_release (name);
ksba_name_release (name2);
}
if (err && gpg_err_code (err) != GPG_ERR_EOF
&& gpg_err_code (err) != GPG_ERR_NO_VALUE)
es_fputs (" crlDP: [error]\n", fp);
else if (!idx)
es_fputs (" crlDP: [none]\n", fp);
/* authorityInfoAccess. */
for (idx=0; !(err=ksba_cert_get_authority_info_access (cert, idx, &string,
&name)); idx++)
{
es_fputs (" authInfo: ", fp);
s = get_oid_desc (string, NULL);
es_fprintf (fp, "%s%s%s%s\n", string, s?" (":"", s?s:"", s?")":"");
print_names_raw (fp, -15, name);
ksba_name_release (name);
ksba_free (string);
}
if (err && gpg_err_code (err) != GPG_ERR_EOF
&& gpg_err_code (err) != GPG_ERR_NO_VALUE)
es_fputs (" authInfo: [error]\n", fp);
else if (!idx)
es_fputs (" authInfo: [none]\n", fp);
/* subjectInfoAccess. */
for (idx=0; !(err=ksba_cert_get_subject_info_access (cert, idx, &string,
&name)); idx++)
{
es_fputs (" subjectInfo: ", fp);
s = get_oid_desc (string, NULL);
es_fprintf (fp, "%s%s%s%s\n", string, s?" (":"", s?s:"", s?")":"");
print_names_raw (fp, -15, name);
ksba_name_release (name);
ksba_free (string);
}
if (err && gpg_err_code (err) != GPG_ERR_EOF
&& gpg_err_code (err) != GPG_ERR_NO_VALUE)
es_fputs (" subjInfo: [error]\n", fp);
else if (!idx)
es_fputs (" subjInfo: [none]\n", fp);
for (idx=0; !(err=ksba_cert_get_extension (cert, idx,
&oid, &i, &off, &len));idx++)
{
unsigned int flag;
s = get_oid_desc (oid, &flag);
if ((flag & OID_FLAG_SKIP))
continue;
es_fprintf (fp, " %s: %s%s%s%s",
i? "critExtn":" extn",
oid, s?" (":"", s?s:"", s?")":"");
if ((flag & OID_FLAG_UTF8))
{
if (!cert_der)
cert_der = ksba_cert_get_image (cert, NULL);
log_assert (cert_der);
es_fprintf (fp, "\n");
print_utf8_extn_raw (fp, -15, cert_der+off, len);
}
else if ((flag & OID_FLAG_HEX))
{
if (!cert_der)
cert_der = ksba_cert_get_image (cert, NULL);
log_assert (cert_der);
es_fprintf (fp, "\n");
print_hex_extn (fp, -15, cert_der+off, len);
}
else
es_fprintf (fp, " [%d octets]\n", (int)len);
}
if (with_validation)
{
err = gpgsm_validate_chain (ctrl, cert, "", NULL, 1, fp, 0, NULL);
if (!err)
es_fprintf (fp, " [certificate is good]\n");
else
es_fprintf (fp, " [certificate is bad: %s]\n", gpg_strerror (err));
}
if (hd)
{
unsigned int blobflags;
err = keydb_get_flags (hd, KEYBOX_FLAG_BLOB, 0, &blobflags);
if (err)
es_fprintf (fp, " [error getting keyflags: %s]\n",gpg_strerror (err));
else if ((blobflags & KEYBOX_FLAG_BLOB_EPHEMERAL))
es_fprintf (fp, " [stored as ephemeral]\n");
}
}
/* List one certificate in standard mode */
static void
list_cert_std (ctrl_t ctrl, ksba_cert_t cert, estream_t fp, int have_secret,
int with_validation)
{
gpg_error_t err;
ksba_sexp_t sexp;
char *dn;
ksba_isotime_t t;
int idx, i;
int is_ca, chainlen;
unsigned int kusage;
char *string, *p, *pend;
size_t off, len;
const char *oid;
const unsigned char *cert_der = NULL;
es_fprintf (fp, " ID: 0x%08lX\n",
gpgsm_get_short_fingerprint (cert, NULL));
sexp = ksba_cert_get_serial (cert);
es_fputs (" S/N: ", fp);
gpgsm_print_serial (fp, sexp);
ksba_free (sexp);
es_putc ('\n', fp);
dn = ksba_cert_get_issuer (cert, 0);
es_fputs (" Issuer: ", fp);
gpgsm_es_print_name (fp, dn);
ksba_free (dn);
es_putc ('\n', fp);
for (idx=1; (dn = ksba_cert_get_issuer (cert, idx)); idx++)
{
es_fputs (" aka: ", fp);
gpgsm_es_print_name (fp, dn);
ksba_free (dn);
es_putc ('\n', fp);
}
dn = ksba_cert_get_subject (cert, 0);
es_fputs (" Subject: ", fp);
gpgsm_es_print_name (fp, dn);
ksba_free (dn);
es_putc ('\n', fp);
for (idx=1; (dn = ksba_cert_get_subject (cert, idx)); idx++)
{
es_fputs (" aka: ", fp);
gpgsm_es_print_name (fp, dn);
ksba_free (dn);
es_putc ('\n', fp);
}
ksba_cert_get_validity (cert, 0, t);
es_fputs (" validity: ", fp);
gpgsm_print_time (fp, t);
es_fputs (" through ", fp);
ksba_cert_get_validity (cert, 1, t);
gpgsm_print_time (fp, t);
es_putc ('\n', fp);
{
const char *algoname;
unsigned int nbits;
algoname = gcry_pk_algo_name (gpgsm_get_key_algo_info (cert, &nbits));
es_fprintf (fp, " key type: %u bit %s\n",
nbits, algoname? algoname:"?");
}
err = ksba_cert_get_key_usage (cert, &kusage);
if (gpg_err_code (err) != GPG_ERR_NO_DATA)
{
es_fputs (" key usage:", fp);
if (err)
es_fprintf (fp, " [error: %s]", gpg_strerror (err));
else
{
if ( (kusage & KSBA_KEYUSAGE_DIGITAL_SIGNATURE))
es_fputs (" digitalSignature", fp);
if ( (kusage & KSBA_KEYUSAGE_NON_REPUDIATION))
es_fputs (" nonRepudiation", fp);
if ( (kusage & KSBA_KEYUSAGE_KEY_ENCIPHERMENT))
es_fputs (" keyEncipherment", fp);
if ( (kusage & KSBA_KEYUSAGE_DATA_ENCIPHERMENT))
es_fputs (" dataEncipherment", fp);
if ( (kusage & KSBA_KEYUSAGE_KEY_AGREEMENT))
es_fputs (" keyAgreement", fp);
if ( (kusage & KSBA_KEYUSAGE_KEY_CERT_SIGN))
es_fputs (" certSign", fp);
if ( (kusage & KSBA_KEYUSAGE_CRL_SIGN))
es_fputs (" crlSign", fp);
if ( (kusage & KSBA_KEYUSAGE_ENCIPHER_ONLY))
es_fputs (" encipherOnly", fp);
if ( (kusage & KSBA_KEYUSAGE_DECIPHER_ONLY))
es_fputs (" decipherOnly", fp);
}
es_putc ('\n', fp);
}
err = ksba_cert_get_ext_key_usages (cert, &string);
if (gpg_err_code (err) != GPG_ERR_NO_DATA)
{
es_fputs ("ext key usage: ", fp);
if (err)
es_fprintf (fp, "[error: %s]", gpg_strerror (err));
else
{
p = string;
while (p && (pend=strchr (p, ':')))
{
*pend++ = 0;
for (i=0; key_purpose_map[i].oid; i++)
if ( !strcmp (key_purpose_map[i].oid, p) )
break;
es_fputs (key_purpose_map[i].oid?key_purpose_map[i].name:p, fp);
p = pend;
if (*p != 'C')
es_fputs (" (suggested)", fp);
if ((p = strchr (p, '\n')))
{
p++;
es_fputs (", ", fp);
}
}
xfree (string);
}
es_putc ('\n', fp);
}
/* Print restrictions. */
for (idx=0; !(err=ksba_cert_get_extension (cert, idx,
&oid, NULL, &off, &len));idx++)
{
if (!strcmp (oid, OIDSTR_restriction) )
{
if (!cert_der)
cert_der = ksba_cert_get_image (cert, NULL);
assert (cert_der);
es_fputs (" restriction: ", fp);
print_utf8_extn (fp, 15, cert_der+off, len);
}
}
/* Print policies. */
err = ksba_cert_get_cert_policies (cert, &string);
if (gpg_err_code (err) != GPG_ERR_NO_DATA)
{
es_fputs (" policies: ", fp);
if (err)
es_fprintf (fp, "[error: %s]", gpg_strerror (err));
else
{
for (p=string; *p; p++)
{
if (*p == '\n')
*p = ',';
}
es_write_sanitized (fp, string, strlen (string), NULL, NULL);
xfree (string);
}
es_putc ('\n', fp);
}
err = ksba_cert_is_ca (cert, &is_ca, &chainlen);
if (err || is_ca)
{
es_fputs (" chain length: ", fp);
if (gpg_err_code (err) == GPG_ERR_NO_VALUE )
es_fprintf (fp, "none");
else if (err)
es_fprintf (fp, "[error: %s]", gpg_strerror (err));
else if (chainlen == -1)
es_fputs ("unlimited", fp);
else
es_fprintf (fp, "%d", chainlen);
es_putc ('\n', fp);
}
if (opt.with_md5_fingerprint)
{
dn = gpgsm_get_fingerprint_string (cert, GCRY_MD_MD5);
es_fprintf (fp, " md5 fpr: %s\n", dn?dn:"error");
xfree (dn);
}
dn = gpgsm_get_fingerprint_string (cert, 0);
es_fprintf (fp, " fingerprint: %s\n", dn?dn:"error");
xfree (dn);
if (opt.with_keygrip)
{
dn = gpgsm_get_keygrip_hexstring (cert);
if (dn)
{
es_fprintf (fp, " keygrip: %s\n", dn);
xfree (dn);
}
}
if (opt.with_key_screening)
print_pk_screening (cert, 0, fp);
if (have_secret)
{
char *cardsn;
p = gpgsm_get_keygrip_hexstring (cert);
if (!gpgsm_agent_keyinfo (ctrl, p, &cardsn) && cardsn)
es_fprintf (fp, " card s/n: %s\n", cardsn);
xfree (cardsn);
xfree (p);
}
if (with_validation)
{
gpg_error_t tmperr;
size_t buflen;
char buffer[1];
err = gpgsm_validate_chain (ctrl, cert, "", NULL, 1, fp, 0, NULL);
tmperr = ksba_cert_get_user_data (cert, "is_qualified",
&buffer, sizeof (buffer), &buflen);
if (!tmperr && buflen)
{
if (*buffer)
es_fputs (" [qualified]\n", fp);
}
else if (gpg_err_code (tmperr) == GPG_ERR_NOT_FOUND)
; /* Don't know - will not get marked as 'q' */
else
log_debug ("get_user_data(is_qualified) failed: %s\n",
gpg_strerror (tmperr));
if (!err)
es_fprintf (fp, " [certificate is good]\n");
else
es_fprintf (fp, " [certificate is bad: %s]\n", gpg_strerror (err));
}
+ if (opt.debug)
+ es_fflush (fp);
}
/* Same as standard mode list all certifying certs too. */
static void
list_cert_chain (ctrl_t ctrl, KEYDB_HANDLE hd,
ksba_cert_t cert, int raw_mode,
estream_t fp, int with_validation)
{
ksba_cert_t next = NULL;
if (raw_mode)
list_cert_raw (ctrl, hd, cert, fp, 0, with_validation);
else
list_cert_std (ctrl, cert, fp, 0, with_validation);
ksba_cert_ref (cert);
while (!gpgsm_walk_cert_chain (ctrl, cert, &next))
{
ksba_cert_release (cert);
es_fputs ("Certified by\n", fp);
if (raw_mode)
list_cert_raw (ctrl, hd, next, fp, 0, with_validation);
else
list_cert_std (ctrl, next, fp, 0, with_validation);
cert = next;
}
ksba_cert_release (cert);
es_putc ('\n', fp);
}
/* List all internal keys or just the keys given as NAMES. MODE is a
bit vector to specify what keys are to be included; see
gpgsm_list_keys (below) for details. If RAW_MODE is true, the raw
output mode will be used instead of the standard beautified one.
*/
static gpg_error_t
list_internal_keys (ctrl_t ctrl, strlist_t names, estream_t fp,
unsigned int mode, int raw_mode)
{
KEYDB_HANDLE hd;
KEYDB_SEARCH_DESC *desc = NULL;
strlist_t sl;
int ndesc;
ksba_cert_t cert = NULL;
ksba_cert_t lastcert = NULL;
gpg_error_t rc = 0;
const char *lastresname, *resname;
int have_secret;
int want_ephemeral = ctrl->with_ephemeral_keys;
hd = keydb_new ();
if (!hd)
{
log_error ("keydb_new failed\n");
rc = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
if (!names)
ndesc = 1;
else
{
for (sl=names, ndesc=0; sl; sl = sl->next, ndesc++)
;
}
desc = xtrycalloc (ndesc, sizeof *desc);
if (!ndesc)
{
rc = gpg_error_from_syserror ();
log_error ("out of core\n");
goto leave;
}
if (!names)
desc[0].mode = KEYDB_SEARCH_MODE_FIRST;
else
{
for (ndesc=0, sl=names; sl; sl = sl->next)
{
rc = classify_user_id (sl->d, desc+ndesc, 0);
if (rc)
{
log_error ("key '%s' not found: %s\n",
sl->d, gpg_strerror (rc));
rc = 0;
}
else
ndesc++;
}
}
/* If all specifications are done by fingerprint or keygrip, we
switch to ephemeral mode so that _all_ currently available and
matching certificates are listed. */
if (!want_ephemeral && names && ndesc)
{
int i;
for (i=0; (i < ndesc
&& (desc[i].mode == KEYDB_SEARCH_MODE_FPR
|| desc[i].mode == KEYDB_SEARCH_MODE_KEYGRIP)); i++)
;
if (i == ndesc)
want_ephemeral = 1;
}
if (want_ephemeral)
keydb_set_ephemeral (hd, 1);
/* It would be nice to see which of the given users did actually
match one in the keyring. To implement this we need to have a
found flag for each entry in desc and to set this we must check
all those entries after a match to mark all matched one -
currently we stop at the first match. To do this we need an
extra flag to enable this feature so */
/* Suppress duplicates at least when they follow each other. */
lastresname = NULL;
while (!(rc = keydb_search (ctrl, hd, desc, ndesc)))
{
unsigned int validity;
if (!names)
desc[0].mode = KEYDB_SEARCH_MODE_NEXT;
rc = keydb_get_flags (hd, KEYBOX_FLAG_VALIDITY, 0, &validity);
if (rc)
{
log_error ("keydb_get_flags failed: %s\n", gpg_strerror (rc));
goto leave;
}
rc = keydb_get_cert (hd, &cert);
if (rc)
{
log_error ("keydb_get_cert failed: %s\n", gpg_strerror (rc));
goto leave;
}
/* Skip duplicated certificates, at least if they follow each
others. This works best if a single key is searched for and
expected. FIXME: Non-sequential duplicates remain. */
if (gpgsm_certs_identical_p (cert, lastcert))
{
ksba_cert_release (cert);
cert = NULL;
continue;
}
resname = keydb_get_resource_name (hd);
if (lastresname != resname )
{
int i;
if (ctrl->no_server)
{
es_fprintf (fp, "%s\n", resname );
for (i=strlen(resname); i; i-- )
es_putc ('-', fp);
es_putc ('\n', fp);
lastresname = resname;
}
}
have_secret = 0;
if (mode)
{
char *p = gpgsm_get_keygrip_hexstring (cert);
if (p)
{
rc = gpgsm_agent_havekey (ctrl, p);
if (!rc)
have_secret = 1;
else if ( gpg_err_code (rc) != GPG_ERR_NO_SECKEY)
goto leave;
rc = 0;
xfree (p);
}
}
if (!mode || ((mode & 1) && !have_secret)
|| ((mode & 2) && have_secret) )
{
if (ctrl->with_colons)
list_cert_colon (ctrl, cert, validity, fp, have_secret);
else if (ctrl->with_chain)
list_cert_chain (ctrl, hd, cert,
raw_mode, fp, ctrl->with_validation);
else
{
if (raw_mode)
list_cert_raw (ctrl, hd, cert, fp, have_secret,
ctrl->with_validation);
else
list_cert_std (ctrl, cert, fp, have_secret,
ctrl->with_validation);
es_putc ('\n', fp);
}
}
ksba_cert_release (lastcert);
lastcert = cert;
cert = NULL;
}
if (gpg_err_code (rc) == GPG_ERR_EOF || rc == -1 )
rc = 0;
if (rc)
log_error ("keydb_search failed: %s\n", gpg_strerror (rc));
leave:
ksba_cert_release (cert);
ksba_cert_release (lastcert);
xfree (desc);
keydb_release (hd);
return rc;
}
static void
list_external_cb (void *cb_value, ksba_cert_t cert)
{
struct list_external_parm_s *parm = cb_value;
if (keydb_store_cert (parm->ctrl, cert, 1, NULL))
log_error ("error storing certificate as ephemeral\n");
if (parm->print_header)
{
const char *resname = "[external keys]";
int i;
es_fprintf (parm->fp, "%s\n", resname );
for (i=strlen(resname); i; i-- )
es_putc('-', parm->fp);
es_putc ('\n', parm->fp);
parm->print_header = 0;
}
if (parm->with_colons)
list_cert_colon (parm->ctrl, cert, 0, parm->fp, 0);
else if (parm->with_chain)
list_cert_chain (parm->ctrl, NULL, cert, parm->raw_mode, parm->fp, 0);
else
{
if (parm->raw_mode)
list_cert_raw (parm->ctrl, NULL, cert, parm->fp, 0, 0);
else
list_cert_std (parm->ctrl, cert, parm->fp, 0, 0);
es_putc ('\n', parm->fp);
}
}
/* List external keys similar to internal one. Note: mode does not
make sense here because it would be unwise to list external secret
keys */
static gpg_error_t
list_external_keys (ctrl_t ctrl, strlist_t names, estream_t fp, int raw_mode)
{
int rc;
struct list_external_parm_s parm;
parm.fp = fp;
parm.ctrl = ctrl,
parm.print_header = ctrl->no_server;
parm.with_colons = ctrl->with_colons;
parm.with_chain = ctrl->with_chain;
parm.raw_mode = raw_mode;
rc = gpgsm_dirmngr_lookup (ctrl, names, 0, list_external_cb, &parm);
if (gpg_err_code (rc) == GPG_ERR_EOF || rc == -1
|| gpg_err_code (rc) == GPG_ERR_NOT_FOUND)
rc = 0; /* "Not found" is not an error here. */
if (rc)
log_error ("listing external keys failed: %s\n", gpg_strerror (rc));
return rc;
}
/* List all keys or just the key given as NAMES.
MODE controls the operation mode:
Bit 0-2:
0 = list all public keys but don't flag secret ones
1 = list only public keys
2 = list only secret keys
3 = list secret and public keys
Bit 6: list internal keys
Bit 7: list external keys
Bit 8: Do a raw format dump.
*/
gpg_error_t
gpgsm_list_keys (ctrl_t ctrl, strlist_t names, estream_t fp,
unsigned int mode)
{
gpg_error_t err = 0;
if ((mode & (1<<6)))
err = list_internal_keys (ctrl, names, fp, (mode & 3), (mode&256));
if (!err && (mode & (1<<7)))
err = list_external_keys (ctrl, names, fp, (mode&256));
return err;
}
diff --git a/sm/server.c b/sm/server.c
index 98505e26d..77ec07fc0 100644
--- a/sm/server.c
+++ b/sm/server.c
@@ -1,1493 +1,1493 @@
/* server.c - Server mode and main entry point
* Copyright (C) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009,
* 2010 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#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 "gpgsm.h"
#include <assuan.h>
#include "../common/sysutils.h"
#include "../common/server-help.h"
#include "../common/asshelp.h"
#include "../common/shareddefs.h"
#define set_error(e,t) assuan_set_error (ctx, gpg_error (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_t assuan_ctx;
int message_fd;
int list_internal;
int list_external;
int list_to_output; /* Write keylistings to the output fd. */
int enable_audit_log; /* Use an audit log. */
certlist_t recplist;
certlist_t signerlist;
certlist_t default_recplist; /* As set by main() - don't release. */
int allow_pinentry_notify; /* Set if pinentry notifications should
be passed back to the client. */
int no_encrypt_to; /* Local version of option. */
};
/* Cookie definition for assuan data line output. */
static gpgrt_ssize_t data_line_cookie_write (void *cookie,
const void *buffer, size_t size);
static int data_line_cookie_close (void *cookie);
static es_cookie_io_functions_t data_line_cookie_functions =
{
NULL,
data_line_cookie_write,
NULL,
data_line_cookie_close
};
static int command_has_option (const char *cmd, const char *cmdopt);
/* 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 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;
}
/* A write handler used by es_fopencookie to write assuan data
lines. */
static gpgrt_ssize_t
data_line_cookie_write (void *cookie, const void *buffer, size_t size)
{
assuan_context_t ctx = cookie;
if (assuan_send_data (ctx, buffer, size))
{
gpg_err_set_errno (EIO);
return -1;
}
return (gpgrt_ssize_t)size;
}
static int
data_line_cookie_close (void *cookie)
{
assuan_context_t ctx = cookie;
if (assuan_send_data (ctx, NULL, 0))
{
gpg_err_set_errno (EIO);
return -1;
}
return 0;
}
static void
close_message_fd (ctrl_t ctrl)
{
if (ctrl->server_local->message_fd != -1)
{
#ifdef HAVE_W32CE_SYSTEM
#warning Is this correct for W32/W32CE?
#endif
close (ctrl->server_local->message_fd);
ctrl->server_local->message_fd = -1;
}
}
/* Start a new audit session if this has been enabled. */
static gpg_error_t
start_audit_session (ctrl_t ctrl)
{
audit_release (ctrl->audit);
ctrl->audit = NULL;
if (ctrl->server_local->enable_audit_log && !(ctrl->audit = audit_new ()) )
return gpg_error_from_syserror ();
return 0;
}
static gpg_error_t
option_handler (assuan_context_t ctx, const char *key, const char *value)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err = 0;
if (!strcmp (key, "putenv"))
{
/* Change the session's environment to be used for the
Pinentry. Valid values are:
<NAME> Delete envvar NAME
<KEY>= Set envvar NAME to the empty string
<KEY>=<VALUE> Set envvar NAME to VALUE
*/
err = session_env_putenv (opt.session_env, value);
}
else if (!strcmp (key, "display"))
{
err = session_env_setenv (opt.session_env, "DISPLAY", value);
}
else if (!strcmp (key, "ttyname"))
{
err = session_env_setenv (opt.session_env, "GPG_TTY", value);
}
else if (!strcmp (key, "ttytype"))
{
err = session_env_setenv (opt.session_env, "TERM", value);
}
else if (!strcmp (key, "lc-ctype"))
{
xfree (opt.lc_ctype);
opt.lc_ctype = xtrystrdup (value);
if (!opt.lc_ctype)
err = gpg_error_from_syserror ();
}
else if (!strcmp (key, "lc-messages"))
{
xfree (opt.lc_messages);
opt.lc_messages = xtrystrdup (value);
if (!opt.lc_messages)
err = gpg_error_from_syserror ();
}
else if (!strcmp (key, "xauthority"))
{
err = session_env_setenv (opt.session_env, "XAUTHORITY", value);
}
else if (!strcmp (key, "pinentry-user-data"))
{
err = session_env_setenv (opt.session_env, "PINENTRY_USER_DATA", value);
}
else if (!strcmp (key, "include-certs"))
{
int i = *value? atoi (value) : -1;
if (ctrl->include_certs < -2)
err = gpg_error (GPG_ERR_ASS_PARAMETER);
else
ctrl->include_certs = i;
}
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
err = gpg_error (GPG_ERR_ASS_PARAMETER);
}
else if (!strcmp (key, "list-to-output"))
{
int i = *value? atoi (value) : 0;
ctrl->server_local->list_to_output = i;
}
else if (!strcmp (key, "with-validation"))
{
int i = *value? atoi (value) : 0;
ctrl->with_validation = i;
}
else if (!strcmp (key, "with-secret"))
{
int i = *value? atoi (value) : 0;
ctrl->with_secret = i;
}
else if (!strcmp (key, "validation-model"))
{
int i = gpgsm_parse_validation_model (value);
if ( i >= 0 && i <= 2 )
ctrl->validation_model = i;
else
err = gpg_error (GPG_ERR_ASS_PARAMETER);
}
else if (!strcmp (key, "with-key-data"))
{
opt.with_key_data = 1;
}
else if (!strcmp (key, "enable-audit-log"))
{
int i = *value? atoi (value) : 0;
ctrl->server_local->enable_audit_log = i;
}
else if (!strcmp (key, "allow-pinentry-notify"))
{
ctrl->server_local->allow_pinentry_notify = 1;
}
else if (!strcmp (key, "with-ephemeral-keys"))
{
int i = *value? atoi (value) : 0;
ctrl->with_ephemeral_keys = i;
}
else if (!strcmp (key, "no-encrypt-to"))
{
ctrl->server_local->no_encrypt_to = 1;
}
else if (!strcmp (key, "offline"))
{
/* We ignore this option if gpgsm has been started with
--disable-dirmngr (which also sets offline). */
if (!opt.disable_dirmngr)
{
int i = *value? !!atoi (value) : 1;
ctrl->offline = i;
}
}
else if (!strcmp (key, "request-origin"))
{
if (!opt.request_origin)
{
int i = parse_request_origin (value);
if (i == -1)
err = gpg_error (GPG_ERR_INV_VALUE);
else
opt.request_origin = i;
}
}
else
err = gpg_error (GPG_ERR_UNKNOWN_OPTION);
return err;
}
static gpg_error_t
reset_notify (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
(void) line;
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);
return 0;
}
static gpg_error_t
input_notify (assuan_context_t ctx, char *line)
{
ctrl_t 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;
return 0;
}
static gpg_error_t
output_notify (assuan_context_t ctx, char *line)
{
ctrl_t 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 */
return 0;
}
static const char hlp_recipient[] =
"RECIPIENT <userID>\n"
"\n"
"Set the recipient for the encryption. USERID shall be the\n"
"internal representation of the key; the server may accept any other\n"
"way of specification [we will support this]. If this is a valid and\n"
"trusted recipient the server does respond with OK, otherwise the\n"
"return is an ERR with the reason why the recipient can't be used,\n"
"the encryption will then not be done for this recipient. If the\n"
"policy is not to encrypt at all if not all recipients are valid, the\n"
"client has to take care of this. All RECIPIENT commands are\n"
"cumulative until a RESET or an successful ENCRYPT command.";
static gpg_error_t
cmd_recipient (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc;
if (!ctrl->audit)
rc = start_audit_session (ctrl);
else
rc = 0;
if (!rc)
rc = gpgsm_add_to_certlist (ctrl, line, 0,
&ctrl->server_local->recplist, 0);
if (rc)
{
gpgsm_status2 (ctrl, STATUS_INV_RECP,
get_inv_recpsgnr_code (rc), line, NULL);
}
return rc;
}
static const char hlp_signer[] =
"SIGNER <userID>\n"
"\n"
"Set the signer's keys for the signature creation. USERID should\n"
"be the internal representation of the key; the server may accept any\n"
"other way of specification [we will support this]. If this is a\n"
"valid and usable signing key the server does respond with OK,\n"
"otherwise it returns an ERR with the reason why the key can't be\n"
"used, the signing will then not be done for this key. If the policy\n"
"is not to sign at all if not all signer keys are valid, the client\n"
"has to take care of this. All SIGNER commands are cumulative until\n"
"a RESET but they are *not* reset by an SIGN command because it can\n"
"be expected that set of signers are used for more than one sign\n"
"operation.";
static gpg_error_t
cmd_signer (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc;
rc = gpgsm_add_to_certlist (ctrl, line, 1,
&ctrl->server_local->signerlist, 0);
if (rc)
{
gpgsm_status2 (ctrl, STATUS_INV_SGNR,
get_inv_recpsgnr_code (rc), line, NULL);
/* For compatibility reasons we also issue the old code after the
new one. */
gpgsm_status2 (ctrl, STATUS_INV_RECP,
get_inv_recpsgnr_code (rc), line, NULL);
}
return rc;
}
static const char hlp_encrypt[] =
"ENCRYPT \n"
"\n"
"Do the actual encryption process. Takes the plaintext from the INPUT\n"
"command, writes to the ciphertext to the file descriptor set with\n"
"the OUTPUT command, take the recipients form all the recipients set\n"
"so far. If this command fails the clients should try to delete all\n"
"output currently done or otherwise mark it as invalid. GPGSM does\n"
"ensure that there won't be any security problem with leftover data\n"
"on the output in this case.\n"
"\n"
"This command should in general not fail, as all necessary checks\n"
"have been done while setting the recipients. The input and output\n"
"pipes are closed.";
static gpg_error_t
cmd_encrypt (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
certlist_t cl;
int inp_fd, out_fd;
estream_t out_fp;
int rc;
(void)line;
inp_fd = translate_sys2libc_fd (assuan_get_input_fd (ctx), 0);
if (inp_fd == -1)
return set_error (GPG_ERR_ASS_NO_INPUT, NULL);
out_fd = translate_sys2libc_fd (assuan_get_output_fd (ctx), 1);
if (out_fd == -1)
return set_error (GPG_ERR_ASS_NO_OUTPUT, NULL);
out_fp = es_fdopen_nc (out_fd, "w");
if (!out_fp)
return set_error (gpg_err_code_from_syserror (), "fdopen() failed");
/* Now add all encrypt-to marked recipients from the default
list. */
rc = 0;
if (!opt.no_encrypt_to && !ctrl->server_local->no_encrypt_to)
{
for (cl=ctrl->server_local->default_recplist; !rc && cl; cl = cl->next)
if (cl->is_encrypt_to)
rc = gpgsm_add_cert_to_certlist (ctrl, cl->cert,
&ctrl->server_local->recplist, 1);
}
if (!rc)
rc = ctrl->audit? 0 : start_audit_session (ctrl);
if (!rc)
rc = gpgsm_encrypt (assuan_get_pointer (ctx),
ctrl->server_local->recplist,
inp_fd, out_fp);
es_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 rc;
}
static const char hlp_decrypt[] =
"DECRYPT\n"
"\n"
"This performs the decrypt operation after doing some check on the\n"
"internal state. (e.g. that only needed data has been set). Because\n"
"it utilizes the GPG-Agent for the session key decryption, there is\n"
"no need to ask the client for a protecting passphrase - GPG-Agent\n"
"does take care of this by requesting this from the user.";
static gpg_error_t
cmd_decrypt (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int inp_fd, out_fd;
estream_t out_fp;
int rc;
(void)line;
inp_fd = translate_sys2libc_fd (assuan_get_input_fd (ctx), 0);
if (inp_fd == -1)
return set_error (GPG_ERR_ASS_NO_INPUT, NULL);
out_fd = translate_sys2libc_fd (assuan_get_output_fd (ctx), 1);
if (out_fd == -1)
return set_error (GPG_ERR_ASS_NO_OUTPUT, NULL);
out_fp = es_fdopen_nc (out_fd, "w");
if (!out_fp)
return set_error (gpg_err_code_from_syserror (), "fdopen() failed");
rc = start_audit_session (ctrl);
if (!rc)
rc = gpgsm_decrypt (ctrl, inp_fd, out_fp);
es_fclose (out_fp);
/* Close and reset the fds. */
close_message_fd (ctrl);
assuan_close_input_fd (ctx);
assuan_close_output_fd (ctx);
return rc;
}
static const char hlp_verify[] =
"VERIFY\n"
"\n"
"This does a verify operation on the message send to the input FD.\n"
"The result is written out using status lines. If an output FD was\n"
"given, the signed text will be written to that.\n"
"\n"
"If the signature is a detached one, the server will inquire about\n"
"the signed material and the client must provide it.";
static gpg_error_t
cmd_verify (assuan_context_t ctx, char *line)
{
int rc;
ctrl_t ctrl = assuan_get_pointer (ctx);
int fd = translate_sys2libc_fd (assuan_get_input_fd (ctx), 0);
int out_fd = translate_sys2libc_fd (assuan_get_output_fd (ctx), 1);
estream_t out_fp = NULL;
(void)line;
if (fd == -1)
return set_error (GPG_ERR_ASS_NO_INPUT, NULL);
if (out_fd != -1)
{
out_fp = es_fdopen_nc (out_fd, "w");
if (!out_fp)
return set_error (gpg_err_code_from_syserror (), "fdopen() failed");
}
rc = start_audit_session (ctrl);
if (!rc)
rc = gpgsm_verify (assuan_get_pointer (ctx), fd,
ctrl->server_local->message_fd, out_fp);
es_fclose (out_fp);
/* Close and reset the fd. */
close_message_fd (ctrl);
assuan_close_input_fd (ctx);
assuan_close_output_fd (ctx);
return rc;
}
static const char hlp_sign[] =
"SIGN [--detached]\n"
"\n"
"Sign the data set with the INPUT command and write it to the sink\n"
"set by OUTPUT. With \"--detached\", a detached signature is\n"
"created (surprise).";
static gpg_error_t
cmd_sign (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int inp_fd, out_fd;
estream_t out_fp;
int detached;
int rc;
inp_fd = translate_sys2libc_fd (assuan_get_input_fd (ctx), 0);
if (inp_fd == -1)
return set_error (GPG_ERR_ASS_NO_INPUT, NULL);
out_fd = translate_sys2libc_fd (assuan_get_output_fd (ctx), 1);
if (out_fd == -1)
return set_error (GPG_ERR_ASS_NO_OUTPUT, NULL);
detached = has_option (line, "--detached");
out_fp = es_fdopen_nc (out_fd, "w");
if (!out_fp)
return set_error (GPG_ERR_ASS_GENERAL, "fdopen() failed");
rc = start_audit_session (ctrl);
if (!rc)
rc = gpgsm_sign (assuan_get_pointer (ctx), ctrl->server_local->signerlist,
inp_fd, detached, out_fp);
es_fclose (out_fp);
/* close and reset the fd */
close_message_fd (ctrl);
assuan_close_input_fd (ctx);
assuan_close_output_fd (ctx);
return rc;
}
static const char hlp_import[] =
"IMPORT [--re-import]\n"
"\n"
"Import the certificates read form the input-fd, return status\n"
"message for each imported one. The import checks the validity of\n"
"the certificate but not of the entire chain. It is possible to\n"
"import expired certificates.\n"
"\n"
"With the option --re-import the input data is expected to a be a LF\n"
"separated list of fingerprints. The command will re-import these\n"
"certificates, meaning that they are made permanent by removing\n"
"their ephemeral flag.";
static gpg_error_t
cmd_import (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc;
int fd = translate_sys2libc_fd (assuan_get_input_fd (ctx), 0);
int reimport = has_option (line, "--re-import");
(void)line;
if (fd == -1)
return set_error (GPG_ERR_ASS_NO_INPUT, NULL);
rc = gpgsm_import (assuan_get_pointer (ctx), fd, reimport);
/* close and reset the fd */
close_message_fd (ctrl);
assuan_close_input_fd (ctx);
assuan_close_output_fd (ctx);
return rc;
}
static const char hlp_export[] =
"EXPORT [--data [--armor|--base64]] [--secret [--(raw|pkcs12)] [--] <pattern>\n"
"\n"
"Export the certificates selected by PATTERN. With --data the output\n"
"is returned using Assuan D lines; the default is to use the sink given\n"
"by the last \"OUTPUT\" command. The options --armor or --base64 encode \n"
"the output using the PEM respective a plain base-64 format; the default\n"
"is a binary format which is only suitable for a single certificate.\n"
"With --secret the secret key is exported using the PKCS#8 format,\n"
"with --raw using PKCS#1, and with --pkcs12 as full PKCS#12 container.";
static gpg_error_t
cmd_export (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
char *p;
strlist_t list, sl;
int use_data;
int opt_secret;
int opt_raw = 0;
int opt_pkcs12 = 0;
use_data = has_option (line, "--data");
if (use_data)
{
/* We need to override any possible setting done by an OUTPUT command. */
ctrl->create_pem = has_option (line, "--armor");
ctrl->create_base64 = has_option (line, "--base64");
}
opt_secret = has_option (line, "--secret");
if (opt_secret)
{
opt_raw = has_option (line, "--raw");
opt_pkcs12 = has_option (line, "--pkcs12");
}
line = skip_options (line);
/* Break the line down into an strlist_t. */
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 out_of_core ();
}
sl->flags = 0;
strcpy_escaped_plus (sl->d, line);
sl->next = list;
list = sl;
}
}
if (opt_secret)
{
if (!list || !*list->d)
return set_error (GPG_ERR_NO_DATA, "No key given");
if (list->next)
return set_error (GPG_ERR_TOO_MANY, "Only one key allowed");
}
if (use_data)
{
estream_t stream;
stream = es_fopencookie (ctx, "w", data_line_cookie_functions);
if (!stream)
{
free_strlist (list);
return set_error (GPG_ERR_ASS_GENERAL,
"error setting up a data stream");
}
if (opt_secret)
gpgsm_p12_export (ctrl, list->d, stream,
opt_raw? 2 : opt_pkcs12 ? 0 : 1);
else
gpgsm_export (ctrl, list, stream);
es_fclose (stream);
}
else
{
int fd = translate_sys2libc_fd (assuan_get_output_fd (ctx), 1);
estream_t out_fp;
if (fd == -1)
{
free_strlist (list);
return set_error (GPG_ERR_ASS_NO_OUTPUT, NULL);
}
out_fp = es_fdopen_nc (fd, "w");
if (!out_fp)
{
free_strlist (list);
return set_error (gpg_err_code_from_syserror (), "fdopen() failed");
}
if (opt_secret)
gpgsm_p12_export (ctrl, list->d, out_fp,
opt_raw? 2 : opt_pkcs12 ? 0 : 1);
else
gpgsm_export (ctrl, list, out_fp);
es_fclose (out_fp);
}
free_strlist (list);
/* Close and reset the fds. */
close_message_fd (ctrl);
assuan_close_input_fd (ctx);
assuan_close_output_fd (ctx);
return 0;
}
static const char hlp_delkeys[] =
"DELKEYS <patterns>\n"
"\n"
"Delete the certificates specified by PATTERNS. Each pattern shall be\n"
"a percent-plus escaped certificate specification. Usually a\n"
"fingerprint will be used for this.";
static gpg_error_t
cmd_delkeys (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
char *p;
strlist_t list, sl;
int rc;
/* break the line down into an strlist_t */
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 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 rc;
}
static const char hlp_output[] =
"OUTPUT FD[=<n>]\n"
"\n"
"Set the file descriptor to write the output data to N. If N is not\n"
"given and the operating system supports file descriptor passing, the\n"
"file descriptor currently in flight will be used. See also the\n"
"\"INPUT\" and \"MESSAGE\" commands.";
static const char hlp_input[] =
"INPUT FD[=<n>]\n"
"\n"
"Set the file descriptor to read the input data to N. If N is not\n"
"given and the operating system supports file descriptor passing, the\n"
"file descriptor currently in flight will be used. See also the\n"
"\"MESSAGE\" and \"OUTPUT\" commands.";
static const char hlp_message[] =
"MESSAGE FD[=<n>]\n"
"\n"
"Set the file descriptor to read the message for a detached\n"
"signatures to N. If N is not given and the operating system\n"
"supports file descriptor passing, the file descriptor currently in\n"
"flight will be used. See also the \"INPUT\" and \"OUTPUT\" commands.";
static gpg_error_t
cmd_message (assuan_context_t ctx, char *line)
{
int rc;
gnupg_fd_t sysfd;
int fd;
ctrl_t ctrl = assuan_get_pointer (ctx);
rc = assuan_command_parse_fd (ctx, line, &sysfd);
if (rc)
return rc;
#ifdef HAVE_W32CE_SYSTEM
sysfd = _assuan_w32ce_finish_pipe ((int)sysfd, 0);
if (sysfd == INVALID_HANDLE_VALUE)
return set_error (gpg_err_code_from_syserror (),
"rvid conversion failed");
#endif
fd = translate_sys2libc_fd (sysfd, 0);
if (fd == -1)
return set_error (GPG_ERR_ASS_NO_INPUT, NULL);
ctrl->server_local->message_fd = fd;
return 0;
}
static const char hlp_listkeys[] =
"LISTKEYS [<patterns>]\n"
"LISTSECRETKEYS [<patterns>]\n"
"DUMPKEYS [<patterns>]\n"
"DUMPSECRETKEYS [<patterns>]\n"
"\n"
"List all certificates or only those specified by PATTERNS. Each\n"
"pattern shall be a percent-plus escaped certificate specification.\n"
"The \"SECRET\" versions of the command filter the output to include\n"
"only certificates where the secret key is available or a corresponding\n"
"smartcard has been registered. The \"DUMP\" versions of the command\n"
"are only useful for debugging. The output format is a percent escaped\n"
"colon delimited listing as described in the manual.\n"
"\n"
"These \"OPTION\" command keys effect the output::\n"
"\n"
" \"list-mode\" set to 0: List only local certificates (default).\n"
" 1: Ditto.\n"
" 2: List only external certificates.\n"
" 3: List local and external certificates.\n"
"\n"
" \"with-validation\" set to true: Validate each certificate.\n"
"\n"
" \"with-ephemeral-key\" set to true: Always include ephemeral\n"
" certificates.\n"
"\n"
" \"list-to-output\" set to true: Write output to the file descriptor\n"
" given by the last \"OUTPUT\" command.";
static int
do_listkeys (assuan_context_t ctx, char *line, int mode)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
estream_t fp;
char *p;
strlist_t list, sl;
unsigned int listmode;
gpg_error_t err;
/* 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 out_of_core ();
}
sl->flags = 0;
strcpy_escaped_plus (sl->d, line);
sl->next = list;
list = sl;
}
}
if (ctrl->server_local->list_to_output)
{
int outfd = translate_sys2libc_fd (assuan_get_output_fd (ctx), 1);
if ( outfd == -1 )
return set_error (GPG_ERR_ASS_NO_OUTPUT, NULL);
fp = es_fdopen_nc (outfd, "w");
if (!fp)
return set_error (gpg_err_code_from_syserror (), "es_fdopen() failed");
}
else
{
fp = es_fopencookie (ctx, "w", data_line_cookie_functions);
if (!fp)
return set_error (GPG_ERR_ASS_GENERAL,
"error setting up a data stream");
}
ctrl->with_colons = 1;
listmode = mode;
if (ctrl->server_local->list_internal)
listmode |= (1<<6);
if (ctrl->server_local->list_external)
listmode |= (1<<7);
err = gpgsm_list_keys (assuan_get_pointer (ctx), list, fp, listmode);
free_strlist (list);
es_fclose (fp);
if (ctrl->server_local->list_to_output)
assuan_close_output_fd (ctx);
return err;
}
static gpg_error_t
cmd_listkeys (assuan_context_t ctx, char *line)
{
return do_listkeys (ctx, line, 3);
}
static gpg_error_t
cmd_dumpkeys (assuan_context_t ctx, char *line)
{
return do_listkeys (ctx, line, 259);
}
static gpg_error_t
cmd_listsecretkeys (assuan_context_t ctx, char *line)
{
return do_listkeys (ctx, line, 2);
}
static gpg_error_t
cmd_dumpsecretkeys (assuan_context_t ctx, char *line)
{
return do_listkeys (ctx, line, 258);
}
static const char hlp_genkey[] =
"GENKEY\n"
"\n"
"Read the parameters in native format from the input fd and write a\n"
"certificate request to the output.";
static gpg_error_t
cmd_genkey (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int inp_fd, out_fd;
estream_t in_stream, out_stream;
int rc;
(void)line;
inp_fd = translate_sys2libc_fd (assuan_get_input_fd (ctx), 0);
if (inp_fd == -1)
return set_error (GPG_ERR_ASS_NO_INPUT, NULL);
out_fd = translate_sys2libc_fd (assuan_get_output_fd (ctx), 1);
if (out_fd == -1)
return set_error (GPG_ERR_ASS_NO_OUTPUT, NULL);
in_stream = es_fdopen_nc (inp_fd, "r");
if (!in_stream)
return set_error (GPG_ERR_ASS_GENERAL, "es_fdopen failed");
out_stream = es_fdopen_nc (out_fd, "w");
if (!out_stream)
{
es_fclose (in_stream);
return set_error (gpg_err_code_from_syserror (), "fdopen() failed");
}
rc = gpgsm_genkey (ctrl, in_stream, out_stream);
es_fclose (out_stream);
es_fclose (in_stream);
/* close and reset the fds */
assuan_close_input_fd (ctx);
assuan_close_output_fd (ctx);
return rc;
}
static const char hlp_getauditlog[] =
"GETAUDITLOG [--data] [--html]\n"
"\n"
"If --data is used, the output is send using D-lines and not to the\n"
"file descriptor given by an OUTPUT command.\n"
"\n"
"If --html is used the output is formatted as an XHTML block. This is\n"
"designed to be incorporated into a HTML document.";
static gpg_error_t
cmd_getauditlog (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int out_fd;
estream_t out_stream;
int opt_data, opt_html;
int rc;
opt_data = has_option (line, "--data");
opt_html = has_option (line, "--html");
/* Not needed: line = skip_options (line); */
if (!ctrl->audit)
return gpg_error (GPG_ERR_NO_DATA);
if (opt_data)
{
out_stream = es_fopencookie (ctx, "w", data_line_cookie_functions);
if (!out_stream)
return set_error (GPG_ERR_ASS_GENERAL,
"error setting up a data stream");
}
else
{
out_fd = translate_sys2libc_fd (assuan_get_output_fd (ctx), 1);
if (out_fd == -1)
return set_error (GPG_ERR_ASS_NO_OUTPUT, NULL);
out_stream = es_fdopen_nc (out_fd, "w");
if (!out_stream)
{
return set_error (GPG_ERR_ASS_GENERAL, "es_fdopen() failed");
}
}
audit_print_result (ctrl->audit, out_stream, opt_html);
rc = 0;
es_fclose (out_stream);
/* Close and reset the fd. */
if (!opt_data)
assuan_close_output_fd (ctx);
return rc;
}
static const char hlp_getinfo[] =
"GETINFO <what>\n"
"\n"
"Multipurpose function to return a variety of information.\n"
"Supported values for WHAT are:\n"
"\n"
" version - Return the version of the program.\n"
" pid - Return the process id of the server.\n"
" agent-check - Return success if the agent is running.\n"
" cmd_has_option CMD OPT\n"
" - Returns OK if the command CMD implements the option OPT.\n"
" offline - Returns OK if the connection is in offline mode.";
static gpg_error_t
cmd_getinfo (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc = 0;
if (!strcmp (line, "version"))
{
const char *s = VERSION;
rc = assuan_send_data (ctx, s, strlen (s));
}
else if (!strcmp (line, "pid"))
{
char numbuf[50];
snprintf (numbuf, sizeof numbuf, "%lu", (unsigned long)getpid ());
rc = assuan_send_data (ctx, numbuf, strlen (numbuf));
}
else if (!strcmp (line, "agent-check"))
{
rc = gpgsm_agent_send_nop (ctrl);
}
else if (!strncmp (line, "cmd_has_option", 14)
&& (line[14] == ' ' || line[14] == '\t' || !line[14]))
{
char *cmd, *cmdopt;
line += 14;
while (*line == ' ' || *line == '\t')
line++;
if (!*line)
rc = gpg_error (GPG_ERR_MISSING_VALUE);
else
{
cmd = line;
while (*line && (*line != ' ' && *line != '\t'))
line++;
if (!*line)
rc = gpg_error (GPG_ERR_MISSING_VALUE);
else
{
*line++ = 0;
while (*line == ' ' || *line == '\t')
line++;
if (!*line)
rc = gpg_error (GPG_ERR_MISSING_VALUE);
else
{
cmdopt = line;
if (!command_has_option (cmd, cmdopt))
- rc = gpg_error (GPG_ERR_GENERAL);
+ rc = gpg_error (GPG_ERR_FALSE);
}
}
}
}
else if (!strcmp (line, "offline"))
{
- rc = ctrl->offline? 0 : gpg_error (GPG_ERR_GENERAL);
+ rc = ctrl->offline? 0 : gpg_error (GPG_ERR_FALSE);
}
else
rc = set_error (GPG_ERR_ASS_PARAMETER, "unknown value for WHAT");
return rc;
}
static const char hlp_passwd[] =
"PASSWD <userID>\n"
"\n"
"Change the passphrase of the secret key for USERID.";
static gpg_error_t
cmd_passwd (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err;
ksba_cert_t cert = NULL;
char *grip = NULL;
line = skip_options (line);
err = gpgsm_find_cert (ctrl, line, NULL, &cert, 0);
if (err)
;
else if (!(grip = gpgsm_get_keygrip_hexstring (cert)))
err = gpg_error (GPG_ERR_INTERNAL);
else
{
char *desc = gpgsm_format_keydesc (cert);
err = gpgsm_agent_passwd (ctrl, grip, desc);
xfree (desc);
}
xfree (grip);
ksba_cert_release (cert);
return err;
}
/* Return true if the command CMD implements the option OPT. */
static int
command_has_option (const char *cmd, const char *cmdopt)
{
if (!strcmp (cmd, "IMPORT"))
{
if (!strcmp (cmdopt, "re-import"))
return 1;
}
return 0;
}
/* Tell the assuan library about our commands */
static int
register_commands (assuan_context_t ctx)
{
static struct {
const char *name;
assuan_handler_t handler;
const char * const help;
} table[] = {
{ "RECIPIENT", cmd_recipient, hlp_recipient },
{ "SIGNER", cmd_signer, hlp_signer },
{ "ENCRYPT", cmd_encrypt, hlp_encrypt },
{ "DECRYPT", cmd_decrypt, hlp_decrypt },
{ "VERIFY", cmd_verify, hlp_verify },
{ "SIGN", cmd_sign, hlp_sign },
{ "IMPORT", cmd_import, hlp_import },
{ "EXPORT", cmd_export, hlp_export },
{ "INPUT", NULL, hlp_input },
{ "OUTPUT", NULL, hlp_output },
{ "MESSAGE", cmd_message, hlp_message },
{ "LISTKEYS", cmd_listkeys, hlp_listkeys },
{ "DUMPKEYS", cmd_dumpkeys, hlp_listkeys },
{ "LISTSECRETKEYS",cmd_listsecretkeys, hlp_listkeys },
{ "DUMPSECRETKEYS",cmd_dumpsecretkeys, hlp_listkeys },
{ "GENKEY", cmd_genkey, hlp_genkey },
{ "DELKEYS", cmd_delkeys, hlp_delkeys },
{ "GETAUDITLOG", cmd_getauditlog, hlp_getauditlog },
{ "GETINFO", cmd_getinfo, hlp_getinfo },
{ "PASSWD", cmd_passwd, hlp_passwd },
{ NULL }
};
int i, rc;
for (i=0; table[i].name; i++)
{
rc = assuan_register_command (ctx, table[i].name, table[i].handler,
table[i].help);
if (rc)
return rc;
}
return 0;
}
/* Startup the server. DEFAULT_RECPLIST is the list of recipients as
set from the command line or config file. We only require those
marked as encrypt-to. */
void
gpgsm_server (certlist_t default_recplist)
{
int rc;
assuan_fd_t filedes[2];
assuan_context_t ctx;
struct server_control_s ctrl;
static const char hello[] = ("GNU Privacy Guard's S/M server "
VERSION " ready");
memset (&ctrl, 0, sizeof ctrl);
gpgsm_init_default_ctrl (&ctrl);
/* We use a pipe based server so that we can work from scripts.
assuan_init_pipe_server will automagically detect when we are
called with a socketpair and ignore FILEDES in this case. */
#ifdef HAVE_W32CE_SYSTEM
#define SERVER_STDIN es_fileno(es_stdin)
#define SERVER_STDOUT es_fileno(es_stdout)
#else
#define SERVER_STDIN 0
#define SERVER_STDOUT 1
#endif
filedes[0] = assuan_fdopen (SERVER_STDIN);
filedes[1] = assuan_fdopen (SERVER_STDOUT);
rc = assuan_new (&ctx);
if (rc)
{
log_error ("failed to allocate assuan context: %s\n",
gpg_strerror (rc));
gpgsm_exit (2);
}
rc = assuan_init_pipe_server (ctx, filedes);
if (rc)
{
log_error ("failed to initialize the server: %s\n",
gpg_strerror (rc));
gpgsm_exit (2);
}
rc = register_commands (ctx);
if (rc)
{
log_error ("failed to the register commands with Assuan: %s\n",
gpg_strerror(rc));
gpgsm_exit (2);
}
if (opt.verbose || opt.debug)
{
char *tmp;
/* Fixme: Use the really used socket name. */
if (asprintf (&tmp,
"Home: %s\n"
"Config: %s\n"
"DirmngrInfo: %s\n"
"%s",
gnupg_homedir (),
opt.config_filename,
dirmngr_socket_name (),
hello) > 0)
{
assuan_set_hello_line (ctx, tmp);
free (tmp);
}
}
else
assuan_set_hello_line (ctx, hello);
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;
ctrl.server_local->default_recplist = default_recplist;
for (;;)
{
rc = assuan_accept (ctx);
if (rc == -1)
{
break;
}
else if (rc)
{
log_info ("Assuan accept problem: %s\n", gpg_strerror (rc));
break;
}
rc = assuan_process (ctx);
if (rc)
{
log_info ("Assuan processing failed: %s\n", gpg_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;
xfree (ctrl.server_local);
audit_release (ctrl.audit);
ctrl.audit = NULL;
assuan_release (ctx);
}
gpg_error_t
gpgsm_status2 (ctrl_t ctrl, int no, ...)
{
gpg_error_t err = 0;
va_list arg_ptr;
const char *text;
va_start (arg_ptr, no);
if (ctrl->no_server && ctrl->status_fd == -1)
; /* No status wanted. */
else if (ctrl->no_server)
{
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
{
err = vprint_assuan_status_strings (ctrl->server_local->assuan_ctx,
get_status_string (no), arg_ptr);
}
va_end (arg_ptr);
return err;
}
gpg_error_t
gpgsm_status (ctrl_t ctrl, int no, const char *text)
{
return gpgsm_status2 (ctrl, no, text, NULL);
}
gpg_error_t
gpgsm_status_with_err_code (ctrl_t ctrl, int no, const char *text,
gpg_err_code_t ec)
{
char buf[30];
sprintf (buf, "%u", (unsigned int)ec);
if (text)
return gpgsm_status2 (ctrl, no, text, buf, NULL);
else
return gpgsm_status2 (ctrl, no, buf, NULL);
}
gpg_error_t
gpgsm_status_with_error (ctrl_t ctrl, int no, const char *text,
gpg_error_t err)
{
char buf[30];
snprintf (buf, sizeof buf, "%u", err);
if (text)
return gpgsm_status2 (ctrl, no, text, buf, NULL);
else
return gpgsm_status2 (ctrl, no, buf, NULL);
}
/* Helper to notify the client about Pinentry events. Because that
might disturb some older clients, this is only done when enabled
via an option. Returns an gpg error code. */
gpg_error_t
gpgsm_proxy_pinentry_notify (ctrl_t ctrl, const unsigned char *line)
{
if (!ctrl || !ctrl->server_local
|| !ctrl->server_local->allow_pinentry_notify)
return 0;
return assuan_inquire (ctrl->server_local->assuan_ctx, line, NULL, NULL, 0);
}
diff --git a/sm/sign.c b/sm/sign.c
index 24ecad3d7..341d8cf68 100644
--- a/sm/sign.c
+++ b/sm/sign.c
@@ -1,828 +1,942 @@
/* sign.c - Sign a message
* Copyright (C) 2001, 2002, 2003, 2008,
* 2010 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#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 "../common/i18n.h"
/* Hash the data and return if something was hashed. Return -1 on error. */
static int
hash_data (int fd, gcry_md_hd_t md)
{
estream_t fp;
char buffer[4096];
int nread;
int rc = 0;
fp = es_fdopen_nc (fd, "rb");
if (!fp)
{
log_error ("fdopen(%d) failed: %s\n", fd, strerror (errno));
return -1;
}
do
{
nread = es_fread (buffer, 1, DIM(buffer), fp);
gcry_md_write (md, buffer, nread);
}
while (nread);
if (es_ferror (fp))
{
log_error ("read error on fd %d: %s\n", fd, strerror (errno));
rc = -1;
}
es_fclose (fp);
return rc;
}
static int
hash_and_copy_data (int fd, gcry_md_hd_t md, ksba_writer_t writer)
{
gpg_error_t err;
estream_t fp;
char buffer[4096];
int nread;
int rc = 0;
int any = 0;
fp = es_fdopen_nc (fd, "rb");
if (!fp)
{
gpg_error_t tmperr = gpg_error_from_syserror ();
log_error ("fdopen(%d) failed: %s\n", fd, strerror (errno));
return tmperr;
}
do
{
nread = es_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", gpg_strerror (err));
rc = err;
}
}
}
while (nread && !rc);
if (es_ferror (fp))
{
rc = gpg_error_from_syserror ();
log_error ("read error on fd %d: %s\n", fd, strerror (errno));
}
es_fclose (fp);
if (!any)
{
/* We can't allow signing 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 and an octet string of size 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", gpg_strerror (err));
rc = err;
}
}
return rc;
}
/* Get the default certificate which is defined as the first
certificate capable of signing returned by the keyDB and has a
secret key available. */
int
gpgsm_get_default_cert (ctrl_t ctrl, ksba_cert_t *r_cert)
{
KEYDB_HANDLE hd;
ksba_cert_t cert = NULL;
int rc;
char *p;
hd = keydb_new ();
if (!hd)
return gpg_error (GPG_ERR_GENERAL);
rc = keydb_search_first (ctrl, 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;
}
- if (!gpgsm_cert_use_sign_p (cert))
+ if (!gpgsm_cert_use_sign_p (cert, 1))
{
p = gpgsm_get_keygrip_hexstring (cert);
if (p)
{
if (!gpgsm_agent_havekey (ctrl, 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 (ctrl, 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 ksba_cert_t
get_default_signer (ctrl_t ctrl)
{
KEYDB_SEARCH_DESC desc;
ksba_cert_t cert = NULL;
KEYDB_HANDLE kh = NULL;
int rc;
if (!opt.local_user)
{
rc = gpgsm_get_default_cert (ctrl, &cert);
if (rc)
{
if (rc != -1)
log_debug ("failed to find default certificate: %s\n",
gpg_strerror (rc));
return NULL;
}
return cert;
}
rc = classify_user_id (opt.local_user, &desc, 0);
if (rc)
{
log_error ("failed to find default signer: %s\n", gpg_strerror (rc));
return NULL;
}
kh = keydb_new ();
if (!kh)
return NULL;
rc = keydb_search (ctrl, 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_t ctrl, ksba_cms_t cms, ksba_cert_t cert)
{
gpg_error_t err;
int rc = 0;
ksba_cert_t next = NULL;
int n;
int not_root = 0;
ksba_cert_ref (cert);
n = ctrl->include_certs;
log_debug ("adding certificates at level %d\n", n);
if (n == -2)
{
not_root = 1;
n = -1;
}
if (n < 0 || n > 50)
n = 50; /* We better apply an upper bound */
/* First add my own certificate unless we don't want any certificate
included at all. */
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;
if (n>0)
n--;
}
/* Walk the chain to include all other certificates. Note that a -1
used for N makes sure that there is no limit and all certs get
included. */
while ( n-- && !(rc = gpgsm_walk_cert_chain (ctrl, 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", gpg_strerror (err));
return err;
}
+#if KSBA_VERSION_NUMBER >= 0x010400 && 0 /* 1.4.0 */
+static gpg_error_t
+add_signed_attribute (ksba_cms_t cms, const char *attrstr)
+{
+ gpg_error_t err;
+ char **fields = NULL;
+ const char *s;
+ int i;
+ unsigned char *der = NULL;
+ size_t derlen;
+
+ fields = strtokenize (attrstr, ":");
+ if (!fields)
+ {
+ err = gpg_error_from_syserror ();
+ log_error ("strtokenize failed: %s\n", gpg_strerror (err));
+ goto leave;
+ }
+
+ for (i=0; fields[i]; i++)
+ ;
+ if (i != 3)
+ {
+ err = gpg_error (GPG_ERR_SYNTAX);
+ log_error ("invalid attribute specification '%s': %s\n",
+ attrstr, i < 3 ? "not enough fields":"too many fields");
+ goto leave;
+ }
+ if (!ascii_strcasecmp (fields[1], "u"))
+ {
+ err = 0;
+ goto leave; /* Skip unsigned attruibutes. */
+ }
+ if (ascii_strcasecmp (fields[1], "s"))
+ {
+ err = gpg_error (GPG_ERR_SYNTAX);
+ log_error ("invalid attribute specification '%s': %s\n",
+ attrstr, "type is not 's' or 'u'");
+ goto leave;
+ }
+ /* Check that the OID is valid. */
+ err = ksba_oid_from_str (fields[0], &der, &derlen);
+ if (err)
+ {
+ log_error ("invalid attribute specification '%s': %s\n",
+ attrstr, gpg_strerror (err));
+ goto leave;
+ }
+ xfree (der);
+ der = NULL;
+
+ if (strchr (fields[2], '/'))
+ {
+ /* FIXME: read from file. */
+ }
+ else /* Directly given in hex. */
+ {
+ for (i=0, s = fields[2]; hexdigitp (s); s++, i++)
+ ;
+ if (*s || !i || (i&1))
+ {
+ log_error ("invalid attribute specification '%s': %s\n",
+ attrstr, "invalid hex encoding of the data");
+ err = gpg_error (GPG_ERR_SYNTAX);
+ goto leave;
+ }
+ der = xtrystrdup (fields[2]);
+ if (!der)
+ {
+ err = gpg_error_from_syserror ();
+ log_error ("malloc failed: %s\n", gpg_strerror (err));
+ goto leave;
+ }
+ for (s=fields[2], derlen=0; s[0] && s[1]; s += 2)
+ der[derlen++] = xtoi_2 (s);
+ }
+
+ /* Store the data in the CMS object for all signers. */
+ err = ksba_cms_add_attribute (cms, -1, fields[0], 0, der, derlen);
+ if (err)
+ {
+ log_error ("invalid attribute specification '%s': %s\n",
+ attrstr, gpg_strerror (err));
+ goto leave;
+ }
+
+ leave:
+ xfree (der);
+ xfree (fields);
+ return err;
+}
+#endif /*ksba >= 1.4.0 */
+
/* 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_t ctrl, certlist_t signerlist,
int data_fd, int detached, estream_t out_fp)
{
int i, rc;
gpg_error_t err;
gnupg_ksba_io_t b64writer = NULL;
ksba_writer_t writer;
ksba_cms_t cms = NULL;
ksba_stop_reason_t stopreason;
KEYDB_HANDLE kh = NULL;
gcry_md_hd_t data_md = NULL;
int signer;
const char *algoid;
int algo;
ksba_isotime_t signed_at;
certlist_t cl;
int release_signerlist = 0;
audit_set_type (ctrl->audit, AUDIT_TYPE_SIGN);
kh = keydb_new ();
if (!kh)
{
log_error (_("failed to allocate keyDB handle\n"));
rc = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
if (!gnupg_rng_is_compliant (opt.compliance))
{
rc = gpg_error (GPG_ERR_FORBIDDEN);
log_error (_("%s is not compliant with %s mode\n"),
"RNG",
gnupg_compliance_option_string (opt.compliance));
gpgsm_status_with_error (ctrl, STATUS_ERROR,
"random-compliance", rc);
goto leave;
}
ctrl->pem_name = "SIGNED MESSAGE";
rc = gnupg_ksba_create_writer
(&b64writer, ((ctrl->create_pem? GNUPG_KSBA_IO_PEM : 0)
| (ctrl->create_base64? GNUPG_KSBA_IO_BASE64 : 0)),
ctrl->pem_name, out_fp, &writer);
if (rc)
{
log_error ("can't create writer: %s\n", gpg_strerror (rc));
goto leave;
}
err = ksba_cms_new (&cms);
if (err)
{
rc = err;
goto leave;
}
err = ksba_cms_set_reader_writer (cms, NULL, writer);
if (err)
{
log_debug ("ksba_cms_set_reader_writer failed: %s\n",
gpg_strerror (err));
rc = err;
goto leave;
}
- /* We are going to create signed data with data as encap. content */
+ /* We are going to create signed data with data as encap. content.
+ * In authenticode mode we use spcIndirectDataContext instead. */
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);
+ err = ksba_cms_set_content_type
+ (cms, 1,
+#if KSBA_VERSION_NUMBER >= 0x010400 && 0
+ opt.authenticode? KSBA_CT_SPC_IND_DATA_CTX :
+#endif
+ KSBA_CT_DATA
+ );
if (err)
{
log_debug ("ksba_cms_set_content_type failed: %s\n",
gpg_strerror (err));
rc = err;
goto leave;
}
/* If no list of signers is given, use the default certificate. */
if (!signerlist)
{
ksba_cert_t cert = get_default_signer (ctrl);
if (!cert)
{
log_error ("no default signer found\n");
gpgsm_status2 (ctrl, STATUS_INV_SGNR,
get_inv_recpsgnr_code (GPG_ERR_NO_SECKEY), NULL);
rc = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
/* Although we don't check for ambiguous specification we will
check that the signer's certificate is usable and valid. */
- rc = gpgsm_cert_use_sign_p (cert);
+ rc = gpgsm_cert_use_sign_p (cert, 0);
if (!rc)
rc = gpgsm_validate_chain (ctrl, cert, "", NULL, 0, NULL, 0, NULL);
if (rc)
{
char *tmpfpr;
tmpfpr = gpgsm_get_fingerprint_hexstring (cert, 0);
gpgsm_status2 (ctrl, STATUS_INV_SGNR,
get_inv_recpsgnr_code (rc), tmpfpr, NULL);
xfree (tmpfpr);
goto leave;
}
/* That one is fine - create signerlist. */
signerlist = xtrycalloc (1, sizeof *signerlist);
if (!signerlist)
{
rc = out_of_core ();
ksba_cert_release (cert);
goto leave;
}
signerlist->cert = cert;
release_signerlist = 1;
}
/* Figure out the hash algorithm to use. We do not want to use the
one for the certificate but if possible an OID for the plain
algorithm. */
if (opt.forced_digest_algo && opt.verbose)
log_info ("user requested hash algorithm %d\n", opt.forced_digest_algo);
for (i=0, cl=signerlist; cl; cl = cl->next, i++)
{
const char *oid;
if (opt.forced_digest_algo)
{
oid = NULL;
cl->hash_algo = opt.forced_digest_algo;
}
else
{
oid = ksba_cert_get_digest_algo (cl->cert);
cl->hash_algo = oid ? gcry_md_map_name (oid) : 0;
}
switch (cl->hash_algo)
{
case GCRY_MD_SHA1: oid = "1.3.14.3.2.26"; break;
case GCRY_MD_RMD160: oid = "1.3.36.3.2.1"; break;
case GCRY_MD_SHA224: oid = "2.16.840.1.101.3.4.2.4"; break;
case GCRY_MD_SHA256: oid = "2.16.840.1.101.3.4.2.1"; break;
case GCRY_MD_SHA384: oid = "2.16.840.1.101.3.4.2.2"; break;
case GCRY_MD_SHA512: oid = "2.16.840.1.101.3.4.2.3"; break;
/* case GCRY_MD_WHIRLPOOL: oid = "No OID yet"; break; */
case GCRY_MD_MD5: /* We don't want to use MD5. */
case 0: /* No algorithm found in cert. */
default: /* Other algorithms. */
log_info (_("hash algorithm %d (%s) for signer %d not supported;"
" using %s\n"),
cl->hash_algo, oid? oid: "?", i,
gcry_md_algo_name (GCRY_MD_SHA1));
cl->hash_algo = GCRY_MD_SHA1;
oid = "1.3.14.3.2.26";
break;
}
cl->hash_algo_oid = oid;
/* Check compliance. */
if (! gnupg_digest_is_allowed (opt.compliance, 1, cl->hash_algo))
{
log_error (_("digest algorithm '%s' may not be used in %s mode\n"),
gcry_md_algo_name (cl->hash_algo),
gnupg_compliance_option_string (opt.compliance));
err = gpg_error (GPG_ERR_DIGEST_ALGO);
goto leave;
}
{
unsigned int nbits;
int pk_algo = gpgsm_get_key_algo_info (cl->cert, &nbits);
if (! gnupg_pk_is_allowed (opt.compliance, PK_USE_SIGNING, pk_algo,
NULL, nbits, NULL))
{
char kidstr[10+1];
snprintf (kidstr, sizeof kidstr, "0x%08lX",
gpgsm_get_short_fingerprint (cl->cert, NULL));
log_error (_("key %s may not be used for signing in %s mode\n"),
kidstr,
gnupg_compliance_option_string (opt.compliance));
err = gpg_error (GPG_ERR_PUBKEY_ALGO);
goto leave;
}
}
}
if (opt.verbose)
{
for (i=0, cl=signerlist; cl; cl = cl->next, i++)
log_info (_("hash algorithm used for signer %d: %s (%s)\n"),
i, gcry_md_algo_name (cl->hash_algo), cl->hash_algo_oid);
}
/* 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);
+ rc = gpgsm_cert_use_sign_p (cl->cert, 0);
if (rc)
goto leave;
err = ksba_cms_add_signer (cms, cl->cert);
if (err)
{
log_error ("ksba_cms_add_signer failed: %s\n", gpg_strerror (err));
rc = 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, cl->hash_algo_oid);
if (err)
{
log_debug ("ksba_cms_add_digest_algo failed: %s\n",
gpg_strerror (err));
rc = err;
goto leave;
}
}
/* Check whether one of the certificates is qualified. Note that we
already validated the certificate and thus the user data stored
flag must be available. */
if (!opt.no_chain_validation)
{
for (cl=signerlist; cl; cl = cl->next)
{
size_t buflen;
char buffer[1];
err = ksba_cert_get_user_data (cl->cert, "is_qualified",
&buffer, sizeof (buffer), &buflen);
if (err || !buflen)
{
log_error (_("checking for qualified certificate failed: %s\n"),
gpg_strerror (err));
rc = err;
goto leave;
}
if (*buffer)
err = gpgsm_qualified_consent (ctrl, cl->cert);
else
err = gpgsm_not_qualified_warning (ctrl, cl->cert);
if (err)
{
rc = err;
goto leave;
}
}
}
/* Prepare hashing (actually we are figuring out what we have set
above). */
rc = gcry_md_open (&data_md, 0, 0);
if (rc)
{
log_error ("md_open failed: %s\n", gpg_strerror (rc));
goto leave;
}
if (DBG_HASHING)
gcry_md_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);
audit_log_i (ctrl->audit, AUDIT_DATA_HASH_ALGO, algo);
}
audit_log (ctrl->audit, AUDIT_SETUP_READY);
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;
if (!hash_data (data_fd, data_md))
audit_log (ctrl->audit, AUDIT_GOT_DATA);
for (cl=signerlist,signer=0; cl; cl = cl->next, signer++)
{
digest = gcry_md_read (data_md, cl->hash_algo);
digest_len = gcry_md_get_algo_dlen (cl->hash_algo);
if ( !digest || !digest_len )
{
log_error ("problem getting the hash of the data\n");
rc = gpg_error (GPG_ERR_BUG);
goto leave;
}
err = ksba_cms_set_message_digest (cms, signer, digest, digest_len);
if (err)
{
log_error ("ksba_cms_set_message_digest failed: %s\n",
gpg_strerror (err));
rc = err;
goto leave;
}
}
}
gnupg_get_isotime (signed_at);
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",
gpg_strerror (err));
rc = err;
goto leave;
}
}
+ /* We can add signed attributes only when build against libksba 1.4. */
+#if KSBA_VERSION_NUMBER >= 0x010400 && 0 /* 1.4.0 */
+ {
+ strlist_t sl;
+
+ for (sl = opt.attributes; sl; sl = sl->next)
+ if ((err = add_signed_attribute (cms, sl->d)))
+ goto leave;
+ }
+#else
+ log_info ("Note: option --attribute is ignored by this version\n");
+#endif /*ksba >= 1.4.0 */
+
+
/* We need to write at least a minimal list of our capabilities to
try to convince some MUAs to use 3DES and not the crippled
RC2. Our list is:
aes128-CBC
des-EDE3-CBC
*/
err = ksba_cms_add_smime_capability (cms, "2.16.840.1.101.3.4.1.2", NULL, 0);
if (!err)
err = ksba_cms_add_smime_capability (cms, "1.2.840.113549.3.7", NULL, 0);
if (err)
{
log_error ("ksba_cms_add_smime_capability failed: %s\n",
gpg_strerror (err));
goto leave;
}
/* Main building loop. */
do
{
err = ksba_cms_build (cms, &stopreason);
if (err)
{
log_debug ("ksba_cms_build failed: %s\n", gpg_strerror (err));
rc = 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);
rc = hash_and_copy_data (data_fd, data_md, writer);
if (rc)
goto leave;
audit_log (ctrl->audit, AUDIT_GOT_DATA);
for (cl=signerlist,signer=0; cl; cl = cl->next, signer++)
{
digest = gcry_md_read (data_md, cl->hash_algo);
digest_len = gcry_md_get_algo_dlen (cl->hash_algo);
if ( !digest || !digest_len )
{
log_error ("problem getting the hash of the data\n");
rc = gpg_error (GPG_ERR_BUG);
goto leave;
}
err = ksba_cms_set_message_digest (cms, signer,
digest, digest_len);
if (err)
{
log_error ("ksba_cms_set_message_digest failed: %s\n",
gpg_strerror (err));
rc = err;
goto leave;
}
}
}
else if (stopreason == KSBA_SR_NEED_SIG)
{
/* Compute the signature for all signers. */
gcry_md_hd_t md;
rc = gcry_md_open (&md, 0, 0);
if (rc)
{
log_error ("md_open failed: %s\n", gpg_strerror (rc));
goto leave;
}
if (DBG_HASHING)
gcry_md_debug (md, "sign.attr");
ksba_cms_set_hash_function (cms, HASH_FNC, md);
for (cl=signerlist,signer=0; cl; cl = cl->next, signer++)
{
unsigned char *sigval = NULL;
char *buf, *fpr;
audit_log_i (ctrl->audit, AUDIT_NEW_SIG, signer);
if (signer)
gcry_md_reset (md);
{
certlist_t cl_tmp;
for (cl_tmp=signerlist; cl_tmp; cl_tmp = cl_tmp->next)
{
gcry_md_enable (md, cl_tmp->hash_algo);
audit_log_i (ctrl->audit, AUDIT_ATTR_HASH_ALGO,
cl_tmp->hash_algo);
}
}
rc = ksba_cms_hash_signed_attrs (cms, signer);
if (rc)
{
log_debug ("hashing signed attrs failed: %s\n",
gpg_strerror (rc));
gcry_md_close (md);
goto leave;
}
rc = gpgsm_create_cms_signature (ctrl, cl->cert,
md, cl->hash_algo, &sigval);
if (rc)
{
audit_log_cert (ctrl->audit, AUDIT_SIGNED_BY, cl->cert, rc);
gcry_md_close (md);
goto leave;
}
err = ksba_cms_set_sig_val (cms, signer, sigval);
xfree (sigval);
if (err)
{
audit_log_cert (ctrl->audit, AUDIT_SIGNED_BY, cl->cert, err);
log_error ("failed to store the signature: %s\n",
gpg_strerror (err));
rc = 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 = 0;
{
int pkalgo = gpgsm_get_key_algo_info (cl->cert, NULL);
buf = xtryasprintf ("%c %d %d 00 %s %s",
detached? 'D':'S',
pkalgo,
cl->hash_algo,
signed_at,
fpr);
if (!buf)
rc = gpg_error_from_syserror ();
}
xfree (fpr);
if (rc)
{
gcry_md_close (md);
goto leave;
}
gpgsm_status (ctrl, STATUS_SIG_CREATED, buf);
xfree (buf);
audit_log_cert (ctrl->audit, AUDIT_SIGNED_BY, cl->cert, 0);
}
gcry_md_close (md);
}
}
while (stopreason != KSBA_SR_READY);
rc = gnupg_ksba_finish_writer (b64writer);
if (rc)
{
log_error ("write failed: %s\n", gpg_strerror (rc));
goto leave;
}
audit_log (ctrl->audit, AUDIT_SIGNING_DONE);
log_info ("signature created\n");
leave:
if (rc)
log_error ("error creating signature: %s <%s>\n",
gpg_strerror (rc), gpg_strsource (rc) );
if (release_signerlist)
gpgsm_release_certlist (signerlist);
ksba_cms_release (cms);
gnupg_ksba_destroy_writer (b64writer);
keydb_release (kh);
gcry_md_close (data_md);
return rc;
}
diff --git a/tests/openpgp/samplekeys/README b/tests/openpgp/samplekeys/README
index f8a7e9ed7..74635c702 100644
--- a/tests/openpgp/samplekeys/README
+++ b/tests/openpgp/samplekeys/README
@@ -1,33 +1,35 @@
no-creation-time.gpg A key with a zero creation time.
ecc-sample-1-pub.asc A NIST P-256 ECC sample key.
ecc-sample-1-sec.asc Ditto, but the secret keyblock.
ecc-sample-2-pub.asc A NIST P-384 ECC sample key.
ecc-sample-2-sec.asc Ditto, but the secret keyblock.
ecc-sample-3-pub.asc A NIST P-521 ECC sample key.
ecc-sample-3-sec.asc Ditto, but the secret keyblock.
eddsa-sample-1-pub.asc An Ed25519 sample key.
eddsa-sample-1-sec.asc Ditto, but as protected secret keyblock.
dda252ebb8ebe1af-1.asc rsa4096 key 1
dda252ebb8ebe1af-2.asc rsa4096 key 2 with a long keyid collision.
whats-new-in-2.1.asc Collection of sample keys.
e2e-p256-1-clr.asc Google End-end-End test key (no protection)
e2e-p256-1-prt.asc Ditto, but protected with passphrase "a".
E657FB607BB4F21C90BB6651BC067AF28BC90111.asc Key with subkeys (no protection)
pgp-desktop-skr.asc Secret key with subkeys w/o signatures
rsa-rsa-sample-1.asc RSA+RSA sample key (no passphrase)
ed25519-cv25519-sample-1.asc Ed25519+CV25519 sample key (no passphrase)
silent-running.asc Collection of sample secret keys (no passphrases)
rsa-primary-auth-only.pub.asc rsa2408 primary only, usage: cert,auth
rsa-primary-auth-only.sec.asc Ditto but the secret keyblock.
+v5-sample-1-pub.asc A version 5 key (ed25519/cert,sign,v5+cv25519/v5)
+v5-sample-1-sec.asc Ditto, but the secret keyblock (unprotected).
Notes:
- pgp-desktop-skr.asc is a secret keyblock without the uid and subkey
binding signatures. When exporting a secret key from PGP desktop
such a file is created which is then directly followed by a separate
armored public key block. To create such a sample concatenate
pgp-desktop-skr.asc and E657FB607BB4F21C90BB6651BC067AF28BC90111.asc
- ecc-sample-2-sec.asc and ecc-sample-3-sec.asc do not have and
binding signatures either. ecc-sample-1-sec.asc has them, though.
diff --git a/tests/openpgp/samplekeys/v5-sample-1-pub.asc b/tests/openpgp/samplekeys/v5-sample-1-pub.asc
new file mode 100644
index 000000000..9f7147130
--- /dev/null
+++ b/tests/openpgp/samplekeys/v5-sample-1-pub.asc
@@ -0,0 +1,19 @@
+pub ed25519 2019-03-20 [SC]
+ 19347BC9872464025F99DF3EC2E0000ED9884892E1F7B3EA4C94009159569B54
+uid emma.goldman@example.net
+sub cv25519 2019-03-20 [E]
+ E4557C2B02FFBF4B04F87401EC336AF7133D0F85BE7FD09BAEFD9CAEB8C93965
+
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+mDcFXJH05BYAAAAtCSsGAQQB2kcPAQEHQFhZlVcVVtwf+21xNQPX+ecMJJBL0MPd
+fj75iux+my8QtBhlbW1hLmdvbGRtYW5AZXhhbXBsZS5uZXSIlgUTFggASCIhBRk0
+e8mHJGQCX5nfPsLgAA7ZiEiS4fez6kyUAJFZVptUBQJckfTkAhsDBQsJCAcCAyIC
+AQYVCgkICwIEFgIDAQIeBwIXgAAA9cAA/jiR3yMsZMeEQ40u6uzEoXa6UXeV/S3w
+wJAXRJy9M8s0AP9vuL/7AyTfFXwwzSjDnYmzS0qAhbLDQ643N+MXGBJ2Bbg8BVyR
+9OQSAAAAMgorBgEEAZdVAQUBAQdA+nysrzml2UCweAqtpDuncSPlvrcBWKU0yfU0
+YvYWWAoDAQgHiHoFGBYIACwiIQUZNHvJhyRkAl+Z3z7C4AAO2YhIkuH3s+pMlACR
+WVabVAUCXJH05AIbDAAAOSQBAP4BOOIR/sGLNMOfeb5fPs/02QMieoiSjIBnijho
+b2U5AQC+RtOHCHx7TcIYl5/Uyoi+FOvPLcNw4hOv2nwUzSSVAw==
+=WYfO
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/tests/openpgp/samplekeys/v5-sample-1-sec.asc b/tests/openpgp/samplekeys/v5-sample-1-sec.asc
new file mode 100644
index 000000000..b5463c93f
--- /dev/null
+++ b/tests/openpgp/samplekeys/v5-sample-1-sec.asc
@@ -0,0 +1,21 @@
+sec ed25519 2019-03-20 [SC]
+ 19347BC9872464025F99DF3EC2E0000ED9884892E1F7B3EA4C94009159569B54
+uid emma.goldman@example.net
+ssb cv25519 2019-03-20 [E]
+ E4557C2B02FFBF4B04F87401EC336AF7133D0F85BE7FD09BAEFD9CAEB8C93965
+
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+
+lGEFXJH05BYAAAAtCSsGAQQB2kcPAQEHQFhZlVcVVtwf+21xNQPX+ecMJJBL0MPd
+fj75iux+my8QAAAAAAAiAQCHZ1SnSUmWqxEsoI6facIVZQu6mph3cBFzzTvcm5lA
+Ng5ctBhlbW1hLmdvbGRtYW5AZXhhbXBsZS5uZXSIlgUTFggASCIhBRk0e8mHJGQC
+X5nfPsLgAA7ZiEiS4fez6kyUAJFZVptUBQJckfTkAhsDBQsJCAcCAyICAQYVCgkI
+CwIEFgIDAQIeBwIXgAAA9cAA/jiR3yMsZMeEQ40u6uzEoXa6UXeV/S3wwJAXRJy9
+M8s0AP9vuL/7AyTfFXwwzSjDnYmzS0qAhbLDQ643N+MXGBJ2BZxmBVyR9OQSAAAA
+MgorBgEEAZdVAQUBAQdA+nysrzml2UCweAqtpDuncSPlvrcBWKU0yfU0YvYWWAoD
+AQgHAAAAAAAiAP9OdAPppjU1WwpqjIItkxr+VPQRT8Zm/Riw7U3F6v3OiBFHiHoF
+GBYIACwiIQUZNHvJhyRkAl+Z3z7C4AAO2YhIkuH3s+pMlACRWVabVAUCXJH05AIb
+DAAAOSQBAP4BOOIR/sGLNMOfeb5fPs/02QMieoiSjIBnijhob2U5AQC+RtOHCHx7
+TcIYl5/Uyoi+FOvPLcNw4hOv2nwUzSSVAw==
+=IiS2
+-----END PGP PRIVATE KEY BLOCK-----
diff --git a/tools/card-call-scd.c b/tools/card-call-scd.c
index 0a01bf5ca..c2580bf5c 100644
--- a/tools/card-call-scd.c
+++ b/tools/card-call-scd.c
@@ -1,1550 +1,1555 @@
/* card-call-scd.c - IPC calls to scdaemon.
* Copyright (C) 2019 g10 Code GmbH
* Copyright (C) 2001-2003, 2006-2011, 2013 Free Software Foundation, Inc.
* Copyright (C) 2013-2015 Werner Koch
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <time.h>
#ifdef HAVE_LOCALE_H
#include <locale.h>
#endif
#include "../common/util.h"
#include "../common/membuf.h"
#include "../common/i18n.h"
#include "../common/asshelp.h"
#include "../common/sysutils.h"
#include "../common/status.h"
#include "../common/host2net.h"
#include "../common/openpgpdefs.h"
#include "gpg-card.h"
#define CONTROL_D ('D' - 'A' + 1)
#define START_AGENT_NO_STARTUP_CMDS 1
#define START_AGENT_SUPPRESS_ERRORS 2
struct default_inq_parm_s
{
assuan_context_t ctx;
struct {
u32 *keyid;
u32 *mainkeyid;
int pubkey_algo;
} keyinfo;
};
struct cipher_parm_s
{
struct default_inq_parm_s *dflt;
assuan_context_t ctx;
unsigned char *ciphertext;
size_t ciphertextlen;
};
struct writecert_parm_s
{
struct default_inq_parm_s *dflt;
const unsigned char *certdata;
size_t certdatalen;
};
struct writekey_parm_s
{
struct default_inq_parm_s *dflt;
const unsigned char *keydata;
size_t keydatalen;
};
struct genkey_parm_s
{
struct default_inq_parm_s *dflt;
const char *keyparms;
const char *passphrase;
};
struct card_cardlist_parm_s
{
gpg_error_t error;
strlist_t list;
};
struct import_key_parm_s
{
struct default_inq_parm_s *dflt;
const void *key;
size_t keylen;
};
struct cache_nonce_parm_s
{
char **cache_nonce_addr;
char **passwd_nonce_addr;
};
/*
* File local variables
*/
/* The established context to the agent. Note that all calls to
* scdaemon are routed via the agent and thus we only need to care
* about the IPC with the agent. */
static assuan_context_t agent_ctx;
/*
* Local prototypes
*/
static gpg_error_t learn_status_cb (void *opaque, const char *line);
/* Release the card info structure INFO. */
void
release_card_info (card_info_t info)
{
int i;
if (!info)
return;
xfree (info->reader); info->reader = NULL;
xfree (info->cardtype); info->cardtype = NULL;
xfree (info->serialno); info->serialno = NULL;
xfree (info->dispserialno); info->dispserialno = NULL;
xfree (info->apptypestr); info->apptypestr = NULL;
info->apptype = APP_TYPE_NONE;
xfree (info->disp_name); info->disp_name = NULL;
xfree (info->disp_lang); info->disp_lang = NULL;
xfree (info->pubkey_url); info->pubkey_url = NULL;
xfree (info->login_data); info->login_data = NULL;
info->cafpr1len = info->cafpr2len = info->cafpr3len = 0;
for (i=0; i < DIM(info->private_do); i++)
{
xfree (info->private_do[i]);
info->private_do[i] = NULL;
}
while (info->kinfo)
{
key_info_t kinfo = info->kinfo->next;
xfree (info->kinfo);
info->kinfo = kinfo;
}
info->chvusage[0] = info->chvusage[1] = 0;
}
/* Map an application type string to an integer. */
static app_type_t
map_apptypestr (const char *string)
{
app_type_t result;
if (!string)
result = APP_TYPE_NONE;
else if (!ascii_strcasecmp (string, "OPENPGP"))
result = APP_TYPE_OPENPGP;
else if (!ascii_strcasecmp (string, "NKS"))
result = APP_TYPE_NKS;
else if (!ascii_strcasecmp (string, "DINSIG"))
result = APP_TYPE_DINSIG;
else if (!ascii_strcasecmp (string, "P15"))
result = APP_TYPE_P15;
else if (!ascii_strcasecmp (string, "GELDKARTE"))
result = APP_TYPE_GELDKARTE;
else if (!ascii_strcasecmp (string, "SC-HSM"))
result = APP_TYPE_SC_HSM;
else if (!ascii_strcasecmp (string, "PIV"))
result = APP_TYPE_PIV;
else
result = APP_TYPE_UNKNOWN;
return result;
}
/* Return a string representation of the application type. */
const char *
app_type_string (app_type_t app_type)
{
const char *result = "?";
switch (app_type)
{
case APP_TYPE_NONE: result = "None"; break;
case APP_TYPE_OPENPGP: result = "OpenPGP"; break;
case APP_TYPE_NKS: result = "NetKey"; break;
case APP_TYPE_DINSIG: result = "DINSIG"; break;
case APP_TYPE_P15: result = "P15"; break;
case APP_TYPE_GELDKARTE: result = "Geldkarte"; break;
case APP_TYPE_SC_HSM: result = "SC-HSM"; break;
case APP_TYPE_PIV: result = "PIV"; break;
case APP_TYPE_UNKNOWN: result = "Unknown"; break;
}
return result;
}
/* If RC is not 0, write an appropriate status message. */
static gpg_error_t
status_sc_op_failure (gpg_error_t err)
{
switch (gpg_err_code (err))
{
case 0:
break;
case GPG_ERR_CANCELED:
case GPG_ERR_FULLY_CANCELED:
gnupg_status_printf (STATUS_SC_OP_FAILURE, "1");
break;
case GPG_ERR_BAD_PIN:
gnupg_status_printf (STATUS_SC_OP_FAILURE, "2");
break;
default:
gnupg_status_printf (STATUS_SC_OP_FAILURE, NULL);
break;
}
return err;
}
/* This is the default inquiry callback. It mainly handles the
Pinentry notifications. */
static gpg_error_t
default_inq_cb (void *opaque, const char *line)
{
gpg_error_t err = 0;
struct default_inq_parm_s *parm = opaque;
(void)parm;
if (has_leading_keyword (line, "PINENTRY_LAUNCHED"))
{
/* err = gpg_proxy_pinentry_notify (parm->ctrl, line); */
/* if (err) */
/* log_error (_("failed to proxy %s inquiry to client\n"), */
/* "PINENTRY_LAUNCHED"); */
/* We do not pass errors to avoid breaking other code. */
}
else
log_debug ("ignoring gpg-agent inquiry '%s'\n", line);
return err;
}
/* Print a warning if the server's version number is less than our
version number. Returns an error code on a connection problem. */
static gpg_error_t
warn_version_mismatch (assuan_context_t ctx, const char *servername, int mode)
{
gpg_error_t err;
char *serverversion;
const char *myversion = strusage (13);
err = get_assuan_server_version (ctx, mode, &serverversion);
if (err)
log_log (gpg_err_code (err) == GPG_ERR_NOT_SUPPORTED?
GPGRT_LOGLVL_INFO : GPGRT_LOGLVL_ERROR,
_("error getting version from '%s': %s\n"),
servername, gpg_strerror (err));
else if (compare_version_strings (serverversion, myversion) < 0)
{
char *warn;
warn = xtryasprintf (_("server '%s' is older than us (%s < %s)"),
servername, serverversion, myversion);
if (!warn)
err = gpg_error_from_syserror ();
else
{
log_info (_("WARNING: %s\n"), warn);
if (!opt.quiet)
{
log_info (_("Note: Outdated servers may lack important"
" security fixes.\n"));
log_info (_("Note: Use the command \"%s\" to restart them.\n"),
"gpgconf --kill all");
}
gnupg_status_printf (STATUS_WARNING, "server_version_mismatch 0 %s",
warn);
xfree (warn);
}
}
xfree (serverversion);
return err;
}
/* Try to connect to the agent via socket or fork it off and work by
* pipes. Handle the server's initial greeting. */
static gpg_error_t
start_agent (unsigned int flags)
{
gpg_error_t err;
+ int started = 0;
if (agent_ctx)
err = 0;
else
{
+ started = 1;
err = start_new_gpg_agent (&agent_ctx,
GPG_ERR_SOURCE_DEFAULT,
opt.agent_program,
opt.lc_ctype, opt.lc_messages,
opt.session_env,
opt.autostart, opt.verbose, DBG_IPC,
NULL, NULL);
if (!opt.autostart && gpg_err_code (err) == GPG_ERR_NO_AGENT)
{
static int shown;
if (!shown)
{
shown = 1;
log_info (_("no gpg-agent running in this session\n"));
}
}
else if (!err
&& !(err = warn_version_mismatch (agent_ctx, GPG_AGENT_NAME, 0)))
{
/* Tell the agent that we support Pinentry notifications.
No error checking so that it will work also with older
agents. */
assuan_transact (agent_ctx, "OPTION allow-pinentry-notify",
NULL, NULL, NULL, NULL, NULL, NULL);
/* Tell the agent about what version we are aware. This is
here used to indirectly enable GPG_ERR_FULLY_CANCELED. */
assuan_transact (agent_ctx, "OPTION agent-awareness=2.1.0",
NULL, NULL, NULL, NULL, NULL, NULL);
}
}
- if (!err && !(flags & START_AGENT_NO_STARTUP_CMDS))
+ if (started && !err && !(flags & START_AGENT_NO_STARTUP_CMDS))
{
/* Request the serial number of the card for an early test. */
struct card_info_s info;
memset (&info, 0, sizeof info);
if (!(flags & START_AGENT_SUPPRESS_ERRORS))
err = warn_version_mismatch (agent_ctx, SCDAEMON_NAME, 2);
if (!err)
err = assuan_transact (agent_ctx, "SCD SERIALNO",
NULL, NULL, NULL, NULL,
learn_status_cb, &info);
if (err && !(flags & START_AGENT_SUPPRESS_ERRORS))
{
switch (gpg_err_code (err))
{
case GPG_ERR_NOT_SUPPORTED:
case GPG_ERR_NO_SCDAEMON:
gnupg_status_printf (STATUS_CARDCTRL, "6"); /* No card support. */
break;
case GPG_ERR_OBJ_TERM_STATE:
/* Card is in termination state. */
gnupg_status_printf (STATUS_CARDCTRL, "7");
break;
default:
gnupg_status_printf (STATUS_CARDCTRL, "4"); /* No card. */
break;
}
}
if (!err && info.serialno)
gnupg_status_printf (STATUS_CARDCTRL, "3 %s", info.serialno);
release_card_info (&info);
}
return err;
}
/* Return a new malloced string by unescaping the string S. Escaping
* is percent escaping and '+'/space mapping. A binary nul will
* silently be replaced by a 0xFF. Function returns NULL to indicate
* an out of memory status. */
static char *
unescape_status_string (const unsigned char *s)
{
return percent_plus_unescape (s, 0xff);
}
/* Take a 20 or 32 byte hexencoded string and put it into the provided
* FPRLEN byte long buffer FPR in binary format. Returns the actual
* used length of the FPR buffer or 0 on error. */
static unsigned int
unhexify_fpr (const char *hexstr, unsigned char *fpr, unsigned int fprlen)
{
const char *s;
int n;
for (s=hexstr, n=0; hexdigitp (s); s++, n++)
;
if ((*s && *s != ' ') || !(n == 40 || n == 64))
return 0; /* no fingerprint (invalid or wrong length). */
for (s=hexstr, n=0; *s && n < fprlen; s += 2, n++)
fpr[n] = xtoi_2 (s);
return (n == 20 || n == 32)? n : 0;
}
/* Take the serial number from LINE and return it verbatim in a newly
* allocated string. We make sure that only hex characters are
* returned. Returns NULL on error. */
static char *
store_serialno (const char *line)
{
const char *s;
char *p;
for (s=line; hexdigitp (s); s++)
;
p = xtrymalloc (s + 1 - line);
if (p)
{
memcpy (p, line, s-line);
p[s-line] = 0;
}
return p;
}
/* Send an APDU to the current card. On success the status word is
* stored at R_SW inless R_SW is NULL. With HEXAPDU being NULL only a
* RESET command is send to scd. With HEXAPDU being the string
* "undefined" the command "SERIALNO undefined" is send to scd. If
* R_DATA is not NULL the data is without the status code is stored
* there. Caller must release it. */
gpg_error_t
scd_apdu (const char *hexapdu, unsigned int *r_sw,
unsigned char **r_data, size_t *r_datalen)
{
gpg_error_t err;
if (r_data)
*r_data = NULL;
if (r_datalen)
*r_datalen = 0;
err = start_agent (START_AGENT_NO_STARTUP_CMDS);
if (err)
return err;
if (!hexapdu)
{
err = assuan_transact (agent_ctx, "SCD RESET",
NULL, NULL, NULL, NULL, NULL, NULL);
}
else if (!strcmp (hexapdu, "undefined"))
{
err = assuan_transact (agent_ctx, "SCD SERIALNO undefined",
NULL, NULL, NULL, NULL, NULL, NULL);
}
else
{
char line[ASSUAN_LINELENGTH];
membuf_t mb;
unsigned char *data;
size_t datalen;
init_membuf (&mb, 256);
snprintf (line, DIM(line), "SCD APDU %s", hexapdu);
err = assuan_transact (agent_ctx, line,
put_membuf_cb, &mb, NULL, NULL, NULL, NULL);
if (!err)
{
data = get_membuf (&mb, &datalen);
if (!data)
err = gpg_error_from_syserror ();
else if (datalen < 2) /* Ooops */
err = gpg_error (GPG_ERR_CARD);
else
{
if (r_sw)
*r_sw = buf16_to_uint (data+datalen-2);
if (r_data && r_datalen)
{
*r_data = data;
*r_datalen = datalen - 2;
data = NULL;
}
}
xfree (data);
}
}
return err;
}
/* This is a dummy data line callback. */
static gpg_error_t
dummy_data_cb (void *opaque, const void *buffer, size_t length)
{
(void)opaque;
(void)buffer;
(void)length;
return 0;
}
/* A simple callback used to return the serialnumber of a card. */
static gpg_error_t
get_serialno_cb (void *opaque, const char *line)
{
char **serialno = opaque;
const char *keyword = line;
const char *s;
int keywordlen, n;
for (keywordlen=0; *line && !spacep (line); line++, keywordlen++)
;
while (spacep (line))
line++;
/* FIXME: Should we use has_leading_keyword? */
if (keywordlen == 8 && !memcmp (keyword, "SERIALNO", keywordlen))
{
if (*serialno)
return gpg_error (GPG_ERR_CONFLICT); /* Unexpected status line. */
for (n=0,s=line; hexdigitp (s); s++, n++)
;
if (!n || (n&1)|| !(spacep (s) || !*s) )
return gpg_error (GPG_ERR_ASS_PARAMETER);
*serialno = xtrymalloc (n+1);
if (!*serialno)
return out_of_core ();
memcpy (*serialno, line, n);
(*serialno)[n] = 0;
}
return 0;
}
/* For historical reasons OpenPGP cards simply use the numbers 1 to 3
* for the <keyref>. Other cards and future versions of
* scd/app-openpgp.c may print the full keyref; i.e. "OpenPGP.1"
* instead of "1". This is a helper to cope with that. */
static const char *
parse_keyref_helper (const char *string)
{
if (*string == '1' && spacep (string+1))
return "OPENPGP.1";
else if (*string == '2' && spacep (string+1))
return "OPENPGP.2";
else if (*string == '3' && spacep (string+1))
return "OPENPGP.3";
else
return string;
}
/* Create a new key info object with KEYREF. All fields but the
* keyref are zeroed out. Never returns NULL. The created object is
* appended to the list at INFO. */
static key_info_t
create_kinfo (card_info_t info, const char *keyref)
{
key_info_t kinfo, ki;
kinfo = xcalloc (1, sizeof *kinfo + strlen (keyref));
strcpy (kinfo->keyref, keyref);
if (!info->kinfo)
info->kinfo = kinfo;
else
{
for (ki=info->kinfo; ki->next; ki = ki->next)
;
ki->next = kinfo;
}
return kinfo;
}
/* The status callback to handle the LEARN and GETATTR commands. */
static gpg_error_t
learn_status_cb (void *opaque, const char *line)
{
struct card_info_s *parm = opaque;
const char *keyword = line;
int keywordlen;
char *line_buffer = NULL; /* In case we need a copy. */
char *pline;
key_info_t kinfo;
const char *keyref;
int i;
for (keywordlen=0; *line && !spacep (line); line++, keywordlen++)
;
while (spacep (line))
line++;
switch (keywordlen)
{
case 3:
if (!memcmp (keyword, "KDF", 3))
{
parm->kdf_do_enabled = 1;
}
break;
case 5:
if (!memcmp (keyword, "UIF-", 4)
&& strchr("123", keyword[4]))
{
unsigned char *data;
int no = keyword[4] - '1';
log_assert (no >= 0 && no <= 2);
data = unescape_status_string (line);
parm->uif[no] = (data[0] != 0xff);
xfree (data);
}
break;
case 6:
if (!memcmp (keyword, "READER", keywordlen))
{
xfree (parm->reader);
parm->reader = unescape_status_string (line);
}
else if (!memcmp (keyword, "EXTCAP", keywordlen))
{
char *p, *p2, *buf;
int abool;
buf = p = unescape_status_string (line);
if (buf)
{
for (p = strtok (buf, " "); p; p = strtok (NULL, " "))
{
p2 = strchr (p, '=');
if (p2)
{
*p2++ = 0;
abool = (*p2 == '1');
if (!strcmp (p, "ki"))
parm->extcap.ki = abool;
else if (!strcmp (p, "aac"))
parm->extcap.aac = abool;
else if (!strcmp (p, "bt"))
parm->extcap.bt = abool;
else if (!strcmp (p, "kdf"))
parm->extcap.kdf = abool;
else if (!strcmp (p, "si"))
parm->status_indicator = strtoul (p2, NULL, 10);
}
}
xfree (buf);
}
}
else if (!memcmp (keyword, "CA-FPR", keywordlen))
{
int no = atoi (line);
while (*line && !spacep (line))
line++;
while (spacep (line))
line++;
if (no == 1)
parm->cafpr1len = unhexify_fpr (line, parm->cafpr1,
sizeof parm->cafpr1);
else if (no == 2)
parm->cafpr2len = unhexify_fpr (line, parm->cafpr2,
sizeof parm->cafpr2);
else if (no == 3)
parm->cafpr3len = unhexify_fpr (line, parm->cafpr3,
sizeof parm->cafpr3);
}
break;
case 7:
if (!memcmp (keyword, "APPTYPE", keywordlen))
{
xfree (parm->apptypestr);
parm->apptypestr = unescape_status_string (line);
parm->apptype = map_apptypestr (parm->apptypestr);
}
else if (!memcmp (keyword, "KEY-FPR", keywordlen))
{
/* The format of such a line is:
* KEY-FPR <keyref> <fingerprintinhex>
*/
const char *fpr;
line_buffer = pline = xstrdup (line);
keyref = parse_keyref_helper (pline);
while (*pline && !spacep (pline))
pline++;
if (*pline)
*pline++ = 0; /* Terminate keyref. */
while (spacep (pline)) /* Skip to the fingerprint. */
pline++;
fpr = pline;
/* Check whether we already have an item for the keyref. */
kinfo = find_kinfo (parm, keyref);
if (!kinfo) /* No: new entry. */
kinfo = create_kinfo (parm, keyref);
else /* Existing entry - clear the fpr. */
memset (kinfo->fpr, 0, sizeof kinfo->fpr);
/* Set or update or the fingerprint. */
kinfo->fprlen = unhexify_fpr (fpr, kinfo->fpr, sizeof kinfo->fpr);
}
break;
case 8:
if (!memcmp (keyword, "SERIALNO", keywordlen))
{
xfree (parm->serialno);
parm->serialno = store_serialno (line);
parm->is_v2 = (strlen (parm->serialno) >= 16
&& xtoi_2 (parm->serialno+12) >= 2 );
}
else if (!memcmp (keyword, "CARDTYPE", keywordlen))
{
xfree (parm->cardtype);
parm->cardtype = unescape_status_string (line);
}
else if (!memcmp (keyword, "DISP-SEX", keywordlen))
{
parm->disp_sex = *line == '1'? 1 : *line == '2' ? 2: 0;
}
else if (!memcmp (keyword, "KEY-TIME", keywordlen))
{
/* The format of such a line is:
* KEY-TIME <keyref> <timestamp>
*/
const char *timestamp;
line_buffer = pline = xstrdup (line);
keyref = parse_keyref_helper (pline);
while (*pline && !spacep (pline))
pline++;
if (*pline)
*pline++ = 0; /* Terminate keyref. */
while (spacep (pline)) /* Skip to the timestamp. */
pline++;
timestamp = pline;
/* Check whether we already have an item for the keyref. */
kinfo = find_kinfo (parm, keyref);
if (!kinfo) /* No: new entry. */
kinfo = create_kinfo (parm, keyref);
kinfo->created = strtoul (timestamp, NULL, 10);
}
else if (!memcmp (keyword, "KEY-ATTR", keywordlen))
{
int keyno = 0;
int algo = GCRY_PK_RSA;
int n = 0;
sscanf (line, "%d %d %n", &keyno, &algo, &n);
keyno--;
if (keyno < 0 || keyno >= DIM (parm->key_attr))
; /* Out of range - ignore. */
else
{
parm->key_attr[keyno].algo = algo;
if (algo == PUBKEY_ALGO_RSA)
parm->key_attr[keyno].nbits = strtoul (line+n+3, NULL, 10);
else if (algo == PUBKEY_ALGO_ECDH || algo == PUBKEY_ALGO_ECDSA
|| algo == PUBKEY_ALGO_EDDSA)
{
parm->key_attr[keyno].curve =
openpgp_is_curve_supported (line + n, NULL, NULL);
}
}
}
break;
case 9:
if (!memcmp (keyword, "DISP-NAME", keywordlen))
{
xfree (parm->disp_name);
parm->disp_name = unescape_status_string (line);
}
else if (!memcmp (keyword, "DISP-LANG", keywordlen))
{
xfree (parm->disp_lang);
parm->disp_lang = unescape_status_string (line);
}
else if (!memcmp (keyword, "CHV-USAGE", keywordlen))
{
unsigned int byte1, byte2;
byte1 = byte2 = 0;
sscanf (line, "%x %x", &byte1, &byte2);
parm->chvusage[0] = byte1;
parm->chvusage[1] = byte2;
}
break;
case 10:
if (!memcmp (keyword, "PUBKEY-URL", keywordlen))
{
xfree (parm->pubkey_url);
parm->pubkey_url = unescape_status_string (line);
}
else if (!memcmp (keyword, "LOGIN-DATA", keywordlen))
{
xfree (parm->login_data);
parm->login_data = unescape_status_string (line);
}
else if (!memcmp (keyword, "CHV-STATUS", keywordlen))
{
char *p, *buf;
buf = p = unescape_status_string (line);
if (buf)
while (spacep (p))
p++;
if (!buf)
;
else if (parm->apptype == APP_TYPE_OPENPGP)
{
parm->chv1_cached = atoi (p);
while (*p && !spacep (p))
p++;
while (spacep (p))
p++;
for (i=0; *p && i < 3; i++)
{
parm->chvmaxlen[i] = atoi (p);
while (*p && !spacep (p))
p++;
while (spacep (p))
p++;
}
for (i=0; *p && i < 3; i++)
{
parm->chvinfo[i] = atoi (p);
while (*p && !spacep (p))
p++;
while (spacep (p))
p++;
}
}
else if (parm->apptype == APP_TYPE_PIV)
{
for (i=0; *p && i < DIM (parm->chvinfo); i++)
{
parm->chvinfo[i] = atoi (p);
while (*p && !spacep (p))
p++;
while (spacep (p))
p++;
}
}
xfree (buf);
}
else if (!memcmp (keyword, "APPVERSION", keywordlen))
{
unsigned int val = 0;
sscanf (line, "%x", &val);
parm->appversion = val;
}
break;
case 11:
if (!memcmp (keyword, "SIG-COUNTER", keywordlen))
{
parm->sig_counter = strtoul (line, NULL, 0);
}
else if (!memcmp (keyword, "KEYPAIRINFO", keywordlen))
{
/* The format of such a line is:
* KEYPAIRINFO <hexgrip> <keyref> [usage]
*/
char *hexgrp, *usage;
line_buffer = pline = xstrdup (line);
hexgrp = pline;
while (*pline && !spacep (pline))
pline++;
while (spacep (pline))
pline++;
keyref = pline;
while (*pline && !spacep (pline))
pline++;
if (*pline)
{
*pline++ = 0;
while (spacep (pline))
pline++;
usage = pline;
while (*pline && !spacep (pline))
pline++;
*pline = 0;
}
else
usage = "";
/* Check whether we already have an item for the keyref. */
kinfo = find_kinfo (parm, keyref);
if (!kinfo) /* New entry. */
kinfo = create_kinfo (parm, keyref);
else /* Existing entry - clear grip and usage */
{
memset (kinfo->grip, 0, sizeof kinfo->grip);
kinfo->usage = 0;
}
/* Set or update the grip. Note that due to the
* calloc/memset an erroneous too short grip will be nul
* padded on the right. */
unhexify_fpr (hexgrp, kinfo->grip, sizeof kinfo->grip);
/* Parse and set the usage. */
for (; *usage; usage++)
{
switch (*usage)
{
case 's': kinfo->usage |= GCRY_PK_USAGE_SIGN; break;
case 'c': kinfo->usage |= GCRY_PK_USAGE_CERT; break;
case 'a': kinfo->usage |= GCRY_PK_USAGE_AUTH; break;
case 'e': kinfo->usage |= GCRY_PK_USAGE_ENCR; break;
}
}
}
else if (!memcmp (keyword, "CARDVERSION", keywordlen))
{
unsigned int val = 0;
sscanf (line, "%x", &val);
parm->cardversion = val;
}
break;
case 12:
if (!memcmp (keyword, "PRIVATE-DO-", 11)
&& strchr("1234", keyword[11]))
{
int no = keyword[11] - '1';
log_assert (no >= 0 && no <= 3);
xfree (parm->private_do[no]);
parm->private_do[no] = unescape_status_string (line);
}
break;
case 13:
if (!memcmp (keyword, "$DISPSERIALNO", keywordlen))
{
xfree (parm->dispserialno);
parm->dispserialno = unescape_status_string (line);
}
break;
default:
/* Unknown. */
break;
}
xfree (line_buffer);
return 0;
}
/* Call the scdaemon to learn about a smartcard. This fills INFO
- * wioth data from the card. */
+ * with data from the card. */
gpg_error_t
scd_learn (card_info_t info)
{
gpg_error_t err;
struct default_inq_parm_s parm;
struct card_info_s dummyinfo;
if (!info)
info = &dummyinfo;
memset (info, 0, sizeof *info);
memset (&parm, 0, sizeof parm);
err = start_agent (0);
if (err)
return err;
parm.ctx = agent_ctx;
err = assuan_transact (agent_ctx, "SCD LEARN --force",
dummy_data_cb, NULL, default_inq_cb, &parm,
learn_status_cb, info);
/* Also try to get some other key attributes. */
if (!err)
{
info->initialized = 1;
err = scd_getattr ("KEY-ATTR", info);
if (gpg_err_code (err) == GPG_ERR_INV_NAME
|| gpg_err_code (err) == GPG_ERR_UNSUPPORTED_OPERATION)
err = 0; /* Not implemented or GETATTR not supported. */
err = scd_getattr ("$DISPSERIALNO", info);
if (gpg_err_code (err) == GPG_ERR_INV_NAME
|| gpg_err_code (err) == GPG_ERR_UNSUPPORTED_OPERATION)
err = 0; /* Not implemented or GETATTR not supported. */
}
if (info == &dummyinfo)
release_card_info (info);
return err;
}
/* Call the agent to retrieve a data object. This function returns
* the data in the same structure as used by the learn command. It is
* allowed to update such a structure using this command. */
gpg_error_t
scd_getattr (const char *name, struct card_info_s *info)
{
gpg_error_t err;
char line[ASSUAN_LINELENGTH];
struct default_inq_parm_s parm;
memset (&parm, 0, sizeof parm);
if (!*name)
return gpg_error (GPG_ERR_INV_VALUE);
/* We assume that NAME does not need escaping. */
if (12 + strlen (name) > DIM(line)-1)
return gpg_error (GPG_ERR_TOO_LARGE);
stpcpy (stpcpy (line, "SCD GETATTR "), name);
err = start_agent (0);
if (err)
return err;
parm.ctx = agent_ctx;
err = assuan_transact (agent_ctx, line, NULL, NULL, default_inq_cb, &parm,
learn_status_cb, info);
return err;
}
/* Send an setattr command to the SCdaemon. */
gpg_error_t
scd_setattr (const char *name,
const unsigned char *value, size_t valuelen)
{
gpg_error_t err;
char *tmp;
char *line = NULL;
struct default_inq_parm_s parm;
if (!*name || !valuelen)
return gpg_error (GPG_ERR_INV_VALUE);
tmp = strconcat ("SCD SETATTR ", name, " ", NULL);
if (!tmp)
{
err = gpg_error_from_syserror ();
goto leave;
}
line = percent_data_escape (1, tmp, value, valuelen);
xfree (tmp);
if (!line)
{
err = gpg_error_from_syserror ();
goto leave;
}
if (strlen (line) + 10 > ASSUAN_LINELENGTH)
{
err = gpg_error (GPG_ERR_TOO_LARGE);
goto leave;
}
err = start_agent (0);
if (err )
goto leave;
memset (&parm, 0, sizeof parm);
parm.ctx = agent_ctx;
err = assuan_transact (agent_ctx, line, NULL, NULL,
default_inq_cb, &parm, NULL, NULL);
leave:
xfree (line);
return status_sc_op_failure (err);
}
/* Handle a CERTDATA inquiry. Note, we only send the data,
* assuan_transact takes care of flushing and writing the END
* command. */
static gpg_error_t
inq_writecert_parms (void *opaque, const char *line)
{
gpg_error_t err;
struct writecert_parm_s *parm = opaque;
if (has_leading_keyword (line, "CERTDATA"))
{
err = assuan_send_data (parm->dflt->ctx,
parm->certdata, parm->certdatalen);
}
else
err = default_inq_cb (parm->dflt, line);
return err;
}
/* Send a WRITECERT command to the SCdaemon. */
gpg_error_t
scd_writecert (const char *certidstr,
const unsigned char *certdata, size_t certdatalen)
{
gpg_error_t err;
char line[ASSUAN_LINELENGTH];
struct writecert_parm_s parms;
struct default_inq_parm_s dfltparm;
memset (&dfltparm, 0, sizeof dfltparm);
err = start_agent (0);
if (err)
return err;
memset (&parms, 0, sizeof parms);
snprintf (line, sizeof line, "SCD WRITECERT %s", certidstr);
dfltparm.ctx = agent_ctx;
parms.dflt = &dfltparm;
parms.certdata = certdata;
parms.certdatalen = certdatalen;
err = assuan_transact (agent_ctx, line, NULL, NULL,
inq_writecert_parms, &parms, NULL, NULL);
return status_sc_op_failure (err);
}
/* Send a WRITEKEY command to the agent (so that the agent can fetch
* the key to write). KEYGRIP is the hexified keygrip of the source
* key which will be written to tye slot KEYREF. FORCE must be true
* to overwrite an existing key. */
gpg_error_t
scd_writekey (const char *keyref, int force, const char *keygrip)
{
gpg_error_t err;
struct default_inq_parm_s parm;
char line[ASSUAN_LINELENGTH];
memset (&parm, 0, sizeof parm);
err = start_agent (0);
if (err)
return err;
/* Note: We don't send the s/n but "-" because gpg-agent has
* currently no use for it. */
/* FIXME: For OpenPGP we should provide the creation time. */
snprintf (line, sizeof line, "KEYTOCARD%s %s - %s",
force? " --force":"", keygrip, keyref);
err = assuan_transact (agent_ctx, line, NULL, NULL,
default_inq_cb, &parm, NULL, NULL);
return status_sc_op_failure (err);
}
/* Status callback for the SCD GENKEY command. */
static gpg_error_t
scd_genkey_cb (void *opaque, const char *line)
{
u32 *createtime = opaque;
const char *keyword = line;
int keywordlen;
for (keywordlen=0; *line && !spacep (line); line++, keywordlen++)
;
while (spacep (line))
line++;
if (keywordlen == 14 && !memcmp (keyword,"KEY-CREATED-AT", keywordlen))
{
if (createtime)
*createtime = (u32)strtoul (line, NULL, 10);
}
else if (keywordlen == 8 && !memcmp (keyword, "PROGRESS", keywordlen))
{
gnupg_status_printf (STATUS_PROGRESS, "%s", line);
}
return 0;
}
/* Send a GENKEY command to the SCdaemon. If *CREATETIME is not 0,
* the value will be passed to SCDAEMON with --timestamp option so that
* the key is created with this. Otherwise, timestamp was generated by
* SCDEAMON. On success, creation time is stored back to
* CREATETIME. */
gpg_error_t
scd_genkey (const char *keyref, int force, const char *algo, u32 *createtime)
{
gpg_error_t err;
char line[ASSUAN_LINELENGTH];
gnupg_isotime_t tbuf;
struct default_inq_parm_s dfltparm;
memset (&dfltparm, 0, sizeof dfltparm);
err = start_agent (0);
if (err)
return err;
if (createtime && *createtime)
epoch2isotime (tbuf, *createtime);
else
*tbuf = 0;
snprintf (line, sizeof line, "SCD GENKEY %s%s %s %s%s -- %s",
*tbuf? "--timestamp=":"", tbuf,
force? "--force":"",
algo? "--algo=":"",
algo? algo:"",
keyref);
dfltparm.ctx = agent_ctx;
err = assuan_transact (agent_ctx, line,
NULL, NULL, default_inq_cb, &dfltparm,
scd_genkey_cb, createtime);
return status_sc_op_failure (err);
}
/* Return the serial number of the card or an appropriate error. The
* serial number is returned as a hexstring. If DEMAND is not NULL
- * the reader with the a card of the serilanumber DEMAND is
+ * the reader with the a card of the serial number DEMAND is
* requested. */
gpg_error_t
scd_serialno (char **r_serialno, const char *demand)
{
int err;
char *serialno = NULL;
char line[ASSUAN_LINELENGTH];
err = start_agent (START_AGENT_SUPPRESS_ERRORS);
if (err)
return err;
if (!demand)
strcpy (line, "SCD SERIALNO");
else
snprintf (line, DIM(line), "SCD SERIALNO --demand=%s", demand);
err = assuan_transact (agent_ctx, line,
NULL, NULL, NULL, NULL,
get_serialno_cb, &serialno);
if (err)
{
xfree (serialno);
return err;
}
- *r_serialno = serialno;
+ if (r_serialno)
+ *r_serialno = serialno;
+ else
+ xfree (serialno);
return 0;
}
/* Send a READCERT command to the SCdaemon. */
gpg_error_t
scd_readcert (const char *certidstr, void **r_buf, size_t *r_buflen)
{
gpg_error_t err;
char line[ASSUAN_LINELENGTH];
membuf_t data;
size_t len;
struct default_inq_parm_s dfltparm;
memset (&dfltparm, 0, sizeof dfltparm);
*r_buf = NULL;
err = start_agent (0);
if (err)
return err;
dfltparm.ctx = agent_ctx;
init_membuf (&data, 2048);
snprintf (line, sizeof line, "SCD READCERT %s", certidstr);
err = assuan_transact (agent_ctx, line,
put_membuf_cb, &data,
default_inq_cb, &dfltparm,
NULL, NULL);
if (err)
{
xfree (get_membuf (&data, &len));
return err;
}
*r_buf = get_membuf (&data, r_buflen);
if (!*r_buf)
return gpg_error_from_syserror ();
return 0;
}
/* Send a READKEY command to the SCdaemon. On success a new
* s-expression is stored at R_RESULT. */
gpg_error_t
scd_readkey (const char *keyrefstr, gcry_sexp_t *r_result)
{
gpg_error_t err;
char line[ASSUAN_LINELENGTH];
membuf_t data;
unsigned char *buf;
size_t len, buflen;
*r_result = NULL;
err = start_agent (0);
if (err)
return err;
init_membuf (&data, 1024);
snprintf (line, DIM(line), "SCD READKEY %s", keyrefstr);
err = assuan_transact (agent_ctx, line,
put_membuf_cb, &data,
NULL, NULL,
NULL, NULL);
if (err)
{
xfree (get_membuf (&data, &len));
return err;
}
buf = get_membuf (&data, &buflen);
if (!buf)
return gpg_error_from_syserror ();
err = gcry_sexp_new (r_result, buf, buflen, 0);
xfree (buf);
return err;
}
/* Callback function for card_cardlist. */
static gpg_error_t
card_cardlist_cb (void *opaque, const char *line)
{
struct card_cardlist_parm_s *parm = opaque;
const char *keyword = line;
int keywordlen;
for (keywordlen=0; *line && !spacep (line); line++, keywordlen++)
;
while (spacep (line))
line++;
if (keywordlen == 8 && !memcmp (keyword, "SERIALNO", keywordlen))
{
const char *s;
int n;
for (n=0,s=line; hexdigitp (s); s++, n++)
;
if (!n || (n&1) || *s)
parm->error = gpg_error (GPG_ERR_ASS_PARAMETER);
else
add_to_strlist (&parm->list, line);
}
return 0;
}
/* Return the serial numbers of all cards currently inserted. */
gpg_error_t
scd_cardlist (strlist_t *result)
{
gpg_error_t err;
struct card_cardlist_parm_s parm;
memset (&parm, 0, sizeof parm);
*result = NULL;
err = start_agent (START_AGENT_SUPPRESS_ERRORS);
if (err)
return err;
err = assuan_transact (agent_ctx, "SCD GETINFO card_list",
NULL, NULL, NULL, NULL,
card_cardlist_cb, &parm);
if (!err && parm.error)
err = parm.error;
if (!err)
*result = parm.list;
else
free_strlist (parm.list);
return err;
}
/* Change the PIN of an OpenPGP card or reset the retry counter.
* CHVNO 1: Change the PIN
* 2: For v1 cards: Same as 1.
* For v2 cards: Reset the PIN using the Reset Code.
* 3: Change the admin PIN
* 101: Set a new PIN and reset the retry counter
* 102: For v1 cars: Same as 101.
* For v2 cards: Set a new Reset Code.
*/
gpg_error_t
scd_change_pin (const char *pinref, int reset_mode)
{
gpg_error_t err;
char line[ASSUAN_LINELENGTH];
struct default_inq_parm_s dfltparm;
memset (&dfltparm, 0, sizeof dfltparm);
err = start_agent (0);
if (err)
return err;
dfltparm.ctx = agent_ctx;
snprintf (line, sizeof line, "SCD PASSWD%s %s",
reset_mode? " --reset":"", pinref);
err = assuan_transact (agent_ctx, line,
NULL, NULL,
default_inq_cb, &dfltparm,
NULL, NULL);
return status_sc_op_failure (err);
}
/* Perform a CHECKPIN operation. SERIALNO should be the serial
* number of the card - optionally followed by the fingerprint;
* however the fingerprint is ignored here. */
gpg_error_t
scd_checkpin (const char *serialno)
{
gpg_error_t err;
char line[ASSUAN_LINELENGTH];
struct default_inq_parm_s dfltparm;
memset (&dfltparm, 0, sizeof dfltparm);
err = start_agent (0);
if (err)
return err;
dfltparm.ctx = agent_ctx;
snprintf (line, sizeof line, "SCD CHECKPIN %s", serialno);
err = assuan_transact (agent_ctx, line,
NULL, NULL,
default_inq_cb, &dfltparm,
NULL, NULL);
return status_sc_op_failure (err);
}
/* Return the S2K iteration count as computed by gpg-agent. On error
* print a warning and return a default value. */
unsigned long
agent_get_s2k_count (void)
{
gpg_error_t err;
membuf_t data;
char *buf;
unsigned long count = 0;
err = start_agent (0);
if (err)
goto leave;
init_membuf (&data, 32);
err = assuan_transact (agent_ctx, "GETINFO s2k_count",
put_membuf_cb, &data,
NULL, NULL, NULL, NULL);
if (err)
xfree (get_membuf (&data, NULL));
else
{
put_membuf (&data, "", 1);
buf = get_membuf (&data, NULL);
if (!buf)
err = gpg_error_from_syserror ();
else
{
count = strtoul (buf, NULL, 10);
xfree (buf);
}
}
leave:
if (err || count < 65536)
{
/* Don't print an error if an older agent is used. */
if (err && gpg_err_code (err) != GPG_ERR_ASS_PARAMETER)
log_error (_("problem with the agent: %s\n"), gpg_strerror (err));
/* Default to 65536 which was used up to 2.0.13. */
count = 65536;
}
return count;
}
diff --git a/tools/card-yubikey.c b/tools/card-yubikey.c
index f9d130988..fff669cc0 100644
--- a/tools/card-yubikey.c
+++ b/tools/card-yubikey.c
@@ -1,438 +1,446 @@
/* card-yubikey.c - Yubikey specific functions.
* Copyright (C) 2019 g10 Code GmbH
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include "../common/util.h"
#include "../common/i18n.h"
#include "../common/tlv.h"
#include "../common/ttyio.h"
#include "gpg-card.h"
/* Object to describe requested interface options. */
struct iface_s {
unsigned int usb:1;
unsigned int nfc:1;
};
/* Bit flags as used by the fields in struct ykapps_s. */
#define YKAPP_USB_SUPPORTED 0x01
#define YKAPP_USB_ENABLED 0x02
#define YKAPP_NFC_SUPPORTED 0x04
#define YKAPP_NFC_ENABLED 0x08
#define YKAPP_SELECTED 0x80 /* Selected by the command. */
/* An object to describe the applications on a Yubikey. Each field
* has 8 bits to hold the above flag values. */
struct ykapps_s {
unsigned int otp:8;
unsigned int u2f:8;
unsigned int opgp:8;
unsigned int piv:8;
unsigned int oath:8;
unsigned int fido2:8;
};
/* Helper to parse an unsigned integer config value consisting of bit
* flags. TAG select the config item and MASK is the mask ORed into
* the value for a set bit. The function modifies YK. */
static gpg_error_t
parse_ul_config_value (struct ykapps_s *yk,
const unsigned char *config, size_t configlen,
int tag, unsigned int mask)
{
const unsigned char *s;
size_t n;
unsigned long ul = 0;
int i;
s = find_tlv (config, configlen, tag, &n);
if (s && n)
{
if (n > sizeof ul)
{
log_error ("too large integer in Yubikey config tag %02x detected\n",
tag);
if (opt.verbose)
log_printhex (config, configlen, "config:");
return gpg_error (GPG_ERR_CARD);
}
for (i=0; i < n; i++)
{
ul <<=8;
ul |= s[i];
}
if (ul & 0x01)
yk->otp |= mask;
if (ul & 0x02)
yk->u2f |= mask;
if (ul & 0x08)
yk->opgp |= mask;
if (ul & 0x10)
yk->piv |= mask;
if (ul & 0x20)
yk->oath |= mask;
if (ul & 0x200)
yk->fido2 |= mask;
}
return 0;
}
/* Create an unsigned integer config value for TAG from the data in YK
* and store it the provided 4 byte buffer RESULT. If ENABLE is true
* the respective APP_SELECTED bit in YK sets the corresponding bit
* flags, it is is false that bit flag is cleared. IF APP_SELECTED is
* not set the bit flag is not changed. */
static void
set_ul_config_value (struct ykapps_s *yk,
unsigned int bitflag, int tag, unsigned int enable,
unsigned char *result)
{
unsigned long ul = 0;
/* First set the current values. */
if ((yk->otp & bitflag))
ul |= 0x01;
if ((yk->u2f & bitflag))
ul |= 0x02;
if ((yk->opgp & bitflag))
ul |= 0x08;
if ((yk->piv & bitflag))
ul |= 0x10;
if ((yk->oath & bitflag))
ul |= 0x20;
if ((yk->fido2 & bitflag))
ul |= 0x200;
/* Then enable or disable the bits according to the selection flag. */
if (enable)
{
if ((yk->otp & YKAPP_SELECTED))
ul |= 0x01;
if ((yk->u2f & YKAPP_SELECTED))
ul |= 0x02;
if ((yk->opgp & YKAPP_SELECTED))
ul |= 0x08;
if ((yk->piv & YKAPP_SELECTED))
ul |= 0x10;
if ((yk->oath & YKAPP_SELECTED))
ul |= 0x20;
if ((yk->fido2 & YKAPP_SELECTED))
ul |= 0x200;
}
else
{
if ((yk->otp & YKAPP_SELECTED))
ul &= ~0x01;
if ((yk->u2f & YKAPP_SELECTED))
ul &= ~0x02;
if ((yk->opgp & YKAPP_SELECTED))
ul &= ~0x08;
if ((yk->piv & YKAPP_SELECTED))
ul &= ~0x10;
if ((yk->oath & YKAPP_SELECTED))
ul &= ~0x20;
if ((yk->fido2 & YKAPP_SELECTED))
ul &= ~0x200;
}
/* Make sure that we do not disable the CCID transport. Without
* CCID we won't have any way to change the configuration again. We
* would instead need one of the other Yubikey tools to enable an
* application and thus its transport again. */
if (bitflag == YKAPP_USB_ENABLED && !(ul & (0x08|0x10|0x20)))
{
log_info ("Enabling PIV to have at least one CCID transport\n");
ul |= 0x10;
}
result[0] = tag;
result[1] = 2;
result[2] = ul >> 8;
result[3] = ul;
}
/* Print the info from YK. */
static void
yk_list (estream_t fp, struct ykapps_s *yk)
{
if (opt.interactive)
tty_fprintf (fp, ("Application USB NFC\n"
"-----------------------\n"));
tty_fprintf (fp, "OTP %s %s\n",
(yk->otp & YKAPP_USB_SUPPORTED)?
(yk->otp & YKAPP_USB_ENABLED? "yes" : "no ") : "- ",
(yk->otp & YKAPP_NFC_SUPPORTED)?
(yk->otp & YKAPP_NFC_ENABLED? "yes" : "no ") : "- ");
tty_fprintf (fp, "U2F %s %s\n",
(yk->otp & YKAPP_USB_SUPPORTED)?
(yk->otp & YKAPP_USB_ENABLED? "yes" : "no ") : "- ",
(yk->otp & YKAPP_NFC_SUPPORTED)?
(yk->otp & YKAPP_NFC_ENABLED? "yes" : "no ") : "- ");
tty_fprintf (fp, "OPGP %s %s\n",
(yk->opgp & YKAPP_USB_SUPPORTED)?
(yk->opgp & YKAPP_USB_ENABLED? "yes" : "no ") : "- ",
(yk->opgp & YKAPP_NFC_SUPPORTED)?
(yk->opgp & YKAPP_NFC_ENABLED? "yes" : "no ") : "- ");
tty_fprintf (fp, "PIV %s %s\n",
(yk->piv & YKAPP_USB_SUPPORTED)?
(yk->piv & YKAPP_USB_ENABLED? "yes" : "no ") : "- ",
(yk->piv & YKAPP_NFC_SUPPORTED)?
(yk->piv & YKAPP_NFC_ENABLED? "yes" : "no ") : "- ");
tty_fprintf (fp, "OATH %s %s\n",
(yk->oath & YKAPP_USB_SUPPORTED)?
(yk->oath & YKAPP_USB_ENABLED? "yes" : "no ") : "- ",
(yk->oath & YKAPP_NFC_SUPPORTED)?
(yk->oath & YKAPP_NFC_ENABLED? "yes" : "no ") : "- ");
tty_fprintf (fp, "FIDO2 %s %s\n",
(yk->fido2 & YKAPP_USB_SUPPORTED)?
(yk->fido2 & YKAPP_USB_ENABLED? "yes" : "no ") : "- ",
(yk->fido2 & YKAPP_NFC_SUPPORTED)?
(yk->fido2 & YKAPP_NFC_ENABLED? "yes" : "no ") : "- ");
}
/* Enable disable the apps as marked in YK with flag YKAPP_SELECTED. */
static gpg_error_t
yk_enable_disable (struct ykapps_s *yk, struct iface_s *iface,
const unsigned char *config, size_t configlen, int enable)
{
gpg_error_t err = 0;
unsigned char apdu[100];
unsigned int apdulen;
/* const unsigned char *s; */
/* size_t n; */
char *hexapdu = NULL;
apdulen = 0;
apdu[apdulen++] = 0x00;
apdu[apdulen++] = 0x1c; /* Write Config instruction. */
apdu[apdulen++] = 0x00;
apdu[apdulen++] = 0x00;
apdu[apdulen++] = 0x00; /* Lc will be fixed up later. */
apdu[apdulen++] = 0x00; /* Length of data will also be fixed up later. */
/* The ykman tool has no way to set NFC and USB flags in one go.
* Reasoning about the Yubikey's firmware it seems plausible that
* combining should work. Let's try it here if the user called for
* setting both interfaces. */
if (iface->nfc)
{
set_ul_config_value (yk, YKAPP_NFC_ENABLED, 0x0e, enable, apdu+apdulen);
apdulen += 4;
}
if (iface->usb)
{
set_ul_config_value (yk, YKAPP_USB_ENABLED, 0x03, enable, apdu+apdulen);
apdulen += 4;
/* Yubikey's ykman copies parts of the config data when writing
* the config for USB. Below is a commented example on how that
* can be done. */
(void)config;
(void)configlen;
/* Copy the device flags. */
/* s = find_tlv (config, configlen, 0x08, &n); */
/* if (s && n) */
/* { */
/* s -= 2; */
/* n += 2; */
/* if (apdulen + n > sizeof apdu) */
/* { */
/* err = gpg_error (GPG_ERR_BUFFER_TOO_SHORT); */
/* goto leave; */
/* } */
/* memcpy (apdu+apdulen, s, n); */
/* apdulen += n; */
/* } */
}
if (iface->nfc || iface->usb)
{
if (apdulen + 2 > sizeof apdu)
{
err = gpg_error (GPG_ERR_BUFFER_TOO_SHORT);
goto leave;
}
/* Disable the next two lines to let the card reboot. Not doing
* this is however more convenient for this tool because further
* commands don't end up with an error. It seems to be better
* that a "reset" command from gpg-card-tool is run at the
* user's discretion. */
/* apdu[apdulen++] = 0x0c; /\* Reboot tag *\/ */
/* apdu[apdulen++] = 0; /\* No data for reboot. *\/ */
/* Fixup the lngth bytes. */
apdu[4] = apdulen - 6 + 1;
apdu[5] = apdulen - 6;
hexapdu = bin2hex (apdu, apdulen, NULL);
if (!hexapdu)
err = gpg_error_from_syserror ();
else
err = send_apdu (hexapdu, "YK.write_config", 0, NULL, NULL);
}
leave:
xfree (hexapdu);
return err;
}
/* Implementation part of cmd_yubikey. ARGV is an array of size ARGc
* with the argumets given to the yubikey command. Note that ARGV has
* no terminating NULL so that ARGC must be considred. FP is the
* stream to output information. This function must only be called on
* Yubikeys. */
gpg_error_t
-yubikey_commands (estream_t fp, int argc, char *argv[])
+yubikey_commands (card_info_t info, estream_t fp, int argc, char *argv[])
{
gpg_error_t err;
enum {ykLIST, ykENABLE, ykDISABLE } cmd;
struct iface_s iface = {0,0};
struct ykapps_s ykapps = {0};
unsigned char *config = NULL;
size_t configlen;
int i;
if (!argc)
return gpg_error (GPG_ERR_SYNTAX);
/* Parse command. */
if (!ascii_strcasecmp (argv[0], "list"))
cmd = ykLIST;
else if (!ascii_strcasecmp (argv[0], "enable"))
cmd = ykENABLE;
else if (!ascii_strcasecmp (argv[0], "disable"))
cmd = ykDISABLE;
else
{
err = gpg_error (GPG_ERR_UNKNOWN_COMMAND);
goto leave;
}
+ if (info->cardversion < 0x050000 && cmd != ykLIST)
+ {
+ log_info ("Sub-command '%s' is only support by Yubikey-5 and later\n",
+ argv[0]);
+ err = gpg_error (GPG_ERR_NOT_SUPPORTED);
+ goto leave;
+ }
+
/* Parse interface if needed. */
if (cmd == ykLIST)
iface.usb = iface.nfc = 1;
else if (argc < 2)
{
err = gpg_error (GPG_ERR_SYNTAX);
goto leave;
}
else if (!ascii_strcasecmp (argv[1], "usb"))
iface.usb = 1;
else if (!ascii_strcasecmp (argv[1], "nfc"))
iface.nfc = 1;
else if (!ascii_strcasecmp (argv[1], "all") || !strcmp (argv[1], "*"))
iface.usb = iface.nfc = 1;
else
{
err = gpg_error (GPG_ERR_SYNTAX);
goto leave;
}
/* Parse list of applications. */
for (i=2; i < argc; i++)
{
if (!ascii_strcasecmp (argv[i], "otp"))
ykapps.otp = 0x80;
else if (!ascii_strcasecmp (argv[i], "u2f"))
ykapps.u2f = 0x80;
else if (!ascii_strcasecmp (argv[i], "opgp")
||!ascii_strcasecmp (argv[i], "openpgp"))
ykapps.opgp = 0x80;
else if (!ascii_strcasecmp (argv[i], "piv"))
ykapps.piv = 0x80;
else if (!ascii_strcasecmp (argv[i], "oath")
|| !ascii_strcasecmp (argv[i], "oauth"))
ykapps.oath = 0x80;
else if (!ascii_strcasecmp (argv[i], "fido2"))
ykapps.fido2 = 0x80;
else if (!ascii_strcasecmp (argv[i], "all")|| !strcmp (argv[i], "*"))
{
ykapps.otp = ykapps.u2f = ykapps.opgp = ykapps.piv = ykapps.oath
= ykapps.fido2 = 0x80;
}
else
{
err = gpg_error (GPG_ERR_SYNTAX);
goto leave;
}
}
/* Select the Yubikey Manager application. */
err = send_apdu ("00A4040008a000000527471117", "Select.YK-Manager", 0,
NULL, NULL);
if (err)
goto leave;
/* Send the read config command. */
err = send_apdu ("001D000000", "YK.read_config", 0, &config, &configlen);
if (err)
goto leave;
if (!configlen || *config > configlen - 1)
{
/* The length byte is shorter than the actual length. */
log_error ("Yubikey returned improper config data\n");
log_printhex (config, configlen, "config:");
err = gpg_error (GPG_ERR_CARD);
goto leave;
}
if (configlen-1 > *config)
{
log_info ("Extra config data ignored\n");
log_printhex (config, configlen, "config:");
}
configlen = *config;
err = parse_ul_config_value (&ykapps, config+1, configlen,
0x01, YKAPP_USB_SUPPORTED);
if (!err)
err = parse_ul_config_value (&ykapps, config+1, configlen,
0x03, YKAPP_USB_ENABLED);
if (!err)
err = parse_ul_config_value (&ykapps, config+1, configlen,
0x0d, YKAPP_NFC_SUPPORTED);
if (!err)
err = parse_ul_config_value (&ykapps, config+1, configlen,
0x0e, YKAPP_NFC_ENABLED);
if (err)
goto leave;
switch (cmd)
{
case ykLIST: yk_list (fp, &ykapps); break;
case ykENABLE: err = yk_enable_disable (&ykapps, &iface,
config+1, configlen, 1); break;
case ykDISABLE: err = yk_enable_disable (&ykapps, &iface,
config+1, configlen, 0); break;
}
leave:
xfree (config);
return err;
}
diff --git a/tools/ccidmon.c b/tools/ccidmon.c
index d61bb3c64..4e99da54d 100644
--- a/tools/ccidmon.c
+++ b/tools/ccidmon.c
@@ -1,882 +1,897 @@
/* ccidmon.c - CCID monitor for use with the Linux usbmon facility.
* Copyright (C) 2009 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
/* This utility takes the output of usbmon, filters out the bulk data
and prints the CCID messages in a human friendly way.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <string.h>
#include <errno.h>
#include <stdarg.h>
#include <assert.h>
#include <unistd.h>
#include <signal.h>
#ifndef PACKAGE_VERSION
# define PACKAGE_VERSION "[build on " __DATE__ " " __TIME__ "]"
#endif
#ifndef PACKAGE_BUGREPORT
# define PACKAGE_BUGREPORT "devnull@example.org"
#endif
#define PGM "ccidmon"
#ifndef GNUPG_NAME
# define GNUPG_NAME "GnuPG"
#endif
/* Option flags. */
static int verbose;
static int debug;
static int skip_escape;
static int usb_bus, usb_dev;
static int sniffusb;
/* Error counter. */
static int any_error;
/* Data storage. */
struct
{
int is_bi;
+ char timestamp[20];
char address[50];
int count;
- char data[2000];
+ char data[16000];
} databuffer;
enum {
RDR_to_PC_NotifySlotChange= 0x50,
RDR_to_PC_HardwareError = 0x51,
PC_to_RDR_SetParameters = 0x61,
PC_to_RDR_IccPowerOn = 0x62,
PC_to_RDR_IccPowerOff = 0x63,
PC_to_RDR_GetSlotStatus = 0x65,
PC_to_RDR_Secure = 0x69,
PC_to_RDR_T0APDU = 0x6a,
PC_to_RDR_Escape = 0x6b,
PC_to_RDR_GetParameters = 0x6c,
PC_to_RDR_ResetParameters = 0x6d,
PC_to_RDR_IccClock = 0x6e,
PC_to_RDR_XfrBlock = 0x6f,
PC_to_RDR_Mechanical = 0x71,
PC_to_RDR_Abort = 0x72,
PC_to_RDR_SetDataRate = 0x73,
RDR_to_PC_DataBlock = 0x80,
RDR_to_PC_SlotStatus = 0x81,
RDR_to_PC_Parameters = 0x82,
RDR_to_PC_Escape = 0x83,
RDR_to_PC_DataRate = 0x84
};
#define digitp(p) (*(p) >= '0' && *(p) <= '9')
#define hexdigitp(a) (digitp (a) \
|| (*(a) >= 'A' && *(a) <= 'F') \
|| (*(a) >= 'a' && *(a) <= 'f'))
#define ascii_isspace(a) ((a)==' ' || (a)=='\n' || (a)=='\r' || (a)=='\t')
#define xtoi_1(p) ((p) <= '9'? ((p)- '0'): \
(p) <= 'F'? ((p)-'A'+10):((p)-'a'+10))
/* Print diagnostic message and exit with failure. */
static void
die (const char *format, ...)
{
va_list arg_ptr;
fflush (stdout);
fprintf (stderr, "%s: ", PGM);
va_start (arg_ptr, format);
vfprintf (stderr, format, arg_ptr);
va_end (arg_ptr);
putc ('\n', stderr);
exit (1);
}
/* Print diagnostic message. */
static void
err (const char *format, ...)
{
va_list arg_ptr;
any_error = 1;
fflush (stdout);
fprintf (stderr, "%s: ", PGM);
va_start (arg_ptr, format);
vfprintf (stderr, format, arg_ptr);
va_end (arg_ptr);
putc ('\n', stderr);
}
/* Convert a little endian stored 4 byte value into an unsigned
integer. */
static unsigned int
convert_le_u32 (const unsigned char *buf)
{
return buf[0] | (buf[1] << 8) | (buf[2] << 16) | ((unsigned int)buf[3] << 24);
}
/* Convert a little endian stored 2 byte value into an unsigned
integer. */
static unsigned int
convert_le_u16 (const unsigned char *buf)
{
return buf[0] | (buf[1] << 8);
}
static void
print_pr_data (const unsigned char *data, size_t datalen, size_t off)
{
int needlf = 0;
int first = 1;
for (; off < datalen; off++)
{
if (!(off % 16) || first)
{
if (needlf)
putchar ('\n');
printf (" [%04lu] ", (unsigned long)off);
}
printf (" %02X", data[off]);
needlf = 1;
first = 0;
}
if (needlf)
putchar ('\n');
}
static void
print_p2r_header (const char *name, const unsigned char *msg, size_t msglen)
{
printf ("%s:\n", name);
if (msglen < 7)
return;
printf (" dwLength ..........: %u\n", convert_le_u32 (msg+1));
printf (" bSlot .............: %u\n", msg[5]);
printf (" bSeq ..............: %u\n", msg[6]);
}
static void
print_p2r_iccpoweron (const unsigned char *msg, size_t msglen)
{
print_p2r_header ("PC_to_RDR_IccPowerOn", msg, msglen);
if (msglen < 10)
return;
printf (" bPowerSelect ......: 0x%02x (%s)\n", msg[7],
msg[7] == 0? "auto":
msg[7] == 1? "5.0 V":
msg[7] == 2? "3.0 V":
msg[7] == 3? "1.8 V":"");
print_pr_data (msg, msglen, 8);
}
static void
print_p2r_iccpoweroff (const unsigned char *msg, size_t msglen)
{
print_p2r_header ("PC_to_RDR_IccPowerOff", msg, msglen);
print_pr_data (msg, msglen, 7);
}
static void
print_p2r_getslotstatus (const unsigned char *msg, size_t msglen)
{
print_p2r_header ("PC_to_RDR_GetSlotStatus", msg, msglen);
print_pr_data (msg, msglen, 7);
}
static void
print_p2r_xfrblock (const unsigned char *msg, size_t msglen)
{
unsigned int val;
print_p2r_header ("PC_to_RDR_XfrBlock", msg, msglen);
if (msglen < 10)
return;
printf (" bBWI ..............: 0x%02x\n", msg[7]);
val = convert_le_u16 (msg+8);
printf (" wLevelParameter ...: 0x%04x%s\n", val,
val == 1? " (continued)":
val == 2? " (continues+ends)":
val == 3? " (continues+continued)":
val == 16? " (DataBlock-expected)":"");
print_pr_data (msg, msglen, 10);
}
static void
print_p2r_getparameters (const unsigned char *msg, size_t msglen)
{
print_p2r_header ("PC_to_RDR_GetParameters", msg, msglen);
print_pr_data (msg, msglen, 7);
}
static void
print_p2r_resetparameters (const unsigned char *msg, size_t msglen)
{
print_p2r_header ("PC_to_RDR_ResetParameters", msg, msglen);
print_pr_data (msg, msglen, 7);
}
static void
print_p2r_setparameters (const unsigned char *msg, size_t msglen)
{
print_p2r_header ("PC_to_RDR_SetParameters", msg, msglen);
if (msglen < 10)
return;
printf (" bProtocolNum ......: 0x%02x\n", msg[7]);
print_pr_data (msg, msglen, 8);
}
static void
print_p2r_escape (const unsigned char *msg, size_t msglen)
{
if (skip_escape)
return;
print_p2r_header ("PC_to_RDR_Escape", msg, msglen);
print_pr_data (msg, msglen, 7);
}
static void
print_p2r_iccclock (const unsigned char *msg, size_t msglen)
{
print_p2r_header ("PC_to_RDR_IccClock", msg, msglen);
if (msglen < 10)
return;
printf (" bClockCommand .....: 0x%02x\n", msg[7]);
print_pr_data (msg, msglen, 8);
}
static void
print_p2r_to0apdu (const unsigned char *msg, size_t msglen)
{
print_p2r_header ("PC_to_RDR_T0APDU", msg, msglen);
if (msglen < 10)
return;
printf (" bmChanges .........: 0x%02x\n", msg[7]);
printf (" bClassGetResponse .: 0x%02x\n", msg[8]);
printf (" bClassEnvelope ....: 0x%02x\n", msg[9]);
print_pr_data (msg, msglen, 10);
}
static void
print_p2r_secure (const unsigned char *msg, size_t msglen)
{
unsigned int val;
print_p2r_header ("PC_to_RDR_Secure", msg, msglen);
if (msglen < 10)
return;
printf (" bBMI ..............: 0x%02x\n", msg[7]);
val = convert_le_u16 (msg+8);
printf (" wLevelParameter ...: 0x%04x%s\n", val,
val == 1? " (continued)":
val == 2? " (continues+ends)":
val == 3? " (continues+continued)":
val == 16? " (DataBlock-expected)":"");
print_pr_data (msg, msglen, 10);
}
static void
print_p2r_mechanical (const unsigned char *msg, size_t msglen)
{
print_p2r_header ("PC_to_RDR_Mechanical", msg, msglen);
if (msglen < 10)
return;
printf (" bFunction .........: 0x%02x\n", msg[7]);
print_pr_data (msg, msglen, 8);
}
static void
print_p2r_abort (const unsigned char *msg, size_t msglen)
{
print_p2r_header ("PC_to_RDR_Abort", msg, msglen);
print_pr_data (msg, msglen, 7);
}
static void
print_p2r_setdatarate (const unsigned char *msg, size_t msglen)
{
print_p2r_header ("PC_to_RDR_SetDataRate", msg, msglen);
if (msglen < 10)
return;
print_pr_data (msg, msglen, 7);
}
static void
print_p2r_unknown (const unsigned char *msg, size_t msglen)
{
char buf[100];
snprintf (buf, sizeof buf, "Unknown PC_to_RDR command 0x%02X",
msglen? msg[0]:0);
print_p2r_header (buf, msg, msglen);
if (msglen < 10)
return;
print_pr_data (msg, msglen, 0);
}
static void
print_p2r (const unsigned char *msg, size_t msglen)
{
switch (msglen? msg[0]:0)
{
case PC_to_RDR_IccPowerOn:
print_p2r_iccpoweron (msg, msglen);
break;
case PC_to_RDR_IccPowerOff:
print_p2r_iccpoweroff (msg, msglen);
break;
case PC_to_RDR_GetSlotStatus:
print_p2r_getslotstatus (msg, msglen);
break;
case PC_to_RDR_XfrBlock:
print_p2r_xfrblock (msg, msglen);
break;
case PC_to_RDR_GetParameters:
print_p2r_getparameters (msg, msglen);
break;
case PC_to_RDR_ResetParameters:
print_p2r_resetparameters (msg, msglen);
break;
case PC_to_RDR_SetParameters:
print_p2r_setparameters (msg, msglen);
break;
case PC_to_RDR_Escape:
print_p2r_escape (msg, msglen);
break;
case PC_to_RDR_IccClock:
print_p2r_iccclock (msg, msglen);
break;
case PC_to_RDR_T0APDU:
print_p2r_to0apdu (msg, msglen);
break;
case PC_to_RDR_Secure:
print_p2r_secure (msg, msglen);
break;
case PC_to_RDR_Mechanical:
print_p2r_mechanical (msg, msglen);
break;
case PC_to_RDR_Abort:
print_p2r_abort (msg, msglen);
break;
case PC_to_RDR_SetDataRate:
print_p2r_setdatarate (msg, msglen);
break;
default:
print_p2r_unknown (msg, msglen);
break;
}
}
static void
print_r2p_header (const char *name, const unsigned char *msg, size_t msglen)
{
printf ("%s:\n", name);
if (msglen < 9)
return;
printf (" dwLength ..........: %u\n", convert_le_u32 (msg+1));
printf (" bSlot .............: %u\n", msg[5]);
printf (" bSeq ..............: %u\n", msg[6]);
printf (" bStatus ...........: %u\n", msg[7]);
if (msg[8])
printf (" bError ............: %u\n", msg[8]);
}
static void
print_r2p_datablock (const unsigned char *msg, size_t msglen)
{
print_r2p_header ("RDR_to_PC_DataBlock", msg, msglen);
if (msglen < 10)
return;
if (msg[9])
printf (" bChainParameter ...: 0x%02x%s\n", msg[9],
msg[9] == 1? " (continued)":
msg[9] == 2? " (continues+ends)":
msg[9] == 3? " (continues+continued)":
msg[9] == 16? " (XferBlock-expected)":"");
print_pr_data (msg, msglen, 10);
}
static void
print_r2p_slotstatus (const unsigned char *msg, size_t msglen)
{
print_r2p_header ("RDR_to_PC_SlotStatus", msg, msglen);
if (msglen < 10)
return;
printf (" bClockStatus ......: 0x%02x%s\n", msg[9],
msg[9] == 0? " (running)":
msg[9] == 1? " (stopped-L)":
msg[9] == 2? " (stopped-H)":
msg[9] == 3? " (stopped)":"");
print_pr_data (msg, msglen, 10);
}
static void
print_r2p_parameters (const unsigned char *msg, size_t msglen)
{
print_r2p_header ("RDR_to_PC_Parameters", msg, msglen);
if (msglen < 10)
return;
printf (" protocol ..........: T=%d\n", msg[9]);
if (msglen == 17 && msg[9] == 1)
{
/* Protocol T=1. */
printf (" bmFindexDindex ....: %02X\n", msg[10]);
printf (" bmTCCKST1 .........: %02X\n", msg[11]);
printf (" bGuardTimeT1 ......: %02X\n", msg[12]);
printf (" bmWaitingIntegersT1: %02X\n", msg[13]);
printf (" bClockStop ........: %02X\n", msg[14]);
printf (" bIFSC .............: %d\n", msg[15]);
printf (" bNadValue .........: %d\n", msg[16]);
}
else
print_pr_data (msg, msglen, 10);
}
static void
print_r2p_escape (const unsigned char *msg, size_t msglen)
{
if (skip_escape)
return;
print_r2p_header ("RDR_to_PC_Escape", msg, msglen);
if (msglen < 10)
return;
printf (" buffer[9] .........: %02X\n", msg[9]);
print_pr_data (msg, msglen, 10);
}
static void
print_r2p_datarate (const unsigned char *msg, size_t msglen)
{
print_r2p_header ("RDR_to_PC_DataRate", msg, msglen);
if (msglen < 10)
return;
if (msglen >= 18)
{
printf (" dwClockFrequency ..: %u\n", convert_le_u32 (msg+10));
printf (" dwDataRate ..... ..: %u\n", convert_le_u32 (msg+14));
print_pr_data (msg, msglen, 18);
}
else
print_pr_data (msg, msglen, 10);
}
static void
print_r2p_unknown (const unsigned char *msg, size_t msglen)
{
char buf[100];
snprintf (buf, sizeof buf, "Unknown RDR_to_PC command 0x%02X",
msglen? msg[0]:0);
print_r2p_header (buf, msg, msglen);
if (msglen < 10)
return;
printf (" bMessageType ......: %02X\n", msg[0]);
printf (" buffer[9] .........: %02X\n", msg[9]);
print_pr_data (msg, msglen, 10);
}
static void
print_r2p (const unsigned char *msg, size_t msglen)
{
switch (msglen? msg[0]:0)
{
case RDR_to_PC_DataBlock:
print_r2p_datablock (msg, msglen);
break;
case RDR_to_PC_SlotStatus:
print_r2p_slotstatus (msg, msglen);
break;
case RDR_to_PC_Parameters:
print_r2p_parameters (msg, msglen);
break;
case RDR_to_PC_Escape:
print_r2p_escape (msg, msglen);
break;
case RDR_to_PC_DataRate:
print_r2p_datarate (msg, msglen);
break;
default:
print_r2p_unknown (msg, msglen);
break;
}
}
static void
flush_data (void)
{
if (!databuffer.count)
return;
if (verbose)
- printf ("Address: %s\n", databuffer.address);
+ {
+ printf ("Timestamp: %s\n", databuffer.timestamp);
+ printf ("Address..: %s\n", databuffer.address);
+ }
if (databuffer.is_bi)
{
print_r2p (databuffer.data, databuffer.count);
if (verbose)
putchar ('\n');
}
else
print_p2r (databuffer.data, databuffer.count);
databuffer.count = 0;
}
static void
-collect_data (char *hexdata, const char *address, unsigned int lineno)
+collect_data (char *hexdata, const char *timestamp,
+ const char *address, unsigned int lineno)
{
size_t length;
int is_bi;
char *s;
unsigned int value;
is_bi = (*address && address[1] == 'i');
if (databuffer.is_bi != is_bi || strcmp (databuffer.address, address))
flush_data ();
databuffer.is_bi = is_bi;
+ if (strlen (timestamp) >= sizeof databuffer.timestamp)
+ die ("timestamp field too long");
+ strcpy (databuffer.timestamp, timestamp);
if (strlen (address) >= sizeof databuffer.address)
die ("address field too long");
strcpy (databuffer.address, address);
length = databuffer.count;
for (s=hexdata; *s; s++ )
{
if (ascii_isspace (*s))
continue;
if (!hexdigitp (s))
{
err ("invalid hex digit in line %u - line skipped", lineno);
break;
}
value = xtoi_1 (*s) * 16;
s++;
if (!hexdigitp (s))
{
err ("invalid hex digit in line %u - line skipped", lineno);
break;
}
value += xtoi_1 (*s);
if (length >= sizeof (databuffer.data))
{
- err ("too much data at line %u - can handle only up to % bytes",
+ err ("too much data at line %u - can handle only up to %zu bytes",
lineno, sizeof (databuffer.data));
break;
}
databuffer.data[length++] = value;
}
databuffer.count = length;
}
static void
parse_line (char *line, unsigned int lineno)
{
char *p;
- char *event_type, *address, *data, *status, *datatag;
+ char *timestamp, *event_type, *address, *data, *status, *datatag;
+
+ if (*line == '#' || !*line)
+ return;
if (debug)
printf ("line[%u] ='%s'\n", lineno, line);
p = strtok (line, " ");
if (!p)
- die ("invalid line %d (no URB)");
- p = strtok (NULL, " ");
- if (!p)
- die ("invalid line %d (no timestamp)");
+ die ("invalid line %d (no URB)", lineno);
+ timestamp = strtok (NULL, " ");
+ if (!timestamp)
+ die ("invalid line %d (no timestamp)", lineno);
event_type = strtok (NULL, " ");
if (!event_type)
- die ("invalid line %d (no event type)");
+ die ("invalid line %d (no event type)", lineno);
address = strtok (NULL, " ");
if (!address)
- die ("invalid line %d (no address");
+ die ("invalid line %d (no address", lineno);
if (usb_bus || usb_dev)
{
int bus, dev;
p = strchr (address, ':');
if (!p)
- die ("invalid line %d (invalid address");
+ die ("invalid line %d (invalid address", lineno);
p++;
bus = atoi (p);
p = strchr (p, ':');
if (!p)
- die ("invalid line %d (invalid address");
+ die ("invalid line %d (invalid address", lineno);
p++;
dev = atoi (p);
if ((usb_bus && usb_bus != bus) || (usb_dev && usb_dev != dev))
return; /* We don't want that one. */
}
- if (*address != 'B' || (address[1] != 'o' && address[1] != 'i'))
- return; /* We only want block in and block out. */
+ if (*address == 'B' && (address[1] == 'o' || address[1] == 'i'))
+ ; /* We want block ind and out. */
+ else if (*address == 'C' && (address[1] == 'o' || address[1] == 'i'))
+ ; /* We want control ind and out. */
+ else
+ return; /* But nothing else. */
status = strtok (NULL, " ");
if (!status)
return;
if (!strchr ("-0123456789", *status))
return; /* Setup packet. */
/* We don't support "Z[io]" types thus we don't need to check here. */
p = strtok (NULL, " ");
if (!p)
return; /* No data length. */
datatag = strtok (NULL, " ");
if (datatag && *datatag == '=')
{
data = strtok (NULL, "");
- collect_data (data?data:"", address, lineno);
+ collect_data (data?data:"", timestamp, address, lineno);
}
}
static void
parse_line_sniffusb (char *line, unsigned int lineno)
{
char *p;
if (debug)
printf ("line[%u] ='%s'\n", lineno, line);
p = strtok (line, " \t");
if (!p)
return;
p = strtok (NULL, " \t");
if (!p)
return;
p = strtok (NULL, " \t");
if (!p)
return;
if (hexdigitp (p+0) && hexdigitp (p+1)
&& hexdigitp (p+2) && hexdigitp (p+3)
&& p[4] == ':' && !p[5])
{
size_t length;
unsigned int value;
length = databuffer.count;
while ((p=strtok (NULL, " \t")))
{
if (!hexdigitp (p+0) || !hexdigitp (p+1))
{
err ("invalid hex digit in line %u (%s)", lineno,p);
break;
}
value = xtoi_1 (p[0]) * 16 + xtoi_1 (p[1]);
if (length >= sizeof (databuffer.data))
{
err ("too much data at line %u - can handle only up to % bytes",
lineno, sizeof (databuffer.data));
break;
}
databuffer.data[length++] = value;
}
databuffer.count = length;
}
else if (!strcmp (p, "TransferFlags"))
{
flush_data ();
*databuffer.address = 0;
while ((p=strtok (NULL, " \t(,)")))
{
if (!strcmp (p, "USBD_TRANSFER_DIRECTION_IN"))
{
databuffer.is_bi = 1;
break;
}
else if (!strcmp (p, "USBD_TRANSFER_DIRECTION_OUT"))
{
databuffer.is_bi = 0;
break;
}
}
}
}
static void
parse_input (FILE *fp)
{
char line[2000];
size_t length;
unsigned int lineno = 0;
while (fgets (line, sizeof (line), fp))
{
lineno++;
length = strlen (line);
if (length && line[length - 1] == '\n')
line[--length] = 0;
else
err ("line number %u too long or last line not terminated", lineno);
if (length && line[length - 1] == '\r')
line[--length] = 0;
if (sniffusb)
parse_line_sniffusb (line, lineno);
else
parse_line (line, lineno);
}
flush_data ();
if (ferror (fp))
err ("error reading input at line %u: %s", lineno, strerror (errno));
}
int
main (int argc, char **argv)
{
int last_argc = -1;
if (argc)
{
argc--; argv++;
}
while (argc && last_argc != argc )
{
last_argc = argc;
if (!strcmp (*argv, "--"))
{
argc--; argv++;
break;
}
else if (!strcmp (*argv, "--version"))
{
fputs (PGM " (" GNUPG_NAME ") " PACKAGE_VERSION "\n", stdout);
exit (0);
}
else if (!strcmp (*argv, "--help"))
{
puts ("Usage: " PGM " [BUS:DEV]\n"
"Parse the output of usbmod assuming it is CCID compliant.\n\n"
" --skip-escape do not show escape packets\n"
" --sniffusb Assume output from Sniffusb.exe\n"
" --verbose enable extra informational output\n"
" --debug enable additional debug output\n"
" --help display this help and exit\n\n"
"Report bugs to " PACKAGE_BUGREPORT ".");
exit (0);
}
else if (!strcmp (*argv, "--verbose"))
{
verbose = 1;
argc--; argv++;
}
else if (!strcmp (*argv, "--debug"))
{
verbose = debug = 1;
argc--; argv++;
}
else if (!strcmp (*argv, "--skip-escape"))
{
skip_escape = 1;
argc--; argv++;
}
else if (!strcmp (*argv, "--sniffusb"))
{
sniffusb = 1;
argc--; argv++;
}
}
if (argc && sniffusb)
die ("no arguments expected when using --sniffusb\n");
else if (argc > 1)
die ("usage: " PGM " [BUS:DEV] (try --help for more information)\n");
if (argc == 1)
{
const char *s = strchr (argv[0], ':');
usb_bus = atoi (argv[0]);
if (s)
usb_dev = atoi (s+1);
if (usb_bus < 1 || usb_bus > 999 || usb_dev < 1 || usb_dev > 999)
die ("invalid bus:dev specified");
}
signal (SIGPIPE, SIG_IGN);
parse_input (stdin);
return any_error? 1:0;
}
/*
Local Variables:
compile-command: "gcc -Wall -Wno-pointer-sign -g -o ccidmon ccidmon.c"
End:
*/
diff --git a/tools/gpg-card.c b/tools/gpg-card.c
index e2d728dab..ddc4d12bf 100644
--- a/tools/gpg-card.c
+++ b/tools/gpg-card.c
@@ -1,3497 +1,3566 @@
/* gpg-card.c - An interactive tool to work with cards.
* Copyright (C) 2019 g10 Code GmbH
*
* This file is part of GnuPG.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This file 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://gnu.org/licenses/>.
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef HAVE_LIBREADLINE
# define GNUPG_LIBREADLINE_H_INCLUDED
# include <readline/readline.h>
#endif /*HAVE_LIBREADLINE*/
#include "../common/util.h"
#include "../common/status.h"
#include "../common/i18n.h"
#include "../common/init.h"
#include "../common/sysutils.h"
#include "../common/asshelp.h"
#include "../common/userids.h"
#include "../common/ccparray.h"
#include "../common/exectool.h"
#include "../common/ttyio.h"
#include "../common/server-help.h"
#include "../common/openpgpdefs.h"
#include "gpg-card.h"
#define CONTROL_D ('D' - 'A' + 1)
/* Constants to identify the commands and options. */
enum opt_values
{
aNull = 0,
oQuiet = 'q',
oVerbose = 'v',
oDebug = 500,
oGpgProgram,
oGpgsmProgram,
oStatusFD,
oWithColons,
oNoAutostart,
oAgentProgram,
oDisplay,
oTTYname,
oTTYtype,
oXauthority,
oLCctype,
oLCmessages,
oDummy
};
/* The list of commands and options. */
static ARGPARSE_OPTS opts[] = {
ARGPARSE_group (301, ("@\nOptions:\n ")),
ARGPARSE_s_n (oVerbose, "verbose", ("verbose")),
ARGPARSE_s_n (oQuiet, "quiet", ("be somewhat more quiet")),
ARGPARSE_s_s (oDebug, "debug", "@"),
ARGPARSE_s_s (oGpgProgram, "gpg", "@"),
ARGPARSE_s_s (oGpgsmProgram, "gpgsm", "@"),
ARGPARSE_s_i (oStatusFD, "status-fd", N_("|FD|write status info to this FD")),
ARGPARSE_s_n (oWithColons, "with-colons", "@"),
ARGPARSE_s_n (oNoAutostart, "no-autostart", "@"),
ARGPARSE_s_s (oAgentProgram, "agent-program", "@"),
ARGPARSE_s_s (oDisplay, "display", "@"),
ARGPARSE_s_s (oTTYname, "ttyname", "@"),
ARGPARSE_s_s (oTTYtype, "ttytype", "@"),
ARGPARSE_s_s (oXauthority, "xauthority", "@"),
ARGPARSE_s_s (oLCctype, "lc-ctype", "@"),
ARGPARSE_s_s (oLCmessages, "lc-messages","@"),
ARGPARSE_end ()
};
/* The list of supported debug flags. */
static struct debug_flags_s debug_flags [] =
{
{ DBG_IPC_VALUE , "ipc" },
{ DBG_EXTPROG_VALUE, "extprog" },
{ 0, NULL }
};
/* An object to create lists of labels and keyrefs. */
struct keyinfolabel_s
{
const char *label;
const char *keyref;
};
typedef struct keyinfolabel_s *keyinfolabel_t;
/* Limit of size of data we read from a file for certain commands. */
#define MAX_GET_DATA_FROM_FILE 16384
/* Constants for OpenPGP cards. */
#define OPENPGP_USER_PIN_DEFAULT "123456"
#define OPENPGP_ADMIN_PIN_DEFAULT "12345678"
#define OPENPGP_KDF_DATA_LENGTH_MIN 90
#define OPENPGP_KDF_DATA_LENGTH_MAX 110
/* Local prototypes. */
static gpg_error_t dispatch_command (card_info_t info, const char *command);
static void interactive_loop (void);
#ifdef HAVE_LIBREADLINE
static char **command_completion (const char *text, int start, int end);
#endif /*HAVE_LIBREADLINE*/
/* Print usage information and provide strings for help. */
static const char *
my_strusage( int level )
{
const char *p;
switch (level)
{
case 11: p = "gpg-card"; break;
case 12: p = "@GNUPG@"; break;
case 13: p = VERSION; break;
case 17: p = PRINTABLE_OS_NAME; break;
case 19: p = ("Please report bugs to <@EMAIL@>.\n"); break;
case 1:
case 40:
p = ("Usage: gpg-card"
" [options] [{[--] command [args]}] (-h for help)");
break;
case 41:
p = ("Syntax: gpg-card"
" [options] [command [args] {-- command [args]}]\n\n"
"Tool to manage cards and tokens. With a command an interactive\n"
"mode is used. Use command \"help\" to list all commands.");
break;
default: p = NULL; break;
}
return p;
}
static void
set_opt_session_env (const char *name, const char *value)
{
gpg_error_t err;
err = session_env_setenv (opt.session_env, name, value);
if (err)
log_fatal ("error setting session environment: %s\n",
gpg_strerror (err));
}
/* Command line parsing. */
static void
parse_arguments (ARGPARSE_ARGS *pargs, ARGPARSE_OPTS *popts)
{
while (optfile_parse (NULL, NULL, NULL, pargs, popts))
{
switch (pargs->r_opt)
{
case oQuiet: opt.quiet = 1; break;
case oVerbose: opt.verbose++; break;
case oDebug:
if (parse_debug_flag (pargs->r.ret_str, &opt.debug, debug_flags))
{
pargs->r_opt = ARGPARSE_INVALID_ARG;
pargs->err = ARGPARSE_PRINT_ERROR;
}
break;
case oGpgProgram: opt.gpg_program = pargs->r.ret_str; break;
case oGpgsmProgram: opt.gpgsm_program = pargs->r.ret_str; break;
case oAgentProgram: opt.agent_program = pargs->r.ret_str; break;
case oStatusFD:
gnupg_set_status_fd (translate_sys2libc_fd_int (pargs->r.ret_int, 1));
break;
case oWithColons: opt.with_colons = 1; break;
case oNoAutostart: opt.autostart = 0; break;
case oDisplay: set_opt_session_env ("DISPLAY", pargs->r.ret_str); break;
case oTTYname: set_opt_session_env ("GPG_TTY", pargs->r.ret_str); break;
case oTTYtype: set_opt_session_env ("TERM", pargs->r.ret_str); break;
case oXauthority: set_opt_session_env ("XAUTHORITY",
pargs->r.ret_str); break;
case oLCctype: opt.lc_ctype = pargs->r.ret_str; break;
case oLCmessages: opt.lc_messages = pargs->r.ret_str; break;
default: pargs->err = 2; break;
}
}
}
/* gpg-card main. */
int
main (int argc, char **argv)
{
gpg_error_t err;
ARGPARSE_ARGS pargs;
char **command_list = NULL;
int cmdidx;
char *command;
gnupg_reopen_std ("gpg-card");
set_strusage (my_strusage);
gnupg_rl_initialize ();
log_set_prefix ("gpg-card", GPGRT_LOG_WITH_PREFIX);
/* Make sure that our subsystems are ready. */
i18n_init();
init_common_subsystems (&argc, &argv);
assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT);
setup_libassuan_logging (&opt.debug, NULL);
/* Setup default options. */
opt.autostart = 1;
opt.session_env = session_env_new ();
if (!opt.session_env)
log_fatal ("error allocating session environment block: %s\n",
gpg_strerror (gpg_error_from_syserror ()));
/* Parse the command line. */
pargs.argc = &argc;
pargs.argv = &argv;
pargs.flags = ARGPARSE_FLAG_KEEP;
parse_arguments (&pargs, opts);
if (log_get_errorcount (0))
exit (2);
/* Set defaults for non given options. */
if (!opt.gpg_program)
opt.gpg_program = gnupg_module_name (GNUPG_MODULE_NAME_GPG);
if (!opt.gpgsm_program)
opt.gpgsm_program = gnupg_module_name (GNUPG_MODULE_NAME_GPGSM);
/* Now build the list of commands. We guess the size of the array
* by assuming each item is a complete command. Obviously this will
* be rarely the case, but it is less code to allocate a possible
* too large array. */
command_list = xcalloc (argc+1, sizeof *command_list);
cmdidx = 0;
command = NULL;
while (argc)
{
for ( ; argc && strcmp (*argv, "--"); argc--, argv++)
{
if (!command)
command = xstrdup (*argv);
else
{
char *tmp = xstrconcat (command, " ", *argv, NULL);
xfree (command);
command = tmp;
}
}
if (argc)
{ /* Skip the double dash. */
argc--;
argv++;
}
if (command)
{
command_list[cmdidx++] = command;
command = NULL;
}
}
opt.interactive = !cmdidx;
if (opt.interactive)
{
interactive_loop ();
err = 0;
}
else
{
struct card_info_s info_buffer = { 0 };
card_info_t info = &info_buffer;
err = 0;
for (cmdidx=0; (command = command_list[cmdidx]); cmdidx++)
{
err = dispatch_command (info, command);
if (err)
break;
}
if (gpg_err_code (err) == GPG_ERR_EOF)
err = 0; /* This was a "quit". */
else if (command && !opt.quiet)
log_info ("stopped at command '%s'\n", command);
}
flush_keyblock_cache ();
if (command_list)
{
for (cmdidx=0; command_list[cmdidx]; cmdidx++)
xfree (command_list[cmdidx]);
xfree (command_list);
}
if (err)
gnupg_status_printf (STATUS_FAILURE, "- %u", err);
else if (log_get_errorcount (0))
gnupg_status_printf (STATUS_FAILURE, "- %u", GPG_ERR_GENERAL);
else
gnupg_status_printf (STATUS_SUCCESS, NULL);
return log_get_errorcount (0)? 1:0;
}
/* Read data from file FNAME up to MAX_GET_DATA_FROM_FILE characters.
* On error return an error code and stores NULL at R_BUFFER; on
* success returns 0 and stores the number of bytes read at R_BUFLEN
* and the address of a newly allocated buffer at R_BUFFER. A
* complementary nul byte is always appended to the data but not
* counted; this allows to pass NULL for R-BUFFER and consider the
* returned data as a string. */
static gpg_error_t
get_data_from_file (const char *fname, char **r_buffer, size_t *r_buflen)
{
gpg_error_t err;
estream_t fp;
char *data;
int n;
*r_buffer = NULL;
if (r_buflen)
*r_buflen = 0;
fp = es_fopen (fname, "rb");
if (!fp)
{
err = gpg_error_from_syserror ();
log_error (_("can't open '%s': %s\n"), fname, gpg_strerror (err));
return err;
}
data = xtrymalloc (MAX_GET_DATA_FROM_FILE);
if (!data)
{
err = gpg_error_from_syserror ();
log_error (_("error allocating enough memory: %s\n"), gpg_strerror (err));
es_fclose (fp);
return err;
}
n = es_fread (data, 1, MAX_GET_DATA_FROM_FILE - 1, fp);
es_fclose (fp);
if (n < 0)
{
err = gpg_error_from_syserror ();
tty_printf (_("error reading '%s': %s\n"), fname, gpg_strerror (err));
xfree (data);
return err;
}
data[n] = 0;
*r_buffer = data;
if (r_buflen)
*r_buflen = n;
return 0;
}
/* Write LENGTH bytes from BUFFER to file FNAME. Return 0 on
* success. */
static gpg_error_t
put_data_to_file (const char *fname, const void *buffer, size_t length)
{
gpg_error_t err;
estream_t fp;
fp = es_fopen (fname, "wb");
if (!fp)
{
err = gpg_error_from_syserror ();
log_error (_("can't create '%s': %s\n"), fname, gpg_strerror (err));
return err;
}
if (length && es_fwrite (buffer, length, 1, fp) != 1)
{
err = gpg_error_from_syserror ();
log_error (_("error writing '%s': %s\n"), fname, gpg_strerror (err));
es_fclose (fp);
return err;
}
if (es_fclose (fp))
{
err = gpg_error_from_syserror ();
log_error (_("error writing '%s': %s\n"), fname, gpg_strerror (err));
return err;
}
return 0;
}
+/* Return a malloced string with the number opf the menu PROMPT.
+ * Control-D is mapped to "Q". */
+static char *
+get_selection (const char *prompt)
+{
+ char *answer;
+
+ tty_printf ("\n");
+ tty_printf ("%s", prompt);
+ tty_printf ("\n");
+ answer = tty_get (_("Your selection? "));
+ tty_kill_prompt ();
+ if (*answer == CONTROL_D)
+ strcpy (answer, "q");
+ return answer;
+}
+
+
/* Simply prints TEXT to the output. Returns 0 as a convenience.
* This is a separate fucntion so that it can be extended to run
* less(1) or so. The extra arguments are int values terminated by a
* 0 to indicate card application types supported with this command.
* If none are given (just teh final 0), this is a general
* command. */
static gpg_error_t
print_help (const char *text, ...)
{
estream_t fp;
va_list arg_ptr;
int value;
int any = 0;
fp = opt.interactive? NULL : es_stdout;
tty_fprintf (fp, "%s\n", text);
va_start (arg_ptr, text);
while ((value = va_arg (arg_ptr, int)))
{
if (!any)
tty_fprintf (fp, "[Supported by: ");
tty_fprintf (fp, "%s%s", any?", ":"", app_type_string (value));
any = 1;
}
if (any)
tty_fprintf (fp, "]\n");
va_end (arg_ptr);
return 0;
}
/* Return the OpenPGP card manufacturer name. */
static const char *
get_manufacturer (unsigned int no)
{
/* Note: Make sure that there is no colon or linefeed in the string. */
switch (no)
{
case 0x0001: return "PPC Card Systems";
case 0x0002: return "Prism";
case 0x0003: return "OpenFortress";
case 0x0004: return "Wewid";
case 0x0005: return "ZeitControl";
case 0x0006: return "Yubico";
case 0x0007: return "OpenKMS";
case 0x0008: return "LogoEmail";
case 0x0009: return "Fidesmo";
case 0x000A: return "Dangerous Things";
case 0x002A: return "Magrathea";
case 0x0042: return "GnuPG e.V.";
case 0x1337: return "Warsaw Hackerspace";
case 0x2342: return "warpzone"; /* hackerspace Muenster. */
case 0x4354: return "Confidential Technologies"; /* cotech.de */
case 0x63AF: return "Trustica";
case 0xBD0E: return "Paranoidlabs";
case 0xF517: return "FSIJ";
/* 0x0000 and 0xFFFF are defined as test cards per spec,
* 0xFF00 to 0xFFFE are assigned for use with randomly created
* serial numbers. */
case 0x0000:
case 0xffff: return "test card";
default: return (no & 0xff00) == 0xff00? "unmanaged S/N range":"unknown";
}
}
/* Print an (OpenPGP) fingerprint. */
static void
print_shax_fpr (estream_t fp, const unsigned char *fpr, unsigned int fprlen)
{
int i;
if (fpr)
{
/* FIXME: Fix formatting for FPRLEN != 20 */
for (i=0; i < fprlen ; i+=2, fpr += 2 )
{
if (i == 10 )
tty_fprintf (fp, " ");
tty_fprintf (fp, " %02X%02X", *fpr, fpr[1]);
}
}
else
tty_fprintf (fp, " [none]");
tty_fprintf (fp, "\n");
}
/* Print the keygrip GRP. */
static void
print_keygrip (estream_t fp, const unsigned char *grp)
{
int i;
for (i=0; i < 20 ; i++, grp++)
tty_fprintf (fp, "%02X", *grp);
tty_fprintf (fp, "\n");
}
/* Print a string but avoid printing control characters. */
static void
print_string (estream_t fp, const char *text, const char *name)
{
tty_fprintf (fp, "%s", text);
/* FIXME: tty_printf_utf8_string2 eats everything after and
including an @ - e.g. when printing an url. */
if (name && *name)
{
if (fp)
print_utf8_buffer2 (fp, name, strlen (name), '\n');
else
tty_print_utf8_string2 (NULL, name, strlen (name), 0);
}
else
tty_fprintf (fp, _("[not set]"));
tty_fprintf (fp, "\n");
}
/* Print an ISO formatted name or "[not set]". */
static void
print_isoname (estream_t fp, const char *name)
{
if (name && *name)
{
char *p, *given, *buf;
buf = xstrdup (name);
given = strstr (buf, "<<");
for (p=buf; *p; p++)
if (*p == '<')
*p = ' ';
if (given && given[2])
{
*given = 0;
given += 2;
if (fp)
print_utf8_buffer2 (fp, given, strlen (given), '\n');
else
tty_print_utf8_string2 (NULL, given, strlen (given), 0);
if (*buf)
tty_fprintf (fp, " ");
}
if (fp)
print_utf8_buffer2 (fp, buf, strlen (buf), '\n');
else
tty_print_utf8_string2 (NULL, buf, strlen (buf), 0);
xfree (buf);
}
else
{
tty_fprintf (fp, _("[not set]"));
}
tty_fprintf (fp, "\n");
}
/* Return true if the buffer MEM of length memlen consists only of zeroes. */
static int
mem_is_zero (const char *mem, unsigned int memlen)
{
int i;
for (i=0; i < memlen && !mem[i]; i++)
;
return (i == memlen);
}
/* Helper to list a single keyref. LABEL_KEYREF is a fallback key
* reference if no info is available; it may be NULL. */
static void
list_one_kinfo (key_info_t firstkinfo, key_info_t kinfo,
const char *label_keyref, estream_t fp)
{
gpg_error_t err;
keyblock_t keyblock = NULL;
keyblock_t kb;
pubkey_t pubkey;
userid_t uid;
key_info_t ki;
const char *s;
gcry_sexp_t s_pkey;
int any;
if (firstkinfo && kinfo)
{
tty_fprintf (fp, " ");
if (mem_is_zero (kinfo->grip, sizeof kinfo->grip))
{
tty_fprintf (fp, "[none]\n");
tty_fprintf (fp, " keyref .....: %s\n", kinfo->keyref);
goto leave;
}
print_keygrip (fp, kinfo->grip);
tty_fprintf (fp, " keyref .....: %s", kinfo->keyref);
if (kinfo->usage)
{
any = 0;
tty_fprintf (fp, " (");
if ((kinfo->usage & GCRY_PK_USAGE_SIGN))
{ tty_fprintf (fp, "sign"); any=1; }
if ((kinfo->usage & GCRY_PK_USAGE_CERT))
{ tty_fprintf (fp, "%scert", any?",":""); any=1; }
if ((kinfo->usage & GCRY_PK_USAGE_AUTH))
{ tty_fprintf (fp, "%sauth", any?",":""); any=1; }
if ((kinfo->usage & GCRY_PK_USAGE_ENCR))
{ tty_fprintf (fp, "%sencr", any?",":""); any=1; }
tty_fprintf (fp, ")");
}
tty_fprintf (fp, "\n");
if (!scd_readkey (kinfo->keyref, &s_pkey))
{
- char *tmp = pubkey_algo_string (s_pkey);
+ char *tmp = pubkey_algo_string (s_pkey, NULL);
tty_fprintf (fp, " algorithm ..: %s\n", tmp);
xfree (tmp);
gcry_sexp_release (s_pkey);
s_pkey = NULL;
}
if (kinfo->fprlen && kinfo->created)
{
tty_fprintf (fp, " fingerprint :");
print_shax_fpr (fp, kinfo->fpr, kinfo->fprlen);
tty_fprintf (fp, " created ....: %s\n",
isotimestamp (kinfo->created));
}
err = get_matching_keys (kinfo->grip,
(GNUPG_PROTOCOL_OPENPGP | GNUPG_PROTOCOL_CMS),
&keyblock);
if (err)
{
if (gpg_err_code (err) != GPG_ERR_NO_PUBKEY)
tty_fprintf (fp, " error ......: %s\n", gpg_strerror (err));
goto leave;
}
for (kb = keyblock; kb; kb = kb->next)
{
tty_fprintf (fp, " used for ...: %s\n",
kb->protocol == GNUPG_PROTOCOL_OPENPGP? "OpenPGP" :
kb->protocol == GNUPG_PROTOCOL_CMS? "X.509" : "?");
pubkey = kb->keys;
/* If this is not the primary key print the primary key's
* fingerprint or a reference to it. */
if (kb->protocol == GNUPG_PROTOCOL_OPENPGP)
{
tty_fprintf (fp, " main key .:");
for (ki=firstkinfo; ki; ki = ki->next)
if (pubkey->grip_valid
&& !memcmp (ki->grip, pubkey->grip, KEYGRIP_LEN))
break;
if (ki)
{
/* Fixme: Replace mapping by a table lookup. */
if (!memcmp (kinfo->grip, pubkey->grip, KEYGRIP_LEN))
s = "this";
else if (!strcmp (ki->keyref, "OPENPGP.1"))
s = "Signature key";
else if (!strcmp (ki->keyref, "OPENPGP.2"))
s = "Encryption key";
else if (!strcmp (ki->keyref, "OPENPGP.3"))
s = "Authentication key";
else
s = NULL;
if (s)
tty_fprintf (fp, " <%s>\n", s);
else
tty_fprintf (fp, " <Key %s>\n", ki->keyref);
}
else
print_shax_fpr (fp, pubkey->fpr, pubkey->fprlen);
}
for (uid = kb->uids; uid; uid = uid->next)
{
print_string (fp, " user id ..: ", uid->value);
}
}
}
else
{
tty_fprintf (fp, " [none]\n");
if (label_keyref)
tty_fprintf (fp, " keyref .....: %s\n", label_keyref);
}
leave:
release_keyblock (keyblock);
}
/* List all keyinfo in INFO using the list of LABELS. */
static void
list_all_kinfo (card_info_t info, keyinfolabel_t labels, estream_t fp)
{
key_info_t kinfo;
int idx, i;
/* Print the keyinfo. We first print those we known and then all
* remaining item. */
for (kinfo = info->kinfo; kinfo; kinfo = kinfo->next)
kinfo->xflag = 0;
if (labels)
{
for (idx=0; labels[idx].label; idx++)
{
tty_fprintf (fp, "%s", labels[idx].label);
kinfo = find_kinfo (info, labels[idx].keyref);
list_one_kinfo (info->kinfo, kinfo, labels[idx].keyref, fp);
if (kinfo)
kinfo->xflag = 1;
}
}
for (kinfo = info->kinfo; kinfo; kinfo = kinfo->next)
{
if (kinfo->xflag)
continue;
tty_fprintf (fp, "Key %s ", kinfo->keyref);
for (i=5+strlen (kinfo->keyref); i < 18; i++)
tty_fprintf (fp, ".");
tty_fprintf (fp, ":");
list_one_kinfo (info->kinfo, kinfo, NULL, fp);
}
}
/* List OpenPGP card specific data. */
static void
list_openpgp (card_info_t info, estream_t fp)
{
static struct keyinfolabel_s keyinfolabels[] = {
{ "Signature key ....:", "OPENPGP.1" },
{ "Encryption key....:", "OPENPGP.2" },
{ "Authentication key:", "OPENPGP.3" },
{ NULL, NULL }
};
int i;
if (!info->serialno
|| strncmp (info->serialno, "D27600012401", 12)
|| strlen (info->serialno) != 32 )
{
tty_fprintf (fp, "invalid OpenPGP card\n");
return;
}
tty_fprintf (fp, "Manufacturer .....: %s\n",
get_manufacturer (xtoi_2(info->serialno+16)*256
+ xtoi_2 (info->serialno+18)));
tty_fprintf (fp, "Name of cardholder: ");
print_isoname (fp, info->disp_name);
print_string (fp, "Language prefs ...: ", info->disp_lang);
tty_fprintf (fp, "Salutation .......: %s\n",
info->disp_sex == 1? _("Mr."):
info->disp_sex == 2? _("Mrs.") : "");
print_string (fp, "URL of public key : ", info->pubkey_url);
print_string (fp, "Login data .......: ", info->login_data);
if (info->private_do[0])
print_string (fp, "Private DO 1 .....: ", info->private_do[0]);
if (info->private_do[1])
print_string (fp, "Private DO 2 .....: ", info->private_do[1]);
if (info->private_do[2])
print_string (fp, "Private DO 3 .....: ", info->private_do[2]);
if (info->private_do[3])
print_string (fp, "Private DO 4 .....: ", info->private_do[3]);
if (info->cafpr1len)
{
tty_fprintf (fp, "CA fingerprint %d .:", 1);
print_shax_fpr (fp, info->cafpr1, info->cafpr1len);
}
if (info->cafpr2len)
{
tty_fprintf (fp, "CA fingerprint %d .:", 2);
print_shax_fpr (fp, info->cafpr2, info->cafpr2len);
}
if (info->cafpr3len)
{
tty_fprintf (fp, "CA fingerprint %d .:", 3);
print_shax_fpr (fp, info->cafpr3, info->cafpr3len);
}
tty_fprintf (fp, "Signature PIN ....: %s\n",
info->chv1_cached? _("not forced"): _("forced"));
if (info->key_attr[0].algo)
{
tty_fprintf (fp, "Key attributes ...:");
for (i=0; i < DIM (info->key_attr); i++)
if (info->key_attr[i].algo == PUBKEY_ALGO_RSA)
tty_fprintf (fp, " rsa%u", info->key_attr[i].nbits);
else if (info->key_attr[i].algo == PUBKEY_ALGO_ECDH
|| info->key_attr[i].algo == PUBKEY_ALGO_ECDSA
|| info->key_attr[i].algo == PUBKEY_ALGO_EDDSA)
{
const char *curve_for_print = "?";
const char *oid;
if (info->key_attr[i].curve
&& (oid = openpgp_curve_to_oid (info->key_attr[i].curve, NULL)))
curve_for_print = openpgp_oid_to_curve (oid, 0);
tty_fprintf (fp, " %s", curve_for_print);
}
tty_fprintf (fp, "\n");
}
tty_fprintf (fp, "Max. PIN lengths .: %d %d %d\n",
info->chvmaxlen[0], info->chvmaxlen[1], info->chvmaxlen[2]);
tty_fprintf (fp, "PIN retry counter : %d %d %d\n",
info->chvinfo[0], info->chvinfo[1], info->chvinfo[2]);
tty_fprintf (fp, "Signature counter : %lu\n", info->sig_counter);
if (info->extcap.kdf)
{
tty_fprintf (fp, "KDF setting ......: %s\n",
info->kdf_do_enabled ? "on" : "off");
}
if (info->extcap.bt)
{
tty_fprintf (fp, "UIF setting ......: Sign=%s Decrypt=%s Auth=%s\n",
info->uif[0] ? "on" : "off", info->uif[1] ? "on" : "off",
info->uif[2] ? "on" : "off");
}
list_all_kinfo (info, keyinfolabels, fp);
}
/* List PIV card specific data. */
static void
list_piv (card_info_t info, estream_t fp)
{
static struct keyinfolabel_s keyinfolabels[] = {
{ "PIV authentication:", "PIV.9A" },
{ "Card authenticat. :", "PIV.9E" },
{ "Digital signature :", "PIV.9C" },
{ "Key management ...:", "PIV.9D" },
{ NULL, NULL }
};
const char *s;
int i;
if (info->chvusage[0] || info->chvusage[1])
{
tty_fprintf (fp, "PIN usage policy .:");
if ((info->chvusage[0] & 0x40))
tty_fprintf (fp, " app-pin");
if ((info->chvusage[0] & 0x20))
tty_fprintf (fp, " global-pin");
if ((info->chvusage[0] & 0x10))
tty_fprintf (fp, " occ");
if ((info->chvusage[0] & 0x08))
tty_fprintf (fp, " vci");
if ((info->chvusage[0] & 0x08) && !(info->chvusage[0] & 0x04))
tty_fprintf (fp, " pairing");
if (info->chvusage[1] == 0x10)
tty_fprintf (fp, " primary:card");
else if (info->chvusage[1] == 0x20)
tty_fprintf (fp, " primary:global");
tty_fprintf (fp, "\n");
}
tty_fprintf (fp, "PIN retry counter :");
for (i=0; i < DIM (info->chvinfo); i++)
{
if (info->chvinfo[i] > 0)
tty_fprintf (fp, " %d", info->chvinfo[i]);
else
{
switch (info->chvinfo[i])
{
case -1: s = "[error]"; break;
case -2: s = "-"; break; /* No such PIN or info not available. */
case -3: s = "[blocked]"; break;
case -5: s = "[verified]"; break;
default: s = "[?]"; break;
}
tty_fprintf (fp, " %s", s);
}
}
tty_fprintf (fp, "\n");
list_all_kinfo (info, keyinfolabels, fp);
}
static void
print_a_version (estream_t fp, const char *prefix, unsigned int value)
{
unsigned int a, b, c, d;
a = ((value >> 24) & 0xff);
b = ((value >> 16) & 0xff);
c = ((value >> 8) & 0xff);
d = ((value ) & 0xff);
if (a)
tty_fprintf (fp, "%s %u.%u.%u.%u\n", prefix, a, b, c, d);
else if (b)
tty_fprintf (fp, "%s %u.%u.%u\n", prefix, b, c, d);
else
tty_fprintf (fp, "%s %u.%u\n", prefix, c, d);
}
/* Print all available information about the current card. */
static void
list_card (card_info_t info)
{
estream_t fp = opt.interactive? NULL : es_stdout;
tty_fprintf (fp, "Reader ...........: %s\n",
info->reader? info->reader : "[none]");
if (info->cardtype)
tty_fprintf (fp, "Card type ........: %s\n", info->cardtype);
if (info->cardversion)
print_a_version (fp, "Card firmware ....:", info->cardversion);
tty_fprintf (fp, "Serial number ....: %s\n",
info->serialno? info->serialno : "[none]");
tty_fprintf (fp, "Application type .: %s%s%s%s\n",
app_type_string (info->apptype),
info->apptype == APP_TYPE_UNKNOWN && info->apptypestr? "(":"",
info->apptype == APP_TYPE_UNKNOWN && info->apptypestr
? info->apptypestr:"",
info->apptype == APP_TYPE_UNKNOWN && info->apptypestr? ")":"");
if (info->appversion)
print_a_version (fp, "Version ..........:", info->appversion);
if (info->serialno && info->dispserialno
&& strcmp (info->serialno, info->dispserialno))
tty_fprintf (fp, "Displayed s/n ....: %s\n", info->dispserialno);
switch (info->apptype)
{
case APP_TYPE_OPENPGP: list_openpgp (info, fp); break;
case APP_TYPE_PIV: list_piv (info, fp); break;
default: break;
}
}
+
+/* The LIST command. This also updates INFO. */
+static gpg_error_t
+cmd_list (card_info_t info, char *argstr)
+{
+ gpg_error_t err;
+ int opt_cards;
+ strlist_t cards = NULL;
+ strlist_t sl;
+ estream_t fp = opt.interactive? NULL : es_stdout;
+ int cardno, count;
+
+
+ if (!info)
+ return print_help
+ ("LIST [--cards] [N]\n\n"
+ "Show the content of the current card or with N given the N-th card.\n"
+ "Option --cards lists available cards.",
+ 0);
+
+ opt_cards = has_leading_option (argstr, "--cards");
+ argstr = skip_options (argstr);
+
+
+ if (digitp (argstr))
+ {
+ cardno = atoi (argstr);
+ while (digitp (argstr))
+ argstr++;
+ while (spacep (argstr))
+ argstr++;
+ }
+ else
+ cardno = -1;
+
+
+ if (opt_cards)
+ {
+ err = scd_cardlist (&cards);
+ if (err)
+ goto leave;
+ for (count = 0, sl = cards; sl; sl = sl->next, count++)
+ tty_fprintf (fp, "%d %s\n", count, sl->d);
+ }
+ else
+ {
+ if (cardno != -1)
+ {
+ err = scd_cardlist (&cards);
+ if (err)
+ goto leave;
+ for (count = 0, sl = cards; sl; sl = sl->next, count++)
+ if (count == cardno)
+ break;
+ if (!sl)
+ {
+ err = gpg_error (GPG_ERR_INV_INDEX);
+ goto leave;
+ }
+ err = scd_serialno (NULL, sl->d);
+ if (err)
+ goto leave;
+ }
+
+ err = scd_learn (info);
+ if (!err)
+ list_card (info);
+ }
+
+ leave:
+ free_strlist (cards);
+ return err;
+}
+
+
/* The VERIFY command. */
static gpg_error_t
cmd_verify (card_info_t info, char *argstr)
{
gpg_error_t err;
const char *pinref;
if (!info)
return print_help ("verify [chvid]", 0);
if (*argstr)
pinref = argstr;
else if (info->apptype == APP_TYPE_OPENPGP)
pinref = info->serialno;
else if (info->apptype == APP_TYPE_PIV)
pinref = "PIV.80";
else
return gpg_error (GPG_ERR_MISSING_VALUE);
err = scd_checkpin (pinref);
if (err)
log_error ("verify failed: %s <%s>\n",
gpg_strerror (err), gpg_strsource (err));
return err;
}
static gpg_error_t
cmd_authenticate (card_info_t info, char *argstr)
{
gpg_error_t err;
int opt_setkey;
int opt_raw;
char *string = NULL;
char *key = NULL;
size_t keylen;
if (!info)
return print_help
("AUTHENTICATE [--setkey] [--raw] [< FILE]|KEY\n\n"
"Perform a mutual autentication either by reading the key\n"
"from FILE or by taking it from the command line. Without\n"
"the option --raw the key is expected to be hex encoded.\n"
"To install a new administration key --setkey is used; this\n"
"requires a prior authentication with the old key.",
APP_TYPE_PIV, 0);
if (info->apptype != APP_TYPE_PIV)
{
log_info ("Note: This is a PIV only command.\n");
return gpg_error (GPG_ERR_NOT_SUPPORTED);
}
opt_setkey = has_leading_option (argstr, "--setkey");
opt_raw = has_leading_option (argstr, "--raw");
argstr = skip_options (argstr);
if (*argstr == '<') /* Read key from a file. */
{
for (argstr++; spacep (argstr); argstr++)
;
err = get_data_from_file (argstr, &string, NULL);
if (err)
goto leave;
}
if (opt_raw)
{
key = string? string : xstrdup (argstr);
string = NULL;
keylen = strlen (key);
}
else
{
key = hex_to_buffer (string? string: argstr, &keylen);
if (!key)
{
err = gpg_error_from_syserror ();
goto leave;
}
}
err = scd_setattr (opt_setkey? "SET-ADM-KEY":"AUTH-ADM-KEY", key, keylen);
leave:
if (key)
{
wipememory (key, keylen);
xfree (key);
}
xfree (string);
return err;
}
/* Helper for cmd_name to qyery a part of name. */
static char *
ask_one_name (const char *prompt)
{
char *name;
int i;
for (;;)
{
name = tty_get (prompt);
trim_spaces (name);
tty_kill_prompt ();
if (!*name || *name == CONTROL_D)
{
if (*name == CONTROL_D)
tty_fprintf (NULL, "\n");
xfree (name);
return NULL;
}
for (i=0; name[i] && name[i] >= ' ' && name[i] <= 126; i++)
;
/* The name must be in Latin-1 and not UTF-8 - lacking the code
* to ensure this we restrict it to ASCII. */
if (name[i])
tty_printf (_("Error: Only plain ASCII is currently allowed.\n"));
else if (strchr (name, '<'))
tty_printf (_("Error: The \"<\" character may not be used.\n"));
else if (strstr (name, " "))
tty_printf (_("Error: Double spaces are not allowed.\n"));
else
return name;
xfree (name);
}
}
/* The NAME command. */
static gpg_error_t
cmd_name (card_info_t info, const char *argstr)
{
gpg_error_t err;
char *surname, *givenname;
char *isoname, *p;
if (!info)
return print_help
("name [--clear]\n\n"
"Set the name field of an OpenPGP card. With --clear the stored\n"
"name is cleared off the card.", APP_TYPE_OPENPGP, APP_TYPE_NKS, 0);
if (info->apptype != APP_TYPE_OPENPGP)
{
log_info ("Note: This is an OpenPGP only command.\n");
return gpg_error (GPG_ERR_NOT_SUPPORTED);
}
again:
if (!strcmp (argstr, "--clear"))
isoname = xstrdup (" "); /* No real way to clear; set to space instead. */
else
{
surname = ask_one_name (_("Cardholder's surname: "));
givenname = ask_one_name (_("Cardholder's given name: "));
if (!surname || !givenname || (!*surname && !*givenname))
{
xfree (surname);
xfree (givenname);
return gpg_error (GPG_ERR_CANCELED);
}
isoname = xstrconcat (surname, "<<", givenname, NULL);
xfree (surname);
xfree (givenname);
for (p=isoname; *p; p++)
if (*p == ' ')
*p = '<';
if (strlen (isoname) > 39 )
{
log_info (_("Error: Combined name too long "
"(limit is %d characters).\n"), 39);
xfree (isoname);
goto again;
}
}
err = scd_setattr ("DISP-NAME", isoname, strlen (isoname));
xfree (isoname);
return err;
}
static gpg_error_t
cmd_url (card_info_t info, const char *argstr)
{
gpg_error_t err;
char *url;
if (!info)
return print_help
("URL [--clear]\n\n"
"Set the URL data object. That data object can be used by\n"
"the FETCH command to retrieve the full public key. The\n"
"option --clear deletes the content of that data object.",
APP_TYPE_OPENPGP, 0);
if (info->apptype != APP_TYPE_OPENPGP)
{
log_info ("Note: This is an OpenPGP only command.\n");
return gpg_error (GPG_ERR_NOT_SUPPORTED);
}
if (!strcmp (argstr, "--clear"))
url = xstrdup (" "); /* No real way to clear; set to space instead. */
else
{
url = tty_get (_("URL to retrieve public key: "));
trim_spaces (url);
tty_kill_prompt ();
if (!*url || *url == CONTROL_D)
{
err = gpg_error (GPG_ERR_CANCELED);
goto leave;
}
}
err = scd_setattr ("PUBKEY-URL", url, strlen (url));
leave:
xfree (url);
return err;
}
/* Fetch the key from the URL given on the card or try to get it from
* the default keyserver. */
static gpg_error_t
cmd_fetch (card_info_t info)
{
gpg_error_t err;
key_info_t kinfo;
if (!info)
return print_help
("FETCH\n\n"
"Retrieve a key using the URL data object or if that is missing\n"
"using the fingerprint.", APP_TYPE_OPENPGP, 0);
if (info->pubkey_url && *info->pubkey_url)
{
/* strlist_t sl = NULL; */
/* add_to_strlist (&sl, info.pubkey_url); */
/* err = keyserver_fetch (ctrl, sl, KEYORG_URL); */
/* free_strlist (sl); */
err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); /* FIXME */
}
else if ((kinfo = find_kinfo (info, "OPENPGP.1")) && kinfo->fprlen)
{
/* rc = keyserver_import_fprint (ctrl, info.fpr1, info.fpr1len, */
/* opt.keyserver, 0); */
err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); /* FIXME */
}
else
err = gpg_error (GPG_ERR_NO_DATA);
return err;
}
static gpg_error_t
cmd_login (card_info_t info, char *argstr)
{
gpg_error_t err;
char *data;
size_t datalen;
if (!info)
return print_help
("LOGIN [--clear] [< FILE]\n\n"
"Set the login data object. If FILE is given the data is\n"
"is read from that file. This allows for binary data.\n"
"The option --clear deletes the login data.",
APP_TYPE_OPENPGP, 0);
if (!strcmp (argstr, "--clear"))
{
data = xstrdup (" "); /* kludge. */
datalen = 1;
}
else if (*argstr == '<') /* Read it from a file */
{
for (argstr++; spacep (argstr); argstr++)
;
err = get_data_from_file (argstr, &data, &datalen);
if (err)
goto leave;
}
else
{
data = tty_get (_("Login data (account name): "));
trim_spaces (data);
tty_kill_prompt ();
if (!*data || *data == CONTROL_D)
{
err = gpg_error (GPG_ERR_CANCELED);
goto leave;
}
datalen = strlen (data);
}
err = scd_setattr ("LOGIN-DATA", data, datalen);
leave:
xfree (data);
return err;
}
static gpg_error_t
cmd_lang (card_info_t info, const char *argstr)
{
gpg_error_t err;
char *data, *p;
if (!info)
return print_help
("LANG [--clear]\n\n"
"Change the language info for the card. This info can be used\n"
"by applications for a personalized greeting. Up to 4 two-digit\n"
"language identifiers can be entered as a preference. The option\n"
"--clear removes all identifiers. GnuPG does not use this info.",
APP_TYPE_OPENPGP, 0);
if (!strcmp (argstr, "--clear"))
data = xstrdup (" "); /* Note that we need two spaces here. */
else
{
again:
data = tty_get (_("Language preferences: "));
trim_spaces (data);
tty_kill_prompt ();
if (!*data || *data == CONTROL_D)
{
err = gpg_error (GPG_ERR_CANCELED);
goto leave;
}
if (strlen (data) > 8 || (strlen (data) & 1))
{
log_info (_("Error: invalid length of preference string.\n"));
xfree (data);
goto again;
}
for (p=data; *p && *p >= 'a' && *p <= 'z'; p++)
;
if (*p)
{
log_info (_("Error: invalid characters in preference string.\n"));
xfree (data);
goto again;
}
}
err = scd_setattr ("DISP-LANG", data, strlen (data));
leave:
xfree (data);
return err;
}
static gpg_error_t
cmd_salut (card_info_t info, const char *argstr)
{
gpg_error_t err;
char *data = NULL;
const char *str;
if (!info)
return print_help
("SALUT [--clear]\n\n"
"Change the salutation info for the card. This info can be used\n"
"by applications for a personalized greeting. The option --clear\n"
"removes this data object. GnuPG does not use this info.",
APP_TYPE_OPENPGP, 0);
again:
if (!strcmp (argstr, "--clear"))
str = "9";
else
{
data = tty_get (_("Salutation (M = Mr., F = Mrs., or space): "));
trim_spaces (data);
tty_kill_prompt ();
if (*data == CONTROL_D)
{
err = gpg_error (GPG_ERR_CANCELED);
goto leave;
}
if (!*data)
str = "9";
else if ((*data == 'M' || *data == 'm') && !data[1])
str = "1";
else if ((*data == 'F' || *data == 'f') && !data[1])
str = "2";
else
{
tty_printf (_("Error: invalid response.\n"));
xfree (data);
goto again;
}
}
err = scd_setattr ("DISP-SEX", str, 1);
leave:
xfree (data);
return err;
}
static gpg_error_t
cmd_cafpr (card_info_t info, char *argstr)
{
gpg_error_t err;
char *data = NULL;
const char *s;
int i, c;
unsigned char fpr[32];
int fprlen;
int fprno;
int opt_clear = 0;
if (!info)
return print_help
("CAFPR [--clear] N\n\n"
"Change the CA fingerprint number N. N must be in the\n"
"range 1 to 3. The option --clear clears the specified\n"
"CA fingerprint N or all of them if N is 0 or not given.",
APP_TYPE_OPENPGP, 0);
opt_clear = has_leading_option (argstr, "--clear");
argstr = skip_options (argstr);
if (digitp (argstr))
{
fprno = atoi (argstr);
while (digitp (argstr))
argstr++;
while (spacep (argstr))
argstr++;
}
else
fprno = 0;
if (opt_clear && !fprno)
; /* Okay: clear all fprs. */
else if (fprno < 1 || fprno > 3)
{
err = gpg_error (GPG_ERR_INV_ARG);
goto leave;
}
again:
if (opt_clear)
{
memset (fpr, 0, 20);
fprlen = 20;
}
else
{
xfree (data);
data = tty_get (_("CA fingerprint: "));
trim_spaces (data);
tty_kill_prompt ();
if (!*data || *data == CONTROL_D)
{
err = gpg_error (GPG_ERR_CANCELED);
goto leave;
}
for (i=0, s=data; i < sizeof fpr && *s; )
{
while (spacep(s))
s++;
if (*s == ':')
s++;
while (spacep(s))
s++;
c = hextobyte (s);
if (c == -1)
break;
fpr[i++] = c;
s += 2;
}
fprlen = i;
if ((fprlen != 20 && fprlen != 32) || *s)
{
log_error (_("Error: invalid formatted fingerprint.\n"));
goto again;
}
}
if (!fprno)
{
log_assert (opt_clear);
err = scd_setattr ("CA-FPR-1", fpr, fprlen);
if (!err)
err = scd_setattr ("CA-FPR-2", fpr, fprlen);
if (!err)
err = scd_setattr ("CA-FPR-3", fpr, fprlen);
}
else
err = scd_setattr (fprno==1?"CA-FPR-1":
fprno==2?"CA-FPR-2":
fprno==3?"CA-FPR-3":"x", fpr, fprlen);
leave:
xfree (data);
return err;
}
static gpg_error_t
cmd_privatedo (card_info_t info, char *argstr)
{
gpg_error_t err;
int opt_clear;
char *do_name = NULL;
char *data = NULL;
size_t datalen;
int do_no;
if (!info)
return print_help
("PRIVATEDO [--clear] N [< FILE]\n\n"
"Change the private data object N. N must be in the\n"
"range 1 to 4. If FILE is given the data is is read\n"
"from that file. The option --clear clears the data.",
APP_TYPE_OPENPGP, 0);
opt_clear = has_leading_option (argstr, "--clear");
argstr = skip_options (argstr);
if (digitp (argstr))
{
do_no = atoi (argstr);
while (digitp (argstr))
argstr++;
while (spacep (argstr))
argstr++;
}
else
do_no = 0;
if (do_no < 1 || do_no > 4)
{
err = gpg_error (GPG_ERR_INV_ARG);
goto leave;
}
do_name = xasprintf ("PRIVATE-DO-%d", do_no);
if (opt_clear)
{
data = xstrdup (" ");
datalen = 1;
}
else if (*argstr == '<') /* Read it from a file */
{
for (argstr++; spacep (argstr); argstr++)
;
err = get_data_from_file (argstr, &data, &datalen);
if (err)
goto leave;
}
else if (*argstr)
{
err = gpg_error (GPG_ERR_INV_ARG);
goto leave;
}
else
{
data = tty_get (_("Private DO data: "));
trim_spaces (data);
tty_kill_prompt ();
datalen = strlen (data);
if (!*data || *data == CONTROL_D)
{
err = gpg_error (GPG_ERR_CANCELED);
goto leave;
}
}
err = scd_setattr (do_name, data, datalen);
leave:
xfree (do_name);
xfree (data);
return err;
}
static gpg_error_t
cmd_writecert (card_info_t info, char *argstr)
{
gpg_error_t err;
int opt_clear;
char *certref_buffer = NULL;
char *certref;
char *data = NULL;
size_t datalen;
if (!info)
return print_help
("WRITECERT [--clear] CERTREF < FILE\n\n"
"Write a certificate for key 3. Unless --clear is given\n"
"the file argument is mandatory. The option --clear removes\n"
"the certificate from the card.",
APP_TYPE_OPENPGP, APP_TYPE_PIV, 0);
opt_clear = has_leading_option (argstr, "--clear");
argstr = skip_options (argstr);
certref = argstr;
if ((argstr = strchr (certref, ' ')))
{
*argstr++ = 0;
trim_spaces (certref);
trim_spaces (argstr);
}
else /* Let argstr point to an empty string. */
argstr = certref + strlen (certref);
if (info->apptype == APP_TYPE_OPENPGP)
{
if (ascii_strcasecmp (certref, "OPENPGP.3") && strcmp (certref, "3"))
{
err = gpg_error (GPG_ERR_INV_ID);
log_error ("Error: CERTREF must be \"3\" or \"OPENPGP.3\"\n");
goto leave;
}
certref = certref_buffer = xstrdup ("OPENPGP.3");
}
else /* Upcase the certref; prepend cardtype if needed. */
{
if (!strchr (certref, '.'))
certref_buffer = xstrconcat (app_type_string (info->apptype), ".",
certref, NULL);
else
certref_buffer = xstrdup (certref);
ascii_strupr (certref_buffer);
certref = certref_buffer;
}
if (opt_clear)
{
data = xstrdup (" ");
datalen = 1;
}
else if (*argstr == '<') /* Read it from a file */
{
for (argstr++; spacep (argstr); argstr++)
;
err = get_data_from_file (argstr, &data, &datalen);
if (err)
goto leave;
if (ascii_memistr (data, datalen, "-----BEGIN CERTIFICATE-----")
&& ascii_memistr (data, datalen, "-----END CERTIFICATE-----")
&& !memchr (data, 0, datalen) && !memchr (data, 1, datalen))
{
struct b64state b64;
err = b64dec_start (&b64, "");
if (!err)
err = b64dec_proc (&b64, data, datalen, &datalen);
if (!err)
err = b64dec_finish (&b64);
if (err)
goto leave;
}
}
else
{
err = gpg_error (GPG_ERR_INV_ARG);
goto leave;
}
err = scd_writecert (certref, data, datalen);
leave:
xfree (data);
xfree (certref_buffer);
return err;
}
static gpg_error_t
cmd_readcert (card_info_t info, char *argstr)
{
gpg_error_t err;
char *certref_buffer = NULL;
char *certref;
void *data = NULL;
size_t datalen;
const char *fname;
if (!info)
return print_help
("READCERT CERTREF > FILE\n\n"
"Read the certificate for key CERTREF and store it in FILE.",
APP_TYPE_OPENPGP, APP_TYPE_PIV, 0);
argstr = skip_options (argstr);
certref = argstr;
if ((argstr = strchr (certref, ' ')))
{
*argstr++ = 0;
trim_spaces (certref);
trim_spaces (argstr);
}
else /* Let argstr point to an empty string. */
argstr = certref + strlen (certref);
if (info->apptype == APP_TYPE_OPENPGP)
{
if (ascii_strcasecmp (certref, "OPENPGP.3") && strcmp (certref, "3"))
{
err = gpg_error (GPG_ERR_INV_ID);
log_error ("Error: CERTREF must be \"3\" or \"OPENPGP.3\"\n");
goto leave;
}
certref = certref_buffer = xstrdup ("OPENPGP.3");
}
if (*argstr == '>') /* Write it to a file */
{
for (argstr++; spacep (argstr); argstr++)
;
fname = argstr;
}
else
{
err = gpg_error (GPG_ERR_INV_ARG);
goto leave;
}
err = scd_readcert (certref, &data, &datalen);
if (err)
goto leave;
err = put_data_to_file (fname, data, datalen);
leave:
xfree (data);
xfree (certref_buffer);
return err;
}
static gpg_error_t
cmd_writekey (card_info_t info, char *argstr)
{
gpg_error_t err;
int opt_force;
char *argv[2];
int argc;
char *keyref_buffer = NULL;
char *keyref;
char *keygrip;
if (!info)
return print_help
("WRITEKEY [--force] KEYREF KEYGRIP\n\n"
"Write a private key object identified by KEYGRIP to slot KEYREF.\n"
"Use --force to overwrite an existing key.",
APP_TYPE_OPENPGP, APP_TYPE_PIV, 0);
opt_force = has_leading_option (argstr, "--force");
argstr = skip_options (argstr);
argc = split_fields (argstr, argv, DIM (argv));
if (argc < 2)
{
err = gpg_error (GPG_ERR_INV_ARG);
goto leave;
}
/* Upcase the keyref; prepend cardtype if needed. */
keyref = argv[0];
if (!strchr (keyref, '.'))
keyref_buffer = xstrconcat (app_type_string (info->apptype), ".",
keyref, NULL);
else
keyref_buffer = xstrdup (keyref);
ascii_strupr (keyref_buffer);
keyref = keyref_buffer;
/* Get the keygrip. */
keygrip = argv[1];
if (strlen (keygrip) != 40
&& !(keygrip[0] == '&' && strlen (keygrip+1) == 40))
{
log_error (_("Not a valid keygrip (expecting 40 hex digits)\n"));
err = gpg_error (GPG_ERR_INV_ARG);
goto leave;
}
err = scd_writekey (keyref, opt_force, keygrip);
leave:
xfree (keyref_buffer);
return err;
}
static gpg_error_t
cmd_forcesig (card_info_t info)
{
gpg_error_t err;
int newstate;
if (!info)
return print_help
("FORCESIG\n\n"
"Toggle the forcesig flag of an OpenPGP card.",
APP_TYPE_OPENPGP, 0);
if (info->apptype != APP_TYPE_OPENPGP)
{
log_info ("Note: This is an OpenPGP only command.\n");
return gpg_error (GPG_ERR_NOT_SUPPORTED);
}
newstate = !info->chv1_cached;
err = scd_setattr ("CHV-STATUS-1", newstate? "\x01":"", 1);
if (err)
goto leave;
/* Read it back to be sure we have the right toggle state the next
* time. */
err = scd_getattr ("CHV-STATUS", info);
leave:
return err;
}
/* Helper for cmd_generate_openpgp. Noe that either 0 or 1 is stored at
* FORCED_CHV1. */
static gpg_error_t
check_pin_for_key_operation (card_info_t info, int *forced_chv1)
{
gpg_error_t err = 0;
*forced_chv1 = !info->chv1_cached;
if (*forced_chv1)
{ /* Switch off the forced mode so that during key generation we
* don't get bothered with PIN queries for each self-signature. */
err = scd_setattr ("CHV-STATUS-1", "\x01", 1);
if (err)
{
log_error ("error clearing forced signature PIN flag: %s\n",
gpg_strerror (err));
*forced_chv1 = -1; /* Not changed. */
goto leave;
}
}
/* Check the PIN now, so that we won't get asked later for each
* binding signature. */
err = scd_checkpin (info->serialno);
if (err)
log_error ("error checking the PIN: %s\n", gpg_strerror (err));
leave:
return err;
}
/* Helper for cmd_generate_openpgp. */
static void
restore_forced_chv1 (int *forced_chv1)
{
gpg_error_t err;
/* Note the possible values stored at FORCED_CHV1:
* 0 - forcesig was not enabled.
* 1 - forcesig was enabled - enable it again.
* -1 - We have not changed anything. */
if (*forced_chv1 == 1)
{ /* Switch back to forced state. */
err = scd_setattr ("CHV-STATUS-1", "", 1);
if (err)
log_error ("error setting forced signature PIN flag: %s\n",
gpg_strerror (err));
*forced_chv1 = 0;
}
}
/* Implementation of cmd_generate for OpenPGP cards. */
static gpg_error_t
generate_openpgp (card_info_t info)
{
gpg_error_t err;
int forced_chv1 = -1;
int want_backup;
char *answer = NULL;
key_info_t kinfo1, kinfo2, kinfo3;
if (info->extcap.ki)
{
xfree (answer);
answer = tty_get (_("Make off-card backup of encryption key? (Y/n) "));
want_backup = answer_is_yes_no_default (answer, 1/*(default to Yes)*/);
tty_kill_prompt ();
if (*answer == CONTROL_D)
{
err = gpg_error (GPG_ERR_CANCELED);
goto leave;
}
}
else
want_backup = 0;
kinfo1 = find_kinfo (info, "OPENPGP.1");
kinfo2 = find_kinfo (info, "OPENPGP.2");
kinfo3 = find_kinfo (info, "OPENPGP.3");
if ((kinfo1 && kinfo1->fprlen && !mem_is_zero (kinfo1->fpr,kinfo1->fprlen))
|| (kinfo2 && kinfo2->fprlen && !mem_is_zero (kinfo2->fpr,kinfo2->fprlen))
|| (kinfo3 && kinfo3->fprlen && !mem_is_zero (kinfo3->fpr,kinfo3->fprlen))
)
{
tty_printf ("\n");
log_info (_("Note: keys are already stored on the card!\n"));
tty_printf ("\n");
answer = tty_get (_("Replace existing keys? (y/N) "));
tty_kill_prompt ();
if (*answer == CONTROL_D)
{
err = gpg_error (GPG_ERR_CANCELED);
goto leave;
}
if (!answer_is_yes_no_default (answer, 0/*(default to No)*/))
{
err = gpg_error (GPG_ERR_CANCELED);
goto leave;
}
}
/* If no displayed name has been set, we assume that this is a fresh
* card and print a hint about the default PINs. */
if (!info->disp_name || !*info->disp_name)
{
tty_printf ("\n");
tty_printf (_("Please note that the factory settings of the PINs are\n"
" PIN = '%s' Admin PIN = '%s'\n"
"You should change them using the command --change-pin\n"),
OPENPGP_USER_PIN_DEFAULT, OPENPGP_ADMIN_PIN_DEFAULT);
tty_printf ("\n");
}
err = check_pin_for_key_operation (info, &forced_chv1);
if (err)
goto leave;
/* FIXME: We need to divert to a function which spwans gpg which
* will then create the key. This also requires new features in
* gpg. We might also first create the keys on the card and then
* tell gpg to use them to create the OpenPGP keyblock. */
/* generate_keypair (ctrl, 1, NULL, info.serialno, want_backup); */
(void)want_backup;
err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
leave:
restore_forced_chv1 (&forced_chv1);
xfree (answer);
return err;
}
/* Generic implementation of cmd_generate. */
static gpg_error_t
generate_generic (card_info_t info, const char *keyref, int force,
const char *algo)
{
gpg_error_t err;
(void)info;
err = scd_genkey (keyref, force, algo, NULL);
return err;
}
static gpg_error_t
cmd_generate (card_info_t info, char *argstr)
{
static char * const valid_algos[] =
{ "rsa2048", "rsa3072", "rsa4096",
"nistp256", "nistp384", "nistp521",
"ed25519", "cv25519",
NULL
};
gpg_error_t err;
int opt_force;
char *opt_algo = NULL; /* Malloced. */
char *keyref_buffer = NULL; /* Malloced. */
char *keyref; /* Points into argstr or keyref_buffer. */
int i;
if (!info)
return print_help
("GENERATE [--force] [--algo=ALGO] KEYREF\n\n"
"Create a new key on a card. For OpenPGP cards are menu is used\n"
"and KEYREF is ignored. Use --force to overwrite an existing key.",
APP_TYPE_OPENPGP, APP_TYPE_PIV, 0);
if (opt.interactive || opt.verbose)
log_info (_("%s card no. %s detected\n"),
app_type_string (info->apptype),
info->dispserialno? info->dispserialno : info->serialno);
opt_force = has_leading_option (argstr, "--force");
err = get_option_value (argstr, "--algo", &opt_algo);
if (err)
goto leave;
argstr = skip_options (argstr);
keyref = argstr;
if ((argstr = strchr (keyref, ' ')))
{
*argstr++ = 0;
trim_spaces (keyref);
trim_spaces (argstr);
}
else /* Let argstr point to an empty string. */
argstr = keyref + strlen (keyref);
if (!*keyref)
keyref = NULL;
if (*argstr)
{
/* Extra arguments found. */
err = gpg_error (GPG_ERR_INV_ARG);
goto leave;
}
if (opt_algo)
{
for (i=0; valid_algos[i]; i++)
if (!strcmp (valid_algos[i], opt_algo))
break;
if (!valid_algos[i])
{
err = gpg_error (GPG_ERR_PUBKEY_ALGO);
log_info ("Invalid algorithm '%s' given. Use one:\n", opt_algo);
for (i=0; valid_algos[i]; i++)
if (!(i%5))
log_info (" %s%s", valid_algos[i], valid_algos[i+1]?",":".");
else
log_printf (" %s%s", valid_algos[i], valid_algos[i+1]?",":".");
log_info ("Note that the card may not support all of them.\n");
goto leave;
}
}
/* Upcase the keyref; if it misses the cardtype, prepend it. */
if (keyref)
{
if (!strchr (keyref, '.'))
keyref_buffer = xstrconcat (app_type_string (info->apptype), ".",
keyref, NULL);
else
keyref_buffer = xstrdup (keyref);
ascii_strupr (keyref_buffer);
keyref = keyref_buffer;
}
/* Special checks. */
if ((info->cardtype && !strcmp (info->cardtype, "yubikey"))
&& info->cardversion >= 0x040200 && info->cardversion < 0x040305)
{
log_error ("On-chip key generation on this YubiKey has been blocked.\n");
log_info ("Please see <https://yubi.co/ysa201701> for details\n");
err = gpg_error (GPG_ERR_NOT_SUPPORTED);
goto leave;
}
/* Divert to dedicated functions. */
if (info->apptype == APP_TYPE_OPENPGP)
{
if (opt_force || opt_algo || keyref)
log_info ("Note: Options are ignored for OpenPGP cards.\n");
err = generate_openpgp (info);
}
else if (!keyref)
err = gpg_error (GPG_ERR_INV_ID);
else
err = generate_generic (info, keyref, opt_force, opt_algo);
leave:
xfree (opt_algo);
xfree (keyref_buffer);
return err;
}
-/* Sub-menu to change a PIN. */
+/* Change a PIN. */
static gpg_error_t
cmd_passwd (card_info_t info, char *argstr)
{
- gpg_error_t err;
+ gpg_error_t err = 0;
char *answer = NULL;
- const char *pinref;
+ const char *pinref = NULL;
+ int reset_mode = 0;
+ int menu_used = 0;
if (!info)
return print_help
("PASSWD [PINREF]\n\n"
- "Menu to change or unblock the PINs. Note that the\n"
- "presented menu options depend on the type of card\n"
- "and whether the admin mode is enabled. For OpenPGP\n"
- "and PIV cards defaults for PINREF are available.",
+ "Change or unblock the PINs. Note that in interactive mode\n"
+ "and without a PINREF a menu is presented for certain cards;\n"
+ "in non-interactive and without a PINREF a default value is\n"
+ "used for these cards.",
0);
if (opt.interactive || opt.verbose)
log_info (_("%s card no. %s detected\n"),
app_type_string (info->apptype),
info->dispserialno? info->dispserialno : info->serialno);
- if (!*argstr && info->apptype == APP_TYPE_OPENPGP)
+ if (*argstr)
+ pinref = argstr;
+ else if (opt.interactive && info->apptype == APP_TYPE_OPENPGP)
{
- /* For an OpenPGP card we present the well known menu if no
- * argument is given. */
- for (;;)
+ menu_used = 1;
+ while (!pinref)
{
- tty_printf ("\n");
- tty_printf ("1 - change PIN\n"
- "2 - unblock and set new PIN\n"
- "3 - change Admin PIN\n"
- "4 - set the Reset Code\n"
- "Q - quit\n");
- tty_printf ("\n");
-
- err = 0;
xfree (answer);
- answer = tty_get (_("Your selection? "));
- tty_kill_prompt ();
- if (*answer == CONTROL_D)
- break; /* Quit. */
+ answer = get_selection ("1 - change the PIN\n"
+ "2 - unblock and set new a PIN\n"
+ "3 - change the Admin PIN\n"
+ "4 - set the Reset Code\n"
+ "Q - quit\n");
if (strlen (answer) != 1)
continue;
- if (*answer == 'q' || *answer == 'Q')
- break; /* Quit. */
-
- if (*answer == '1')
- {
- /* Change PIN (same as the direct thing in non-admin mode). */
- err = scd_change_pin ("OPENPGP.1", 0);
- if (err)
- log_error ("Error changing the PIN: %s\n", gpg_strerror (err));
- else
- log_info ("PIN changed.\n");
- }
+ else if (*answer == 'q' || *answer == 'Q')
+ goto leave;
+ else if (*answer == '1')
+ pinref = "OPENPGP.1";
else if (*answer == '2')
- {
- /* Unblock PIN by setting a new PIN. */
- err = scd_change_pin ("OPENPGP.1", 1);
- if (err)
- log_error ("Error unblocking the PIN: %s\n", gpg_strerror(err));
- else
- log_info ("PIN unblocked and new PIN set.\n");
- }
+ { pinref = "OPENPGP.1"; reset_mode = 1; }
else if (*answer == '3')
- {
- /* Change Admin PIN. */
- err = scd_change_pin ("OPENPGP.3", 0);
- if (err)
- log_error ("Error changing the PIN: %s\n", gpg_strerror (err));
- else
- log_info ("PIN changed.\n");
- }
+ pinref = "OPENPGP.3";
else if (*answer == '4')
- {
- /* Set a new Reset Code. */
- err = scd_change_pin ("OPENPGP.2", 1);
- if (err)
- log_error ("Error setting the Reset Code: %s\n",
- gpg_strerror (err));
- else
- log_info ("Reset Code set.\n");
- }
-
- } /*end for loop*/
+ { pinref = "OPENPGP.2"; reset_mode = 1; }
+ }
}
- else
+ else if (info->apptype == APP_TYPE_OPENPGP)
+ pinref = "OPENPGP.1";
+ else if (opt.interactive && info->apptype == APP_TYPE_PIV)
{
- if (*argstr)
- pinref = argstr;
- else if (info->apptype == APP_TYPE_PIV)
- pinref = "PIV.80";
- else
+ menu_used = 1;
+ while (!pinref)
{
- /* Note that we do not have a default value for OpenPGP
- * because we want to be mostly compatible to "gpg
- * --card-edit" and show a menu in that case (above). */
- err = gpg_error (GPG_ERR_MISSING_VALUE);
- goto leave;
+ xfree (answer);
+ answer = get_selection ("1 - change the PIN\n"
+ "2 - change the PUK\n"
+ "3 - change the Global PIN\n"
+ "Q - quit\n");
+ if (strlen (answer) != 1)
+ ;
+ else if (*answer == 'q' || *answer == 'Q')
+ goto leave;
+ else if (*answer == '1')
+ pinref = "PIV.80";
+ else if (*answer == '2')
+ pinref = "PIV.81";
+ else if (*answer == '3')
+ pinref = "PIV.00";
}
- err = scd_change_pin (pinref, 0);
- if (err)
- goto leave;
+ }
+ else if (info->apptype == APP_TYPE_PIV)
+ pinref = "PIV.80";
+ else
+ {
+ err = gpg_error (GPG_ERR_MISSING_VALUE);
+ goto leave;
+ }
- if (info->apptype == APP_TYPE_PIV
- && !ascii_strcasecmp (pinref, "PIV.81"))
+ err = scd_change_pin (pinref, reset_mode);
+ if (err)
+ {
+ if (!opt.interactive && !menu_used && !opt.verbose)
+ ;
+ else if (!ascii_strcasecmp (pinref, "PIV.81"))
+ log_error ("Error changing the PUK.\n");
+ else if (!ascii_strcasecmp (pinref, "OPENPGP.1") && reset_mode)
+ log_error ("Error unblocking the PIN.\n");
+ else if (!ascii_strcasecmp (pinref, "OPENPGP.2") && reset_mode)
+ log_error ("Error setting the Reset Code.\n");
+ else if (!ascii_strcasecmp (pinref, "OPENPGP.3"))
+ log_error ("Error changing the Admin PIN.\n");
+ else
+ log_error ("Error changing the PIN.\n");
+ }
+ else
+ {
+ if (!opt.interactive && !opt.verbose)
+ ;
+ else if (!ascii_strcasecmp (pinref, "PIV.81"))
log_info ("PUK changed.\n");
+ else if (!ascii_strcasecmp (pinref, "OPENPGP.1") && reset_mode)
+ log_info ("PIN unblocked and new PIN set.\n");
+ else if (!ascii_strcasecmp (pinref, "OPENPGP.2") && reset_mode)
+ log_info ("Reset Code set.\n");
+ else if (!ascii_strcasecmp (pinref, "OPENPGP.3"))
+ log_info ("Admin PIN changed.\n");
else
log_info ("PIN changed.\n");
}
leave:
xfree (answer);
return err;
}
static gpg_error_t
cmd_unblock (card_info_t info)
{
gpg_error_t err = 0;
if (!info)
return print_help
("UNBLOCK\n\n"
"Unblock a PIN using a PUK or Reset Code. Note that OpenPGP\n"
"cards prior to version 2 can't use this; instead the PASSWD\n"
"command can be used to set a new PIN.",
0);
if (opt.interactive || opt.verbose)
log_info (_("%s card no. %s detected\n"),
app_type_string (info->apptype),
info->dispserialno? info->dispserialno : info->serialno);
if (info->apptype == APP_TYPE_OPENPGP)
{
if (!info->is_v2)
{
log_error (_("This command is only available for version 2 cards\n"));
err = gpg_error (GPG_ERR_NOT_SUPPORTED);
}
else if (!info->chvinfo[1])
{
log_error (_("Reset Code not or not anymore available\n"));
err = gpg_error (GPG_ERR_PIN_BLOCKED);
}
else
{
err = scd_change_pin ("OPENPGP.2", 0);
if (!err)
log_info ("PIN changed.\n");
}
}
else if (info->apptype == APP_TYPE_PIV)
{
/* Unblock the Application PIN. */
err = scd_change_pin ("PIV.80", 1);
if (!err)
log_info ("PIN unblocked and changed.\n");
}
else
{
- log_info ("Unblocking not yet supported for '%s'\n",
+ log_info ("Unblocking not supported for '%s'.\n",
app_type_string (info->apptype));
err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
}
return err;
}
/* Note: On successful execution a redisplay should be scheduled. If
* this function fails the card may be in an unknown state. */
static gpg_error_t
cmd_factoryreset (card_info_t info)
{
gpg_error_t err;
char *answer = NULL;
int termstate = 0;
int any_apdu = 0;
int is_yubikey = 0;
int i;
if (!info)
return print_help
("FACTORY-RESET\n\n"
"Do a complete reset of some OpenPGP and PIV cards. This\n"
"deletes all data and keys and resets the PINs to their default.\n"
"This is mainly used by developers with scratch cards. Don't\n"
"worry, you need to confirm before the command proceeds.",
APP_TYPE_OPENPGP, APP_TYPE_PIV, 0);
/* We support the factory reset for most OpenPGP cards and Yubikeys
* with the PIV application. */
if (info->apptype == APP_TYPE_OPENPGP)
;
else if (info->apptype == APP_TYPE_PIV
&& info->cardtype && !strcmp (info->cardtype, "yubikey"))
is_yubikey = 1;
else
return gpg_error (GPG_ERR_NOT_SUPPORTED);
/* For an OpenPGP card the code below basically does the same what
* this gpg-connect-agent script does:
*
* scd reset
* scd serialno undefined
* scd apdu 00 A4 04 00 06 D2 76 00 01 24 01
* scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40
* scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40
* scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40
* scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40
* scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40
* scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40
* scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40
* scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40
* scd apdu 00 e6 00 00
* scd apdu 00 44 00 00
* scd reset
* /echo Card has been reset to factory defaults
*
* For a PIV application on a Yubikey it merely issues the Yubikey
* specific resset command.
*/
err = scd_learn (info);
if (gpg_err_code (err) == GPG_ERR_OBJ_TERM_STATE
&& gpg_err_source (err) == GPG_ERR_SOURCE_SCD)
termstate = 1;
else if (err)
{
log_error (_("OpenPGP card not available: %s\n"), gpg_strerror (err));
goto leave;
}
if (opt.interactive || opt.verbose)
log_info (_("%s card no. %s detected\n"),
app_type_string (info->apptype),
info->dispserialno? info->dispserialno : info->serialno);
if (!termstate || is_yubikey)
{
if (!is_yubikey)
{
if (!(info->status_indicator == 3 || info->status_indicator == 5))
{
/* Note: We won't see status-indicator 3 here because it
* is not possible to select a card application in
* termination state. */
log_error (_("This command is not supported by this card\n"));
err = gpg_error (GPG_ERR_NOT_SUPPORTED);
goto leave;
}
}
tty_printf ("\n");
log_info
(_("Note: This command destroys all keys stored on the card!\n"));
tty_printf ("\n");
xfree (answer);
answer = tty_get (_("Continue? (y/N) "));
tty_kill_prompt ();
trim_spaces (answer);
if (*answer == CONTROL_D
|| !answer_is_yes_no_default (answer, 0/*(default to no)*/))
{
err = gpg_error (GPG_ERR_CANCELED);
goto leave;
}
xfree (answer);
answer = tty_get (_("Really do a factory reset? (enter \"yes\") "));
tty_kill_prompt ();
trim_spaces (answer);
if (strcmp (answer, "yes") && strcmp (answer,_("yes")))
{
err = gpg_error (GPG_ERR_CANCELED);
goto leave;
}
if (is_yubikey)
{
/* The PIV application si already selected, we only need to
* send the special reset APDU after having blocked PIN and
* PUK. Note that blocking the PUK is done using the
* unblock PIN command. */
any_apdu = 1;
for (i=0; i < 5; i++)
send_apdu ("0020008008FFFFFFFFFFFFFFFF", "VERIFY", 0xffff,
NULL, NULL);
for (i=0; i < 5; i++)
send_apdu ("002C008010FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF",
"RESET RETRY COUNTER", 0xffff, NULL, NULL);
err = send_apdu ("00FB000001FF", "YUBIKEY RESET", 0, NULL, NULL);
if (err)
goto leave;
}
else /* OpenPGP card. */
{
any_apdu = 1;
/* We need to select a card application before we can send APDUs
* to the card without scdaemon doing anything on its own. */
err = send_apdu (NULL, "RESET", 0, NULL, NULL);
if (err)
goto leave;
err = send_apdu ("undefined", "dummy select ", 0, NULL, NULL);
if (err)
goto leave;
/* Select the OpenPGP application. */
err = send_apdu ("00A4040006D27600012401", "SELECT AID", 0,
NULL, NULL);
if (err)
goto leave;
/* Do some dummy verifies with wrong PINs to set the retry
* counter to zero. We can't easily use the card version 2.1
* feature of presenting the admin PIN to allow the terminate
* command because there is no machinery in scdaemon to catch
* the verify command and ask for the PIN when the "APDU"
* command is used.
* Here, the length of dummy wrong PIN is 32-byte, also
* supporting authentication with KDF DO. */
for (i=0; i < 4; i++)
send_apdu ("0020008120"
"40404040404040404040404040404040"
"40404040404040404040404040404040", "VERIFY", 0xffff,
NULL, NULL);
for (i=0; i < 4; i++)
send_apdu ("0020008320"
"40404040404040404040404040404040"
"40404040404040404040404040404040", "VERIFY", 0xffff,
NULL, NULL);
/* Send terminate datafile command. */
err = send_apdu ("00e60000", "TERMINATE DF", 0x6985, NULL, NULL);
if (err)
goto leave;
}
}
if (!is_yubikey)
{
any_apdu = 1;
/* Send activate datafile command. This is used without
* confirmation if the card is already in termination state. */
err = send_apdu ("00440000", "ACTIVATE DF", 0, NULL, NULL);
if (err)
goto leave;
}
/* Finally we reset the card reader once more. */
err = send_apdu (NULL, "RESET", 0, NULL, NULL);
if (err)
goto leave;
- /* Then, connect the card again (answer used as a dummy). */
- xfree (answer); answer = NULL;
- err = scd_serialno (&answer, NULL);
+ /* Then, connect the card again. */
+ err = scd_serialno (NULL, NULL);
leave:
if (err && any_apdu && !is_yubikey)
{
log_info ("Due to an error the card might be in an inconsistent state\n"
"You should run the LIST command to check this.\n");
/* FIXME: We need a better solution in the case that the card is
* in a termination state, i.e. the card was removed before the
* activate was sent. The best solution I found with v2.1
* Zeitcontrol card was to kill scdaemon and the issue this
* sequence with gpg-connect-agent:
* scd reset
* scd serialno undefined
* scd apdu 00A4040006D27600012401 (returns error)
* scd apdu 00440000
* Then kill scdaemon again and issue:
* scd reset
* scd serialno openpgp
*/
}
xfree (answer);
return err;
}
/* Generate KDF data. This is a helper for cmd_kdfsetup. */
static gpg_error_t
gen_kdf_data (unsigned char *data, int single_salt)
{
gpg_error_t err;
const unsigned char h0[] = { 0x81, 0x01, 0x03,
0x82, 0x01, 0x08,
0x83, 0x04 };
const unsigned char h1[] = { 0x84, 0x08 };
const unsigned char h2[] = { 0x85, 0x08 };
const unsigned char h3[] = { 0x86, 0x08 };
const unsigned char h4[] = { 0x87, 0x20 };
const unsigned char h5[] = { 0x88, 0x20 };
unsigned char *p, *salt_user, *salt_admin;
unsigned char s2k_char;
unsigned int iterations;
unsigned char count_4byte[4];
p = data;
s2k_char = encode_s2k_iterations (agent_get_s2k_count ());
iterations = S2K_DECODE_COUNT (s2k_char);
count_4byte[0] = (iterations >> 24) & 0xff;
count_4byte[1] = (iterations >> 16) & 0xff;
count_4byte[2] = (iterations >> 8) & 0xff;
count_4byte[3] = (iterations & 0xff);
memcpy (p, h0, sizeof h0);
p += sizeof h0;
memcpy (p, count_4byte, sizeof count_4byte);
p += sizeof count_4byte;
memcpy (p, h1, sizeof h1);
salt_user = (p += sizeof h1);
gcry_randomize (p, 8, GCRY_STRONG_RANDOM);
p += 8;
if (single_salt)
salt_admin = salt_user;
else
{
memcpy (p, h2, sizeof h2);
p += sizeof h2;
gcry_randomize (p, 8, GCRY_STRONG_RANDOM);
p += 8;
memcpy (p, h3, sizeof h3);
salt_admin = (p += sizeof h3);
gcry_randomize (p, 8, GCRY_STRONG_RANDOM);
p += 8;
}
memcpy (p, h4, sizeof h4);
p += sizeof h4;
err = gcry_kdf_derive (OPENPGP_USER_PIN_DEFAULT,
strlen (OPENPGP_USER_PIN_DEFAULT),
GCRY_KDF_ITERSALTED_S2K, GCRY_MD_SHA256,
salt_user, 8, iterations, 32, p);
p += 32;
if (!err)
{
memcpy (p, h5, sizeof h5);
p += sizeof h5;
err = gcry_kdf_derive (OPENPGP_ADMIN_PIN_DEFAULT,
strlen (OPENPGP_ADMIN_PIN_DEFAULT),
GCRY_KDF_ITERSALTED_S2K, GCRY_MD_SHA256,
salt_admin, 8, iterations, 32, p);
}
return err;
}
static gpg_error_t
cmd_kdfsetup (card_info_t info, char *argstr)
{
gpg_error_t err;
unsigned char kdf_data[OPENPGP_KDF_DATA_LENGTH_MAX];
int single = (*argstr != 0);
if (!info)
return print_help
("KDF-SETUP\n\n"
"Prepare the OpenPGP card KDF feature for this card.",
APP_TYPE_OPENPGP, 0);
if (info->apptype != APP_TYPE_OPENPGP)
{
log_info ("Note: This is an OpenPGP only command.\n");
return gpg_error (GPG_ERR_NOT_SUPPORTED);
}
if (!info->extcap.kdf)
{
log_error (_("This command is not supported by this card\n"));
err = gpg_error (GPG_ERR_NOT_SUPPORTED);
goto leave;
}
err = gen_kdf_data (kdf_data, single);
if (err)
goto leave;
err = scd_setattr ("KDF", kdf_data,
single ? OPENPGP_KDF_DATA_LENGTH_MIN
/* */ : OPENPGP_KDF_DATA_LENGTH_MAX);
if (err)
goto leave;
err = scd_getattr ("KDF", info);
leave:
return err;
}
static void
show_keysize_warning (void)
{
static int shown;
if (shown)
return;
shown = 1;
tty_printf
(_("Note: There is no guarantee that the card supports the requested\n"
" key type or size. If the key generation does not succeed,\n"
" please check the documentation of your card to see which\n"
" key types and sizes are supported.\n")
);
}
/* Ask for the size of a card key. NBITS is the current size
* configured for the card. Returns 0 on success and stored the
* chosen key size at R_KEYSIZE; 0 is stored to indicate that the
* default size shall be used. */
static gpg_error_t
ask_card_rsa_keysize (unsigned int nbits, unsigned int *r_keysize)
{
unsigned int min_nbits = 1024;
unsigned int max_nbits = 4096;
char*answer;
unsigned int req_nbits;
for (;;)
{
answer = tty_getf (_("What keysize do you want? (%u) "), nbits);
trim_spaces (answer);
tty_kill_prompt ();
if (*answer == CONTROL_D)
{
xfree (answer);
return gpg_error (GPG_ERR_CANCELED);
}
req_nbits = *answer? atoi (answer): nbits;
xfree (answer);
if (req_nbits != nbits && (req_nbits % 32) )
{
req_nbits = ((req_nbits + 31) / 32) * 32;
tty_printf (_("rounded up to %u bits\n"), req_nbits);
}
if (req_nbits == nbits)
{
/* Use default. */
*r_keysize = 0;
return 0;
}
if (req_nbits < min_nbits || req_nbits > max_nbits)
{
tty_printf (_("%s keysizes must be in the range %u-%u\n"),
"RSA", min_nbits, max_nbits);
}
else
{
*r_keysize = req_nbits;
return 0;
}
}
}
/* Ask for the key attribute of a card key. CURRENT is the current
* attribute configured for the card. KEYNO is the number of the key
* used to select the prompt. Stores NULL at result to use the
* default attribute or stores the selected attribute structure at
* RESULT. On error an error code is returned. */
static gpg_error_t
ask_card_keyattr (int keyno, const struct key_attr *current,
struct key_attr **result)
{
gpg_error_t err;
struct key_attr *key_attr = NULL;
char *answer = NULL;
int selection;
*result = NULL;
key_attr = xcalloc (1, sizeof *key_attr);
tty_printf (_("Changing card key attribute for: "));
if (keyno == 0)
tty_printf (_("Signature key\n"));
else if (keyno == 1)
tty_printf (_("Encryption key\n"));
else
tty_printf (_("Authentication key\n"));
tty_printf (_("Please select what kind of key you want:\n"));
tty_printf (_(" (%d) RSA\n"), 1 );
tty_printf (_(" (%d) ECC\n"), 2 );
for (;;)
{
xfree (answer);
answer = tty_get (_("Your selection? "));
trim_spaces (answer);
tty_kill_prompt ();
if (!*answer || *answer == CONTROL_D)
{
err = gpg_error (GPG_ERR_CANCELED);
goto leave;
}
selection = *answer? atoi (answer) : 0;
if (selection == 1 || selection == 2)
break;
else
tty_printf (_("Invalid selection.\n"));
}
if (selection == 1)
{
unsigned int nbits, result_nbits;
if (current->algo == PUBKEY_ALGO_RSA)
nbits = current->nbits;
else
nbits = 2048;
err = ask_card_rsa_keysize (nbits, &result_nbits);
if (err)
goto leave;
if (result_nbits == 0)
{
if (current->algo == PUBKEY_ALGO_RSA)
{
xfree (key_attr);
key_attr = NULL;
}
else
result_nbits = nbits;
}
if (key_attr)
{
key_attr->algo = PUBKEY_ALGO_RSA;
key_attr->nbits = result_nbits;
}
}
else if (selection == 2)
{
const char *curve;
/* const char *oid_str; */
int algo;
if (current->algo == PUBKEY_ALGO_RSA)
{
if (keyno == 1) /* Encryption key */
algo = PUBKEY_ALGO_ECDH;
else /* Signature key or Authentication key */
algo = PUBKEY_ALGO_ECDSA;
curve = NULL;
}
else
{
algo = current->algo;
curve = current->curve;
}
(void)curve;
(void)algo;
err = GPG_ERR_NOT_IMPLEMENTED;
goto leave;
/* FIXME: We need to move the ask_cure code out to common or
* provide another sultion. */
/* curve = ask_curve (&algo, NULL, curve); */
/* if (curve) */
/* { */
/* key_attr->algo = algo; */
/* oid_str = openpgp_curve_to_oid (curve, NULL); */
/* key_attr->curve = openpgp_oid_to_curve (oid_str, 0); */
/* } */
/* else */
/* { */
/* xfree (key_attr); */
/* key_attr = NULL; */
/* } */
}
else
{
err = gpg_error (GPG_ERR_BUG);
goto leave;
}
/* Tell the user what we are going to do. */
if (key_attr->algo == PUBKEY_ALGO_RSA)
{
tty_printf (_("The card will now be re-configured"
" to generate a key of %u bits\n"), key_attr->nbits);
}
else if (key_attr->algo == PUBKEY_ALGO_ECDH
|| key_attr->algo == PUBKEY_ALGO_ECDSA
|| key_attr->algo == PUBKEY_ALGO_EDDSA)
{
tty_printf (_("The card will now be re-configured"
" to generate a key of type: %s\n"), key_attr->curve);
}
show_keysize_warning ();
*result = key_attr;
key_attr = NULL;
leave:
xfree (key_attr);
xfree (answer);
return err;
}
/* Change the key attribute of key KEYNO (0..2) and show an error
* message if that fails. */
static gpg_error_t
do_change_keyattr (int keyno, const struct key_attr *key_attr)
{
gpg_error_t err = 0;
char args[100];
if (key_attr->algo == PUBKEY_ALGO_RSA)
snprintf (args, sizeof args, "--force %d 1 rsa%u", keyno+1,
key_attr->nbits);
else if (key_attr->algo == PUBKEY_ALGO_ECDH
|| key_attr->algo == PUBKEY_ALGO_ECDSA
|| key_attr->algo == PUBKEY_ALGO_EDDSA)
snprintf (args, sizeof args, "--force %d %d %s",
keyno+1, key_attr->algo, key_attr->curve);
else
{
/* FIXME: Above we use openpgp algo names but in the error
* message we use the gcrypt names. We should settle for a
* consistent solution. */
log_error (_("public key algorithm %d (%s) is not supported\n"),
key_attr->algo, gcry_pk_algo_name (key_attr->algo));
err = gpg_error (GPG_ERR_PUBKEY_ALGO);
goto leave;
}
err = scd_setattr ("KEY-ATTR", args, strlen (args));
if (err)
log_error (_("error changing key attribute for key %d: %s\n"),
keyno+1, gpg_strerror (err));
leave:
return err;
}
static gpg_error_t
cmd_keyattr (card_info_t info, char *argstr)
{
gpg_error_t err = 0;
int keyno;
struct key_attr *key_attr = NULL;
(void)argstr;
if (!info)
return print_help
("KEY-ATTR\n\n"
"Menu to change the key attributes of an OpenPGP card.",
APP_TYPE_OPENPGP, 0);
if (info->apptype != APP_TYPE_OPENPGP)
{
log_info ("Note: This is an OpenPGP only command.\n");
return gpg_error (GPG_ERR_NOT_SUPPORTED);
}
if (!(info->is_v2 && info->extcap.aac))
{
log_error (_("This command is not supported by this card\n"));
err = gpg_error (GPG_ERR_NOT_SUPPORTED);
goto leave;
}
for (keyno = 0; keyno < DIM (info->key_attr); keyno++)
{
xfree (key_attr);
key_attr = NULL;
err = ask_card_keyattr (keyno, &info->key_attr[keyno], &key_attr);
if (err)
goto leave;
err = do_change_keyattr (keyno, key_attr);
if (err)
{
/* Error: Better read the default key attribute again. */
log_debug ("FIXME\n");
/* Ask again for this key. */
keyno--;
}
}
leave:
xfree (key_attr);
return err;
}
static gpg_error_t
cmd_uif (card_info_t info, char *argstr)
{
gpg_error_t err;
int keyno;
if (!info)
return print_help
("UIF N [on|off|permanent]\n\n"
"Change the User Interaction Flag. N must in the range 1 to 3.",
APP_TYPE_OPENPGP, APP_TYPE_PIV, 0);
argstr = skip_options (argstr);
if (digitp (argstr))
{
keyno = atoi (argstr);
while (digitp (argstr))
argstr++;
while (spacep (argstr))
argstr++;
}
else
keyno = 0;
if (keyno < 1 || keyno > 3)
{
err = gpg_error (GPG_ERR_INV_ARG);
goto leave;
}
err = GPG_ERR_NOT_IMPLEMENTED;
leave:
return err;
}
static gpg_error_t
cmd_yubikey (card_info_t info, char *argstr)
{
gpg_error_t err, err2;
estream_t fp = opt.interactive? NULL : es_stdout;
char *words[20];
int nwords;
if (!info)
return print_help
("YUBIKEY <cmd> args\n\n"
"Various commands pertaining to Yubikey tokens with <cmd> being:\n"
"\n"
" LIST \n"
"\n"
"List supported and enabled applications.\n"
"\n"
" ENABLE usb|nfc|all [otp|u2f|opgp|piv|oath|fido2|all]\n"
" DISABLE usb|nfc|all [otp|u2f|opgp|piv|oath|fido2|all]\n"
"\n"
"Enable or disable the specified or all applications on the\n"
"given interface.",
0);
argstr = skip_options (argstr);
if (!info->cardtype || strcmp (info->cardtype, "yubikey"))
{
log_info ("This command can only be used with Yubikeys.\n");
err = gpg_error (GPG_ERR_NOT_SUPPORTED);
goto leave;
}
nwords = split_fields (argstr, words, DIM (words));
if (nwords < 1)
{
err = gpg_error (GPG_ERR_SYNTAX);
goto leave;
}
/* Note that we always do a learn to get a chance to the card back
* into a usable state. */
- err = yubikey_commands (fp, nwords, words);
+ err = yubikey_commands (info, fp, nwords, words);
err2 = scd_learn (info);
if (err2)
log_error ("Error re-reading card: %s\n", gpg_strerror (err));
leave:
return err;
}
/* Data used by the command parser. This needs to be outside of the
* function scope to allow readline based command completion. */
enum cmdids
{
cmdNOP = 0,
cmdQUIT, cmdHELP, cmdLIST, cmdRESET, cmdVERIFY,
cmdNAME, cmdURL, cmdFETCH, cmdLOGIN, cmdLANG, cmdSALUT, cmdCAFPR,
cmdFORCESIG, cmdGENERATE, cmdPASSWD, cmdPRIVATEDO, cmdWRITECERT,
cmdREADCERT, cmdWRITEKEY, cmdUNBLOCK, cmdFACTRST, cmdKDFSETUP,
cmdKEYATTR, cmdUIF, cmdAUTH, cmdYUBIKEY,
cmdINVCMD
};
static struct
{
const char *name;
enum cmdids id;
const char *desc;
} cmds[] = {
{ "quit" , cmdQUIT, N_("quit this menu")},
{ "q" , cmdQUIT, NULL },
{ "help" , cmdHELP, N_("show this help")},
{ "?" , cmdHELP, NULL },
{ "list" , cmdLIST, N_("list all available data")},
{ "l" , cmdLIST, NULL },
{ "name" , cmdNAME, N_("change card holder's name")},
{ "url" , cmdURL, N_("change URL to retrieve key")},
{ "fetch" , cmdFETCH, N_("fetch the key specified in the card URL")},
{ "login" , cmdLOGIN, N_("change the login name")},
{ "lang" , cmdLANG, N_("change the language preferences")},
{ "salutation",cmdSALUT, N_("change card holder's salutation")},
{ "salut" , cmdSALUT, NULL },
{ "cafpr" , cmdCAFPR , N_("change a CA fingerprint")},
{ "forcesig", cmdFORCESIG, N_("toggle the signature force PIN flag")},
{ "generate", cmdGENERATE, N_("generate new keys")},
{ "passwd" , cmdPASSWD, N_("menu to change or unblock the PIN")},
{ "verify" , cmdVERIFY, N_("verify the PIN and list all data")},
{ "unblock" , cmdUNBLOCK, N_("unblock the PIN using a Reset Code")},
{ "authenticate",cmdAUTH, N_("authenticate to the card")},
{ "auth" , cmdAUTH, NULL },
{ "reset" , cmdRESET, N_("send a reset to the card daemon")},
{ "factory-reset",cmdFACTRST, N_("destroy all keys and data")},
{ "kdf-setup", cmdKDFSETUP, N_("setup KDF for PIN authentication")},
{ "key-attr", cmdKEYATTR, N_("change the key attribute")},
{ "uif", cmdUIF, N_("change the User Interaction Flag")},
{ "privatedo", cmdPRIVATEDO, N_("change a private data object")},
{ "readcert", cmdREADCERT, N_("read a certificate from a data object")},
{ "writecert", cmdWRITECERT, N_("store a certificate to a data object")},
{ "writekey", cmdWRITEKEY, N_("store a private key to a data object")},
{ "yubikey", cmdYUBIKEY, N_("Yubikey management commands")},
{ NULL, cmdINVCMD, NULL }
};
/* The command line command dispatcher. */
static gpg_error_t
dispatch_command (card_info_t info, const char *orig_command)
{
gpg_error_t err = 0;
enum cmdids cmd; /* The command. */
char *command; /* A malloced copy of ORIG_COMMAND. */
char *argstr; /* The argument as a string. */
int i;
int ignore_error;
if ((ignore_error = *orig_command == '-'))
orig_command++;
command = xstrdup (orig_command);
argstr = NULL;
if ((argstr = strchr (command, ' ')))
{
*argstr++ = 0;
trim_spaces (command);
trim_spaces (argstr);
}
for (i=0; cmds[i].name; i++ )
if (!ascii_strcasecmp (command, cmds[i].name ))
break;
cmd = cmds[i].id; /* (If not found this will be cmdINVCMD). */
/* Make sure we have valid strings for the args. They are allowed
* to be modified and must thus point to a buffer. */
if (!argstr)
argstr = command + strlen (command);
/* For most commands we need to make sure that we have a card. */
if (!info)
; /* Help mode */
else if (!(cmd == cmdNOP || cmd == cmdQUIT || cmd == cmdHELP
|| cmd == cmdINVCMD)
&& !info->initialized)
{
err = scd_learn (info);
if (err)
{
log_error ("Error reading card: %s\n", gpg_strerror (err));
goto leave;
}
}
switch (cmd)
{
case cmdNOP:
if (!info)
print_help ("NOP\n\n"
"Dummy command.", 0);
break;
case cmdQUIT:
if (!info)
print_help ("QUIT\n\n"
"Stop processing.", 0);
else
{
err = gpg_error (GPG_ERR_EOF);
goto leave;
}
break;
case cmdHELP:
if (!info)
print_help ("HELP [command]\n\n"
"Show all commands. With an argument show help\n"
"for that command.", 0);
else if (*argstr)
dispatch_command (NULL, argstr);
else
{
es_printf
("List of commands (\"help <command>\" for details):\n");
for (i=0; cmds[i].name; i++ )
if(cmds[i].desc)
es_printf("%-14s %s\n", cmds[i].name, _(cmds[i].desc) );
es_printf ("Prefix a command with a dash to ignore its error.\n");
}
break;
- case cmdLIST:
- if (!info)
- print_help ("LIST\n\n"
- "Show content of the card.", 0);
- else
- {
- err = scd_learn (info);
- if (err)
- log_error ("Error reading card: %s\n", gpg_strerror (err));
- else
- list_card (info);
- }
- break;
-
case cmdRESET:
if (!info)
print_help ("RESET\n\n"
"Send a RESET to the card daemon.", 0);
else
{
flush_keyblock_cache ();
err = scd_apdu (NULL, NULL, NULL, NULL);
}
break;
+ case cmdLIST: err = cmd_list (info, argstr); break;
case cmdVERIFY: err = cmd_verify (info, argstr); break;
case cmdAUTH: err = cmd_authenticate (info, argstr); break;
case cmdNAME: err = cmd_name (info, argstr); break;
case cmdURL: err = cmd_url (info, argstr); break;
case cmdFETCH: err = cmd_fetch (info); break;
case cmdLOGIN: err = cmd_login (info, argstr); break;
case cmdLANG: err = cmd_lang (info, argstr); break;
case cmdSALUT: err = cmd_salut (info, argstr); break;
case cmdCAFPR: err = cmd_cafpr (info, argstr); break;
case cmdPRIVATEDO: err = cmd_privatedo (info, argstr); break;
case cmdWRITECERT: err = cmd_writecert (info, argstr); break;
case cmdREADCERT: err = cmd_readcert (info, argstr); break;
case cmdWRITEKEY: err = cmd_writekey (info, argstr); break;
case cmdFORCESIG: err = cmd_forcesig (info); break;
case cmdGENERATE: err = cmd_generate (info, argstr); break;
case cmdPASSWD: err = cmd_passwd (info, argstr); break;
case cmdUNBLOCK: err = cmd_unblock (info); break;
case cmdFACTRST: err = cmd_factoryreset (info); break;
case cmdKDFSETUP: err = cmd_kdfsetup (info, argstr); break;
case cmdKEYATTR: err = cmd_keyattr (info, argstr); break;
case cmdUIF: err = cmd_uif (info, argstr); break;
case cmdYUBIKEY: err = cmd_yubikey (info, argstr); break;
case cmdINVCMD:
default:
log_error (_("Invalid command (try \"help\")\n"));
break;
} /* End command switch. */
leave:
/* Return GPG_ERR_EOF only if its origin was "quit". */
es_fflush (es_stdout);
if (gpg_err_code (err) == GPG_ERR_EOF && cmd != cmdQUIT)
err = gpg_error (GPG_ERR_GENERAL);
if (err && gpg_err_code (err) != GPG_ERR_EOF)
{
if (ignore_error)
{
log_info ("Command '%s' failed: %s\n", command, gpg_strerror (err));
err = 0;
}
else
log_error ("Command '%s' failed: %s\n", command, gpg_strerror (err));
}
xfree (command);
return err;
}
/* The interactive main loop. */
static void
interactive_loop (void)
{
gpg_error_t err;
char *answer = NULL; /* The input line. */
enum cmdids cmd = cmdNOP; /* The command. */
char *argstr; /* The argument as a string. */
int redisplay = 1; /* Whether to redisplay the main info. */
char *help_arg = NULL; /* Argument of the HELP command. */
struct card_info_s info_buffer = { 0 };
card_info_t info = &info_buffer;
char *p;
int i;
/* In the interactive mode we do not want to print the program prefix. */
log_set_prefix (NULL, 0);
for (;;)
{
if (help_arg)
{
/* Clear info to indicate helpmode */
info = NULL;
}
else if (!info)
{
/* Get out of help. */
info = &info_buffer;
help_arg = NULL;
redisplay = 0;
}
else if (redisplay)
{
- err = scd_learn (info);
+ err = cmd_list (info, "");
if (err)
- {
- log_error ("Error reading card: %s\n", gpg_strerror (err));
- }
+ log_error ("Error reading card: %s\n", gpg_strerror (err));
else
{
- list_card (info);
tty_printf("\n");
redisplay = 0;
}
}
if (!info)
{
/* Copy the pending help arg into our answer. Noe that
* help_arg points into answer. */
p = xstrdup (help_arg);
help_arg = NULL;
xfree (answer);
answer = p;
}
else
{
do
{
xfree (answer);
tty_enable_completion (command_completion);
answer = tty_get (_("gpg/card> "));
tty_kill_prompt();
tty_disable_completion ();
trim_spaces(answer);
}
while ( *answer == '#' );
}
argstr = NULL;
if (!*answer)
cmd = cmdLIST; /* We default to the list command */
else if (*answer == CONTROL_D)
cmd = cmdQUIT;
else
{
if ((argstr = strchr (answer,' ')))
{
*argstr++ = 0;
trim_spaces (answer);
trim_spaces (argstr);
}
for (i=0; cmds[i].name; i++ )
if (!ascii_strcasecmp (answer, cmds[i].name ))
break;
cmd = cmds[i].id;
}
/* Make sure we have valid strings for the args. They are
* allowed to be modified and must thus point to a buffer. */
if (!argstr)
argstr = answer + strlen (answer);
if (!(cmd == cmdNOP || cmd == cmdQUIT || cmd == cmdHELP
|| cmd == cmdINVCMD))
{
/* If redisplay is set we know that there was an error reading
* the card. In this case we force a LIST command to retry. */
if (!info)
; /* In help mode. */
else if (redisplay)
{
cmd = cmdLIST;
}
else if (!info->serialno)
{
/* Without a serial number most commands won't work.
* Catch it here. */
tty_printf ("\n");
tty_printf ("Serial number missing\n");
continue;
}
}
err = 0;
switch (cmd)
{
case cmdNOP:
if (!info)
print_help ("NOP\n\n"
"Dummy command.", 0);
break;
case cmdQUIT:
if (!info)
print_help ("QUIT\n\n"
"Leave this tool.", 0);
else
{
tty_printf ("\n");
goto leave;
}
break;
case cmdHELP:
if (!info)
print_help ("HELP [command]\n\n"
"Show all commands. With an argument show help\n"
"for that command.", 0);
else if (*argstr)
help_arg = argstr; /* Trigger help for a command. */
else
{
tty_printf
("List of commands (\"help <command>\" for details):\n");
for (i=0; cmds[i].name; i++ )
if(cmds[i].desc)
tty_printf("%-14s %s\n", cmds[i].name, _(cmds[i].desc) );
}
break;
- case cmdLIST:
- if (!info)
- print_help ("LIST\n\n"
- "Show content of the card.", 0);
- else
- {
- /* Actual work is done by the redisplay code block. */
- redisplay = 1;
- }
- break;
-
case cmdRESET:
if (!info)
print_help ("RESET\n\n"
"Send a RESET to the card daemon.", 0);
else
{
flush_keyblock_cache ();
err = scd_apdu (NULL, NULL, NULL, NULL);
}
break;
+ case cmdLIST: err = cmd_list (info, argstr); break;
case cmdVERIFY:
err = cmd_verify (info, argstr);
if (!err)
redisplay = 1;
break;
case cmdAUTH: err = cmd_authenticate (info, argstr); break;
case cmdNAME: err = cmd_name (info, argstr); break;
case cmdURL: err = cmd_url (info, argstr); break;
case cmdFETCH: err = cmd_fetch (info); break;
case cmdLOGIN: err = cmd_login (info, argstr); break;
case cmdLANG: err = cmd_lang (info, argstr); break;
case cmdSALUT: err = cmd_salut (info, argstr); break;
case cmdCAFPR: err = cmd_cafpr (info, argstr); break;
case cmdPRIVATEDO: err = cmd_privatedo (info, argstr); break;
case cmdWRITECERT: err = cmd_writecert (info, argstr); break;
case cmdREADCERT: err = cmd_readcert (info, argstr); break;
case cmdWRITEKEY: err = cmd_writekey (info, argstr); break;
case cmdFORCESIG: err = cmd_forcesig (info); break;
case cmdGENERATE: err = cmd_generate (info, argstr); break;
case cmdPASSWD: err = cmd_passwd (info, argstr); break;
case cmdUNBLOCK: err = cmd_unblock (info); break;
case cmdFACTRST:
err = cmd_factoryreset (info);
if (!err)
redisplay = 1;
break;
case cmdKDFSETUP: err = cmd_kdfsetup (info, argstr); break;
case cmdKEYATTR: err = cmd_keyattr (info, argstr); break;
case cmdUIF: err = cmd_uif (info, argstr); break;
case cmdYUBIKEY: err = cmd_yubikey (info, argstr); break;
case cmdINVCMD:
default:
tty_printf ("\n");
tty_printf (_("Invalid command (try \"help\")\n"));
break;
} /* End command switch. */
if (gpg_err_code (err) == GPG_ERR_CANCELED)
tty_fprintf (NULL, "\n");
else if (err)
{
const char *s = "?";
for (i=0; cmds[i].name; i++ )
if (cmd == cmds[i].id)
{
s = cmds[i].name;
break;
}
log_error ("Command '%s' failed: %s\n", s, gpg_strerror (err));
}
} /* End of main menu loop. */
leave:
release_card_info (info);
xfree (answer);
}
#ifdef HAVE_LIBREADLINE
/* Helper function for readline's command completion. */
static char *
command_generator (const char *text, int state)
{
static int list_index, len;
const char *name;
/* If this is a new word to complete, initialize now. This includes
* saving the length of TEXT for efficiency, and initializing the
index variable to 0. */
if (!state)
{
list_index = 0;
len = strlen(text);
}
/* Return the next partial match */
while ((name = cmds[list_index].name))
{
/* Only complete commands that have help text. */
if (cmds[list_index++].desc && !strncmp (name, text, len))
return strdup(name);
}
return NULL;
}
/* Second helper function for readline's command completion. */
static char **
command_completion (const char *text, int start, int end)
{
(void)end;
/* If we are at the start of a line, we try and command-complete.
* If not, just do nothing for now. The support for help completion
* needs to be more smarter. */
if (!start)
return rl_completion_matches (text, command_generator);
else if (start == 5 && !ascii_strncasecmp (rl_line_buffer, "help ", 5))
return rl_completion_matches (text, command_generator);
rl_attempted_completion_over = 1;
return NULL;
}
#endif /*HAVE_LIBREADLINE*/
diff --git a/tools/gpg-card.h b/tools/gpg-card.h
index 099ea5448..35db14d25 100644
--- a/tools/gpg-card.h
+++ b/tools/gpg-card.h
@@ -1,230 +1,231 @@
/* gpg-card.h - Common definitions for the gpg-card-tool
* Copyright (C) 2019 g10 Code GmbH
*
* This file is part of GnuPG.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This file 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://gnu.org/licenses/>.
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#ifndef GNUPG_GPG_CARD_H
#define GNUPG_GPG_CARD_H
#include "../common/session-env.h"
/* We keep all global options in the structure OPT. */
struct
{
int interactive;
int verbose;
unsigned int debug;
int quiet;
int with_colons;
const char *gpg_program;
const char *gpgsm_program;
const char *agent_program;
int autostart;
/* Options passed to the gpg-agent: */
session_env_t session_env;
char *lc_ctype;
char *lc_messages;
} opt;
/* Debug values and macros. */
#define DBG_IPC_VALUE 1024 /* Debug assuan communication. */
#define DBG_EXTPROG_VALUE 16384 /* Debug external program calls */
#define DBG_IPC (opt.debug & DBG_IPC_VALUE)
#define DBG_EXTPROG (opt.debug & DBG_EXTPROG_VALUE)
/* The maximum length of a binary fingerprint. */
#define MAX_FINGERPRINT_LEN 32
/*
* Data structures to store keyblocks (aka certificates).
*/
struct pubkey_s
{
struct pubkey_s *next; /* The next key. */
unsigned char grip[KEYGRIP_LEN];
unsigned char fpr[MAX_FINGERPRINT_LEN];
unsigned char fprlen; /* The used length of a FPR. */
unsigned int grip_valid:1;/* The grip is valid. */
unsigned int requested: 1;/* This is the requested grip. */
};
typedef struct pubkey_s *pubkey_t;
struct userid_s
{
struct userid_s *next;
char *value; /* Malloced. */
};
typedef struct userid_s *userid_t;
struct keyblock_s
{
struct keyblock_s *next; /* Allow to link several keyblocks. */
int protocol; /* GPGME_PROTOCOL_OPENPGP or _CMS. */
pubkey_t keys; /* The key. For OpenPGP primary + list of subkeys. */
userid_t uids; /* The list of user ids. */
};
typedef struct keyblock_s *keyblock_t;
/* Enumeration of the known card application types. */
typedef enum
{
APP_TYPE_NONE, /* Not yet known or for direct APDU sending. */
APP_TYPE_OPENPGP,
APP_TYPE_NKS,
APP_TYPE_DINSIG,
APP_TYPE_P15,
APP_TYPE_GELDKARTE,
APP_TYPE_SC_HSM,
APP_TYPE_PIV,
APP_TYPE_UNKNOWN /* Unknown by this tool. */
} app_type_t;
/* OpenPGP card key attributes. */
struct key_attr
{
int algo; /* Algorithm identifier. */
union {
unsigned int nbits; /* Supported keysize. */
const char *curve; /* Name of curve. */
};
};
/* An object to store information pertaining to a key pair as stored
* on a card. This is commonly used as a linked list with all keys
* known for the current card. */
struct key_info_s
{
struct key_info_s *next;
unsigned char grip[20];/* The keygrip. */
unsigned char xflag; /* Temporary flag to help processing a list. */
/* The three next items are mostly useful for OpenPGP cards. */
unsigned char fprlen; /* Use length of the next item. */
unsigned char fpr[32]; /* The binary fingerprint of length FPRLEN. */
u32 created; /* The time the key was created. */
unsigned int usage; /* Usage flags. (GCRY_PK_USAGE_*) */
char keyref[1]; /* String with the keyref (e.g. OPENPGP.1). */
};
typedef struct key_info_s *key_info_t;
/*
* The object used to store information about a card.
*/
struct card_info_s
{
int initialized; /* True if a learn command was successful. */
int error; /* private. */
char *reader; /* Reader information. */
char *cardtype; /* NULL or type of the card. */
unsigned int cardversion; /* Firmware version of the card. */
char *apptypestr; /* Malloced application type string. */
app_type_t apptype;/* Translated from APPTYPESTR. */
unsigned int appversion; /* Version of the application. */
char *serialno; /* malloced hex string. */
char *dispserialno;/* malloced string. */
char *disp_name; /* malloced. */
char *disp_lang; /* malloced. */
int disp_sex; /* 0 = unspecified, 1 = male, 2 = female */
char *pubkey_url; /* malloced. */
char *login_data; /* malloced. */
char *private_do[4]; /* malloced. */
char cafpr1len; /* Length of the CA-fingerprint or 0 if invalid. */
char cafpr2len;
char cafpr3len;
char cafpr1[20];
char cafpr2[20];
char cafpr3[20];
key_info_t kinfo; /* Linked list with all keypair related data. */
unsigned long sig_counter;
int chv1_cached; /* For openpgp this is true if a PIN is not
required for each signing. Note that the
gpg-agent might cache it anyway. */
int is_v2; /* True if this is a v2 openpgp card. */
int chvmaxlen[3]; /* Maximum allowed length of a CHV. */
int chvinfo[3]; /* Allowed retries for the CHV; 0 = blocked. */
unsigned char chvusage[2]; /* Data object 5F2F */
struct key_attr key_attr[3]; /* OpenPGP card key attributes. */
struct {
unsigned int ki:1; /* Key import available. */
unsigned int aac:1; /* Algorithm attributes are changeable. */
unsigned int kdf:1; /* KDF object to support PIN hashing available. */
unsigned int bt:1; /* Button for confirmation available. */
} extcap;
unsigned int status_indicator;
int kdf_do_enabled; /* True if card has a KDF object. */
int uif[3]; /* True if User Interaction Flag is on. */
};
typedef struct card_info_s *card_info_t;
/*-- card-keys.c --*/
void release_keyblock (keyblock_t keyblock);
void flush_keyblock_cache (void);
gpg_error_t get_matching_keys (const unsigned char *keygrip, int protocol,
keyblock_t *r_keyblock);
gpg_error_t test_get_matching_keys (const char *hexgrip);
/*-- card-misc.c --*/
key_info_t find_kinfo (card_info_t info, const char *keyref);
void *hex_to_buffer (const char *string, size_t *r_length);
gpg_error_t send_apdu (const char *hexapdu, const char *desc,
unsigned int ignore,
unsigned char **r_data, size_t *r_datalen);
/*-- card-call-scd.c --*/
void release_card_info (card_info_t info);
const char *app_type_string (app_type_t app_type);
gpg_error_t scd_apdu (const char *hexapdu, unsigned int *r_sw,
unsigned char **r_data, size_t *r_datalen);
gpg_error_t scd_learn (card_info_t info);
gpg_error_t scd_getattr (const char *name, struct card_info_s *info);
gpg_error_t scd_setattr (const char *name,
const unsigned char *value, size_t valuelen);
gpg_error_t scd_writecert (const char *certidstr,
const unsigned char *certdata, size_t certdatalen);
gpg_error_t scd_writekey (const char *keyref, int force, const char *keygrip);
gpg_error_t scd_genkey (const char *keyref, int force, const char *algo,
u32 *createtime);
gpg_error_t scd_serialno (char **r_serialno, const char *demand);
gpg_error_t scd_readcert (const char *certidstr,
void **r_buf, size_t *r_buflen);
gpg_error_t scd_readkey (const char *keyrefstr, gcry_sexp_t *r_result);
gpg_error_t scd_cardlist (strlist_t *result);
gpg_error_t scd_change_pin (const char *pinref, int reset_mode);
gpg_error_t scd_checkpin (const char *serialno);
unsigned long agent_get_s2k_count (void);
/*-- card-yubikey.c --*/
-gpg_error_t yubikey_commands (estream_t fp, int argc, char *argv[]);
+gpg_error_t yubikey_commands (card_info_t info,
+ estream_t fp, int argc, char *argv[]);
#endif /*GNUPG_GPG_CARD_H*/
diff --git a/tools/gpg-pair-tool.c b/tools/gpg-pair-tool.c
index 347b29d24..9a781def1 100644
--- a/tools/gpg-pair-tool.c
+++ b/tools/gpg-pair-tool.c
@@ -1,2020 +1,2020 @@
/* gpg-pair-tool.c - The tool to run the pairing protocol.
* Copyright (C) 2018 g10 Code GmbH
*
* This file is part of GnuPG.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This file 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
/* Protocol:
*
* Initiator Responder
* | |
* | COMMIT |
* |-------------------->|
* | |
* | DHPART1 |
* |<--------------------|
* | |
* | DHPART2 |
* |-------------------->|
* | |
* | CONFIRM |
* |<--------------------|
* | |
*
* The initiator creates a keypar (PKi,SKi) and sends this COMMIT
* message to the responder:
*
* 7 byte Magic, value: "GPG-pa1"
* 1 byte MessageType, value 1 (COMMIT)
* 8 byte SessionId, value: 8 random bytes
* 1 byte Realm, value 1
* 2 byte reserved, value 0
* 5 byte ExpireTime, value: seconds since Epoch as an unsigned int.
* 32 byte Hash(PKi)
*
* The initiator also needs to locally store the sessionid, the realm,
* the expiration time, the keypair and a hash of the entire message
* sent.
*
* The responder checks that the received message has not expired and
* stores sessionid, realm, expiretime and the Hash(PKi). The
* Responder then creates and locally stores its own keypair (PKr,SKr)
* and sends the DHPART1 message back:
*
* 7 byte Magic, value: "GPG-pa1"
* 1 byte MessageType, value 2 (DHPART1)
* 8 byte SessionId from COMMIT message
* 32 byte PKr
* 32 byte Hash(Hash(COMMIT) || DHPART1[0..47])
*
* Note that Hash(COMMIT) is the hash over the entire received COMMIT
* message. DHPART1[0..47] are the first 48 bytes of the created
* DHPART1 message.
*
* The Initiator receives the DHPART1 message and checks that the hash
* matches. Although this hash is easily malleable it is later in the
* protocol used to assert the integrity of all messages. The
* Initiator then computes the shared master secret from its SKi and
* the received PKr. Using this master secret several keys are
* derived:
*
* - HMACi-key using the label "GPG-pa1-HMACi-key".
* - SYMx-key using the label "GPG-pa1-SYMx-key"
*
* For details on the KDF see the implementation of the function kdf.
* The master secret is stored securily in the local state. The
* DHPART2 message is then created and send to the Responder:
*
* 7 byte Magic, value: "GPG-pa1"
* 1 byte MessageType, value 3 (DHPART2)
* 8 byte SessionId from COMMIT message
* 32 byte PKi
* 32 byte MAC(HMACi-key, Hash(DHPART1) || DHPART2[0..47] || SYMx-key)
*
* The Responder receives the DHPART2 message and checks that the hash
* of the received PKi matches the Hash(PKi) value as received earlier
* with the COMMIT message. The Responder now also computes the
* shared master secret from its SKr and the recived PKi and derives
* the keys:
*
* - HMACi-key using the label "GPG-pa1-HMACi-key".
* - HMACr-key using the label "GPG-pa1-HMACr-key".
* - SYMx-key using the label "GPG-pa1-SYMx-key"
* - SAS using the label "GPG-pa1-SAS"
*
* With these keys the MAC from the received DHPART2 message is
* checked. On success a SAS is displayed to the user and a CONFIRM
* message send back:
*
* 7 byte Magic, value: "GPG-pa1"
* 1 byte MessageType, value 4 (CONFIRM)
* 8 byte SessionId from COMMIT message
* 32 byte MAC(HMACr-key, Hash(DHPART2) || CONFIRM[0..15] || SYMx-key)
*
* The Initiator receives this CONFIRM message, gets the master shared
* secrey from its local state and derives the keys. It checks the
* the MAC in the received CONFIRM message and ask the user to enter
* the SAS as displayed by the responder. Iff the SAS matches the
* master key is flagged as confirmed and the Initiator may now use a
* derived key to send encrypted data to the Responder.
*
* In case the Responder also needs to send encrypted data we need to
* introduce another final message to tell the responder that the
* Initiator validated the SAS.
*
* TODO: Encrypt the state files using a key stored in gpg-agent's cache.
*
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <dirent.h>
#include <stdarg.h>
#include "../common/util.h"
#include "../common/status.h"
#include "../common/i18n.h"
#include "../common/sysutils.h"
#include "../common/init.h"
#include "../common/name-value.h"
/* Constants to identify the commands and options. */
enum cmd_and_opt_values
{
aNull = 0,
oQuiet = 'q',
oVerbose = 'v',
oOutput = 'o',
oArmor = 'a',
aInitiate = 400,
aRespond = 401,
aGet = 402,
aCleanup = 403,
oDebug = 500,
oStatusFD,
oHomedir,
oSAS,
oDummy
};
/* The list of commands and options. */
static gpgrt_opt_t opts[] = {
ARGPARSE_group (300, ("@Commands:\n ")),
ARGPARSE_c (aInitiate, "initiate", N_("initiate a pairing request")),
ARGPARSE_c (aRespond, "respond", N_("respond to a pairing request")),
ARGPARSE_c (aGet, "get", N_("return the keys")),
ARGPARSE_c (aCleanup, "cleanup", N_("remove expired states etc.")),
ARGPARSE_group (301, ("@\nOptions:\n ")),
ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")),
ARGPARSE_s_n (oQuiet, "quiet", N_("be somewhat more quiet")),
ARGPARSE_s_n (oArmor, "armor", N_("create ascii armored output")),
ARGPARSE_s_s (oSAS, "sas", N_("|SAS|the SAS as shown by the peer")),
ARGPARSE_s_s (oDebug, "debug", "@"),
ARGPARSE_s_s (oOutput, "output", N_("|FILE|write the request to FILE")),
ARGPARSE_s_i (oStatusFD, "status-fd", N_("|FD|write status info to this FD")),
ARGPARSE_s_s (oHomedir, "homedir", "@"),
ARGPARSE_end ()
};
/* We keep all global options in the structure OPT. */
static struct
{
int verbose;
unsigned int debug;
int quiet;
int armor;
const char *output;
estream_t statusfp;
unsigned int ttl;
const char *sas;
} opt;
/* Debug values and macros. */
#define DBG_MESSAGE_VALUE 2 /* Debug the messages. */
#define DBG_CRYPTO_VALUE 4 /* Debug low level crypto. */
#define DBG_MEMORY_VALUE 32 /* Debug memory allocation stuff. */
#define DBG_MESSAGE (opt.debug & DBG_MESSAGE_VALUE)
#define DBG_CRYPTO (opt.debug & DBG_CRYPTO_VALUE)
/* The list of supported debug flags. */
static struct debug_flags_s debug_flags [] =
{
{ DBG_MESSAGE_VALUE, "message" },
{ DBG_CRYPTO_VALUE , "crypto" },
{ DBG_MEMORY_VALUE , "memory" },
{ 0, NULL }
};
/* The directory name below the cache dir to store paring states. */
#define PAIRING_STATE_DIR "state"
/* Message types. */
#define MSG_TYPE_COMMIT 1
#define MSG_TYPE_DHPART1 2
#define MSG_TYPE_DHPART2 3
#define MSG_TYPE_CONFIRM 4
/* Realm values. */
#define REALM_STANDARD 1
/* Local prototypes. */
static void wrong_args (const char *text) GPGRT_ATTR_NORETURN;
static void xnvc_set_printf (nvc_t nvc, const char *name, const char *format,
...) GPGRT_ATTR_PRINTF(3,4);
static void *hash_data (void *result, size_t resultsize,
...) GPGRT_ATTR_SENTINEL(0);
static void *hmac_data (void *result, size_t resultsize,
const unsigned char *key, size_t keylen,
...) GPGRT_ATTR_SENTINEL(0);
static gpg_error_t command_initiate (void);
static gpg_error_t command_respond (void);
static gpg_error_t command_cleanup (void);
static gpg_error_t command_get (const char *sessionidstr);
/* Print usage information and provide strings for help. */
static const char *
my_strusage( int level )
{
const char *p;
switch (level)
{
case 9: p = "LGPL-2.1-or-later"; break;
case 11: p = "gpg-pair-tool"; break;
case 12: p = "@GNUPG@"; break;
case 13: p = VERSION; break;
case 14: p = GNUPG_DEF_COPYRIGHT_LINE; break;
case 17: p = PRINTABLE_OS_NAME; break;
case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break;
case 1:
case 40:
p = ("Usage: gpg-pair-tool [command] [options] [args] (-h for help)");
break;
case 41:
p = ("Syntax: gpg-pair-tool [command] [options] [args]\n"
"Client to run the pairing protocol\n");
break;
default: p = NULL; break;
}
return p;
}
static void
wrong_args (const char *text)
{
es_fprintf (es_stderr, _("usage: %s [options] %s\n"), strusage (11), text);
exit (2);
}
/* Set the status FD. */
static void
set_status_fd (int fd)
{
static int last_fd = -1;
if (fd != -1 && last_fd == fd)
return;
if (opt.statusfp && opt.statusfp != es_stdout && opt.statusfp != es_stderr)
es_fclose (opt.statusfp);
opt.statusfp = NULL;
if (fd == -1)
return;
if (fd == 1)
opt.statusfp = es_stdout;
else if (fd == 2)
opt.statusfp = es_stderr;
else
opt.statusfp = es_fdopen (fd, "w");
if (!opt.statusfp)
{
log_fatal ("can't open fd %d for status output: %s\n",
fd, gpg_strerror (gpg_error_from_syserror ()));
}
last_fd = fd;
}
/* Write a status line with code NO followed by the outout of the
* printf style FORMAT. The caller needs to make sure that LFs and
* CRs are not printed. */
static void
write_status (int no, const char *format, ...)
{
va_list arg_ptr;
if (!opt.statusfp)
return; /* Not enabled. */
es_fputs ("[GNUPG:] ", opt.statusfp);
es_fputs (get_status_string (no), opt.statusfp);
if (format)
{
es_putc (' ', opt.statusfp);
va_start (arg_ptr, format);
es_vfprintf (opt.statusfp, format, arg_ptr);
va_end (arg_ptr);
}
es_putc ('\n', opt.statusfp);
}
/* gpg-pair-tool main. */
int
main (int argc, char **argv)
{
gpg_error_t err;
gpgrt_argparse_t pargs = { &argc, &argv };
enum cmd_and_opt_values cmd = 0;
opt.ttl = 8*3600; /* Default to 8 hours. */
gnupg_reopen_std ("gpg-pair-tool");
gpgrt_set_strusage (my_strusage);
log_set_prefix ("gpg-pair-tool", GPGRT_LOG_WITH_PREFIX);
/* Make sure that our subsystems are ready. */
i18n_init();
init_common_subsystems (&argc, &argv);
/* Parse the command line. */
while (gpgrt_argparse (NULL, &pargs, opts))
{
switch (pargs.r_opt)
{
case oQuiet: opt.quiet = 1; break;
case oVerbose: opt.verbose++; break;
case oArmor: opt.armor = 1; break;
case oDebug:
if (parse_debug_flag (pargs.r.ret_str, &opt.debug, debug_flags))
{
pargs.r_opt = ARGPARSE_INVALID_ARG;
pargs.err = ARGPARSE_PRINT_ERROR;
}
break;
case oOutput:
opt.output = pargs.r.ret_str;
break;
case oStatusFD:
set_status_fd (translate_sys2libc_fd_int (pargs.r.ret_int, 1));
break;
case oHomedir:
gnupg_set_homedir (pargs.r.ret_str);
break;
case oSAS:
opt.sas = pargs.r.ret_str;
break;
case aInitiate:
case aRespond:
case aGet:
case aCleanup:
if (cmd && cmd != pargs.r_opt)
log_error (_("conflicting commands\n"));
else
cmd = pargs.r_opt;
break;
default: pargs.err = ARGPARSE_PRINT_WARNING; break;
}
}
/* Print a warning if an argument looks like an option. */
if (!opt.quiet && !(pargs.flags & ARGPARSE_FLAG_STOP_SEEN))
{
int i;
for (i=0; i < argc; i++)
if (argv[i][0] == '-' && argv[i][1] == '-')
log_info (("NOTE: '%s' is not considered an option\n"), argv[i]);
}
gpgrt_argparse (NULL, &pargs, NULL); /* Free internal memory. */
if (opt.sas)
{
if (strlen (opt.sas) != 11
|| !digitp (opt.sas+0) || !digitp (opt.sas+1) || !digitp (opt.sas+2)
|| opt.sas[3] != '-'
|| !digitp (opt.sas+4) || !digitp (opt.sas+5) || !digitp (opt.sas+6)
|| opt.sas[7] != '-'
|| !digitp (opt.sas+8) || !digitp (opt.sas+9) || !digitp (opt.sas+10))
log_error ("invalid formatted SAS\n");
}
/* Stop if any error, inclduing ARGPARSE_PRINT_WARNING, occurred. */
if (log_get_errorcount (0))
exit (2);
if (DBG_CRYPTO)
gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 1|2);
/* Now run the requested command. */
switch (cmd)
{
case aInitiate:
if (argc)
wrong_args ("--initiate");
err = command_initiate ();
break;
case aRespond:
if (argc)
wrong_args ("--respond");
err = command_respond ();
break;
case aGet:
if (argc > 1)
wrong_args ("--respond [sessionid]");
err = command_get (argc? *argv:NULL);
break;
case aCleanup:
if (argc)
wrong_args ("--cleanup");
err = command_cleanup ();
break;
default:
gpgrt_usage (1);
err = 0;
break;
}
if (err)
write_status (STATUS_FAILURE, "- %u", err);
else if (log_get_errorcount (0))
write_status (STATUS_FAILURE, "- %u", GPG_ERR_GENERAL);
else
write_status (STATUS_SUCCESS, NULL);
return log_get_errorcount (0)? 1:0;
}
/* Wrapper around nvc_new which terminates in the error case. */
static nvc_t
xnvc_new (void)
{
nvc_t c = nvc_new ();
if (!c)
log_fatal ("error creating NVC object: %s\n",
gpg_strerror (gpg_error_from_syserror ()));
return c;
}
/* Wrapper around nvc_set which terminates in the error case. */
static void
xnvc_set (nvc_t nvc, const char *name, const char *value)
{
gpg_error_t err = nvc_set (nvc, name, value);
if (err)
log_fatal ("error updating NVC object: %s\n", gpg_strerror (err));
}
/* Call vnc_set with (BUFFER, BUFLEN) converted to a hex string as
* value. Terminates in the error case. */
static void
xnvc_set_hex (nvc_t nvc, const char *name, const void *buffer, size_t buflen)
{
char *hex;
hex = bin2hex (buffer, buflen, NULL);
if (!hex)
xoutofcore ();
strlwr (hex);
xnvc_set (nvc, name, hex);
xfree (hex);
}
/* Call nvc_set with a value created from the string generated using
* the printf style FORMAT. Terminates in the error case. */
static void
xnvc_set_printf (nvc_t nvc, const char *name, const char *format, ...)
{
va_list arg_ptr;
char *buffer;
va_start (arg_ptr, format);
if (gpgrt_vasprintf (&buffer, format, arg_ptr) < 0)
log_fatal ("estream_asprintf failed: %s\n",
gpg_strerror (gpg_error_from_syserror ()));
va_end (arg_ptr);
xnvc_set (nvc, name, buffer);
xfree (buffer);
}
/* Return the string for the first entry in NVC with NAME. If NAME is
* missing, an empty string is returned. The returned string is a
* pointer into NVC. */
static const char *
xnvc_get_string (nvc_t nvc, const char *name)
{
nve_t item;
if (!nvc)
return "";
item = nvc_lookup (nvc, name);
if (!item)
return "";
return nve_value (item);
}
/* Return a string for MSGTYPE. */
const char *
msgtypestr (int msgtype)
{
switch (msgtype)
{
case MSG_TYPE_COMMIT: return "Commit";
case MSG_TYPE_DHPART1: return "DHPart1";
case MSG_TYPE_DHPART2: return "DHPart2";
case MSG_TYPE_CONFIRM: return "Confirm";
}
return "?";
}
/* Private to {get,set}_session_id(). */
static struct {
int initialized;
unsigned char sessid[8];
} session_id;
/* Return the 8 octet session. */
static unsigned char *
get_session_id (void)
{
if (!session_id.initialized)
{
session_id.initialized = 1;
gcry_create_nonce (session_id.sessid, sizeof session_id.sessid);
}
return session_id.sessid;
}
static void
set_session_id (const void *sessid, size_t len)
{
log_assert (!session_id.initialized);
if (len > sizeof session_id.sessid)
len = sizeof session_id.sessid;
memcpy (session_id.sessid, sessid, len);
if (len < sizeof session_id.sessid)
memset (session_id.sessid+len, 0, sizeof session_id.sessid - len);
session_id.initialized = 1;
}
/* Return a string with the hexified session id. */
static const char *
get_session_id_hex (void)
{
static char hexstr[16+1];
bin2hex (get_session_id (), 8, hexstr);
strlwr (hexstr);
return hexstr;
}
/* Return a fixed string with the directory used to store the state of
* pairings. On error a diagnostic is printed but the file name is
* returned anyway. It is expected that the expected failure of the
* following open is responsible for error handling. */
static const char *
get_pairing_statedir (void)
{
static char *fname;
gpg_error_t err = 0;
char *tmpstr;
struct stat statbuf;
if (fname)
return fname;
fname = make_filename (gnupg_homedir (), GNUPG_CACHE_DIR, NULL);
if (stat (fname, &statbuf) && errno == ENOENT)
{
if (gnupg_mkdir (fname, "-rwx"))
{
err = gpg_error_from_syserror ();
log_error (_("can't create directory '%s': %s\n"),
fname, gpg_strerror (err) );
}
else if (!opt.quiet)
log_info (_("directory '%s' created\n"), fname);
}
tmpstr = make_filename (fname, PAIRING_STATE_DIR, NULL);
xfree (fname);
fname = tmpstr;
if (stat (fname, &statbuf) && errno == ENOENT)
{
if (gnupg_mkdir (fname, "-rwx"))
{
if (!err)
{
err = gpg_error_from_syserror ();
log_error (_("can't create directory '%s': %s\n"),
fname, gpg_strerror (err) );
}
}
else if (!opt.quiet)
log_info (_("directory '%s' created\n"), fname);
}
return fname;
}
/* Open the pairing state file. SESSIONID is a 8 byte buffer with the
* session-id. If CREATE_FLAG is set the file is created and will
* always return a valid stream. If CREATE_FLAG is not set the file
* is opened for reading and writing. If the file does not exist NULL
* is return; in all other error cases the process is terminated. If
* R_FNAME is not NULL the name of the file is stored there and the
* caller needs to free it. */
static estream_t
open_pairing_state (const unsigned char *sessionid, int create_flag,
char **r_fname)
{
gpg_error_t err;
char *fname, *tmpstr;
estream_t fp;
/* The filename is the session id with a "pa1" suffix. Note that
* the state dir may eventually be used for other purposes as well
* and thus the suffix identifies that the file belongs to this
* tool. We use lowercase file names for no real reason. */
tmpstr = bin2hex (sessionid, 8, NULL);
if (!tmpstr)
xoutofcore ();
strlwr (tmpstr);
fname = xstrconcat (tmpstr, ".pa1", NULL);
xfree (tmpstr);
tmpstr = make_filename (get_pairing_statedir (), fname, NULL);
xfree (fname);
fname = tmpstr;
fp = es_fopen (fname, create_flag? "wbx,mode=-rw": "rb+,mode=-rw");
if (!fp)
{
err = gpg_error_from_syserror ();
if (create_flag)
{
/* We should always be able to create a file. Also we use a
* 64 bit session id, it is theoretically possible that such
* a session already exists. However, that is rare enough
* and thus the fatal error message should still be okay. */
log_fatal ("can't create '%s': %s\n", fname, gpg_strerror (err));
}
else if (gpg_err_code (err) == GPG_ERR_ENOENT)
{
/* That is an expected error; return NULL. */
}
else
{
log_fatal ("can't open '%s': %s\n", fname, gpg_strerror (err));
}
}
if (r_fname)
*r_fname = fname;
else
xfree (fname);
return fp;
}
/* Write the state to a possible new state file. */
static void
write_state (nvc_t state, int create_flag)
{
gpg_error_t err;
char *fname = NULL;
estream_t fp;
fp = open_pairing_state (get_session_id (), create_flag, &fname);
log_assert (fp);
err = nvc_write (state, fp);
if (err)
{
es_fclose (fp);
gnupg_remove (fname);
log_fatal ("error writing '%s': %s\n", fname, gpg_strerror (err));
}
/* If we did not create the file, we need to truncate the file. */
if (!create_flag && ftruncate (es_fileno (fp), es_ftello (fp)))
{
err = gpg_error_from_syserror ();
log_fatal ("error truncating '%s': %s\n", fname, gpg_strerror (err));
}
if (es_ferror (fp) || es_fclose (fp))
{
err = gpg_error_from_syserror ();
es_fclose (fp);
gnupg_remove (fname);
log_fatal ("error writing '%s': %s\n", fname, gpg_strerror (err));
}
}
/* Read the state into a newly allocated state object and store that
* at R_STATE. If no state is available GPG_ERR_NOT_FOUND is returned
* and as with all errors NULL is tored at R_STATE. SESSIONID is an
* input with the 8 session id. */
static gpg_error_t
read_state (nvc_t *r_state)
{
gpg_error_t err;
char *fname = NULL;
estream_t fp;
nvc_t state = NULL;
nve_t item;
const char *value;
unsigned long expire;
*r_state = NULL;
fp = open_pairing_state (get_session_id (), 0, &fname);
if (!fp)
return gpg_error (GPG_ERR_NOT_FOUND);
err = nvc_parse (&state, NULL, fp);
if (err)
{
log_info ("failed to parse state file '%s': %s\n",
fname, gpg_strerror (err));
goto leave;
}
/* Check whether the state already expired. */
item = nvc_lookup (state, "Expires:");
if (!item)
{
log_info ("invalid state file '%s': %s\n",
fname, "field 'expire' not found");
goto leave;
}
value = nve_value (item);
if (!value || !(expire = strtoul (value, NULL, 10)))
{
log_info ("invalid state file '%s': %s\n",
fname, "field 'expire' has an invalid value");
goto leave;
}
if (expire <= gnupg_get_time ())
{
es_fclose (fp);
fp = NULL;
if (gnupg_remove (fname))
{
err = gpg_error_from_syserror ();
log_info ("failed to delete state file '%s': %s\n",
fname, gpg_strerror (err));
}
else if (opt.verbose)
log_info ("state file '%s' deleted\n", fname);
err = gpg_error (GPG_ERR_NOT_FOUND);
goto leave;
}
*r_state = state;
state = NULL;
leave:
nvc_release (state);
es_fclose (fp);
return err;
}
/* Send (MSG,MSGLEN) to the output device. */
static void
send_message (const unsigned char *msg, size_t msglen)
{
gpg_error_t err;
if (opt.verbose)
log_info ("session %s: sending %s message\n",
get_session_id_hex (), msgtypestr (msg[7]));
if (DBG_MESSAGE)
log_printhex (msg, msglen, "send msg(%s):", msgtypestr (msg[7]));
/* FIXME: For now only stdout. */
if (opt.armor)
{
gpgrt_b64state_t state;
state = gpgrt_b64enc_start (es_stdout, "");
if (!state)
log_fatal ("error setting up base64 encoder: %s\n",
gpg_strerror (gpg_error_from_syserror ()));
err = gpgrt_b64enc_write (state, msg, msglen);
if (!err)
err = gpgrt_b64enc_finish (state);
if (err)
log_fatal ("error writing base64 to stdout: %s\n", gpg_strerror (err));
}
else
{
if (es_fwrite (msg, msglen, 1, es_stdout) != 1)
log_fatal ("error writing to stdout: %s\n",
gpg_strerror (gpg_error_from_syserror ()));
}
es_fputc ('\n', es_stdout);
}
/* Read a message from stdin and store it at the address (R_MSG,
* R_MSGLEN). This function detects armoring and removes it. On
* error NULL is stored at R_MSG, a diagnostic printed and an error
* code returned. The returned message has a proper message type and
* an appropriate length. The message type is stored at R_MSGTYPE and
* if a state is availabale it is stored at R_STATE. */
static gpg_error_t
read_message (unsigned char **r_msg, size_t *r_msglen, int *r_msgtype,
nvc_t *r_state)
{
gpg_error_t err;
unsigned char msg[128]; /* max msg size is 80 but 107 with base64. */
size_t msglen;
size_t reqlen;
*r_msg = NULL;
*r_state = NULL;
es_setvbuf (es_stdin, NULL, _IONBF, 0);
es_set_binary (es_stdin);
if (es_read (es_stdin, msg, sizeof msg, &msglen))
{
err = gpg_error_from_syserror ();
log_error ("error reading from message: %s\n", gpg_strerror (err));
return err;
}
if (msglen > 4 && !memcmp (msg, "R1BH", 4))
{
/* This is base64 of the first 3 bytes. */
gpgrt_b64state_t state = gpgrt_b64dec_start (NULL);
if (!state)
log_fatal ("error setting up base64 decoder: %s\n",
gpg_strerror (gpg_error_from_syserror ()));
err = gpgrt_b64dec_proc (state, msg, msglen, &msglen);
gpgrt_b64dec_finish (state);
if (err)
{
log_error ("error decoding message: %s\n", gpg_strerror (err));
return err;
}
}
if (msglen < 16 || memcmp (msg, "GPG-pa1", 7))
{
log_error ("error parsing message: %s\n",
msglen? "invalid header":"empty message");
return gpg_error (GPG_ERR_INV_RESPONSE);
}
switch (msg[7])
{
case MSG_TYPE_COMMIT: reqlen = 56; break;
case MSG_TYPE_DHPART1: reqlen = 80; break;
case MSG_TYPE_DHPART2: reqlen = 80; break;
case MSG_TYPE_CONFIRM: reqlen = 48; break;
default:
log_error ("error parsing message: %s\n", "invalid message type");
return gpg_error (GPG_ERR_INV_RESPONSE);
}
if (msglen < reqlen)
{
log_error ("error parsing message: %s\n", "message too short");
return gpg_error (GPG_ERR_INV_RESPONSE);
}
if (DBG_MESSAGE)
log_printhex (msg, msglen, "recv msg(%s):", msgtypestr (msg[7]));
/* Note that we ignore any garbage at the end of a message. */
msglen = reqlen;
set_session_id (msg+8, 8);
if (opt.verbose)
log_info ("session %s: received %s message\n",
get_session_id_hex (), msgtypestr (msg[7]));
/* Read the state. */
err = read_state (r_state);
if (err && gpg_err_code (err) != GPG_ERR_NOT_FOUND)
return err;
*r_msg = xmalloc (msglen);
memcpy (*r_msg, msg, msglen);
*r_msglen = msglen;
*r_msgtype = msg[7];
return err;
}
/* Display the Short Authentication String (SAS). If WAIT is true the
* function waits until the user has entered the SAS as seen at the
* peer.
*
* To construct the SAS we take the 4 most significant octets of HASH,
* interpret them as a 32 bit big endian unsigned integer, divide that
* integer by 10^9 and take the remainder. The remainder is displayed
* as 3 groups of 3 decimal digits delimited by a hyphens. This gives
* a search space of close to 2^30 and is still easy to compare.
*/
static gpg_error_t
display_sas (const unsigned char *hash, size_t hashlen, int wait)
{
- gpg_error_t err;
+ gpg_error_t err = 0;
unsigned long sas = 0;
char sasbuf[12];
log_assert (hashlen >= 4);
sas |= (unsigned long)hash[20] << 24;
sas |= (unsigned long)hash[21] << 16;
sas |= (unsigned long)hash[22] << 8;
sas |= (unsigned long)hash[23];
sas %= 1000000000ul;
snprintf (sasbuf, sizeof sasbuf, "%09lu", sas);
memmove (sasbuf+8, sasbuf+6, 3);
memmove (sasbuf+4, sasbuf+3, 3);
sasbuf[3] = sasbuf[7] = '-';
sasbuf[11] = 0;
if (wait)
log_info ("Please check the SAS:\n");
else
log_info ("Please note the SAS:\n");
log_info ("\n");
log_info (" %s\n", sasbuf);
log_info ("\n");
if (wait)
{
if (!opt.sas || strcmp (sasbuf, opt.sas))
err = gpg_error (GPG_ERR_NOT_CONFIRMED);
else
log_info ("SAS confirmed\n");
}
if (err)
log_info ("checking SAS failed: %s\n", gpg_strerror (err));
return err;
}
static gpg_error_t
create_dh_keypair (unsigned char *dh_secret, size_t dh_secret_len,
unsigned char *dh_public, size_t dh_public_len)
{
gpg_error_t err;
gcry_sexp_t sexp;
gcry_sexp_t s_keypair;
gcry_buffer_t secret;
gcry_buffer_t public;
unsigned char publicbuf[33];
/* We need a temporary buffer for the public key. Check the length
* for the later memcpy. */
if (dh_public_len < 32)
return gpg_error (GPG_ERR_BUFFER_TOO_SHORT);
secret.size = dh_secret_len;
secret.data = dh_secret;
secret.off = 0;
public.size = sizeof publicbuf;
public.data = publicbuf;
public.off = 0;
err = gcry_sexp_build (&sexp, NULL,
"(genkey(ecc(curve Curve25519)(flags djb-tweak)))");
if (err)
return err;
err = gcry_pk_genkey (&s_keypair, sexp);
gcry_sexp_release (sexp);
if (err)
return err;
err = gcry_sexp_extract_param (s_keypair, "key-data!private-key",
"&dq", &secret, &public, NULL);
gcry_sexp_release (s_keypair);
if (err)
return err;
/* Gcrypt prepends a 0x40 indicator - remove that. */
if (public.len == 33)
{
public.len = 32;
memmove (public.data, publicbuf+1, 32);
}
memcpy (dh_public, public.data, public.len);
if (DBG_CRYPTO)
{
log_printhex (secret.data, secret.len, "DH secret:");
log_printhex (public.data, public.len, "DH public:");
}
return 0;
}
/* SHA256 the data given as varargs tuples of (const void*, size_t)
* and store the result in RESULT. The end of the list is indicated
* by a NULL element in a tuple. RESULTLEN gives the length of the
* RESULT buffer which must be at least 32. Note that the second item
* of the tuple is the length and it is a size_t. */
static void *
hash_data (void *result, size_t resultsize, ...)
{
va_list arg_ptr;
gpg_error_t err;
gcry_md_hd_t hd;
const void *data;
size_t datalen;
log_assert (resultsize >= 32);
err = gcry_md_open (&hd, GCRY_MD_SHA256, 0);
if (err)
log_fatal ("error creating a Hash handle: %s\n", gpg_strerror (err));
/* log_printhex ("", 0, "Hash-256:"); */
va_start (arg_ptr, resultsize);
while ((data = va_arg (arg_ptr, const void *)))
{
datalen = va_arg (arg_ptr, size_t);
/* log_printhex (data, datalen, " data:"); */
gcry_md_write (hd, data, datalen);
}
va_end (arg_ptr);
memcpy (result, gcry_md_read (hd, 0), 32);
/* log_printhex (result, 32, " result:"); */
gcry_md_close (hd);
return result;
}
/* HMAC-SHA256 the data given as varargs tuples of (const void*,
* size_t) using (KEYLEN,KEY) and store the result in RESULT. The end
* of the list is indicated by a NULL element in a tuple. RESULTLEN
* gives the length of the RESULT buffer which must be at least 32.
* Note that the second item of the tuple is the length and it is a
* size_t. */
static void *
hmac_data (void *result, size_t resultsize,
const unsigned char *key, size_t keylen, ...)
{
va_list arg_ptr;
gpg_error_t err;
gcry_mac_hd_t hd;
const void *data;
size_t datalen;
log_assert (resultsize >= 32);
err = gcry_mac_open (&hd, GCRY_MAC_HMAC_SHA256, 0, NULL);
if (err)
log_fatal ("error creating a MAC handle: %s\n", gpg_strerror (err));
err = gcry_mac_setkey (hd, key, keylen);
if (err)
log_fatal ("error setting the MAC key: %s\n", gpg_strerror (err));
/* log_printhex (key, keylen, "HMAC-key:"); */
va_start (arg_ptr, keylen);
while ((data = va_arg (arg_ptr, const void *)))
{
datalen = va_arg (arg_ptr, size_t);
/* log_printhex (data, datalen, " data:"); */
err = gcry_mac_write (hd, data, datalen);
if (err)
log_fatal ("error writing to the MAC handle: %s\n", gpg_strerror (err));
}
va_end (arg_ptr);
err = gcry_mac_read (hd, result, &resultsize);
if (err || resultsize != 32)
log_fatal ("error reading MAC value: %s\n", gpg_strerror (err));
/* log_printhex (result, resultsize, " result:"); */
gcry_mac_close (hd);
return result;
}
/* Key derivation function:
*
* FIXME(doc)
*/
static void
kdf (unsigned char *result, size_t resultlen,
const unsigned char *master, size_t masterlen,
const unsigned char *sessionid, size_t sessionidlen,
const unsigned char *expire, size_t expirelen,
const char *label)
{
log_assert (masterlen == 32 && sessionidlen == 8 && expirelen == 5);
log_assert (*label);
log_assert (resultlen == 32);
hmac_data (result, resultlen, master, masterlen,
"\x00\x00\x00\x01", (size_t)4, /* Counter=1*/
label, strlen (label) + 1, /* Label, 0x00 */
sessionid, sessionidlen, /* Context */
expire, expirelen, /* Context */
"\x00\x00\x01\x00", (size_t)4, /* L=256 */
NULL);
}
static gpg_error_t
compute_master_secret (unsigned char *master, size_t masterlen,
const unsigned char *sk_a, size_t sk_a_len,
const unsigned char *pk_b, size_t pk_b_len)
{
gpg_error_t err;
gcry_sexp_t s_sk_a = NULL;
gcry_sexp_t s_pk_b = NULL;
gcry_sexp_t s_shared = NULL;
gcry_sexp_t s_tmp;
const char *s;
size_t n;
log_assert (masterlen == 32);
err = gcry_sexp_build (&s_sk_a, NULL, "%b", (int)sk_a_len, sk_a);
if (!err)
err = gcry_sexp_build (&s_pk_b, NULL,
"(public-key(ecdh(curve Curve25519)"
" (flags djb-tweak)(q%b)))",
(int)pk_b_len, pk_b);
if (err)
{
log_error ("error building S-expression: %s\n", gpg_strerror (err));
goto leave;
}
err = gcry_pk_encrypt (&s_shared, s_sk_a, s_pk_b);
if (err)
{
log_error ("error computing DH: %s\n", gpg_strerror (err));
goto leave;
}
/* gcry_log_debugsxp ("sk_a", s_sk_a); */
/* gcry_log_debugsxp ("pk_b", s_pk_b); */
/* gcry_log_debugsxp ("shared", s_shared); */
s_tmp = gcry_sexp_find_token (s_shared, "s", 0);
if (!s_tmp || !(s = gcry_sexp_nth_data (s_tmp, 1, &n))
|| n != 33 || s[0] != 0x40)
{
err = gpg_error (GPG_ERR_INTERNAL);
log_error ("error computing DH: %s\n", gpg_strerror (err));
goto leave;
}
memcpy (master, s+1, 32);
leave:
gcry_sexp_release (s_sk_a);
gcry_sexp_release (s_pk_b);
gcry_sexp_release (s_shared);
return err;
}
/* We are the Initiator: Create the commit message. This function
* sends the COMMIT message and writes STATE. */
static gpg_error_t
make_msg_commit (nvc_t state)
{
gpg_error_t err;
uint64_t now, expire;
unsigned char secret[32];
unsigned char public[32];
unsigned char *newmsg;
size_t newmsglen;
unsigned char tmphash[32];
err = create_dh_keypair (secret, sizeof secret, public, sizeof public );
if (err)
log_error ("creating DH keypair failed: %s\n", gpg_strerror (err));
now = gnupg_get_time ();
expire = now + opt.ttl;
newmsglen = 7+1+8+1+2+5+32;
newmsg = xmalloc (newmsglen);
memcpy (newmsg+0, "GPG-pa1", 7);
newmsg[7] = MSG_TYPE_COMMIT;
memcpy (newmsg+8, get_session_id (), 8);
newmsg[16] = REALM_STANDARD;
newmsg[17] = 0;
newmsg[18] = 0;
newmsg[19] = expire >> 32;
newmsg[20] = expire >> 24;
newmsg[21] = expire >> 16;
newmsg[22] = expire >> 8;
newmsg[23] = expire;
gcry_md_hash_buffer (GCRY_MD_SHA256, newmsg+24, public, 32);
/* Create the state file. */
xnvc_set (state, "State:", "Commit-sent");
xnvc_set_printf (state, "Created:", "%llu", (unsigned long long)now);
xnvc_set_printf (state, "Expires:", "%llu", (unsigned long long)expire);
xnvc_set_hex (state, "DH-PKi:", public, 32);
xnvc_set_hex (state, "DH-SKi:", secret, 32);
gcry_md_hash_buffer (GCRY_MD_SHA256, tmphash, newmsg, newmsglen);
xnvc_set_hex (state, "Hash-Commit:", tmphash, 32);
/* Write the state. Note that we need to create it. The state
* updating should in theory be done atomically with send_message.
* However, we can't assure that the message will actually be
* delivered and thus it doesn't matter whether we have an already
* update state when we later fail in send_message. */
write_state (state, 1);
/* Write the message. */
send_message (newmsg, newmsglen);
xfree (newmsg);
return err;
}
/* We are the Responder: Process a commit message in (MSG,MSGLEN)
* which has already been validated to have a correct header and
* message type. Sends the DHPart1 message and writes STATE. */
static gpg_error_t
proc_msg_commit (nvc_t state, const unsigned char *msg, size_t msglen)
{
gpg_error_t err;
uint64_t now, expire;
unsigned char tmphash[32];
unsigned char secret[32];
unsigned char public[32];
unsigned char *newmsg = NULL;
size_t newmsglen;
log_assert (msglen >= 56);
now = gnupg_get_time ();
/* Check that the message has not expired. */
expire = (uint64_t)msg[19] << 32;
expire |= (uint64_t)msg[20] << 24;
expire |= (uint64_t)msg[21] << 16;
expire |= (uint64_t)msg[22] << 8;
expire |= (uint64_t)msg[23];
if (expire < now)
{
log_error ("received %s message is too old\n",
msgtypestr (MSG_TYPE_COMMIT));
err = gpg_error (GPG_ERR_TOO_OLD);
goto leave;
}
/* Create the response. */
err = create_dh_keypair (secret, sizeof secret, public, sizeof public );
if (err)
{
log_error ("creating DH keypair failed: %s\n", gpg_strerror (err));
goto leave;
}
newmsglen = 7+1+8+32+32;
newmsg = xmalloc (newmsglen);
memcpy (newmsg+0, "GPG-pa1", 7);
newmsg[7] = MSG_TYPE_DHPART1;
memcpy (newmsg+8, msg + 8, 8); /* SessionID. */
memcpy (newmsg+16, public, 32); /* PKr */
/* Hash(Hash(Commit) || DHPart1[0..47]) */
gcry_md_hash_buffer (GCRY_MD_SHA256, tmphash, msg, msglen);
hash_data (newmsg+48, 32,
tmphash, sizeof tmphash,
newmsg, (size_t)48,
NULL);
/* Update the state. */
xnvc_set (state, "State:", "DHPart1-sent");
xnvc_set_printf (state, "Created:", "%llu", (unsigned long long)now);
xnvc_set_printf (state, "Expires:", "%llu", (unsigned long long)expire);
xnvc_set_hex (state, "Hash-PKi:", msg+24, 32);
xnvc_set_hex (state, "DH-PKr:", public, 32);
xnvc_set_hex (state, "DH-SKr:", secret, 32);
gcry_md_hash_buffer (GCRY_MD_SHA256, tmphash, newmsg, newmsglen);
xnvc_set_hex (state, "Hash-DHPart1:", tmphash, 32);
/* Write the state. Note that we need to create it. */
write_state (state, 1);
/* Write the message. */
send_message (newmsg, newmsglen);
leave:
xfree (newmsg);
return err;
}
/* We are the Initiator: Process a DHPART1 message in (MSG,MSGLEN)
* which has already been validated to have a correct header and
* message type. Sends the DHPart2 message and writes STATE. */
static gpg_error_t
proc_msg_dhpart1 (nvc_t state, const unsigned char *msg, size_t msglen)
{
gpg_error_t err;
unsigned char hash[32];
unsigned char tmphash[32];
unsigned char pki[32];
unsigned char pkr[32];
unsigned char ski[32];
unsigned char master[32];
uint64_t expire;
unsigned char expirebuf[5];
unsigned char hmacikey[32];
unsigned char symxkey[32];
unsigned char *newmsg = NULL;
size_t newmsglen;
log_assert (msglen >= 80);
/* Check that the message includes the Hash(Commit). */
if (hex2bin (xnvc_get_string (state, "Hash-Commit:"), hash, sizeof hash) < 0)
{
err = gpg_error (GPG_ERR_INV_VALUE);
log_error ("no or garbled 'Hash-Commit' in our state file\n");
goto leave;
}
hash_data (tmphash, 32,
hash, sizeof hash,
msg, (size_t)48,
NULL);
if (memcmp (msg+48, tmphash, 32))
{
err = gpg_error (GPG_ERR_BAD_DATA);
log_error ("manipulation of received %s message detected: %s\n",
msgtypestr (MSG_TYPE_DHPART1), "Bad Hash");
goto leave;
}
/* Check that the received PKr is different from our PKi and copy
* PKr into PKR. */
if (hex2bin (xnvc_get_string (state, "DH-PKi:"), pki, sizeof pki) < 0)
{
err = gpg_error (GPG_ERR_INV_VALUE);
log_error ("no or garbled 'DH-PKi' in our state file\n");
goto leave;
}
if (!memcmp (msg+16, pki, 32))
{
/* This can only happen if the state file leaked to the
* responder. */
err = gpg_error (GPG_ERR_BAD_DATA);
log_error ("received our own public key PKi instead of PKr\n");
goto leave;
}
memcpy (pkr, msg+16, 32);
/* Put the expire value into a buffer. */
expire = string_to_u64 (xnvc_get_string (state, "Expires:"));
if (!expire)
{
err = gpg_error (GPG_ERR_INV_VALUE);
log_error ("no 'Expire' in our state file\n");
goto leave;
}
expirebuf[0] = expire >> 32;
expirebuf[1] = expire >> 24;
expirebuf[2] = expire >> 16;
expirebuf[3] = expire >> 8;
expirebuf[4] = expire;
/* Get our secret from the state. */
if (hex2bin (xnvc_get_string (state, "DH-SKi:"), ski, sizeof ski) < 0)
{
err = gpg_error (GPG_ERR_INV_VALUE);
log_error ("no or garbled 'DH-SKi' in our state file\n");
goto leave;
}
/* Compute the shared secrets. */
err = compute_master_secret (master, sizeof master,
ski, sizeof ski, pkr, sizeof pkr);
if (err)
{
log_error ("creating DH keypair failed: %s\n", gpg_strerror (err));
goto leave;
}
kdf (hmacikey, sizeof hmacikey,
master, sizeof master, msg+8, 8, expirebuf, sizeof expirebuf,
"GPG-pa1-HMACi-key");
kdf (symxkey, sizeof symxkey,
master, sizeof master, msg+8, 8, expirebuf, sizeof expirebuf,
"GPG-pa1-SYMx-key");
/* Create the response. */
newmsglen = 7+1+8+32+32;
newmsg = xmalloc (newmsglen);
memcpy (newmsg+0, "GPG-pa1", 7);
newmsg[7] = MSG_TYPE_DHPART2;
memcpy (newmsg+8, msg + 8, 8); /* SessionID. */
memcpy (newmsg+16, pki, 32); /* PKi */
/* MAC(HMACi-key, Hash(DHPART1) || DHPART2[0..47] || SYMx-key) */
gcry_md_hash_buffer (GCRY_MD_SHA256, tmphash, msg, msglen);
hmac_data (newmsg+48, 32, hmacikey, sizeof hmacikey,
tmphash, sizeof tmphash,
newmsg, (size_t)48,
symxkey, sizeof symxkey,
NULL);
/* Update the state. */
xnvc_set (state, "State:", "DHPart2-sent");
xnvc_set_hex (state, "DH-Master:", master, sizeof master);
gcry_md_hash_buffer (GCRY_MD_SHA256, tmphash, newmsg, newmsglen);
xnvc_set_hex (state, "Hash-DHPart2:", tmphash, 32);
/* Write the state. */
write_state (state, 0);
/* Write the message. */
send_message (newmsg, newmsglen);
leave:
xfree (newmsg);
return err;
}
/* We are the Responder: Process a DHPART2 message in (MSG,MSGLEN)
* which has already been validated to have a correct header and
* message type. Sends the CONFIRM message and writes STATE. */
static gpg_error_t
proc_msg_dhpart2 (nvc_t state, const unsigned char *msg, size_t msglen)
{
gpg_error_t err;
unsigned char hash[32];
unsigned char tmphash[32];
uint64_t expire;
unsigned char expirebuf[5];
unsigned char pki[32];
unsigned char pkr[32];
unsigned char skr[32];
unsigned char master[32];
unsigned char hmacikey[32];
unsigned char hmacrkey[32];
unsigned char symxkey[32];
unsigned char sas[32];
unsigned char *newmsg = NULL;
size_t newmsglen;
log_assert (msglen >= 80);
/* Check that the PKi in the message matches the Hash(Pki) received
* with the Commit message. */
memcpy (pki, msg + 16, 32);
gcry_md_hash_buffer (GCRY_MD_SHA256, hash, pki, 32);
if (hex2bin (xnvc_get_string (state, "Hash-PKi:"),
tmphash, sizeof tmphash) < 0)
{
err = gpg_error (GPG_ERR_INV_VALUE);
log_error ("no or garbled 'Hash-PKi' in our state file\n");
goto leave;
}
if (memcmp (hash, tmphash, 32))
{
err = gpg_error (GPG_ERR_BAD_DATA);
log_error ("Initiator sent a different key in %s than announced in %s\n",
msgtypestr (MSG_TYPE_DHPART2),
msgtypestr (MSG_TYPE_COMMIT));
goto leave;
}
/* Check that the received PKi is different from our PKr. */
if (hex2bin (xnvc_get_string (state, "DH-PKr:"), pkr, sizeof pkr) < 0)
{
err = gpg_error (GPG_ERR_INV_VALUE);
log_error ("no or garbled 'DH-PKr' in our state file\n");
goto leave;
}
if (!memcmp (pkr, pki, 32))
{
err = gpg_error (GPG_ERR_BAD_DATA);
log_error ("Initiator sent our own PKr back\n");
goto leave;
}
/* Put the expire value into a buffer. */
expire = string_to_u64 (xnvc_get_string (state, "Expires:"));
if (!expire)
{
err = gpg_error (GPG_ERR_INV_VALUE);
log_error ("no 'Expire' in our state file\n");
goto leave;
}
expirebuf[0] = expire >> 32;
expirebuf[1] = expire >> 24;
expirebuf[2] = expire >> 16;
expirebuf[3] = expire >> 8;
expirebuf[4] = expire;
/* Get our secret from the state. */
if (hex2bin (xnvc_get_string (state, "DH-SKr:"), skr, sizeof skr) < 0)
{
err = gpg_error (GPG_ERR_INV_VALUE);
log_error ("no or garbled 'DH-SKr' in our state file\n");
goto leave;
}
/* Compute the shared secrets. */
err = compute_master_secret (master, sizeof master,
skr, sizeof skr, pki, sizeof pki);
if (err)
{
log_error ("creating DH keypair failed: %s\n", gpg_strerror (err));
goto leave;
}
kdf (hmacikey, sizeof hmacikey,
master, sizeof master, msg+8, 8, expirebuf, sizeof expirebuf,
"GPG-pa1-HMACi-key");
kdf (hmacrkey, sizeof hmacrkey,
master, sizeof master, msg+8, 8, expirebuf, sizeof expirebuf,
"GPG-pa1-HMACr-key");
kdf (symxkey, sizeof symxkey,
master, sizeof master, msg+8, 8, expirebuf, sizeof expirebuf,
"GPG-pa1-SYMx-key");
kdf (sas, sizeof sas,
master, sizeof master, msg+8, 8, expirebuf, sizeof expirebuf,
"GPG-pa1-SAS");
/* Check the MAC from the message which is
* MAC(HMACi-key, Hash(DHPART1) || DHPART2[0..47] || SYMx-key).
* For that we need to fetch the stored hash from the state. */
if (hex2bin (xnvc_get_string (state, "Hash-DHPart1:"),
tmphash, sizeof tmphash) < 0)
{
err = gpg_error (GPG_ERR_INV_VALUE);
log_error ("no or garbled 'Hash-DHPart1' in our state file\n");
goto leave;
}
hmac_data (hash, 32, hmacikey, sizeof hmacikey,
tmphash, sizeof tmphash,
msg, 48,
symxkey, sizeof symxkey,
NULL);
if (memcmp (msg+48, hash, 32))
{
err = gpg_error (GPG_ERR_BAD_DATA);
log_error ("manipulation of received %s message detected: %s\n",
msgtypestr (MSG_TYPE_DHPART2), "Bad MAC");
goto leave;
}
/* Create the response. */
newmsglen = 7+1+8+32;
newmsg = xmalloc (newmsglen);
memcpy (newmsg+0, "GPG-pa1", 7);
newmsg[7] = MSG_TYPE_CONFIRM;
memcpy (newmsg+8, msg + 8, 8); /* SessionID. */
/* MAC(HMACr-key, Hash(DHPART2) || CONFIRM[0..15] || SYMx-key) */
gcry_md_hash_buffer (GCRY_MD_SHA256, tmphash, msg, msglen);
hmac_data (newmsg+16, 32, hmacrkey, sizeof hmacrkey,
tmphash, sizeof tmphash,
newmsg, (size_t)16,
symxkey, sizeof symxkey,
NULL);
/* Update the state. */
xnvc_set (state, "State:", "Confirm-sent");
xnvc_set_hex (state, "DH-Master:", master, sizeof master);
/* Write the state. */
write_state (state, 0);
/* Write the message. */
send_message (newmsg, newmsglen);
display_sas (sas, sizeof sas, 0);
leave:
xfree (newmsg);
return err;
}
/* We are the Initiator: Process a CONFIRM message in (MSG,MSGLEN)
* which has already been validated to have a correct header and
* message type. Does not send anything back. */
static gpg_error_t
proc_msg_confirm (nvc_t state, const unsigned char *msg, size_t msglen)
{
gpg_error_t err;
unsigned char hash[32];
unsigned char tmphash[32];
unsigned char master[32];
uint64_t expire;
unsigned char expirebuf[5];
unsigned char hmacrkey[32];
unsigned char symxkey[32];
unsigned char sas[32];
log_assert (msglen >= 48);
/* Put the expire value into a buffer. */
expire = string_to_u64 (xnvc_get_string (state, "Expires:"));
if (!expire)
{
err = gpg_error (GPG_ERR_INV_VALUE);
log_error ("no 'Expire' in our state file\n");
goto leave;
}
expirebuf[0] = expire >> 32;
expirebuf[1] = expire >> 24;
expirebuf[2] = expire >> 16;
expirebuf[3] = expire >> 8;
expirebuf[4] = expire;
/* Get the master secret. */
if (hex2bin (xnvc_get_string (state, "DH-Master:"),master,sizeof master) < 0)
{
err = gpg_error (GPG_ERR_INV_VALUE);
log_error ("no or garbled 'DH-Master' in our state file\n");
goto leave;
}
kdf (hmacrkey, sizeof hmacrkey,
master, sizeof master, msg+8, 8, expirebuf, sizeof expirebuf,
"GPG-pa1-HMACr-key");
kdf (symxkey, sizeof symxkey,
master, sizeof master, msg+8, 8, expirebuf, sizeof expirebuf,
"GPG-pa1-SYMx-key");
kdf (sas, sizeof sas,
master, sizeof master, msg+8, 8, expirebuf, sizeof expirebuf,
"GPG-pa1-SAS");
/* Check the MAC from the message which is */
/* MAC(HMACr-key, Hash(DHPART2) || CONFIRM[0..15] || SYMx-key). */
if (hex2bin (xnvc_get_string (state, "Hash-DHPart2:"),
tmphash, sizeof tmphash) < 0)
{
err = gpg_error (GPG_ERR_INV_VALUE);
log_error ("no or garbled 'Hash-DHPart2' in our state file\n");
goto leave;
}
hmac_data (hash, 32, hmacrkey, sizeof hmacrkey,
tmphash, sizeof tmphash,
msg, (size_t)16,
symxkey, sizeof symxkey,
NULL);
if (!memcmp (msg+48, hash, 32))
{
err = gpg_error (GPG_ERR_BAD_DATA);
log_error ("manipulation of received %s message detected: %s\n",
msgtypestr (MSG_TYPE_CONFIRM), "Bad MAC");
goto leave;
}
err = display_sas (sas, sizeof sas, 1);
if (err)
goto leave;
/* Update the state. */
xnvc_set (state, "State:", "Confirmed");
/* Write the state. */
write_state (state, 0);
leave:
return err;
}
/* Expire old state files. This loops over all state files and remove
* those which are expired. */
static void
expire_old_states (void)
{
gpg_error_t err = 0;
const char *dirname;
DIR *dir = NULL;
struct dirent *dir_entry;
char *fname = NULL;
estream_t fp = NULL;
nvc_t nvc = NULL;
nve_t item;
const char *value;
unsigned long expire;
unsigned long now = gnupg_get_time ();
dirname = get_pairing_statedir ();
dir = opendir (dirname);
if (!dir)
{
err = gpg_error_from_syserror ();
goto leave;
}
while ((dir_entry = readdir (dir)))
{
if (strlen (dir_entry->d_name) != 16+4
|| strcmp (dir_entry->d_name + 16, ".pa1"))
continue;
xfree (fname);
fname = make_filename (dirname, dir_entry->d_name, NULL);
es_fclose (fp);
fp = es_fopen (fname, "rb");
if (!fp)
{
err = gpg_error_from_syserror ();
if (gpg_err_code (err) != GPG_ERR_ENOENT)
log_info ("failed to open state file '%s': %s\n",
fname, gpg_strerror (err));
continue;
}
nvc_release (nvc);
/* NB.: The following is similar to code in read_state. */
err = nvc_parse (&nvc, NULL, fp);
if (err)
{
log_info ("failed to parse state file '%s': %s\n",
fname, gpg_strerror (err));
continue; /* Skip */
}
item = nvc_lookup (nvc, "Expires:");
if (!item)
{
log_info ("invalid state file '%s': %s\n",
fname, "field 'expire' not found");
continue; /* Skip */
}
value = nve_value (item);
if (!value || !(expire = strtoul (value, NULL, 10)))
{
log_info ("invalid state file '%s': %s\n",
fname, "field 'expire' has an invalid value");
continue; /* Skip */
}
if (expire <= now)
{
es_fclose (fp);
fp = NULL;
if (gnupg_remove (fname))
{
err = gpg_error_from_syserror ();
log_info ("failed to delete state file '%s': %s\n",
fname, gpg_strerror (err));
}
else if (opt.verbose)
log_info ("state file '%s' deleted\n", fname);
}
}
leave:
if (err)
log_error ("expiring old states in '%s' failed: %s\n",
dirname, gpg_strerror (err));
if (dir)
closedir (dir);
es_fclose (fp);
xfree (fname);
}
/* Initiate a pairing. The output needs to be conveyed to the
* peer */
static gpg_error_t
command_initiate (void)
{
gpg_error_t err;
nvc_t state;
state = xnvc_new ();
xnvc_set (state, "Version:", "GPG-pa1");
xnvc_set_hex (state, "Session:", get_session_id (), 8);
xnvc_set (state, "Role:", "Initiator");
err = make_msg_commit (state);
nvc_release (state);
return err;
}
/* Helper for command_respond(). */
static gpg_error_t
expect_state (int msgtype, const char *statestr, const char *expected)
{
if (strcmp (statestr, expected))
{
log_error ("received %s message in %s state (should be %s)\n",
msgtypestr (msgtype), statestr, expected);
return gpg_error (GPG_ERR_INV_RESPONSE);
}
return 0;
}
/* Respond to a pairing intiation. This is used by the peer and later
* by the original responder. Depending on the state the output needs
* to be conveyed to the peer. */
static gpg_error_t
command_respond (void)
{
gpg_error_t err;
unsigned char *msg;
size_t msglen = 0; /* In case that read_message returns an error. */
int msgtype = 0; /* ditto. */
nvc_t state;
const char *rolestr;
const char *statestr;
err = read_message (&msg, &msglen, &msgtype, &state);
if (err && gpg_err_code (err) != GPG_ERR_NOT_FOUND)
goto leave;
rolestr = xnvc_get_string (state, "Role:");
statestr = xnvc_get_string (state, "State:");
if (DBG_MESSAGE)
{
if (!state)
log_debug ("no state available\n");
else
log_debug ("we are %s, our current state is %s\n", rolestr, statestr);
log_debug ("got message of type %s (%d)\n",
msgtypestr (msgtype), msgtype);
}
if (!state)
{
if (msgtype == MSG_TYPE_COMMIT)
{
state = xnvc_new ();
xnvc_set (state, "Version:", "GPG-pa1");
xnvc_set_hex (state, "Session:", get_session_id (), 8);
xnvc_set (state, "Role:", "Responder");
err = proc_msg_commit (state, msg, msglen);
}
else
{
log_error ("%s message expected but got %s\n",
msgtypestr (MSG_TYPE_COMMIT), msgtypestr (msgtype));
if (msgtype == MSG_TYPE_DHPART1)
log_info ("the pairing probably took too long and timed out\n");
err = gpg_error (GPG_ERR_INV_RESPONSE);
goto leave;
}
}
else if (!strcmp (rolestr, "Initiator"))
{
if (msgtype == MSG_TYPE_DHPART1)
{
if (!(err = expect_state (msgtype, statestr, "Commit-sent")))
err = proc_msg_dhpart1 (state, msg, msglen);
}
else if (msgtype == MSG_TYPE_CONFIRM)
{
if (!(err = expect_state (msgtype, statestr, "DHPart2-sent")))
err = proc_msg_confirm (state, msg, msglen);
}
else
{
log_error ("%s message not expected by Initiator\n",
msgtypestr (msgtype));
err = gpg_error (GPG_ERR_INV_RESPONSE);
goto leave;
}
}
else if (!strcmp (rolestr, "Responder"))
{
if (msgtype == MSG_TYPE_DHPART2)
{
if (!(err = expect_state (msgtype, statestr, "DHPart1-sent")))
err = proc_msg_dhpart2 (state, msg, msglen);
}
else
{
log_error ("%s message not expected by Responder\n",
msgtypestr (msgtype));
err = gpg_error (GPG_ERR_INV_RESPONSE);
goto leave;
}
}
else
log_fatal ("invalid role '%s' in state file\n", rolestr);
leave:
xfree (msg);
nvc_release (state);
return err;
}
/* Return the keys for SESSIONIDSTR or the last one if it is NULL.
* Two keys are returned: The first is the one for sending encrypted
* data and the second one for decrypting received data. The keys are
* always returned hex encoded and both are terminated by a LF. */
static gpg_error_t
command_get (const char *sessionidstr)
{
gpg_error_t err;
unsigned char sessid[8];
nvc_t state;
if (!sessionidstr)
{
log_error ("calling without session-id is not yet implemented\n");
err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
goto leave;
}
if (hex2bin (sessionidstr, sessid, sizeof sessid) < 0)
{
err = gpg_error (GPG_ERR_INV_VALUE);
log_error ("invalid session id given\n");
goto leave;
}
set_session_id (sessid, sizeof sessid);
err = read_state (&state);
if (err)
{
log_error ("reading state of session %s failed: %s\n",
sessionidstr, gpg_strerror (err));
goto leave;
}
leave:
return err;
}
/* Cleanup command. */
static gpg_error_t
command_cleanup (void)
{
expire_old_states ();
return 0;
}
diff --git a/tools/gpg-wks-client.c b/tools/gpg-wks-client.c
index 0f08737c4..861a1fc61 100644
--- a/tools/gpg-wks-client.c
+++ b/tools/gpg-wks-client.c
@@ -1,1493 +1,1589 @@
/* gpg-wks-client.c - A client for the Web Key Service protocols.
* Copyright (C) 2016 Werner Koch
* Copyright (C) 2016 Bundesamt für Sicherheit in der Informationstechnik
*
* This file is part of GnuPG.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This file 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "../common/util.h"
#include "../common/status.h"
#include "../common/i18n.h"
#include "../common/sysutils.h"
#include "../common/init.h"
#include "../common/asshelp.h"
#include "../common/userids.h"
#include "../common/ccparray.h"
#include "../common/exectool.h"
#include "../common/mbox-util.h"
#include "../common/name-value.h"
#include "call-dirmngr.h"
#include "mime-maker.h"
#include "send-mail.h"
#include "gpg-wks.h"
/* Constants to identify the commands and options. */
enum cmd_and_opt_values
{
aNull = 0,
oQuiet = 'q',
oVerbose = 'v',
oOutput = 'o',
oDirectory = 'C',
oDebug = 500,
aSupported,
aCheck,
aCreate,
aReceive,
aRead,
aInstallKey,
aRemoveKey,
+ aPrintWKDHash,
+ aPrintWKDURL,
oGpgProgram,
oSend,
oFakeSubmissionAddr,
oStatusFD,
oWithColons,
oDummy
};
/* The list of commands and options. */
static ARGPARSE_OPTS opts[] = {
ARGPARSE_group (300, ("@Commands:\n ")),
ARGPARSE_c (aSupported, "supported",
("check whether provider supports WKS")),
ARGPARSE_c (aCheck, "check",
("check whether a key is available")),
ARGPARSE_c (aCreate, "create",
("create a publication request")),
ARGPARSE_c (aReceive, "receive",
("receive a MIME confirmation request")),
ARGPARSE_c (aRead, "read",
("receive a plain text confirmation request")),
ARGPARSE_c (aInstallKey, "install-key",
"install a key into a directory"),
ARGPARSE_c (aRemoveKey, "remove-key",
"remove a key from a directory"),
+ ARGPARSE_c (aPrintWKDHash, "print-wkd-hash",
+ "Print the WKD identifier for the given user ids"),
+ ARGPARSE_c (aPrintWKDURL, "print-wkd-url",
+ "Print the WKD URL for the given user id"),
ARGPARSE_group (301, ("@\nOptions:\n ")),
ARGPARSE_s_n (oVerbose, "verbose", ("verbose")),
ARGPARSE_s_n (oQuiet, "quiet", ("be somewhat more quiet")),
ARGPARSE_s_s (oDebug, "debug", "@"),
ARGPARSE_s_s (oGpgProgram, "gpg", "@"),
ARGPARSE_s_n (oSend, "send", "send the mail using sendmail"),
ARGPARSE_s_s (oOutput, "output", "|FILE|write the mail to FILE"),
ARGPARSE_s_i (oStatusFD, "status-fd", N_("|FD|write status info to this FD")),
ARGPARSE_s_n (oWithColons, "with-colons", "@"),
ARGPARSE_s_s (oDirectory, "directory", "@"),
ARGPARSE_s_s (oFakeSubmissionAddr, "fake-submission-addr", "@"),
ARGPARSE_end ()
};
/* The list of supported debug flags. */
static struct debug_flags_s debug_flags [] =
{
{ DBG_MIME_VALUE , "mime" },
{ DBG_PARSER_VALUE , "parser" },
{ DBG_CRYPTO_VALUE , "crypto" },
{ DBG_MEMORY_VALUE , "memory" },
{ DBG_MEMSTAT_VALUE, "memstat" },
{ DBG_IPC_VALUE , "ipc" },
{ DBG_EXTPROG_VALUE, "extprog" },
{ 0, NULL }
};
/* Value of the option --fake-submission-addr. */
const char *fake_submission_addr;
static void wrong_args (const char *text) GPGRT_ATTR_NORETURN;
+static gpg_error_t proc_userid_from_stdin (gpg_error_t (*func)(const char *),
+ const char *text);
static gpg_error_t command_supported (char *userid);
static gpg_error_t command_check (char *userid);
static gpg_error_t command_send (const char *fingerprint, const char *userid);
static gpg_error_t encrypt_response (estream_t *r_output, estream_t input,
const char *addrspec,
const char *fingerprint);
static gpg_error_t read_confirmation_request (estream_t msg);
static gpg_error_t command_receive_cb (void *opaque,
const char *mediatype, estream_t fp,
unsigned int flags);
/* Print usage information and provide strings for help. */
static const char *
my_strusage( int level )
{
const char *p;
switch (level)
{
case 11: p = "gpg-wks-client"; break;
case 12: p = "@GNUPG@"; break;
case 13: p = VERSION; break;
case 17: p = PRINTABLE_OS_NAME; break;
case 19: p = ("Please report bugs to <@EMAIL@>.\n"); break;
case 1:
case 40:
p = ("Usage: gpg-wks-client [command] [options] [args] (-h for help)");
break;
case 41:
p = ("Syntax: gpg-wks-client [command] [options] [args]\n"
"Client for the Web Key Service\n");
break;
default: p = NULL; break;
}
return p;
}
static void
wrong_args (const char *text)
{
es_fprintf (es_stderr, _("usage: %s [options] %s\n"), strusage (11), text);
exit (2);
}
/* Command line parsing. */
static enum cmd_and_opt_values
parse_arguments (ARGPARSE_ARGS *pargs, ARGPARSE_OPTS *popts)
{
enum cmd_and_opt_values cmd = 0;
int no_more_options = 0;
while (!no_more_options && optfile_parse (NULL, NULL, NULL, pargs, popts))
{
switch (pargs->r_opt)
{
case oQuiet: opt.quiet = 1; break;
case oVerbose: opt.verbose++; break;
case oDebug:
if (parse_debug_flag (pargs->r.ret_str, &opt.debug, debug_flags))
{
pargs->r_opt = ARGPARSE_INVALID_ARG;
pargs->err = ARGPARSE_PRINT_ERROR;
}
break;
case oGpgProgram:
opt.gpg_program = pargs->r.ret_str;
break;
case oDirectory:
opt.directory = pargs->r.ret_str;
break;
case oSend:
opt.use_sendmail = 1;
break;
case oOutput:
opt.output = pargs->r.ret_str;
break;
case oFakeSubmissionAddr:
fake_submission_addr = pargs->r.ret_str;
break;
case oStatusFD:
wks_set_status_fd (translate_sys2libc_fd_int (pargs->r.ret_int, 1));
break;
case oWithColons:
opt.with_colons = 1;
break;
case aSupported:
case aCreate:
case aReceive:
case aRead:
case aCheck:
case aInstallKey:
case aRemoveKey:
+ case aPrintWKDHash:
+ case aPrintWKDURL:
cmd = pargs->r_opt;
break;
default: pargs->err = 2; break;
}
}
return cmd;
}
/* gpg-wks-client main. */
int
main (int argc, char **argv)
{
- gpg_error_t err;
+ gpg_error_t err, delayed_err;
ARGPARSE_ARGS pargs;
enum cmd_and_opt_values cmd;
gnupg_reopen_std ("gpg-wks-client");
set_strusage (my_strusage);
log_set_prefix ("gpg-wks-client", GPGRT_LOG_WITH_PREFIX);
/* Make sure that our subsystems are ready. */
i18n_init();
init_common_subsystems (&argc, &argv);
assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT);
setup_libassuan_logging (&opt.debug, NULL);
/* Parse the command line. */
pargs.argc = &argc;
pargs.argv = &argv;
pargs.flags = ARGPARSE_FLAG_KEEP;
cmd = parse_arguments (&pargs, opts);
if (log_get_errorcount (0))
exit (2);
/* Print a warning if an argument looks like an option. */
if (!opt.quiet && !(pargs.flags & ARGPARSE_FLAG_STOP_SEEN))
{
int i;
for (i=0; i < argc; i++)
if (argv[i][0] == '-' && argv[i][1] == '-')
log_info (("NOTE: '%s' is not considered an option\n"), argv[i]);
}
/* Set defaults for non given options. */
if (!opt.gpg_program)
opt.gpg_program = gnupg_module_name (GNUPG_MODULE_NAME_GPG);
if (!opt.directory)
opt.directory = "openpgpkey";
/* Tell call-dirmngr what options we want. */
set_dirmngr_options (opt.verbose, (opt.debug & DBG_IPC_VALUE), 1);
/* Check that the top directory exists. */
if (cmd == aInstallKey || cmd == aRemoveKey)
{
struct stat sb;
if (stat (opt.directory, &sb))
{
err = gpg_error_from_syserror ();
log_error ("error accessing directory '%s': %s\n",
opt.directory, gpg_strerror (err));
goto leave;
}
if (!S_ISDIR(sb.st_mode))
{
log_error ("error accessing directory '%s': %s\n",
opt.directory, "not a directory");
err = gpg_error (GPG_ERR_ENOENT);
goto leave;
}
}
/* Run the selected command. */
switch (cmd)
{
case aSupported:
if (opt.with_colons)
{
for (; argc; argc--, argv++)
command_supported (*argv);
err = 0;
}
else
{
if (argc != 1)
wrong_args ("--supported DOMAIN");
err = command_supported (argv[0]);
if (err && gpg_err_code (err) != GPG_ERR_FALSE)
log_error ("checking support failed: %s\n", gpg_strerror (err));
}
break;
case aCreate:
if (argc != 2)
wrong_args ("--create FINGERPRINT USER-ID");
err = command_send (argv[0], argv[1]);
if (err)
log_error ("creating request failed: %s\n", gpg_strerror (err));
break;
case aReceive:
if (argc)
wrong_args ("--receive < MIME-DATA");
err = wks_receive (es_stdin, command_receive_cb, NULL);
if (err)
log_error ("processing mail failed: %s\n", gpg_strerror (err));
break;
case aRead:
if (argc)
wrong_args ("--read < WKS-DATA");
err = read_confirmation_request (es_stdin);
if (err)
log_error ("processing mail failed: %s\n", gpg_strerror (err));
break;
case aCheck:
if (argc != 1)
wrong_args ("--check USER-ID");
err = command_check (argv[0]);
break;
case aInstallKey:
if (!argc)
err = wks_cmd_install_key (NULL, NULL);
else if (argc == 2)
err = wks_cmd_install_key (*argv, argv[1]);
else
wrong_args ("--install-key [FILE|FINGERPRINT USER-ID]");
break;
case aRemoveKey:
if (argc != 1)
wrong_args ("--remove-key USER-ID");
err = wks_cmd_remove_key (*argv);
break;
+ case aPrintWKDHash:
+ case aPrintWKDURL:
+ if (!argc)
+ {
+ if (cmd == aPrintWKDHash)
+ err = proc_userid_from_stdin (wks_cmd_print_wkd_hash,
+ "printing WKD hash");
+ else
+ err = proc_userid_from_stdin (wks_cmd_print_wkd_url,
+ "printing WKD URL");
+ }
+ else
+ {
+ for (err = delayed_err = 0; !err && argc; argc--, argv++)
+ {
+ if (cmd == aPrintWKDHash)
+ err = wks_cmd_print_wkd_hash (*argv);
+ else
+ err = wks_cmd_print_wkd_url (*argv);
+ if (gpg_err_code (err) == GPG_ERR_INV_USER_ID)
+ {
+ /* Diagnostic already printed. */
+ delayed_err = err;
+ err = 0;
+ }
+ else if (err)
+ log_error ("printing hash failed: %s\n", gpg_strerror (err));
+ }
+ if (!err)
+ err = delayed_err;
+ }
+ break;
+
default:
usage (1);
err = 0;
break;
}
leave:
if (err)
wks_write_status (STATUS_FAILURE, "- %u", err);
else if (log_get_errorcount (0))
wks_write_status (STATUS_FAILURE, "- %u", GPG_ERR_GENERAL);
else
wks_write_status (STATUS_SUCCESS, NULL);
- return log_get_errorcount (0)? 1:0;
+ return (err || log_get_errorcount (0))? 1:0;
+}
+
+
+/* Read user ids from stdin and call FUNC for each user id. TEXT is
+ * used for error messages. */
+static gpg_error_t
+proc_userid_from_stdin (gpg_error_t (*func)(const char *), const char *text)
+{
+ gpg_error_t err = 0;
+ gpg_error_t delayed_err = 0;
+ char line[2048];
+ size_t n = 0;
+
+ /* If we are on a terminal disable buffering to get direct response. */
+ if (gnupg_isatty (es_fileno (es_stdin))
+ && gnupg_isatty (es_fileno (es_stdout)))
+ {
+ es_setvbuf (es_stdin, NULL, _IONBF, 0);
+ es_setvbuf (es_stdout, NULL, _IOLBF, 0);
+ }
+
+ while (es_fgets (line, sizeof line - 1, es_stdin))
+ {
+ n = strlen (line);
+ if (!n || line[n-1] != '\n')
+ {
+ err = gpg_error (*line? GPG_ERR_LINE_TOO_LONG
+ : GPG_ERR_INCOMPLETE_LINE);
+ log_error ("error reading stdin: %s\n", gpg_strerror (err));
+ break;
+ }
+ trim_spaces (line);
+ err = func (line);
+ if (gpg_err_code (err) == GPG_ERR_INV_USER_ID)
+ {
+ delayed_err = err;
+ err = 0;
+ }
+ else if (err)
+ log_error ("%s failed: %s\n", text, gpg_strerror (err));
+ }
+ if (es_ferror (es_stdin))
+ {
+ err = gpg_error_from_syserror ();
+ log_error ("error reading stdin: %s\n", gpg_strerror (err));
+ goto leave;
+ }
+
+ leave:
+ if (!err)
+ err = delayed_err;
+ return err;
}
+
/* Add the user id UID to the key identified by FINGERPRINT. */
static gpg_error_t
add_user_id (const char *fingerprint, const char *uid)
{
gpg_error_t err;
ccparray_t ccp;
const char **argv = NULL;
ccparray_init (&ccp, 0);
ccparray_put (&ccp, "--no-options");
if (!opt.verbose)
ccparray_put (&ccp, "--quiet");
else if (opt.verbose > 1)
ccparray_put (&ccp, "--verbose");
ccparray_put (&ccp, "--batch");
ccparray_put (&ccp, "--always-trust");
ccparray_put (&ccp, "--quick-add-uid");
ccparray_put (&ccp, fingerprint);
ccparray_put (&ccp, uid);
ccparray_put (&ccp, NULL);
argv = ccparray_get (&ccp, NULL);
if (!argv)
{
err = gpg_error_from_syserror ();
goto leave;
}
err = gnupg_exec_tool_stream (opt.gpg_program, argv, NULL,
NULL, NULL,
NULL, NULL);
if (err)
{
log_error ("adding user id failed: %s\n", gpg_strerror (err));
goto leave;
}
leave:
xfree (argv);
return err;
}
struct decrypt_stream_parm_s
{
char *fpr;
char *mainfpr;
int otrust;
};
static void
decrypt_stream_status_cb (void *opaque, const char *keyword, char *args)
{
struct decrypt_stream_parm_s *decinfo = opaque;
if (DBG_CRYPTO)
log_debug ("gpg status: %s %s\n", keyword, args);
if (!strcmp (keyword, "DECRYPTION_KEY") && !decinfo->fpr)
{
char *fields[3];
if (split_fields (args, fields, DIM (fields)) >= 3)
{
decinfo->fpr = xstrdup (fields[0]);
decinfo->mainfpr = xstrdup (fields[1]);
decinfo->otrust = *fields[2];
}
}
}
/* Decrypt the INPUT stream to a new stream which is stored at success
* at R_OUTPUT. */
static gpg_error_t
decrypt_stream (estream_t *r_output, struct decrypt_stream_parm_s *decinfo,
estream_t input)
{
gpg_error_t err;
ccparray_t ccp;
const char **argv;
estream_t output;
*r_output = NULL;
memset (decinfo, 0, sizeof *decinfo);
output = es_fopenmem (0, "w+b");
if (!output)
{
err = gpg_error_from_syserror ();
log_error ("error allocating memory buffer: %s\n", gpg_strerror (err));
return err;
}
ccparray_init (&ccp, 0);
ccparray_put (&ccp, "--no-options");
/* We limit the output to 64 KiB to avoid DoS using compression
* tricks. A regular client will anyway only send a minimal key;
* that is one w/o key signatures and attribute packets. */
ccparray_put (&ccp, "--max-output=0x10000");
if (!opt.verbose)
ccparray_put (&ccp, "--quiet");
else if (opt.verbose > 1)
ccparray_put (&ccp, "--verbose");
ccparray_put (&ccp, "--batch");
ccparray_put (&ccp, "--status-fd=2");
ccparray_put (&ccp, "--decrypt");
ccparray_put (&ccp, "--");
ccparray_put (&ccp, NULL);
argv = ccparray_get (&ccp, NULL);
if (!argv)
{
err = gpg_error_from_syserror ();
goto leave;
}
err = gnupg_exec_tool_stream (opt.gpg_program, argv, input,
NULL, output,
decrypt_stream_status_cb, decinfo);
if (!err && (!decinfo->fpr || !decinfo->mainfpr || !decinfo->otrust))
err = gpg_error (GPG_ERR_INV_ENGINE);
if (err)
{
log_error ("decryption failed: %s\n", gpg_strerror (err));
goto leave;
}
else if (opt.verbose)
log_info ("decryption succeeded\n");
es_rewind (output);
*r_output = output;
output = NULL;
leave:
if (err)
{
xfree (decinfo->fpr);
xfree (decinfo->mainfpr);
memset (decinfo, 0, sizeof *decinfo);
}
es_fclose (output);
xfree (argv);
return err;
}
/* Return the submission address for the address or just the domain in
* ADDRSPEC. The submission address is stored as a malloced string at
* R_SUBMISSION_ADDRESS. At R_POLICY the policy flags of the domain
* are stored. The caller needs to free them with wks_free_policy.
* The function returns an error code on failure to find a submission
* address or policy file. Note: The function may store NULL at
* R_SUBMISSION_ADDRESS but return success to indicate that the web
* key directory is supported but not the web key service. As per WKD
* specs a policy file is always required and will thus be return on
* success. */
static gpg_error_t
get_policy_and_sa (const char *addrspec, int silent,
policy_flags_t *r_policy, char **r_submission_address)
{
gpg_error_t err;
estream_t mbuf = NULL;
const char *domain;
const char *s;
policy_flags_t policy = NULL;
char *submission_to = NULL;
*r_submission_address = NULL;
*r_policy = NULL;
domain = strchr (addrspec, '@');
if (domain)
domain++;
if (opt.with_colons)
{
s = domain? domain : addrspec;
es_write_sanitized (es_stdout, s, strlen (s), ":", NULL);
es_putc (':', es_stdout);
}
/* We first try to get the submission address from the policy file
* (this is the new method). If both are available we check that
* they match and print a warning if not. In the latter case we
* keep on using the one from the submission-address file. */
err = wkd_get_policy_flags (addrspec, &mbuf);
if (err && gpg_err_code (err) != GPG_ERR_NO_DATA
&& gpg_err_code (err) != GPG_ERR_NO_NAME)
{
if (!opt.with_colons)
log_error ("error reading policy flags for '%s': %s\n",
domain, gpg_strerror (err));
goto leave;
}
if (!mbuf)
{
if (!opt.with_colons)
log_error ("provider for '%s' does NOT support the Web Key Directory\n",
addrspec);
err = gpg_error (GPG_ERR_FALSE);
goto leave;
}
policy = xtrycalloc (1, sizeof *policy);
if (!policy)
err = gpg_error_from_syserror ();
else
err = wks_parse_policy (policy, mbuf, 1);
es_fclose (mbuf);
mbuf = NULL;
if (err)
goto leave;
err = wkd_get_submission_address (addrspec, &submission_to);
if (err && !policy->submission_address)
{
if (!silent && !opt.with_colons)
log_error (_("error looking up submission address for domain '%s'"
": %s\n"), domain, gpg_strerror (err));
if (!silent && gpg_err_code (err) == GPG_ERR_NO_DATA && !opt.with_colons)
log_error (_("this domain probably doesn't support WKS.\n"));
goto leave;
}
if (submission_to && policy->submission_address
&& ascii_strcasecmp (submission_to, policy->submission_address))
log_info ("Warning: different submission addresses (sa=%s, po=%s)\n",
submission_to, policy->submission_address);
if (!submission_to && policy->submission_address)
{
submission_to = xtrystrdup (policy->submission_address);
if (!submission_to)
{
err = gpg_error_from_syserror ();
goto leave;
}
}
leave:
*r_submission_address = submission_to;
submission_to = NULL;
*r_policy = policy;
policy = NULL;
if (opt.with_colons)
{
if (*r_policy && !*r_submission_address)
es_fprintf (es_stdout, "1:0::");
else if (*r_policy && *r_submission_address)
es_fprintf (es_stdout, "1:1::");
else if (err && !(gpg_err_code (err) == GPG_ERR_FALSE
|| gpg_err_code (err) == GPG_ERR_NO_DATA
|| gpg_err_code (err) == GPG_ERR_UNKNOWN_HOST))
es_fprintf (es_stdout, "0:0:%d:", err);
else
es_fprintf (es_stdout, "0:0::");
if (*r_policy)
{
es_fprintf (es_stdout, "%u:%u:%u:",
(*r_policy)->protocol_version,
(*r_policy)->auth_submit,
(*r_policy)->mailbox_only);
}
es_putc ('\n', es_stdout);
}
xfree (submission_to);
wks_free_policy (policy);
xfree (policy);
es_fclose (mbuf);
return err;
}
/* Check whether the provider supports the WKS protocol. */
static gpg_error_t
command_supported (char *userid)
{
gpg_error_t err;
char *addrspec = NULL;
char *submission_to = NULL;
policy_flags_t policy = NULL;
if (!strchr (userid, '@'))
{
char *tmp = xstrconcat ("foo@", userid, NULL);
addrspec = mailbox_from_userid (tmp, 0);
xfree (tmp);
}
else
addrspec = mailbox_from_userid (userid, 0);
if (!addrspec)
{
log_error (_("\"%s\" is not a proper mail address\n"), userid);
err = gpg_error (GPG_ERR_INV_USER_ID);
goto leave;
}
/* Get the submission address. */
err = get_policy_and_sa (addrspec, 1, &policy, &submission_to);
if (err || !submission_to)
{
if (!submission_to
|| gpg_err_code (err) == GPG_ERR_FALSE
|| gpg_err_code (err) == GPG_ERR_NO_DATA
|| gpg_err_code (err) == GPG_ERR_UNKNOWN_HOST
)
{
/* FALSE is returned if we already figured out that even the
* Web Key Directory is not supported and thus printed an
* error message. */
if (opt.verbose && gpg_err_code (err) != GPG_ERR_FALSE
&& !opt.with_colons)
{
if (gpg_err_code (err) == GPG_ERR_NO_DATA)
log_info ("provider for '%s' does NOT support WKS\n",
addrspec);
else
log_info ("provider for '%s' does NOT support WKS (%s)\n",
addrspec, gpg_strerror (err));
}
err = gpg_error (GPG_ERR_FALSE);
if (!opt.with_colons)
log_inc_errorcount ();
}
goto leave;
}
if (opt.verbose && !opt.with_colons)
log_info ("provider for '%s' supports WKS\n", addrspec);
leave:
wks_free_policy (policy);
xfree (policy);
xfree (submission_to);
xfree (addrspec);
return err;
}
/* Check whether the key for USERID is available in the WKD. */
static gpg_error_t
command_check (char *userid)
{
gpg_error_t err;
char *addrspec = NULL;
estream_t key = NULL;
char *fpr = NULL;
uidinfo_list_t mboxes = NULL;
uidinfo_list_t sl;
int found = 0;
addrspec = mailbox_from_userid (userid, 0);
if (!addrspec)
{
log_error (_("\"%s\" is not a proper mail address\n"), userid);
err = gpg_error (GPG_ERR_INV_USER_ID);
goto leave;
}
/* Get the submission address. */
err = wkd_get_key (addrspec, &key);
switch (gpg_err_code (err))
{
case 0:
if (opt.verbose)
log_info ("public key for '%s' found via WKD\n", addrspec);
/* Fixme: Check that the key contains the user id. */
break;
case GPG_ERR_NO_DATA: /* No such key. */
if (opt.verbose)
log_info ("public key for '%s' NOT found via WKD\n", addrspec);
err = gpg_error (GPG_ERR_NO_PUBKEY);
log_inc_errorcount ();
break;
case GPG_ERR_UNKNOWN_HOST:
if (opt.verbose)
log_info ("error looking up '%s' via WKD: %s\n",
addrspec, gpg_strerror (err));
err = gpg_error (GPG_ERR_NOT_SUPPORTED);
break;
default:
log_error ("error looking up '%s' via WKD: %s\n",
addrspec, gpg_strerror (err));
break;
}
if (err)
goto leave;
/* Look closer at the key. */
err = wks_list_key (key, &fpr, &mboxes);
if (err)
{
log_error ("error parsing key: %s\n", gpg_strerror (err));
err = gpg_error (GPG_ERR_NO_PUBKEY);
goto leave;
}
if (opt.verbose)
log_info ("fingerprint: %s\n", fpr);
for (sl = mboxes; sl; sl = sl->next)
{
if (sl->mbox && !strcmp (sl->mbox, addrspec))
found = 1;
if (opt.verbose)
{
log_info (" user-id: %s\n", sl->uid);
log_info (" created: %s\n", asctimestamp (sl->created));
if (sl->mbox)
log_info (" addr-spec: %s\n", sl->mbox);
}
}
if (!found)
{
log_error ("public key for '%s' has no user id with the mail address\n",
addrspec);
err = gpg_error (GPG_ERR_CERT_REVOKED);
}
leave:
xfree (fpr);
free_uidinfo_list (mboxes);
es_fclose (key);
xfree (addrspec);
return err;
}
/* Locate the key by fingerprint and userid and send a publication
* request. */
static gpg_error_t
command_send (const char *fingerprint, const char *userid)
{
gpg_error_t err;
KEYDB_SEARCH_DESC desc;
char *addrspec = NULL;
estream_t key = NULL;
estream_t keyenc = NULL;
char *submission_to = NULL;
mime_maker_t mime = NULL;
policy_flags_t policy = NULL;
int no_encrypt = 0;
int posteo_hack = 0;
const char *domain;
uidinfo_list_t uidlist = NULL;
uidinfo_list_t uid, thisuid;
time_t thistime;
if (classify_user_id (fingerprint, &desc, 1)
|| desc.mode != KEYDB_SEARCH_MODE_FPR)
{
log_error (_("\"%s\" is not a fingerprint\n"), fingerprint);
err = gpg_error (GPG_ERR_INV_NAME);
goto leave;
}
addrspec = mailbox_from_userid (userid, 0);
if (!addrspec)
{
log_error (_("\"%s\" is not a proper mail address\n"), userid);
err = gpg_error (GPG_ERR_INV_USER_ID);
goto leave;
}
err = wks_get_key (&key, fingerprint, addrspec, 0);
if (err)
goto leave;
domain = strchr (addrspec, '@');
log_assert (domain);
domain++;
/* Get the submission address. */
if (fake_submission_addr)
{
policy = xcalloc (1, sizeof *policy);
submission_to = xstrdup (fake_submission_addr);
err = 0;
}
else
{
err = get_policy_and_sa (addrspec, 0, &policy, &submission_to);
if (err)
goto leave;
if (!submission_to)
{
log_error (_("this domain probably doesn't support WKS.\n"));
err = gpg_error (GPG_ERR_NO_DATA);
goto leave;
}
}
log_info ("submitting request to '%s'\n", submission_to);
if (policy->auth_submit)
log_info ("no confirmation required for '%s'\n", addrspec);
/* In case the key has several uids with the same addr-spec we will
* use the newest one. */
err = wks_list_key (key, NULL, &uidlist);
if (err)
{
log_error ("error parsing key: %s\n",gpg_strerror (err));
err = gpg_error (GPG_ERR_NO_PUBKEY);
goto leave;
}
thistime = 0;
thisuid = NULL;
for (uid = uidlist; uid; uid = uid->next)
{
if (!uid->mbox)
continue; /* Should not happen anyway. */
if (policy->mailbox_only && ascii_strcasecmp (uid->uid, uid->mbox))
continue; /* UID has more than just the mailbox. */
if (uid->created > thistime)
{
thistime = uid->created;
thisuid = uid;
}
}
if (!thisuid)
thisuid = uidlist; /* This is the case for a missing timestamp. */
if (opt.verbose)
log_info ("submitting key with user id '%s'\n", thisuid->uid);
/* If we have more than one user id we need to filter the key to
* include only THISUID. */
if (uidlist->next)
{
estream_t newkey;
es_rewind (key);
err = wks_filter_uid (&newkey, key, thisuid->uid, 0);
if (err)
{
log_error ("error filtering key: %s\n", gpg_strerror (err));
err = gpg_error (GPG_ERR_NO_PUBKEY);
goto leave;
}
es_fclose (key);
key = newkey;
}
if (policy->mailbox_only
&& (!thisuid->mbox || ascii_strcasecmp (thisuid->uid, thisuid->mbox)))
{
log_info ("Warning: policy requires 'mailbox-only'"
" - adding user id '%s'\n", addrspec);
err = add_user_id (fingerprint, addrspec);
if (err)
goto leave;
/* Need to get the key again. This time we request filtering
* for the full user id, so that we do not need check and filter
* the key again. */
es_fclose (key);
key = NULL;
err = wks_get_key (&key, fingerprint, addrspec, 1);
if (err)
goto leave;
}
/* Hack to support posteo but let them disable this by setting the
* new policy-version flag. */
if (policy->protocol_version < 3
&& !ascii_strcasecmp (domain, "posteo.de"))
{
log_info ("Warning: Using draft-1 method for domain '%s'\n", domain);
no_encrypt = 1;
posteo_hack = 1;
}
/* Encrypt the key part. */
if (!no_encrypt)
{
es_rewind (key);
err = encrypt_response (&keyenc, key, submission_to, fingerprint);
if (err)
goto leave;
es_fclose (key);
key = NULL;
}
/* Send the key. */
err = mime_maker_new (&mime, NULL);
if (err)
goto leave;
err = mime_maker_add_header (mime, "From", addrspec);
if (err)
goto leave;
err = mime_maker_add_header (mime, "To", submission_to);
if (err)
goto leave;
err = mime_maker_add_header (mime, "Subject", "Key publishing request");
if (err)
goto leave;
/* Tell server which draft we support. */
err = mime_maker_add_header (mime, "Wks-Draft-Version",
STR2(WKS_DRAFT_VERSION));
if (err)
goto leave;
if (no_encrypt)
{
void *data;
size_t datalen, n;
if (posteo_hack)
{
/* Needs a multipart/mixed with one(!) attachment. It does
* not grok a non-multipart mail. */
err = mime_maker_add_header (mime, "Content-Type", "multipart/mixed");
if (err)
goto leave;
err = mime_maker_add_container (mime);
if (err)
goto leave;
}
err = mime_maker_add_header (mime, "Content-type",
"application/pgp-keys");
if (err)
goto leave;
if (es_fclose_snatch (key, &data, &datalen))
{
err = gpg_error_from_syserror ();
goto leave;
}
key = NULL;
/* We need to skip over the first line which has a content-type
* header not needed here. */
for (n=0; n < datalen ; n++)
if (((const char *)data)[n] == '\n')
{
n++;
break;
}
err = mime_maker_add_body_data (mime, (char*)data + n, datalen - n);
xfree (data);
if (err)
goto leave;
}
else
{
err = mime_maker_add_header (mime, "Content-Type",
"multipart/encrypted; "
"protocol=\"application/pgp-encrypted\"");
if (err)
goto leave;
err = mime_maker_add_container (mime);
if (err)
goto leave;
err = mime_maker_add_header (mime, "Content-Type",
"application/pgp-encrypted");
if (err)
goto leave;
err = mime_maker_add_body (mime, "Version: 1\n");
if (err)
goto leave;
err = mime_maker_add_header (mime, "Content-Type",
"application/octet-stream");
if (err)
goto leave;
err = mime_maker_add_stream (mime, &keyenc);
if (err)
goto leave;
}
err = wks_send_mime (mime);
leave:
mime_maker_release (mime);
xfree (submission_to);
free_uidinfo_list (uidlist);
es_fclose (keyenc);
es_fclose (key);
wks_free_policy (policy);
xfree (policy);
xfree (addrspec);
return err;
}
static void
encrypt_response_status_cb (void *opaque, const char *keyword, char *args)
{
gpg_error_t *failure = opaque;
char *fields[2];
if (DBG_CRYPTO)
log_debug ("gpg status: %s %s\n", keyword, args);
if (!strcmp (keyword, "FAILURE"))
{
if (split_fields (args, fields, DIM (fields)) >= 2
&& !strcmp (fields[0], "encrypt"))
*failure = strtoul (fields[1], NULL, 10);
}
}
/* Encrypt the INPUT stream to a new stream which is stored at success
* at R_OUTPUT. Encryption is done for ADDRSPEC and for FINGERPRINT
* (so that the sent message may later be inspected by the user). We
* currently retrieve that key from the WKD, DANE, or from "local".
* "local" is last to prefer the latest key version but use a local
* copy in case we are working offline. It might be useful for the
* server to send the fingerprint of its encryption key - or even the
* entire key back. */
static gpg_error_t
encrypt_response (estream_t *r_output, estream_t input, const char *addrspec,
const char *fingerprint)
{
gpg_error_t err;
ccparray_t ccp;
const char **argv;
estream_t output;
gpg_error_t gpg_err = 0;
*r_output = NULL;
output = es_fopenmem (0, "w+b");
if (!output)
{
err = gpg_error_from_syserror ();
log_error ("error allocating memory buffer: %s\n", gpg_strerror (err));
return err;
}
ccparray_init (&ccp, 0);
ccparray_put (&ccp, "--no-options");
if (!opt.verbose)
ccparray_put (&ccp, "--quiet");
else if (opt.verbose > 1)
ccparray_put (&ccp, "--verbose");
ccparray_put (&ccp, "--batch");
ccparray_put (&ccp, "--status-fd=2");
ccparray_put (&ccp, "--always-trust");
ccparray_put (&ccp, "--armor");
ccparray_put (&ccp, "-z0"); /* No compression for improved robustness. */
if (fake_submission_addr)
ccparray_put (&ccp, "--auto-key-locate=clear,local");
else
ccparray_put (&ccp, "--auto-key-locate=clear,wkd,dane,local");
ccparray_put (&ccp, "--recipient");
ccparray_put (&ccp, addrspec);
ccparray_put (&ccp, "--recipient");
ccparray_put (&ccp, fingerprint);
ccparray_put (&ccp, "--encrypt");
ccparray_put (&ccp, "--");
ccparray_put (&ccp, NULL);
argv = ccparray_get (&ccp, NULL);
if (!argv)
{
err = gpg_error_from_syserror ();
goto leave;
}
err = gnupg_exec_tool_stream (opt.gpg_program, argv, input,
NULL, output,
encrypt_response_status_cb, &gpg_err);
if (err)
{
if (gpg_err)
err = gpg_err;
log_error ("encryption failed: %s\n", gpg_strerror (err));
goto leave;
}
es_rewind (output);
*r_output = output;
output = NULL;
leave:
es_fclose (output);
xfree (argv);
return err;
}
static gpg_error_t
send_confirmation_response (const char *sender, const char *address,
const char *nonce, int encrypt,
const char *fingerprint)
{
gpg_error_t err;
estream_t body = NULL;
estream_t bodyenc = NULL;
mime_maker_t mime = NULL;
body = es_fopenmem (0, "w+b");
if (!body)
{
err = gpg_error_from_syserror ();
log_error ("error allocating memory buffer: %s\n", gpg_strerror (err));
return err;
}
/* It is fine to use 8 bit encoding because that is encrypted and
* only our client will see it. */
if (encrypt)
{
es_fputs ("Content-Type: application/vnd.gnupg.wks\n"
"Content-Transfer-Encoding: 8bit\n"
"\n",
body);
}
es_fprintf (body, ("type: confirmation-response\n"
"sender: %s\n"
"address: %s\n"
"nonce: %s\n"),
sender,
address,
nonce);
es_rewind (body);
if (encrypt)
{
err = encrypt_response (&bodyenc, body, sender, fingerprint);
if (err)
goto leave;
es_fclose (body);
body = NULL;
}
err = mime_maker_new (&mime, NULL);
if (err)
goto leave;
err = mime_maker_add_header (mime, "From", address);
if (err)
goto leave;
err = mime_maker_add_header (mime, "To", sender);
if (err)
goto leave;
err = mime_maker_add_header (mime, "Subject", "Key publication confirmation");
if (err)
goto leave;
err = mime_maker_add_header (mime, "Wks-Draft-Version",
STR2(WKS_DRAFT_VERSION));
if (err)
goto leave;
if (encrypt)
{
err = mime_maker_add_header (mime, "Content-Type",
"multipart/encrypted; "
"protocol=\"application/pgp-encrypted\"");
if (err)
goto leave;
err = mime_maker_add_container (mime);
if (err)
goto leave;
err = mime_maker_add_header (mime, "Content-Type",
"application/pgp-encrypted");
if (err)
goto leave;
err = mime_maker_add_body (mime, "Version: 1\n");
if (err)
goto leave;
err = mime_maker_add_header (mime, "Content-Type",
"application/octet-stream");
if (err)
goto leave;
err = mime_maker_add_stream (mime, &bodyenc);
if (err)
goto leave;
}
else
{
err = mime_maker_add_header (mime, "Content-Type",
"application/vnd.gnupg.wks");
if (err)
goto leave;
err = mime_maker_add_stream (mime, &body);
if (err)
goto leave;
}
err = wks_send_mime (mime);
leave:
mime_maker_release (mime);
es_fclose (bodyenc);
es_fclose (body);
return err;
}
/* Reply to a confirmation request. The MSG has already been
* decrypted and we only need to send the nonce back. MAINFPR is
* either NULL or the primary key fingerprint of the key used to
* decrypt the request. */
static gpg_error_t
process_confirmation_request (estream_t msg, const char *mainfpr)
{
gpg_error_t err;
nvc_t nvc;
nve_t item;
const char *value, *sender, *address, *fingerprint, *nonce;
err = nvc_parse (&nvc, NULL, msg);
if (err)
{
log_error ("parsing the WKS message failed: %s\n", gpg_strerror (err));
goto leave;
}
if (DBG_MIME)
{
log_debug ("request follows:\n");
nvc_write (nvc, log_get_stream ());
}
/* Check that this is a confirmation request. */
if (!((item = nvc_lookup (nvc, "type:")) && (value = nve_value (item))
&& !strcmp (value, "confirmation-request")))
{
if (item && value)
log_error ("received unexpected wks message '%s'\n", value);
else
log_error ("received invalid wks message: %s\n", "'type' missing");
err = gpg_error (GPG_ERR_UNEXPECTED_MSG);
goto leave;
}
/* Get the fingerprint. */
if (!((item = nvc_lookup (nvc, "fingerprint:"))
&& (value = nve_value (item))
&& strlen (value) >= 40))
{
log_error ("received invalid wks message: %s\n",
"'fingerprint' missing or invalid");
err = gpg_error (GPG_ERR_INV_DATA);
goto leave;
}
fingerprint = value;
/* Check that the fingerprint matches the key used to decrypt the
* message. In --read mode or with the old format we don't have the
* decryption key; thus we can't bail out. */
if (!mainfpr || ascii_strcasecmp (mainfpr, fingerprint))
{
log_info ("target fingerprint: %s\n", fingerprint);
log_info ("but decrypted with: %s\n", mainfpr);
log_error ("confirmation request not decrypted with target key\n");
if (mainfpr)
{
err = gpg_error (GPG_ERR_INV_DATA);
goto leave;
}
}
/* Get the address. */
if (!((item = nvc_lookup (nvc, "address:")) && (value = nve_value (item))
&& is_valid_mailbox (value)))
{
log_error ("received invalid wks message: %s\n",
"'address' missing or invalid");
err = gpg_error (GPG_ERR_INV_DATA);
goto leave;
}
address = value;
/* FIXME: Check that the "address" matches the User ID we want to
* publish. */
/* Get the sender. */
if (!((item = nvc_lookup (nvc, "sender:")) && (value = nve_value (item))
&& is_valid_mailbox (value)))
{
log_error ("received invalid wks message: %s\n",
"'sender' missing or invalid");
err = gpg_error (GPG_ERR_INV_DATA);
goto leave;
}
sender = value;
/* FIXME: Check that the "sender" matches the From: address. */
/* Get the nonce. */
if (!((item = nvc_lookup (nvc, "nonce:")) && (value = nve_value (item))
&& strlen (value) > 16))
{
log_error ("received invalid wks message: %s\n",
"'nonce' missing or too short");
err = gpg_error (GPG_ERR_INV_DATA);
goto leave;
}
nonce = value;
/* Send the confirmation. If no key was found, try again without
* encryption. */
err = send_confirmation_response (sender, address, nonce, 1, fingerprint);
if (gpg_err_code (err) == GPG_ERR_NO_PUBKEY)
{
log_info ("no encryption key found - sending response in the clear\n");
err = send_confirmation_response (sender, address, nonce, 0, NULL);
}
leave:
nvc_release (nvc);
return err;
}
/* Read a confirmation request and decrypt it if needed. This
* function may not be used with a mail or MIME message but only with
* the actual encrypted or plaintext WKS data. */
static gpg_error_t
read_confirmation_request (estream_t msg)
{
gpg_error_t err;
int c;
estream_t plaintext = NULL;
/* We take a really simple approach to check whether MSG is
* encrypted: We know that an encrypted message is always armored
* and thus starts with a few dashes. It is even sufficient to
* check for a single dash, because that can never be a proper first
* WKS data octet. We need to skip leading spaces, though. */
while ((c = es_fgetc (msg)) == ' ' || c == '\t' || c == '\r' || c == '\n')
;
if (c == EOF)
{
log_error ("can't process an empty message\n");
return gpg_error (GPG_ERR_INV_DATA);
}
if (es_ungetc (c, msg) != c)
{
log_error ("error ungetting octet from message\n");
return gpg_error (GPG_ERR_INTERNAL);
}
if (c != '-')
err = process_confirmation_request (msg, NULL);
else
{
struct decrypt_stream_parm_s decinfo;
err = decrypt_stream (&plaintext, &decinfo, msg);
if (err)
log_error ("decryption failed: %s\n", gpg_strerror (err));
else if (decinfo.otrust != 'u')
{
err = gpg_error (GPG_ERR_WRONG_SECKEY);
log_error ("key used to decrypt the confirmation request"
" was not generated by us\n");
}
else
err = process_confirmation_request (plaintext, decinfo.mainfpr);
xfree (decinfo.fpr);
xfree (decinfo.mainfpr);
}
es_fclose (plaintext);
return err;
}
/* Called from the MIME receiver to process the plain text data in MSG. */
static gpg_error_t
command_receive_cb (void *opaque, const char *mediatype,
estream_t msg, unsigned int flags)
{
gpg_error_t err;
(void)opaque;
(void)flags;
if (!strcmp (mediatype, "application/vnd.gnupg.wks"))
err = read_confirmation_request (msg);
else
{
log_info ("ignoring unexpected message of type '%s'\n", mediatype);
err = gpg_error (GPG_ERR_UNEXPECTED_MSG);
}
return err;
}
diff --git a/tools/gpg-wks-server.c b/tools/gpg-wks-server.c
index f83ef6528..2082fb87d 100644
--- a/tools/gpg-wks-server.c
+++ b/tools/gpg-wks-server.c
@@ -1,1984 +1,1984 @@
/* gpg-wks-server.c - A server for the Web Key Service protocols.
* Copyright (C) 2016, 2018 Werner Koch
* Copyright (C) 2016 Bundesamt für Sicherheit in der Informationstechnik
*
* This file is part of GnuPG.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This file 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
/* The Web Key Service I-D defines an update protocol to store a
* public key in the Web Key Directory. The current specification is
* draft-koch-openpgp-webkey-service-05.txt.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include "../common/util.h"
#include "../common/init.h"
#include "../common/sysutils.h"
#include "../common/userids.h"
#include "../common/ccparray.h"
#include "../common/exectool.h"
#include "../common/zb32.h"
#include "../common/mbox-util.h"
#include "../common/name-value.h"
#include "mime-maker.h"
#include "send-mail.h"
#include "gpg-wks.h"
/* The time we wait for a confirmation response. */
#define PENDING_TTL (86400 * 3) /* 3 days. */
/* Constants to identify the commands and options. */
enum cmd_and_opt_values
{
aNull = 0,
oQuiet = 'q',
oVerbose = 'v',
oOutput = 'o',
oDirectory = 'C',
oDebug = 500,
aReceive,
aCron,
aListDomains,
aInstallKey,
aRevokeKey,
aRemoveKey,
aCheck,
oGpgProgram,
oSend,
oFrom,
oHeader,
oWithDir,
oWithFile,
oDummy
};
/* The list of commands and options. */
static ARGPARSE_OPTS opts[] = {
ARGPARSE_group (300, ("@Commands:\n ")),
ARGPARSE_c (aReceive, "receive",
("receive a submission or confirmation")),
ARGPARSE_c (aCron, "cron",
("run regular jobs")),
ARGPARSE_c (aListDomains, "list-domains",
("list configured domains")),
ARGPARSE_c (aCheck, "check",
("check whether a key is installed")),
ARGPARSE_c (aCheck, "check-key", "@"),
ARGPARSE_c (aInstallKey, "install-key",
"install a key from FILE into the WKD"),
ARGPARSE_c (aRemoveKey, "remove-key",
"remove a key from the WKD"),
ARGPARSE_c (aRevokeKey, "revoke-key",
"mark a key as revoked"),
ARGPARSE_group (301, ("@\nOptions:\n ")),
ARGPARSE_s_n (oVerbose, "verbose", ("verbose")),
ARGPARSE_s_n (oQuiet, "quiet", ("be somewhat more quiet")),
ARGPARSE_s_s (oDebug, "debug", "@"),
ARGPARSE_s_s (oGpgProgram, "gpg", "@"),
ARGPARSE_s_n (oSend, "send", "send the mail using sendmail"),
ARGPARSE_s_s (oOutput, "output", "|FILE|write the mail to FILE"),
ARGPARSE_s_s (oDirectory, "directory", "|DIR|use DIR as top directory"),
ARGPARSE_s_s (oFrom, "from", "|ADDR|use ADDR as the default sender"),
ARGPARSE_s_s (oHeader, "header" ,
"|NAME=VALUE|add \"NAME: VALUE\" as header to all mails"),
ARGPARSE_s_n (oWithDir, "with-dir", "@"),
ARGPARSE_s_n (oWithFile, "with-file", "@"),
ARGPARSE_end ()
};
/* The list of supported debug flags. */
static struct debug_flags_s debug_flags [] =
{
{ DBG_MIME_VALUE , "mime" },
{ DBG_PARSER_VALUE , "parser" },
{ DBG_CRYPTO_VALUE , "crypto" },
{ DBG_MEMORY_VALUE , "memory" },
{ DBG_MEMSTAT_VALUE, "memstat" },
{ DBG_IPC_VALUE , "ipc" },
{ DBG_EXTPROG_VALUE, "extprog" },
{ 0, NULL }
};
/* State for processing a message. */
struct server_ctx_s
{
char *fpr;
uidinfo_list_t mboxes; /* List with addr-specs taken from the UIDs. */
unsigned int draft_version_2:1; /* Client supports the draft 2. */
};
typedef struct server_ctx_s *server_ctx_t;
/* Flag for --with-dir. */
static int opt_with_dir;
/* Flag for --with-file. */
static int opt_with_file;
/* Prototypes. */
static gpg_error_t get_domain_list (strlist_t *r_list);
static gpg_error_t command_receive_cb (void *opaque,
const char *mediatype, estream_t fp,
unsigned int flags);
static gpg_error_t command_list_domains (void);
static gpg_error_t command_revoke_key (const char *mailaddr);
static gpg_error_t command_check_key (const char *mailaddr);
static gpg_error_t command_cron (void);
/* Print usage information and provide strings for help. */
static const char *
my_strusage( int level )
{
const char *p;
switch (level)
{
case 11: p = "gpg-wks-server"; break;
case 12: p = "@GNUPG@"; break;
case 13: p = VERSION; break;
case 17: p = PRINTABLE_OS_NAME; break;
case 19: p = ("Please report bugs to <@EMAIL@>.\n"); break;
case 1:
case 40:
p = ("Usage: gpg-wks-server command [options] (-h for help)");
break;
case 41:
p = ("Syntax: gpg-wks-server command [options]\n"
"Server for the Web Key Service protocol\n");
break;
default: p = NULL; break;
}
return p;
}
static void
wrong_args (const char *text)
{
es_fprintf (es_stderr, "usage: %s [options] %s\n", strusage (11), text);
exit (2);
}
/* Command line parsing. */
static enum cmd_and_opt_values
parse_arguments (ARGPARSE_ARGS *pargs, ARGPARSE_OPTS *popts)
{
enum cmd_and_opt_values cmd = 0;
int no_more_options = 0;
while (!no_more_options && optfile_parse (NULL, NULL, NULL, pargs, popts))
{
switch (pargs->r_opt)
{
case oQuiet: opt.quiet = 1; break;
case oVerbose: opt.verbose++; break;
case oDebug:
if (parse_debug_flag (pargs->r.ret_str, &opt.debug, debug_flags))
{
pargs->r_opt = ARGPARSE_INVALID_ARG;
pargs->err = ARGPARSE_PRINT_ERROR;
}
break;
case oGpgProgram:
opt.gpg_program = pargs->r.ret_str;
break;
case oDirectory:
opt.directory = pargs->r.ret_str;
break;
case oFrom:
opt.default_from = pargs->r.ret_str;
break;
case oHeader:
append_to_strlist (&opt.extra_headers, pargs->r.ret_str);
break;
case oSend:
opt.use_sendmail = 1;
break;
case oOutput:
opt.output = pargs->r.ret_str;
break;
case oWithDir:
opt_with_dir = 1;
break;
case oWithFile:
opt_with_file = 1;
break;
case aReceive:
case aCron:
case aListDomains:
case aCheck:
case aInstallKey:
case aRemoveKey:
case aRevokeKey:
cmd = pargs->r_opt;
break;
default: pargs->err = 2; break;
}
}
return cmd;
}
/* gpg-wks-server main. */
int
main (int argc, char **argv)
{
gpg_error_t err, firsterr;
ARGPARSE_ARGS pargs;
enum cmd_and_opt_values cmd;
gnupg_reopen_std ("gpg-wks-server");
set_strusage (my_strusage);
log_set_prefix ("gpg-wks-server", GPGRT_LOG_WITH_PREFIX);
/* Make sure that our subsystems are ready. */
init_common_subsystems (&argc, &argv);
/* Parse the command line. */
pargs.argc = &argc;
pargs.argv = &argv;
pargs.flags = ARGPARSE_FLAG_KEEP;
cmd = parse_arguments (&pargs, opts);
if (log_get_errorcount (0))
exit (2);
/* Print a warning if an argument looks like an option. */
if (!opt.quiet && !(pargs.flags & ARGPARSE_FLAG_STOP_SEEN))
{
int i;
for (i=0; i < argc; i++)
if (argv[i][0] == '-' && argv[i][1] == '-')
log_info (("NOTE: '%s' is not considered an option\n"), argv[i]);
}
/* Set defaults for non given options. */
if (!opt.gpg_program)
opt.gpg_program = gnupg_module_name (GNUPG_MODULE_NAME_GPG);
if (!opt.directory)
opt.directory = "/var/lib/gnupg/wks";
/* Check for syntax errors in the --header option to avoid later
* error messages with a not easy to find cause */
if (opt.extra_headers)
{
strlist_t sl;
for (sl = opt.extra_headers; sl; sl = sl->next)
{
err = mime_maker_add_header (NULL, sl->d, NULL);
if (err)
log_error ("syntax error in \"--header %s\": %s\n",
sl->d, gpg_strerror (err));
}
}
if (log_get_errorcount (0))
exit (2);
/* Check that we have a working directory. */
#if defined(HAVE_STAT)
{
struct stat sb;
if (stat (opt.directory, &sb))
{
err = gpg_error_from_syserror ();
log_error ("error accessing directory '%s': %s\n",
opt.directory, gpg_strerror (err));
exit (2);
}
if (!S_ISDIR(sb.st_mode))
{
log_error ("error accessing directory '%s': %s\n",
opt.directory, "not a directory");
exit (2);
}
if (sb.st_uid != getuid())
{
log_error ("directory '%s' not owned by user\n", opt.directory);
exit (2);
}
if ((sb.st_mode & (S_IROTH|S_IWOTH)))
{
log_error ("directory '%s' has too relaxed permissions\n",
opt.directory);
log_info ("Fix by running: chmod o-rw '%s'\n", opt.directory);
exit (2);
}
}
#else /*!HAVE_STAT*/
log_fatal ("program build w/o stat() call\n");
#endif /*!HAVE_STAT*/
/* Run the selected command. */
switch (cmd)
{
case aReceive:
if (argc)
wrong_args ("--receive");
err = wks_receive (es_stdin, command_receive_cb, NULL);
break;
case aCron:
if (argc)
wrong_args ("--cron");
err = command_cron ();
break;
case aListDomains:
err = command_list_domains ();
break;
case aInstallKey:
if (!argc)
err = wks_cmd_install_key (NULL, NULL);
else if (argc == 2)
err = wks_cmd_install_key (*argv, argv[1]);
else
wrong_args ("--install-key [FILE|FINGERPRINT USER-ID]");
break;
case aRemoveKey:
if (argc != 1)
wrong_args ("--remove-key USER-ID");
err = wks_cmd_remove_key (*argv);
break;
case aRevokeKey:
if (argc != 1)
wrong_args ("--revoke-key USER-ID");
err = command_revoke_key (*argv);
break;
case aCheck:
if (!argc)
wrong_args ("--check USER-IDs");
firsterr = 0;
for (; argc; argc--, argv++)
{
err = command_check_key (*argv);
if (!firsterr)
firsterr = err;
}
err = firsterr;
break;
default:
usage (1);
err = gpg_error (GPG_ERR_BUG);
break;
}
if (err)
log_error ("command failed: %s\n", gpg_strerror (err));
return log_get_errorcount (0)? 1:0;
}
/* Take the key in KEYFILE and write it to OUTFILE in binary encoding.
* If ADDRSPEC is given only matching user IDs are included in the
* output. */
static gpg_error_t
copy_key_as_binary (const char *keyfile, const char *outfile,
const char *addrspec)
{
gpg_error_t err;
ccparray_t ccp;
const char **argv = NULL;
char *filterexp = NULL;
if (addrspec)
{
filterexp = es_bsprintf ("keep-uid=mbox = %s", addrspec);
if (!filterexp)
{
err = gpg_error_from_syserror ();
log_error ("error allocating memory buffer: %s\n",
gpg_strerror (err));
goto leave;
}
}
ccparray_init (&ccp, 0);
ccparray_put (&ccp, "--no-options");
if (!opt.verbose)
ccparray_put (&ccp, "--quiet");
else if (opt.verbose > 1)
ccparray_put (&ccp, "--verbose");
ccparray_put (&ccp, "--batch");
ccparray_put (&ccp, "--yes");
ccparray_put (&ccp, "--always-trust");
ccparray_put (&ccp, "--no-keyring");
ccparray_put (&ccp, "--output");
ccparray_put (&ccp, outfile);
ccparray_put (&ccp, "--import-options=import-export");
if (filterexp)
{
ccparray_put (&ccp, "--import-filter");
ccparray_put (&ccp, filterexp);
}
ccparray_put (&ccp, "--import");
ccparray_put (&ccp, "--");
ccparray_put (&ccp, keyfile);
ccparray_put (&ccp, NULL);
argv = ccparray_get (&ccp, NULL);
if (!argv)
{
err = gpg_error_from_syserror ();
goto leave;
}
err = gnupg_exec_tool_stream (opt.gpg_program, argv, NULL,
NULL, NULL, NULL, NULL);
if (err)
{
log_error ("%s failed: %s\n", __func__, gpg_strerror (err));
goto leave;
}
leave:
xfree (filterexp);
xfree (argv);
return err;
}
/* Take the key in KEYFILE and write it to DANEFILE using the DANE
* output format. */
static gpg_error_t
copy_key_as_dane (const char *keyfile, const char *danefile)
{
gpg_error_t err;
ccparray_t ccp;
const char **argv;
ccparray_init (&ccp, 0);
ccparray_put (&ccp, "--no-options");
if (!opt.verbose)
ccparray_put (&ccp, "--quiet");
else if (opt.verbose > 1)
ccparray_put (&ccp, "--verbose");
ccparray_put (&ccp, "--batch");
ccparray_put (&ccp, "--yes");
ccparray_put (&ccp, "--always-trust");
ccparray_put (&ccp, "--no-keyring");
ccparray_put (&ccp, "--output");
ccparray_put (&ccp, danefile);
ccparray_put (&ccp, "--export-options=export-dane");
ccparray_put (&ccp, "--import-options=import-export");
ccparray_put (&ccp, "--import");
ccparray_put (&ccp, "--");
ccparray_put (&ccp, keyfile);
ccparray_put (&ccp, NULL);
argv = ccparray_get (&ccp, NULL);
if (!argv)
{
err = gpg_error_from_syserror ();
goto leave;
}
err = gnupg_exec_tool_stream (opt.gpg_program, argv, NULL,
NULL, NULL, NULL, NULL);
if (err)
{
log_error ("%s failed: %s\n", __func__, gpg_strerror (err));
goto leave;
}
leave:
xfree (argv);
return err;
}
static void
encrypt_stream_status_cb (void *opaque, const char *keyword, char *args)
{
(void)opaque;
if (DBG_CRYPTO)
log_debug ("gpg status: %s %s\n", keyword, args);
}
/* Encrypt the INPUT stream to a new stream which is stored at success
* at R_OUTPUT. Encryption is done for the key in file KEYFIL. */
static gpg_error_t
encrypt_stream (estream_t *r_output, estream_t input, const char *keyfile)
{
gpg_error_t err;
ccparray_t ccp;
const char **argv;
estream_t output;
*r_output = NULL;
output = es_fopenmem (0, "w+b");
if (!output)
{
err = gpg_error_from_syserror ();
log_error ("error allocating memory buffer: %s\n", gpg_strerror (err));
return err;
}
ccparray_init (&ccp, 0);
ccparray_put (&ccp, "--no-options");
if (!opt.verbose)
ccparray_put (&ccp, "--quiet");
else if (opt.verbose > 1)
ccparray_put (&ccp, "--verbose");
ccparray_put (&ccp, "--batch");
ccparray_put (&ccp, "--status-fd=2");
ccparray_put (&ccp, "--always-trust");
ccparray_put (&ccp, "--no-keyring");
ccparray_put (&ccp, "--armor");
ccparray_put (&ccp, "-z0"); /* No compression for improved robustness. */
ccparray_put (&ccp, "--recipient-file");
ccparray_put (&ccp, keyfile);
ccparray_put (&ccp, "--encrypt");
ccparray_put (&ccp, "--");
ccparray_put (&ccp, NULL);
argv = ccparray_get (&ccp, NULL);
if (!argv)
{
err = gpg_error_from_syserror ();
goto leave;
}
err = gnupg_exec_tool_stream (opt.gpg_program, argv, input,
NULL, output,
encrypt_stream_status_cb, NULL);
if (err)
{
log_error ("encryption failed: %s\n", gpg_strerror (err));
goto leave;
}
es_rewind (output);
*r_output = output;
output = NULL;
leave:
es_fclose (output);
xfree (argv);
return err;
}
static void
sign_stream_status_cb (void *opaque, const char *keyword, char *args)
{
(void)opaque;
if (DBG_CRYPTO)
log_debug ("gpg status: %s %s\n", keyword, args);
}
/* Sign the INPUT stream to a new stream which is stored at success at
* R_OUTPUT. A detached signature is created using the key specified
* by USERID. */
static gpg_error_t
sign_stream (estream_t *r_output, estream_t input, const char *userid)
{
gpg_error_t err;
ccparray_t ccp;
const char **argv;
estream_t output;
*r_output = NULL;
output = es_fopenmem (0, "w+b");
if (!output)
{
err = gpg_error_from_syserror ();
log_error ("error allocating memory buffer: %s\n", gpg_strerror (err));
return err;
}
ccparray_init (&ccp, 0);
ccparray_put (&ccp, "--no-options");
if (!opt.verbose)
ccparray_put (&ccp, "--quiet");
else if (opt.verbose > 1)
ccparray_put (&ccp, "--verbose");
ccparray_put (&ccp, "--batch");
ccparray_put (&ccp, "--status-fd=2");
ccparray_put (&ccp, "--armor");
ccparray_put (&ccp, "--local-user");
ccparray_put (&ccp, userid);
ccparray_put (&ccp, "--detach-sign");
ccparray_put (&ccp, "--");
ccparray_put (&ccp, NULL);
argv = ccparray_get (&ccp, NULL);
if (!argv)
{
err = gpg_error_from_syserror ();
goto leave;
}
err = gnupg_exec_tool_stream (opt.gpg_program, argv, input,
NULL, output,
sign_stream_status_cb, NULL);
if (err)
{
log_error ("signing failed: %s\n", gpg_strerror (err));
goto leave;
}
es_rewind (output);
*r_output = output;
output = NULL;
leave:
es_fclose (output);
xfree (argv);
return err;
}
/* Get the submission address for address MBOX. Caller must free the
* value. If no address can be found NULL is returned. */
static char *
get_submission_address (const char *mbox)
{
gpg_error_t err;
const char *domain;
char *fname, *line, *p;
size_t n;
estream_t fp;
domain = strchr (mbox, '@');
if (!domain)
return NULL;
domain++;
fname = make_filename_try (opt.directory, domain, "submission-address", NULL);
if (!fname)
{
err = gpg_error_from_syserror ();
log_error ("make_filename failed in %s: %s\n",
__func__, gpg_strerror (err));
return NULL;
}
fp = es_fopen (fname, "r");
if (!fp)
{
err = gpg_error_from_syserror ();
if (gpg_err_code (err) == GPG_ERR_ENOENT)
log_info ("Note: no specific submission address configured"
" for domain '%s'\n", domain);
else
log_error ("error reading '%s': %s\n", fname, gpg_strerror (err));
xfree (fname);
return NULL;
}
line = NULL;
n = 0;
if (es_getline (&line, &n, fp) < 0)
{
err = gpg_error_from_syserror ();
log_error ("error reading '%s': %s\n", fname, gpg_strerror (err));
xfree (line);
es_fclose (fp);
xfree (fname);
return NULL;
}
es_fclose (fp);
xfree (fname);
p = strchr (line, '\n');
if (p)
*p = 0;
trim_spaces (line);
if (!is_valid_mailbox (line))
{
log_error ("invalid submission address for domain '%s' detected\n",
domain);
xfree (line);
return NULL;
}
return line;
}
/* Get the policy flags for address MBOX and store them in POLICY. */
static gpg_error_t
get_policy_flags (policy_flags_t policy, const char *mbox)
{
gpg_error_t err;
const char *domain;
char *fname;
estream_t fp;
memset (policy, 0, sizeof *policy);
domain = strchr (mbox, '@');
if (!domain)
return gpg_error (GPG_ERR_INV_USER_ID);
domain++;
fname = make_filename_try (opt.directory, domain, "policy", NULL);
if (!fname)
{
err = gpg_error_from_syserror ();
log_error ("make_filename failed in %s: %s\n",
__func__, gpg_strerror (err));
return err;
}
fp = es_fopen (fname, "r");
if (!fp)
{
err = gpg_error_from_syserror ();
if (gpg_err_code (err) == GPG_ERR_ENOENT)
err = 0;
else
log_error ("error reading '%s': %s\n", fname, gpg_strerror (err));
xfree (fname);
return err;
}
err = wks_parse_policy (policy, fp, 0);
es_fclose (fp);
xfree (fname);
return err;
}
/* We store the key under the name of the nonce we will then send to
* the user. On success the nonce is stored at R_NONCE and the file
* name at R_FNAME. */
static gpg_error_t
store_key_as_pending (const char *dir, estream_t key,
char **r_nonce, char **r_fname)
{
gpg_error_t err;
char *dname = NULL;
char *fname = NULL;
char *nonce = NULL;
estream_t outfp = NULL;
char buffer[1024];
size_t nbytes, nwritten;
*r_nonce = NULL;
*r_fname = NULL;
dname = make_filename_try (dir, "pending", NULL);
if (!dname)
{
err = gpg_error_from_syserror ();
goto leave;
}
/* Create the nonce. We use 20 bytes so that we don't waste a
* character in our zBase-32 encoding. Using the gcrypt's nonce
* function is faster than using the strong random function; this is
* Good Enough for our purpose. */
log_assert (sizeof buffer > 20);
gcry_create_nonce (buffer, 20);
nonce = zb32_encode (buffer, 8 * 20);
memset (buffer, 0, 20); /* Not actually needed but it does not harm. */
if (!nonce)
{
err = gpg_error_from_syserror ();
goto leave;
}
fname = strconcat (dname, "/", nonce, NULL);
if (!fname)
{
err = gpg_error_from_syserror ();
goto leave;
}
/* With 128 bits of random we can expect that no other file exists
* under this name. We use "x" to detect internal errors. */
outfp = es_fopen (fname, "wbx,mode=-rw");
if (!outfp)
{
err = gpg_error_from_syserror ();
log_error ("error creating '%s': %s\n", fname, gpg_strerror (err));
goto leave;
}
es_rewind (key);
for (;;)
{
if (es_read (key, buffer, sizeof buffer, &nbytes))
{
err = gpg_error_from_syserror ();
log_error ("error reading '%s': %s\n",
es_fname_get (key), gpg_strerror (err));
break;
}
if (!nbytes)
{
err = 0;
goto leave; /* Ready. */
}
if (es_write (outfp, buffer, nbytes, &nwritten))
{
err = gpg_error_from_syserror ();
log_error ("error writing '%s': %s\n", fname, gpg_strerror (err));
goto leave;
}
else if (nwritten != nbytes)
{
err = gpg_error (GPG_ERR_EIO);
log_error ("error writing '%s': %s\n", fname, "short write");
goto leave;
}
}
leave:
if (err)
{
es_fclose (outfp);
gnupg_remove (fname);
}
else if (es_fclose (outfp))
{
err = gpg_error_from_syserror ();
log_error ("error closing '%s': %s\n", fname, gpg_strerror (err));
}
if (!err)
{
*r_nonce = nonce;
*r_fname = fname;
}
else
{
xfree (nonce);
xfree (fname);
}
xfree (dname);
return err;
}
/* Send a confirmation request. DIR is the directory used for the
* address MBOX. NONCE is the nonce we want to see in the response to
* this mail. FNAME the name of the file with the key. */
static gpg_error_t
send_confirmation_request (server_ctx_t ctx,
const char *mbox, const char *nonce,
const char *keyfile)
{
gpg_error_t err;
estream_t body = NULL;
estream_t bodyenc = NULL;
estream_t signeddata = NULL;
estream_t signature = NULL;
mime_maker_t mime = NULL;
char *from_buffer = NULL;
const char *from;
strlist_t sl;
from = from_buffer = get_submission_address (mbox);
if (!from)
{
from = opt.default_from;
if (!from)
{
log_error ("no sender address found for '%s'\n", mbox);
err = gpg_error (GPG_ERR_CONFIGURATION);
goto leave;
}
log_info ("Note: using default sender address '%s'\n", from);
}
body = es_fopenmem (0, "w+b");
if (!body)
{
err = gpg_error_from_syserror ();
log_error ("error allocating memory buffer: %s\n", gpg_strerror (err));
goto leave;
}
if (!ctx->draft_version_2)
{
/* It is fine to use 8 bit encoding because that is encrypted and
* only our client will see it. */
es_fputs ("Content-Type: application/vnd.gnupg.wks\n"
"Content-Transfer-Encoding: 8bit\n"
"\n",
body);
}
es_fprintf (body, ("type: confirmation-request\n"
"sender: %s\n"
"address: %s\n"
"fingerprint: %s\n"
"nonce: %s\n"),
from,
mbox,
ctx->fpr,
nonce);
es_rewind (body);
err = encrypt_stream (&bodyenc, body, keyfile);
if (err)
goto leave;
es_fclose (body);
body = NULL;
err = mime_maker_new (&mime, NULL);
if (err)
goto leave;
err = mime_maker_add_header (mime, "From", from);
if (err)
goto leave;
err = mime_maker_add_header (mime, "To", mbox);
if (err)
goto leave;
err = mime_maker_add_header (mime, "Subject", "Confirm your key publication");
if (err)
goto leave;
err = mime_maker_add_header (mime, "Wks-Draft-Version",
STR2(WKS_DRAFT_VERSION));
if (err)
goto leave;
/* Help Enigmail to identify messages. Note that this is in no way
* secured. */
err = mime_maker_add_header (mime, "WKS-Phase", "confirm");
if (err)
goto leave;
for (sl = opt.extra_headers; sl; sl = sl->next)
{
err = mime_maker_add_header (mime, sl->d, NULL);
if (err)
goto leave;
}
if (!ctx->draft_version_2)
{
err = mime_maker_add_header (mime, "Content-Type",
"multipart/encrypted; "
"protocol=\"application/pgp-encrypted\"");
if (err)
goto leave;
err = mime_maker_add_container (mime);
if (err)
goto leave;
err = mime_maker_add_header (mime, "Content-Type",
"application/pgp-encrypted");
if (err)
goto leave;
err = mime_maker_add_body (mime, "Version: 1\n");
if (err)
goto leave;
err = mime_maker_add_header (mime, "Content-Type",
"application/octet-stream");
if (err)
goto leave;
err = mime_maker_add_stream (mime, &bodyenc);
if (err)
goto leave;
}
else
{
unsigned int partid;
/* FIXME: Add micalg. */
err = mime_maker_add_header (mime, "Content-Type",
"multipart/signed; "
"protocol=\"application/pgp-signature\"");
if (err)
goto leave;
err = mime_maker_add_container (mime);
if (err)
goto leave;
err = mime_maker_add_header (mime, "Content-Type", "multipart/mixed");
if (err)
goto leave;
err = mime_maker_add_container (mime);
if (err)
goto leave;
partid = mime_maker_get_partid (mime);
err = mime_maker_add_header (mime, "Content-Type", "text/plain");
if (err)
goto leave;
err = mime_maker_add_body
(mime,
"This message has been send to confirm your request\n"
"to publish your key. If you did not request a key\n"
"publication, simply ignore this message.\n"
"\n"
"Most mail software can handle this kind of message\n"
"automatically and thus you would not have seen this\n"
"message. It seems that your client does not fully\n"
"support this service. The web page\n"
"\n"
" https://gnupg.org/faq/wkd.html\n"
"\n"
"explains how you can process this message anyway in\n"
"a few manual steps.\n");
if (err)
goto leave;
err = mime_maker_add_header (mime, "Content-Type",
"application/vnd.gnupg.wks");
if (err)
goto leave;
err = mime_maker_add_stream (mime, &bodyenc);
if (err)
goto leave;
err = mime_maker_end_container (mime);
if (err)
goto leave;
/* mime_maker_dump_tree (mime); */
err = mime_maker_get_part (mime, partid, &signeddata);
if (err)
goto leave;
err = sign_stream (&signature, signeddata, from);
if (err)
goto leave;
err = mime_maker_add_header (mime, "Content-Type",
"application/pgp-signature");
if (err)
goto leave;
err = mime_maker_add_stream (mime, &signature);
if (err)
goto leave;
}
err = wks_send_mime (mime);
leave:
mime_maker_release (mime);
es_fclose (signature);
es_fclose (signeddata);
es_fclose (bodyenc);
es_fclose (body);
xfree (from_buffer);
return err;
}
/* Store the key given by KEY into the pending directory and send a
* confirmation requests. */
static gpg_error_t
process_new_key (server_ctx_t ctx, estream_t key)
{
gpg_error_t err;
uidinfo_list_t sl;
const char *s;
char *dname = NULL;
char *nonce = NULL;
char *fname = NULL;
struct policy_flags_s policybuf;
memset (&policybuf, 0, sizeof policybuf);
/* First figure out the user id from the key. */
xfree (ctx->fpr);
free_uidinfo_list (ctx->mboxes);
err = wks_list_key (key, &ctx->fpr, &ctx->mboxes);
if (err)
goto leave;
log_assert (ctx->fpr);
log_info ("fingerprint: %s\n", ctx->fpr);
for (sl = ctx->mboxes; sl; sl = sl->next)
{
if (sl->mbox)
log_info (" addr-spec: %s\n", sl->mbox);
}
/* Walk over all user ids and send confirmation requests for those
* we support. */
for (sl = ctx->mboxes; sl; sl = sl->next)
{
if (!sl->mbox)
continue;
s = strchr (sl->mbox, '@');
log_assert (s && s[1]);
xfree (dname);
dname = make_filename_try (opt.directory, s+1, NULL);
if (!dname)
{
err = gpg_error_from_syserror ();
goto leave;
}
if (access (dname, W_OK))
{
log_info ("skipping address '%s': Domain not configured\n", sl->mbox);
continue;
}
if (get_policy_flags (&policybuf, sl->mbox))
{
log_info ("skipping address '%s': Bad policy flags\n", sl->mbox);
continue;
}
if (policybuf.auth_submit)
{
/* Bypass the confirmation stuff and publish the key as is. */
log_info ("publishing address '%s'\n", sl->mbox);
/* FIXME: We need to make sure that we do this only for the
* address in the mail. */
log_debug ("auth-submit not yet working!\n");
}
else
{
log_info ("storing address '%s'\n", sl->mbox);
xfree (nonce);
xfree (fname);
err = store_key_as_pending (dname, key, &nonce, &fname);
if (err)
goto leave;
err = send_confirmation_request (ctx, sl->mbox, nonce, fname);
if (err)
goto leave;
}
}
leave:
if (nonce)
wipememory (nonce, strlen (nonce));
xfree (nonce);
xfree (fname);
xfree (dname);
wks_free_policy (&policybuf);
return err;
}
/* Send a message to tell the user at MBOX that their key has been
* published. FNAME the name of the file with the key. */
static gpg_error_t
send_congratulation_message (const char *mbox, const char *keyfile)
{
gpg_error_t err;
estream_t body = NULL;
estream_t bodyenc = NULL;
mime_maker_t mime = NULL;
char *from_buffer = NULL;
const char *from;
strlist_t sl;
from = from_buffer = get_submission_address (mbox);
if (!from)
{
from = opt.default_from;
if (!from)
{
log_error ("no sender address found for '%s'\n", mbox);
err = gpg_error (GPG_ERR_CONFIGURATION);
goto leave;
}
log_info ("Note: using default sender address '%s'\n", from);
}
body = es_fopenmem (0, "w+b");
if (!body)
{
err = gpg_error_from_syserror ();
log_error ("error allocating memory buffer: %s\n", gpg_strerror (err));
goto leave;
}
/* It is fine to use 8 bit encoding because that is encrypted and
* only our client will see it. */
es_fputs ("Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"\n",
body);
es_fprintf (body,
"Hello!\n\n"
"The key for your address '%s' has been published\n"
"and can now be retrieved from the Web Key Directory.\n"
"\n"
"For more information on this system see:\n"
"\n"
" https://gnupg.org/faq/wkd.html\n"
"\n"
"Best regards\n"
"\n"
" Gnu Key Publisher\n\n\n"
"-- \n"
"The GnuPG Project welcomes donations: %s\n",
mbox, "https://gnupg.org/donate");
es_rewind (body);
err = encrypt_stream (&bodyenc, body, keyfile);
if (err)
goto leave;
es_fclose (body);
body = NULL;
err = mime_maker_new (&mime, NULL);
if (err)
goto leave;
err = mime_maker_add_header (mime, "From", from);
if (err)
goto leave;
err = mime_maker_add_header (mime, "To", mbox);
if (err)
goto leave;
err = mime_maker_add_header (mime, "Subject", "Your key has been published");
if (err)
goto leave;
err = mime_maker_add_header (mime, "Wks-Draft-Version",
STR2(WKS_DRAFT_VERSION));
if (err)
goto leave;
err = mime_maker_add_header (mime, "WKS-Phase", "done");
if (err)
goto leave;
for (sl = opt.extra_headers; sl; sl = sl->next)
{
err = mime_maker_add_header (mime, sl->d, NULL);
if (err)
goto leave;
}
err = mime_maker_add_header (mime, "Content-Type",
"multipart/encrypted; "
"protocol=\"application/pgp-encrypted\"");
if (err)
goto leave;
err = mime_maker_add_container (mime);
if (err)
goto leave;
err = mime_maker_add_header (mime, "Content-Type",
"application/pgp-encrypted");
if (err)
goto leave;
err = mime_maker_add_body (mime, "Version: 1\n");
if (err)
goto leave;
err = mime_maker_add_header (mime, "Content-Type",
"application/octet-stream");
if (err)
goto leave;
err = mime_maker_add_stream (mime, &bodyenc);
if (err)
goto leave;
err = wks_send_mime (mime);
leave:
mime_maker_release (mime);
es_fclose (bodyenc);
es_fclose (body);
xfree (from_buffer);
return err;
}
/* Check that we have send a request with NONCE and publish the key. */
static gpg_error_t
check_and_publish (server_ctx_t ctx, const char *address, const char *nonce)
{
gpg_error_t err;
char *fname = NULL;
char *fnewname = NULL;
estream_t key = NULL;
char *hash = NULL;
const char *domain;
const char *s;
uidinfo_list_t sl;
char shaxbuf[32]; /* Used for SHA-1 and SHA-256 */
/* FIXME: There is a bug in name-value.c which adds white space for
* the last pair and thus we strip the nonce here until this has
* been fixed. */
char *nonce2 = xstrdup (nonce);
trim_trailing_spaces (nonce2);
nonce = nonce2;
domain = strchr (address, '@');
log_assert (domain && domain[1]);
domain++;
fname = make_filename_try (opt.directory, domain, "pending", nonce, NULL);
if (!fname)
{
err = gpg_error_from_syserror ();
goto leave;
}
/* Try to open the file with the key. */
key = es_fopen (fname, "rb");
if (!key)
{
err = gpg_error_from_syserror ();
if (gpg_err_code (err) == GPG_ERR_ENOENT)
{
log_info ("no pending request for '%s'\n", address);
err = gpg_error (GPG_ERR_NOT_FOUND);
}
else
log_error ("error reading '%s': %s\n", fname, gpg_strerror (err));
goto leave;
}
/* We need to get the fingerprint from the key. */
xfree (ctx->fpr);
free_uidinfo_list (ctx->mboxes);
err = wks_list_key (key, &ctx->fpr, &ctx->mboxes);
if (err)
goto leave;
log_assert (ctx->fpr);
log_info ("fingerprint: %s\n", ctx->fpr);
for (sl = ctx->mboxes; sl; sl = sl->next)
if (sl->mbox)
log_info (" addr-spec: %s\n", sl->mbox);
/* Check that the key has 'address' as a user id. We use
* case-insensitive matching because the client is expected to
* return the address verbatim. */
for (sl = ctx->mboxes; sl; sl = sl->next)
if (sl->mbox && !strcmp (sl->mbox, address))
break;
if (!sl)
{
log_error ("error publishing key: '%s' is not a user ID of %s\n",
address, ctx->fpr);
err = gpg_error (GPG_ERR_NO_PUBKEY);
goto leave;
}
/* Hash user ID and create filename. */
err = wks_compute_hu_fname (&fnewname, address);
if (err)
goto leave;
/* Publish. */
err = copy_key_as_binary (fname, fnewname, address);
if (err)
{
err = gpg_error_from_syserror ();
log_error ("copying '%s' to '%s' failed: %s\n",
fname, fnewname, gpg_strerror (err));
goto leave;
}
/* Make sure it is world readable. */
if (gnupg_chmod (fnewname, "-rwxr--r--"))
log_error ("can't set permissions of '%s': %s\n",
fnewname, gpg_strerror (gpg_err_code_from_syserror()));
log_info ("key %s published for '%s'\n", ctx->fpr, address);
send_congratulation_message (address, fnewname);
/* Try to publish as DANE record if the DANE directory exists. */
xfree (fname);
fname = fnewname;
fnewname = make_filename_try (opt.directory, domain, "dane", NULL);
if (!fnewname)
{
err = gpg_error_from_syserror ();
goto leave;
}
if (!access (fnewname, W_OK))
{
/* Yes, we have a dane directory. */
s = strchr (address, '@');
log_assert (s);
gcry_md_hash_buffer (GCRY_MD_SHA256, shaxbuf, address, s - address);
xfree (hash);
hash = bin2hex (shaxbuf, 28, NULL);
if (!hash)
{
err = gpg_error_from_syserror ();
goto leave;
}
xfree (fnewname);
fnewname = make_filename_try (opt.directory, domain, "dane", hash, NULL);
if (!fnewname)
{
err = gpg_error_from_syserror ();
goto leave;
}
err = copy_key_as_dane (fname, fnewname);
if (err)
goto leave;
log_info ("key %s published for '%s' (DANE record)\n", ctx->fpr, address);
}
leave:
es_fclose (key);
xfree (hash);
xfree (fnewname);
xfree (fname);
xfree (nonce2);
return err;
}
/* Process a confirmation response in MSG. */
static gpg_error_t
process_confirmation_response (server_ctx_t ctx, estream_t msg)
{
gpg_error_t err;
nvc_t nvc;
nve_t item;
const char *value, *sender, *address, *nonce;
err = nvc_parse (&nvc, NULL, msg);
if (err)
{
log_error ("parsing the WKS message failed: %s\n", gpg_strerror (err));
goto leave;
}
if (opt.debug)
{
log_debug ("response follows:\n");
nvc_write (nvc, log_get_stream ());
}
/* Check that this is a confirmation response. */
if (!((item = nvc_lookup (nvc, "type:")) && (value = nve_value (item))
&& !strcmp (value, "confirmation-response")))
{
if (item && value)
log_error ("received unexpected wks message '%s'\n", value);
else
log_error ("received invalid wks message: %s\n", "'type' missing");
err = gpg_error (GPG_ERR_UNEXPECTED_MSG);
goto leave;
}
/* Get the sender. */
if (!((item = nvc_lookup (nvc, "sender:")) && (value = nve_value (item))
&& is_valid_mailbox (value)))
{
log_error ("received invalid wks message: %s\n",
"'sender' missing or invalid");
err = gpg_error (GPG_ERR_INV_DATA);
goto leave;
}
sender = value;
(void)sender;
/* FIXME: Do we really need the sender?. */
/* Get the address. */
if (!((item = nvc_lookup (nvc, "address:")) && (value = nve_value (item))
&& is_valid_mailbox (value)))
{
log_error ("received invalid wks message: %s\n",
"'address' missing or invalid");
err = gpg_error (GPG_ERR_INV_DATA);
goto leave;
}
address = value;
/* Get the nonce. */
if (!((item = nvc_lookup (nvc, "nonce:")) && (value = nve_value (item))
&& strlen (value) > 16))
{
log_error ("received invalid wks message: %s\n",
"'nonce' missing or too short");
err = gpg_error (GPG_ERR_INV_DATA);
goto leave;
}
nonce = value;
err = check_and_publish (ctx, address, nonce);
leave:
nvc_release (nvc);
return err;
}
/* Called from the MIME receiver to process the plain text data in MSG . */
static gpg_error_t
command_receive_cb (void *opaque, const char *mediatype,
estream_t msg, unsigned int flags)
{
gpg_error_t err;
struct server_ctx_s ctx;
(void)opaque;
memset (&ctx, 0, sizeof ctx);
if ((flags & WKS_RECEIVE_DRAFT2))
ctx.draft_version_2 = 1;
if (!strcmp (mediatype, "application/pgp-keys"))
err = process_new_key (&ctx, msg);
else if (!strcmp (mediatype, "application/vnd.gnupg.wks"))
err = process_confirmation_response (&ctx, msg);
else
{
log_info ("ignoring unexpected message of type '%s'\n", mediatype);
err = gpg_error (GPG_ERR_UNEXPECTED_MSG);
}
xfree (ctx.fpr);
free_uidinfo_list (ctx.mboxes);
return err;
}
/* Return a list of all configured domains. Each list element is the
* top directory for the domain. To figure out the actual domain
* name strrchr(name, '/') can be used. */
static gpg_error_t
get_domain_list (strlist_t *r_list)
{
gpg_error_t err;
DIR *dir = NULL;
char *fname = NULL;
struct dirent *dentry;
struct stat sb;
strlist_t list = NULL;
*r_list = NULL;
dir = opendir (opt.directory);
if (!dir)
{
err = gpg_error_from_syserror ();
goto leave;
}
while ((dentry = readdir (dir)))
{
if (*dentry->d_name == '.')
continue;
if (!strchr (dentry->d_name, '.'))
continue; /* No dot - can't be a domain subdir. */
xfree (fname);
fname = make_filename_try (opt.directory, dentry->d_name, NULL);
if (!fname)
{
err = gpg_error_from_syserror ();
log_error ("make_filename failed in %s: %s\n",
__func__, gpg_strerror (err));
goto leave;
}
if (stat (fname, &sb))
{
err = gpg_error_from_syserror ();
log_error ("error accessing '%s': %s\n", fname, gpg_strerror (err));
continue;
}
if (!S_ISDIR(sb.st_mode))
continue;
if (!add_to_strlist_try (&list, fname))
{
err = gpg_error_from_syserror ();
log_error ("add_to_strlist failed in %s: %s\n",
__func__, gpg_strerror (err));
goto leave;
}
}
err = 0;
*r_list = list;
list = NULL;
leave:
free_strlist (list);
if (dir)
closedir (dir);
xfree (fname);
return err;
}
static gpg_error_t
expire_one_domain (const char *top_dirname, const char *domain)
{
gpg_error_t err;
char *dirname;
char *fname = NULL;
DIR *dir = NULL;
struct dirent *dentry;
struct stat sb;
time_t now = gnupg_get_time ();
dirname = make_filename_try (top_dirname, "pending", NULL);
if (!dirname)
{
err = gpg_error_from_syserror ();
log_error ("make_filename failed in %s: %s\n",
__func__, gpg_strerror (err));
goto leave;
}
dir = opendir (dirname);
if (!dir)
{
err = gpg_error_from_syserror ();
log_error (("can't access directory '%s': %s\n"),
dirname, gpg_strerror (err));
goto leave;
}
while ((dentry = readdir (dir)))
{
if (*dentry->d_name == '.')
continue;
xfree (fname);
fname = make_filename_try (dirname, dentry->d_name, NULL);
if (!fname)
{
err = gpg_error_from_syserror ();
log_error ("make_filename failed in %s: %s\n",
__func__, gpg_strerror (err));
goto leave;
}
if (strlen (dentry->d_name) != 32)
{
log_info ("garbage file '%s' ignored\n", fname);
continue;
}
if (stat (fname, &sb))
{
err = gpg_error_from_syserror ();
log_error ("error accessing '%s': %s\n", fname, gpg_strerror (err));
continue;
}
if (S_ISDIR(sb.st_mode))
{
log_info ("garbage directory '%s' ignored\n", fname);
continue;
}
if (sb.st_mtime + PENDING_TTL < now)
{
if (opt.verbose)
log_info ("domain %s: removing pending key '%s'\n",
domain, dentry->d_name);
if (remove (fname))
{
err = gpg_error_from_syserror ();
/* In case the file has just been renamed or another
* processes is cleaning up, we don't print a diagnostic
* for ENOENT. */
if (gpg_err_code (err) != GPG_ERR_ENOENT)
log_error ("error removing '%s': %s\n",
fname, gpg_strerror (err));
}
}
}
err = 0;
leave:
if (dir)
closedir (dir);
xfree (dirname);
xfree (fname);
return err;
}
/* Scan spool directories and expire too old pending keys. */
static gpg_error_t
expire_pending_confirmations (strlist_t domaindirs)
{
gpg_error_t err = 0;
strlist_t sl;
const char *domain;
for (sl = domaindirs; sl; sl = sl->next)
{
domain = strrchr (sl->d, '/');
log_assert (domain);
domain++;
expire_one_domain (sl->d, domain);
}
return err;
}
/* List all configured domains. */
static gpg_error_t
command_list_domains (void)
{
static struct {
const char *name;
const char *perm;
} requireddirs[] = {
{ "pending", "-rwx" },
{ "hu", "-rwxr-xr-x" }
};
gpg_error_t err;
strlist_t domaindirs;
strlist_t sl;
const char *domain;
char *fname = NULL;
int i;
estream_t fp;
err = get_domain_list (&domaindirs);
if (err)
{
log_error ("error reading list of domains: %s\n", gpg_strerror (err));
return err;
}
for (sl = domaindirs; sl; sl = sl->next)
{
domain = strrchr (sl->d, '/');
log_assert (domain);
domain++;
if (opt_with_dir)
es_printf ("%s %s\n", domain, sl->d);
else
es_printf ("%s\n", domain);
/* Check that the required directories are there. */
for (i=0; i < DIM (requireddirs); i++)
{
xfree (fname);
fname = make_filename_try (sl->d, requireddirs[i].name, NULL);
if (!fname)
{
err = gpg_error_from_syserror ();
goto leave;
}
if (access (fname, W_OK))
{
err = gpg_error_from_syserror ();
if (gpg_err_code (err) == GPG_ERR_ENOENT)
{
if (gnupg_mkdir (fname, requireddirs[i].perm))
{
err = gpg_error_from_syserror ();
log_error ("domain %s: error creating subdir '%s': %s\n",
domain, requireddirs[i].name,
gpg_strerror (err));
}
else
log_info ("domain %s: subdir '%s' created\n",
domain, requireddirs[i].name);
}
else if (err)
log_error ("domain %s: problem with subdir '%s': %s\n",
domain, requireddirs[i].name, gpg_strerror (err));
}
}
/* Print a warning if the submission address is not configured. */
xfree (fname);
fname = make_filename_try (sl->d, "submission-address", NULL);
if (!fname)
{
err = gpg_error_from_syserror ();
goto leave;
}
if (access (fname, F_OK))
{
err = gpg_error_from_syserror ();
if (gpg_err_code (err) == GPG_ERR_ENOENT)
log_error ("domain %s: submission address not configured\n",
domain);
else
log_error ("domain %s: problem with '%s': %s\n",
domain, fname, gpg_strerror (err));
}
/* Check the syntax of the optional policy file. */
xfree (fname);
fname = make_filename_try (sl->d, "policy", NULL);
if (!fname)
{
err = gpg_error_from_syserror ();
goto leave;
}
fp = es_fopen (fname, "r");
if (!fp)
{
err = gpg_error_from_syserror ();
if (gpg_err_code (err) == GPG_ERR_ENOENT)
{
fp = es_fopen (fname, "w");
if (!fp)
log_error ("domain %s: can't create policy file: %s\n",
domain, gpg_strerror (err));
else
es_fclose (fp);
fp = NULL;
}
else
log_error ("domain %s: error in policy file: %s\n",
domain, gpg_strerror (err));
}
else
{
struct policy_flags_s policy;
err = wks_parse_policy (&policy, fp, 0);
es_fclose (fp);
wks_free_policy (&policy);
}
}
err = 0;
leave:
xfree (fname);
free_strlist (domaindirs);
return err;
}
/* Run regular maintenance jobs. */
static gpg_error_t
command_cron (void)
{
gpg_error_t err;
strlist_t domaindirs;
err = get_domain_list (&domaindirs);
if (err)
{
log_error ("error reading list of domains: %s\n", gpg_strerror (err));
return err;
}
err = expire_pending_confirmations (domaindirs);
free_strlist (domaindirs);
return err;
}
/* Check whether the key with USER_ID is installed. */
static gpg_error_t
command_check_key (const char *userid)
{
gpg_error_t err;
char *addrspec = NULL;
char *fname = NULL;
- err = wks_fname_from_userid (userid, &fname, &addrspec);
+ err = wks_fname_from_userid (userid, 0, &fname, &addrspec);
if (err)
goto leave;
if (access (fname, R_OK))
{
err = gpg_error_from_syserror ();
if (opt_with_file)
es_printf ("%s n %s\n", addrspec, fname);
if (gpg_err_code (err) == GPG_ERR_ENOENT)
{
if (!opt.quiet)
log_info ("key for '%s' is NOT installed\n", addrspec);
log_inc_errorcount ();
err = 0;
}
else
log_error ("error stating '%s': %s\n", fname, gpg_strerror (err));
goto leave;
}
if (opt_with_file)
es_printf ("%s i %s\n", addrspec, fname);
if (opt.verbose)
log_info ("key for '%s' is installed\n", addrspec);
err = 0;
leave:
xfree (fname);
xfree (addrspec);
return err;
}
/* Revoke the key with mail address MAILADDR. */
static gpg_error_t
command_revoke_key (const char *mailaddr)
{
/* Remove should be different from removing but we have not yet
* defined a suitable way to do this. */
return wks_cmd_remove_key (mailaddr);
}
diff --git a/tools/gpg-wks.h b/tools/gpg-wks.h
index e36943090..9acd7c37f 100644
--- a/tools/gpg-wks.h
+++ b/tools/gpg-wks.h
@@ -1,122 +1,124 @@
/* gpg-wks.h - Common definitions for wks server and client.
* Copyright (C) 2016 g10 Code GmbH
* Copyright (C) 2016 Bundesamt für Sicherheit in der Informationstechnik
*
* This file is part of GnuPG.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This file 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#ifndef GNUPG_GPG_WKS_H
#define GNUPG_GPG_WKS_H
#include "../common/util.h"
#include "../common/strlist.h"
#include "mime-maker.h"
/* The draft version we implement. */
#define WKS_DRAFT_VERSION 3
/* We keep all global options in the structure OPT. */
struct
{
int verbose;
unsigned int debug;
int quiet;
int use_sendmail;
int with_colons;
const char *output;
const char *gpg_program;
const char *directory;
const char *default_from;
strlist_t extra_headers;
} opt;
/* Debug values and macros. */
#define DBG_MIME_VALUE 1 /* Debug the MIME structure. */
#define DBG_PARSER_VALUE 2 /* Debug the Mail parser. */
#define DBG_CRYPTO_VALUE 4 /* Debug low level crypto. */
#define DBG_MEMORY_VALUE 32 /* Debug memory allocation stuff. */
#define DBG_MEMSTAT_VALUE 128 /* Show memory statistics. */
#define DBG_IPC_VALUE 1024 /* Debug assuan communication. */
#define DBG_EXTPROG_VALUE 16384 /* debug external program calls */
#define DBG_MIME (opt.debug & DBG_MIME_VALUE)
#define DBG_PARSER (opt.debug & DBG_PARSER_VALUE)
#define DBG_CRYPTO (opt.debug & DBG_CRYPTO_VALUE)
/* The parsed policy flags. */
struct policy_flags_s
{
char *submission_address;
unsigned int mailbox_only : 1;
unsigned int dane_only : 1;
unsigned int auth_submit : 1;
unsigned int protocol_version; /* The supported WKS_DRAFT_VERION or 0 */
unsigned int max_pending; /* Seconds to wait for a confirmation. */
};
typedef struct policy_flags_s *policy_flags_t;
/* An object to convey user ids of a key. */
struct uidinfo_list_s
{
struct uidinfo_list_s *next;
time_t created; /* Time the userid was created. */
char *mbox; /* NULL or the malloced mailbox from UID. */
char uid[1];
};
typedef struct uidinfo_list_s *uidinfo_list_t;
/*-- wks-util.c --*/
void wks_set_status_fd (int fd);
void wks_write_status (int no, const char *format, ...) GPGRT_ATTR_PRINTF(2,3);
void free_uidinfo_list (uidinfo_list_t list);
gpg_error_t wks_get_key (estream_t *r_key, const char *fingerprint,
const char *addrspec, int exact);
gpg_error_t wks_list_key (estream_t key, char **r_fpr,
uidinfo_list_t *r_mboxes);
gpg_error_t wks_filter_uid (estream_t *r_newkey, estream_t key,
const char *uid, int binary);
gpg_error_t wks_send_mime (mime_maker_t mime);
gpg_error_t wks_parse_policy (policy_flags_t flags, estream_t stream,
int ignore_unknown);
void wks_free_policy (policy_flags_t policy);
-gpg_error_t wks_fname_from_userid (const char *userid,
+gpg_error_t wks_fname_from_userid (const char *userid, int hash_only,
char **r_fname, char **r_addrspec);
gpg_error_t wks_compute_hu_fname (char **r_fname, const char *addrspec);
gpg_error_t wks_cmd_install_key (const char *fname, const char *userid);
gpg_error_t wks_cmd_remove_key (const char *userid);
+gpg_error_t wks_cmd_print_wkd_hash (const char *userid);
+gpg_error_t wks_cmd_print_wkd_url (const char *userid);
/*-- wks-receive.c --*/
/* Flag values for the receive callback. */
#define WKS_RECEIVE_DRAFT2 1
gpg_error_t wks_receive (estream_t fp,
gpg_error_t (*result_cb)(void *opaque,
const char *mediatype,
estream_t data,
unsigned int flags),
void *cb_data);
#endif /*GNUPG_GPG_WKS_H*/
diff --git a/tools/gpgconf-comp.c b/tools/gpgconf-comp.c
index 2ae79d91d..628ca358f 100644
--- a/tools/gpgconf-comp.c
+++ b/tools/gpgconf-comp.c
@@ -1,4197 +1,4204 @@
/* gpgconf-comp.c - Configuration utility for GnuPG.
* Copyright (C) 2004, 2007-2011 Free Software Foundation, Inc.
* Copyright (C) 2016 Werner Koch
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with GnuPG; if not, see <https://www.gnu.org/licenses/>.
*/
#if HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <assert.h>
#include <errno.h>
#include <time.h>
#include <stdarg.h>
#ifdef HAVE_SIGNAL_H
# include <signal.h>
#endif
#include <ctype.h>
#ifdef HAVE_W32_SYSTEM
# define WIN32_LEAN_AND_MEAN 1
# include <windows.h>
#else
# include <pwd.h>
# include <grp.h>
#endif
/* For log_logv(), asctimestamp(), gnupg_get_time (). */
#include "../common/util.h"
#include "../common/i18n.h"
#include "../common/exechelp.h"
#include "../common/sysutils.h"
#include "../common/status.h"
#include "../common/gc-opt-flags.h"
#include "gpgconf.h"
/* There is a problem with gpg 1.4 under Windows: --gpgconf-list
returns a plain filename without escaping. As long as we have not
fixed that we need to use gpg2. */
#if defined(HAVE_W32_SYSTEM) && !defined(HAVE_W32CE_SYSTEM)
#define GPGNAME "gpg2"
#else
#define GPGNAME GPG_NAME
#endif
/* TODO:
Components: Add more components and their options.
Robustness: Do more validation. Call programs to do validation for us.
Add options to change backend binary path.
Extract binary path for some backends from gpgsm/gpg config.
*/
#if (__GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 5 ))
void gc_error (int status, int errnum, const char *fmt, ...) \
__attribute__ ((format (printf, 3, 4)));
#endif
/* Output a diagnostic message. If ERRNUM is not 0, then the output
is followed by a colon, a white space, and the error string for the
error number ERRNUM. In any case the output is finished by a
newline. The message is prepended by the program name, a colon,
and a whitespace. The output may be further formatted or
redirected by the jnlib logging facility. */
void
gc_error (int status, int errnum, const char *fmt, ...)
{
va_list arg_ptr;
va_start (arg_ptr, fmt);
log_logv (GPGRT_LOGLVL_ERROR, fmt, arg_ptr);
va_end (arg_ptr);
if (errnum)
log_printf (": %s\n", strerror (errnum));
else
log_printf ("\n");
if (status)
{
log_printf (NULL);
log_printf ("fatal error (exit status %i)\n", status);
gpgconf_failure (gpg_error_from_errno (errnum));
}
}
/* Forward declaration. */
static void gpg_agent_runtime_change (int killflag);
static void scdaemon_runtime_change (int killflag);
static void dirmngr_runtime_change (int killflag);
/* Backend configuration. Backends are used to decide how the default
and current value of an option can be determined, and how the
option can be changed. To every option in every component belongs
exactly one backend that controls and determines the option. Some
backends are programs from the GPG system. Others might be
implemented by GPGConf itself. If you change this enum, don't
forget to update GC_BACKEND below. */
typedef enum
{
/* Any backend, used for find_option (). */
GC_BACKEND_ANY,
/* The Gnu Privacy Guard. */
GC_BACKEND_GPG,
/* The Gnu Privacy Guard for S/MIME. */
GC_BACKEND_GPGSM,
/* The GPG Agent. */
GC_BACKEND_GPG_AGENT,
/* The GnuPG SCDaemon. */
GC_BACKEND_SCDAEMON,
/* The GnuPG directory manager. */
GC_BACKEND_DIRMNGR,
/* The LDAP server list file for the director manager. */
GC_BACKEND_DIRMNGR_LDAP_SERVER_LIST,
/* The Pinentry (not a part of GnuPG, proper). */
GC_BACKEND_PINENTRY,
/* The number of the above entries. */
GC_BACKEND_NR
} gc_backend_t;
/* To be able to implement generic algorithms for the various
backends, we collect all information about them in this struct. */
static const struct
{
/* The name of the backend. */
const char *name;
/* The name of the program that acts as the backend. Some backends
don't have an associated program, but are implemented directly by
GPGConf. In this case, PROGRAM is NULL. */
char *program;
/* The module name (GNUPG_MODULE_NAME_foo) as defined by
../common/util.h. This value is used to get the actual installed
path of the program. 0 is used if no backend program is
available. */
char module_name;
/* The runtime change callback. If KILLFLAG is true the component
is killed and not just reloaded. */
void (*runtime_change) (int killflag);
/* The option name for the configuration filename of this backend.
This must be an absolute filename. It can be an option from a
different backend (but then ordering of the options might
matter). Note: This must be unique among all components. */
const char *option_config_filename;
/* If this is a file backend rather than a program backend, then
this is the name of the option associated with the file. */
const char *option_name;
} gc_backend[GC_BACKEND_NR] =
{
{ NULL }, /* GC_BACKEND_ANY dummy entry. */
{ GPG_DISP_NAME, GPGNAME, GNUPG_MODULE_NAME_GPG,
NULL, GPGCONF_NAME "-" GPG_NAME ".conf" },
{ GPGSM_DISP_NAME, GPGSM_NAME, GNUPG_MODULE_NAME_GPGSM,
NULL, GPGCONF_NAME "-" GPGSM_NAME ".conf" },
{ GPG_AGENT_DISP_NAME, GPG_AGENT_NAME, GNUPG_MODULE_NAME_AGENT,
gpg_agent_runtime_change, GPGCONF_NAME"-" GPG_AGENT_NAME ".conf" },
{ SCDAEMON_DISP_NAME, SCDAEMON_NAME, GNUPG_MODULE_NAME_SCDAEMON,
scdaemon_runtime_change, GPGCONF_NAME"-" SCDAEMON_NAME ".conf" },
{ DIRMNGR_DISP_NAME, DIRMNGR_NAME, GNUPG_MODULE_NAME_DIRMNGR,
dirmngr_runtime_change, GPGCONF_NAME "-" DIRMNGR_NAME ".conf" },
{ DIRMNGR_DISP_NAME " LDAP Server List", NULL, 0,
NULL, "ldapserverlist-file", "LDAP Server" },
{ "Pinentry", "pinentry", GNUPG_MODULE_NAME_PINENTRY,
NULL, GPGCONF_NAME "-pinentry.conf" },
};
/* Option configuration. */
/* An option might take an argument, or not. Argument types can be
basic or complex. Basic types are generic and easy to validate.
Complex types provide more specific information about the intended
use, but can be difficult to validate. If you add to this enum,
don't forget to update GC_ARG_TYPE below. YOU MUST NOT CHANGE THE
NUMBERS OF THE EXISTING ENTRIES, AS THEY ARE PART OF THE EXTERNAL
INTERFACE. */
typedef enum
{
/* Basic argument types. */
/* No argument. */
GC_ARG_TYPE_NONE = 0,
/* A String argument. */
GC_ARG_TYPE_STRING = 1,
/* A signed integer argument. */
GC_ARG_TYPE_INT32 = 2,
/* An unsigned integer argument. */
GC_ARG_TYPE_UINT32 = 3,
/* ADD NEW BASIC TYPE ENTRIES HERE. */
/* Complex argument types. */
/* A complete filename. */
GC_ARG_TYPE_FILENAME = 32,
/* An LDAP server in the format
HOSTNAME:PORT:USERNAME:PASSWORD:BASE_DN. */
GC_ARG_TYPE_LDAP_SERVER = 33,
/* A 40 character fingerprint. */
GC_ARG_TYPE_KEY_FPR = 34,
/* A user ID or key ID or fingerprint for a certificate. */
GC_ARG_TYPE_PUB_KEY = 35,
/* A user ID or key ID or fingerprint for a certificate with a key. */
GC_ARG_TYPE_SEC_KEY = 36,
/* A alias list made up of a key, an equal sign and a space
separated list of values. */
GC_ARG_TYPE_ALIAS_LIST = 37,
/* ADD NEW COMPLEX TYPE ENTRIES HERE. */
/* The number of the above entries. */
GC_ARG_TYPE_NR
} gc_arg_type_t;
/* For every argument, we record some information about it in the
following struct. */
static const struct
{
/* For every argument type exists a basic argument type that can be
used as a fallback for input and validation purposes. */
gc_arg_type_t fallback;
/* Human-readable name of the type. */
const char *name;
} gc_arg_type[GC_ARG_TYPE_NR] =
{
/* The basic argument types have their own types as fallback. */
{ GC_ARG_TYPE_NONE, "none" },
{ GC_ARG_TYPE_STRING, "string" },
{ GC_ARG_TYPE_INT32, "int32" },
{ GC_ARG_TYPE_UINT32, "uint32" },
/* Reserved basic type entries for future extension. */
{ GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL },
{ GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL },
{ GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL },
{ GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL },
{ GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL },
{ GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL },
{ GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL },
{ GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL },
{ GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL },
{ GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL },
{ GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL },
{ GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL },
{ GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL },
{ GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL },
/* The complex argument types have a basic type as fallback. */
{ GC_ARG_TYPE_STRING, "filename" },
{ GC_ARG_TYPE_STRING, "ldap server" },
{ GC_ARG_TYPE_STRING, "key fpr" },
{ GC_ARG_TYPE_STRING, "pub key" },
{ GC_ARG_TYPE_STRING, "sec key" },
{ GC_ARG_TYPE_STRING, "alias list" },
};
/* Every option has an associated expert level, than can be used to
hide advanced and expert options from beginners. If you add to
this list, don't forget to update GC_LEVEL below. YOU MUST NOT
CHANGE THE NUMBERS OF THE EXISTING ENTRIES, AS THEY ARE PART OF THE
EXTERNAL INTERFACE. */
typedef enum
{
/* The basic options should always be displayed. */
GC_LEVEL_BASIC,
/* The advanced options may be hidden from beginners. */
GC_LEVEL_ADVANCED,
/* The expert options should only be displayed to experts. */
GC_LEVEL_EXPERT,
/* The invisible options should normally never be displayed. */
GC_LEVEL_INVISIBLE,
/* The internal options are never exported, they mark options that
are recorded for internal use only. */
GC_LEVEL_INTERNAL,
/* ADD NEW ENTRIES HERE. */
/* The number of the above entries. */
GC_LEVEL_NR
} gc_expert_level_t;
/* A description for each expert level. */
static const struct
{
const char *name;
} gc_level[] =
{
{ "basic" },
{ "advanced" },
{ "expert" },
{ "invisible" },
{ "internal" }
};
/* Option flags. The flags which are used by the backends are defined
by gc-opt-flags.h, included above.
YOU MUST NOT CHANGE THE NUMBERS OF THE EXISTING FLAGS, AS THEY ARE
PART OF THE EXTERNAL INTERFACE. */
/* Some entries in the option list are not options, but mark the
beginning of a new group of options. These entries have the GROUP
flag set. */
#define GC_OPT_FLAG_GROUP (1UL << 0)
/* The ARG_OPT flag for an option indicates that the argument is
optional. This is never set for GC_ARG_TYPE_NONE options. */
#define GC_OPT_FLAG_ARG_OPT (1UL << 1)
/* The LIST flag for an option indicates that the option can occur
several times. A comma separated list of arguments is used as the
argument value. */
#define GC_OPT_FLAG_LIST (1UL << 2)
/* A human-readable description for each flag. */
static const struct
{
const char *name;
} gc_flag[] =
{
{ "group" },
{ "optional arg" },
{ "list" },
{ "runtime" },
{ "default" },
{ "default desc" },
{ "no arg desc" },
{ "no change" }
};
/* To each option, or group marker, the information in the GC_OPTION
struct is provided. If you change this, don't forget to update the
option list of each component. */
struct gc_option
{
/* If this is NULL, then this is a terminator in an array of unknown
length. Otherwise, if this entry is a group marker (see FLAGS),
then this is the name of the group described by this entry.
Otherwise it is the name of the option described by this
entry. The name must not contain a colon. */
const char *name;
/* The option flags. If the GROUP flag is set, then this entry is a
group marker, not an option, and only the fields LEVEL,
DESC_DOMAIN and DESC are valid. In all other cases, this entry
describes a new option and all fields are valid. */
unsigned long flags;
/* The expert level. This field is valid for options and groups. A
group has the expert level of the lowest-level option in the
group. */
gc_expert_level_t level;
/* A gettext domain in which the following description can be found.
If this is NULL, then DESC is not translated. Valid for groups
and options.
Note that we try to keep the description of groups within the
gnupg domain.
IMPORTANT: If you add a new domain please make sure to add a code
set switching call to the function my_dgettext further below. */
const char *desc_domain;
/* A gettext description for this group or option. If it starts
with a '|', then the string up to the next '|' describes the
argument, and the description follows the second '|'.
In general enclosing these description in N_() is not required
because the description should be identical to the one in the
help menu of the respective program. */
const char *desc;
/* The following fields are only valid for options. */
/* The type of the option argument. */
gc_arg_type_t arg_type;
/* The backend that implements this option. */
gc_backend_t backend;
/* The following fields are set to NULL at startup (because all
option's are declared as static variables). They are at the end
of the list so that they can be omitted from the option
declarations. */
/* This is true if the option is supported by this version of the
backend. */
int active;
/* The default value for this option. This is NULL if the option is
not present in the backend, the empty string if no default is
available, and otherwise a quoted string. */
char *default_value;
/* The default argument is only valid if the "optional arg" flag is
set, and specifies the default argument (value) that is used if
the argument is omitted. */
char *default_arg;
/* The current value of this option. */
char *value;
/* The new flags for this option. The only defined flag is actually
GC_OPT_FLAG_DEFAULT, and it means that the option should be
deleted. In this case, NEW_VALUE is NULL. */
unsigned long new_flags;
/* The new value of this option. */
char *new_value;
};
typedef struct gc_option gc_option_t;
/* Use this macro to terminate an option list. */
#define GC_OPTION_NULL { NULL }
#ifndef BUILD_WITH_AGENT
#define gc_options_gpg_agent NULL
#else
/* The options of the GC_COMPONENT_GPG_AGENT component. */
static gc_option_t gc_options_gpg_agent[] =
{
/* The configuration file to which we write the changes. */
{ GPGCONF_NAME"-" GPG_AGENT_NAME ".conf",
GC_OPT_FLAG_NONE, GC_LEVEL_INTERNAL,
NULL, NULL, GC_ARG_TYPE_FILENAME, GC_BACKEND_GPG_AGENT },
{ "Monitor",
GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC,
"gnupg", N_("Options controlling the diagnostic output") },
{ "verbose", GC_OPT_FLAG_LIST|GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC,
"gnupg", "verbose",
GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT },
{ "quiet", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC,
"gnupg", "be somewhat more quiet",
GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT },
{ "no-greeting", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE,
NULL, NULL,
GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT },
{ "Configuration",
GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC,
"gnupg", N_("Options controlling the configuration") },
{ "options", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT,
"gnupg", "|FILE|read options from FILE",
GC_ARG_TYPE_FILENAME, GC_BACKEND_GPG_AGENT },
{ "disable-scdaemon", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
"gnupg", "do not use the SCdaemon",
GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT },
{ "enable-ssh-support", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
"gnupg", "enable ssh support",
GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT },
{ "ssh-fingerprint-digest",
GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT,
"gnupg", "|ALGO|use ALGO to show ssh fingerprints",
GC_ARG_TYPE_STRING, GC_BACKEND_GPG_AGENT },
{ "enable-putty-support", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
"gnupg", "enable putty support",
GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT },
{ "enable-extended-key-format", GC_OPT_FLAG_RUNTIME, GC_LEVEL_INVISIBLE,
NULL, NULL,
GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT },
{ "Debug",
GC_OPT_FLAG_GROUP, GC_LEVEL_ADVANCED,
"gnupg", N_("Options useful for debugging") },
{ "debug-level", GC_OPT_FLAG_ARG_OPT|GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED,
"gnupg", "|LEVEL|set the debugging level to LEVEL",
GC_ARG_TYPE_STRING, GC_BACKEND_GPG_AGENT },
{ "log-file", GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED,
"gnupg", N_("|FILE|write server mode logs to FILE"),
GC_ARG_TYPE_FILENAME, GC_BACKEND_GPG_AGENT },
{ "faked-system-time", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE,
NULL, NULL,
GC_ARG_TYPE_UINT32, GC_BACKEND_GPG_AGENT },
{ "Security",
GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC,
"gnupg", N_("Options controlling the security") },
{ "default-cache-ttl", GC_OPT_FLAG_RUNTIME,
GC_LEVEL_BASIC, "gnupg",
"|N|expire cached PINs after N seconds",
GC_ARG_TYPE_UINT32, GC_BACKEND_GPG_AGENT },
{ "default-cache-ttl-ssh", GC_OPT_FLAG_RUNTIME,
GC_LEVEL_ADVANCED, "gnupg",
N_("|N|expire SSH keys after N seconds"),
GC_ARG_TYPE_UINT32, GC_BACKEND_GPG_AGENT },
{ "max-cache-ttl", GC_OPT_FLAG_RUNTIME,
GC_LEVEL_EXPERT, "gnupg",
N_("|N|set maximum PIN cache lifetime to N seconds"),
GC_ARG_TYPE_UINT32, GC_BACKEND_GPG_AGENT },
{ "max-cache-ttl-ssh", GC_OPT_FLAG_RUNTIME,
GC_LEVEL_EXPERT, "gnupg",
N_("|N|set maximum SSH key lifetime to N seconds"),
GC_ARG_TYPE_UINT32, GC_BACKEND_GPG_AGENT },
{ "ignore-cache-for-signing", GC_OPT_FLAG_RUNTIME,
GC_LEVEL_BASIC, "gnupg", "do not use the PIN cache when signing",
GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT },
{ "allow-emacs-pinentry", GC_OPT_FLAG_RUNTIME,
GC_LEVEL_ADVANCED,
"gnupg", "allow passphrase to be prompted through Emacs",
GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT },
{ "grab", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT,
"gnupg", NULL,
GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT },
{ "no-allow-external-cache", GC_OPT_FLAG_RUNTIME,
GC_LEVEL_BASIC, "gnupg", "disallow the use of an external password cache",
GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT },
{ "no-allow-mark-trusted", GC_OPT_FLAG_RUNTIME,
GC_LEVEL_ADVANCED, "gnupg", "disallow clients to mark keys as \"trusted\"",
GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT },
{ "no-allow-loopback-pinentry", GC_OPT_FLAG_RUNTIME,
GC_LEVEL_EXPERT, "gnupg", "disallow caller to override the pinentry",
GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT },
{ "Passphrase policy",
GC_OPT_FLAG_GROUP, GC_LEVEL_ADVANCED,
"gnupg", N_("Options enforcing a passphrase policy") },
{ "enforce-passphrase-constraints", GC_OPT_FLAG_RUNTIME,
GC_LEVEL_EXPERT, "gnupg",
N_("do not allow bypassing the passphrase policy"),
GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT },
{ "min-passphrase-len", GC_OPT_FLAG_RUNTIME,
GC_LEVEL_ADVANCED, "gnupg",
N_("|N|set minimal required length for new passphrases to N"),
GC_ARG_TYPE_UINT32, GC_BACKEND_GPG_AGENT },
{ "min-passphrase-nonalpha", GC_OPT_FLAG_RUNTIME,
GC_LEVEL_EXPERT, "gnupg",
N_("|N|require at least N non-alpha characters for a new passphrase"),
GC_ARG_TYPE_UINT32, GC_BACKEND_GPG_AGENT },
{ "check-passphrase-pattern", GC_OPT_FLAG_RUNTIME,
GC_LEVEL_EXPERT,
"gnupg", N_("|FILE|check new passphrases against pattern in FILE"),
GC_ARG_TYPE_FILENAME, GC_BACKEND_GPG_AGENT },
{ "max-passphrase-days", GC_OPT_FLAG_RUNTIME,
GC_LEVEL_EXPERT, "gnupg",
N_("|N|expire the passphrase after N days"),
GC_ARG_TYPE_UINT32, GC_BACKEND_GPG_AGENT },
{ "enable-passphrase-history", GC_OPT_FLAG_RUNTIME,
GC_LEVEL_EXPERT, "gnupg",
N_("do not allow the reuse of old passphrases"),
GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT },
{ "pinentry-timeout", GC_OPT_FLAG_RUNTIME,
GC_LEVEL_ADVANCED, "gnupg",
N_("|N|set the Pinentry timeout to N seconds"),
GC_ARG_TYPE_UINT32, GC_BACKEND_GPG_AGENT },
GC_OPTION_NULL
};
#endif /*BUILD_WITH_AGENT*/
#ifndef BUILD_WITH_SCDAEMON
#define gc_options_scdaemon NULL
#else
/* The options of the GC_COMPONENT_SCDAEMON component. */
static gc_option_t gc_options_scdaemon[] =
{
/* The configuration file to which we write the changes. */
{ GPGCONF_NAME"-"SCDAEMON_NAME".conf",
GC_OPT_FLAG_NONE, GC_LEVEL_INTERNAL,
NULL, NULL, GC_ARG_TYPE_FILENAME, GC_BACKEND_SCDAEMON },
{ "Monitor",
GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC,
"gnupg", N_("Options controlling the diagnostic output") },
{ "verbose", GC_OPT_FLAG_LIST|GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC,
"gnupg", "verbose",
GC_ARG_TYPE_NONE, GC_BACKEND_SCDAEMON },
{ "quiet", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
"gnupg", "be somewhat more quiet",
GC_ARG_TYPE_NONE, GC_BACKEND_SCDAEMON },
{ "no-greeting", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE,
NULL, NULL,
GC_ARG_TYPE_NONE, GC_BACKEND_SCDAEMON },
{ "Configuration",
GC_OPT_FLAG_GROUP, GC_LEVEL_EXPERT,
"gnupg", N_("Options controlling the configuration") },
{ "options", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT,
"gnupg", "|FILE|read options from FILE",
GC_ARG_TYPE_FILENAME, GC_BACKEND_SCDAEMON },
{ "reader-port", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC,
"gnupg", "|N|connect to reader at port N",
GC_ARG_TYPE_STRING, GC_BACKEND_SCDAEMON },
{ "ctapi-driver", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED,
"gnupg", "|NAME|use NAME as ct-API driver",
GC_ARG_TYPE_STRING, GC_BACKEND_SCDAEMON },
{ "pcsc-driver", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED,
"gnupg", "|NAME|use NAME as PC/SC driver",
GC_ARG_TYPE_STRING, GC_BACKEND_SCDAEMON },
{ "disable-ccid", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT,
"gnupg", "do not use the internal CCID driver",
GC_ARG_TYPE_NONE, GC_BACKEND_SCDAEMON },
{ "disable-pinpad", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC,
"gnupg", "do not use a reader's pinpad",
GC_ARG_TYPE_NONE, GC_BACKEND_SCDAEMON },
{ "enable-pinpad-varlen",
GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC,
"gnupg", "use variable length input for pinpad",
GC_ARG_TYPE_NONE, GC_BACKEND_SCDAEMON },
{ "card-timeout", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC,
"gnupg", "|N|disconnect the card after N seconds of inactivity",
GC_ARG_TYPE_UINT32, GC_BACKEND_SCDAEMON },
+ { "application-priority",
+ GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED,
+ "gnupg", "|LIST|Change the application priority to LIST",
+ GC_ARG_TYPE_STRING, GC_BACKEND_SCDAEMON },
{ "Debug",
GC_OPT_FLAG_GROUP, GC_LEVEL_ADVANCED,
"gnupg", N_("Options useful for debugging") },
{ "debug-level", GC_OPT_FLAG_ARG_OPT|GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED,
"gnupg", "|LEVEL|set the debugging level to LEVEL",
GC_ARG_TYPE_STRING, GC_BACKEND_SCDAEMON },
{ "log-file", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED,
"gnupg", N_("|FILE|write a log to FILE"),
GC_ARG_TYPE_FILENAME, GC_BACKEND_SCDAEMON },
{ "Security",
GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC,
"gnupg", N_("Options controlling the security") },
{ "deny-admin", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC,
"gnupg", "deny the use of admin card commands",
GC_ARG_TYPE_NONE, GC_BACKEND_SCDAEMON },
GC_OPTION_NULL
};
#endif /*BUILD_WITH_SCDAEMON*/
#ifndef BUILD_WITH_GPG
#define gc_options_gpg NULL
#else
/* The options of the GC_COMPONENT_GPG component. */
static gc_option_t gc_options_gpg[] =
{
/* The configuration file to which we write the changes. */
{ GPGCONF_NAME"-"GPG_NAME".conf",
GC_OPT_FLAG_NONE, GC_LEVEL_INTERNAL,
NULL, NULL, GC_ARG_TYPE_FILENAME, GC_BACKEND_GPG },
{ "Monitor",
GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC,
"gnupg", N_("Options controlling the diagnostic output") },
{ "verbose", GC_OPT_FLAG_LIST, GC_LEVEL_BASIC,
"gnupg", "verbose",
GC_ARG_TYPE_NONE, GC_BACKEND_GPG },
{ "quiet", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
"gnupg", "be somewhat more quiet",
GC_ARG_TYPE_NONE, GC_BACKEND_GPG },
{ "no-greeting", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE,
NULL, NULL,
GC_ARG_TYPE_NONE, GC_BACKEND_GPG },
{ "Configuration",
GC_OPT_FLAG_GROUP, GC_LEVEL_EXPERT,
"gnupg", N_("Options controlling the configuration") },
{ "default-key", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
"gnupg", N_("|NAME|use NAME as default secret key"),
GC_ARG_TYPE_STRING, GC_BACKEND_GPG },
{ "encrypt-to", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
"gnupg", N_("|NAME|encrypt to user ID NAME as well"),
GC_ARG_TYPE_STRING, GC_BACKEND_GPG },
{ "group", GC_OPT_FLAG_LIST, GC_LEVEL_ADVANCED,
"gnupg", N_("|SPEC|set up email aliases"),
GC_ARG_TYPE_ALIAS_LIST, GC_BACKEND_GPG },
{ "options", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE,
NULL, NULL,
GC_ARG_TYPE_FILENAME, GC_BACKEND_GPG },
{ "compliance", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT,
NULL, NULL,
GC_ARG_TYPE_STRING, GC_BACKEND_GPG },
{ "default-new-key-algo", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE,
NULL, NULL,
GC_ARG_TYPE_STRING, GC_BACKEND_GPG },
{ "default_pubkey_algo",
(GC_OPT_FLAG_ARG_OPT|GC_OPT_FLAG_NO_CHANGE), GC_LEVEL_INVISIBLE,
NULL, NULL,
GC_ARG_TYPE_STRING, GC_BACKEND_GPG },
{ "trust-model",
GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE,
NULL, NULL,
GC_ARG_TYPE_STRING, GC_BACKEND_GPG },
{ "Debug",
GC_OPT_FLAG_GROUP, GC_LEVEL_ADVANCED,
"gnupg", N_("Options useful for debugging") },
{ "debug-level", GC_OPT_FLAG_ARG_OPT, GC_LEVEL_ADVANCED,
"gnupg", "|LEVEL|set the debugging level to LEVEL",
GC_ARG_TYPE_STRING, GC_BACKEND_GPG },
{ "log-file", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
"gnupg", N_("|FILE|write server mode logs to FILE"),
GC_ARG_TYPE_FILENAME, GC_BACKEND_GPG },
/* { "faked-system-time", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE, */
/* NULL, NULL, */
/* GC_ARG_TYPE_UINT32, GC_BACKEND_GPG }, */
{ "Keyserver",
GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC,
"gnupg", N_("Configuration for Keyservers") },
{ "keyserver", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT,
"gnupg", N_("|URL|use keyserver at URL"), /* Deprecated - use dirmngr */
GC_ARG_TYPE_STRING, GC_BACKEND_GPG },
{ "allow-pka-lookup", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
"gnupg", N_("allow PKA lookups (DNS requests)"),
GC_ARG_TYPE_NONE, GC_BACKEND_GPG },
{ "auto-key-locate", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
"gnupg", N_("|MECHANISMS|use MECHANISMS to locate keys by mail address"),
GC_ARG_TYPE_STRING, GC_BACKEND_GPG },
{ "auto-key-retrieve", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT,
NULL, NULL, GC_ARG_TYPE_NONE, GC_BACKEND_GPG },
{ "no-auto-key-retrieve", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE,
NULL, NULL, GC_ARG_TYPE_NONE, GC_BACKEND_GPG },
{ "disable-dirmngr", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT,
"gnupg", N_("disable all access to the dirmngr"),
GC_ARG_TYPE_NONE, GC_BACKEND_GPG },
{ "max-cert-depth",
GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE,
NULL, NULL,
GC_ARG_TYPE_UINT32, GC_BACKEND_GPG },
{ "completes-needed",
GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE,
NULL, NULL,
GC_ARG_TYPE_UINT32, GC_BACKEND_GPG },
{ "marginals-needed",
GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE,
NULL, NULL,
GC_ARG_TYPE_UINT32, GC_BACKEND_GPG },
GC_OPTION_NULL
};
#endif /*BUILD_WITH_GPG*/
#ifndef BUILD_WITH_GPGSM
#define gc_options_gpgsm NULL
#else
/* The options of the GC_COMPONENT_GPGSM component. */
static gc_option_t gc_options_gpgsm[] =
{
/* The configuration file to which we write the changes. */
{ GPGCONF_NAME"-"GPGSM_NAME".conf",
GC_OPT_FLAG_NONE, GC_LEVEL_INTERNAL,
NULL, NULL, GC_ARG_TYPE_FILENAME, GC_BACKEND_GPGSM },
{ "Monitor",
GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC,
"gnupg", N_("Options controlling the diagnostic output") },
{ "verbose", GC_OPT_FLAG_LIST, GC_LEVEL_BASIC,
"gnupg", "verbose",
GC_ARG_TYPE_NONE, GC_BACKEND_GPGSM },
{ "quiet", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
"gnupg", "be somewhat more quiet",
GC_ARG_TYPE_NONE, GC_BACKEND_GPGSM },
{ "no-greeting", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE,
NULL, NULL,
GC_ARG_TYPE_NONE, GC_BACKEND_GPGSM },
{ "Configuration",
GC_OPT_FLAG_GROUP, GC_LEVEL_EXPERT,
"gnupg", N_("Options controlling the configuration") },
{ "default-key", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
"gnupg", N_("|NAME|use NAME as default secret key"),
GC_ARG_TYPE_STRING, GC_BACKEND_GPGSM },
{ "encrypt-to", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
"gnupg", N_("|NAME|encrypt to user ID NAME as well"),
GC_ARG_TYPE_STRING, GC_BACKEND_GPGSM },
{ "options", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT,
"gnupg", "|FILE|read options from FILE",
GC_ARG_TYPE_FILENAME, GC_BACKEND_GPGSM },
{ "prefer-system-dirmngr", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
"gnupg", "use system's dirmngr if available",
GC_ARG_TYPE_NONE, GC_BACKEND_GPGSM },
{ "disable-dirmngr", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT,
"gnupg", N_("disable all access to the dirmngr"),
GC_ARG_TYPE_NONE, GC_BACKEND_GPGSM },
{ "p12-charset", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
"gnupg", N_("|NAME|use encoding NAME for PKCS#12 passphrases"),
GC_ARG_TYPE_STRING, GC_BACKEND_GPGSM },
{ "keyserver", GC_OPT_FLAG_LIST, GC_LEVEL_BASIC,
"gnupg", N_("|SPEC|use this keyserver to lookup keys"),
GC_ARG_TYPE_LDAP_SERVER, GC_BACKEND_GPGSM },
{ "default_pubkey_algo",
(GC_OPT_FLAG_ARG_OPT|GC_OPT_FLAG_NO_CHANGE), GC_LEVEL_INVISIBLE,
NULL, NULL,
GC_ARG_TYPE_STRING, GC_BACKEND_GPGSM },
{ "compliance", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT,
NULL, NULL,
GC_ARG_TYPE_STRING, GC_BACKEND_GPGSM },
{ "Debug",
GC_OPT_FLAG_GROUP, GC_LEVEL_ADVANCED,
"gnupg", N_("Options useful for debugging") },
{ "debug-level", GC_OPT_FLAG_ARG_OPT, GC_LEVEL_ADVANCED,
"gnupg", "|LEVEL|set the debugging level to LEVEL",
GC_ARG_TYPE_STRING, GC_BACKEND_GPGSM },
{ "log-file", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
"gnupg", N_("|FILE|write server mode logs to FILE"),
GC_ARG_TYPE_FILENAME, GC_BACKEND_GPGSM },
{ "faked-system-time", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE,
NULL, NULL,
GC_ARG_TYPE_UINT32, GC_BACKEND_GPGSM },
{ "Security",
GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC,
"gnupg", N_("Options controlling the security") },
{ "disable-crl-checks", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
"gnupg", "never consult a CRL",
GC_ARG_TYPE_NONE, GC_BACKEND_GPGSM },
{ "enable-crl-checks", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE,
NULL, NULL,
GC_ARG_TYPE_NONE, GC_BACKEND_GPGSM },
{ "disable-trusted-cert-crl-check", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT,
"gnupg", N_("do not check CRLs for root certificates"),
GC_ARG_TYPE_NONE, GC_BACKEND_GPGSM },
{ "enable-ocsp", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
"gnupg", "check validity using OCSP",
GC_ARG_TYPE_NONE, GC_BACKEND_GPGSM },
{ "include-certs", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT,
"gnupg", "|N|number of certificates to include",
GC_ARG_TYPE_INT32, GC_BACKEND_GPGSM },
{ "disable-policy-checks", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
"gnupg", "do not check certificate policies",
GC_ARG_TYPE_NONE, GC_BACKEND_GPGSM },
{ "auto-issuer-key-retrieve", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
"gnupg", "fetch missing issuer certificates",
GC_ARG_TYPE_NONE, GC_BACKEND_GPGSM },
{ "cipher-algo", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
"gnupg", "|NAME|use cipher algorithm NAME",
GC_ARG_TYPE_STRING, GC_BACKEND_GPGSM },
GC_OPTION_NULL
};
#endif /*BUILD_WITH_GPGSM*/
#ifndef BUILD_WITH_DIRMNGR
#define gc_options_dirmngr NULL
#else
/* The options of the GC_COMPONENT_DIRMNGR component. */
static gc_option_t gc_options_dirmngr[] =
{
/* The configuration file to which we write the changes. */
{ GPGCONF_NAME"-"DIRMNGR_NAME".conf",
GC_OPT_FLAG_NONE, GC_LEVEL_INTERNAL,
NULL, NULL, GC_ARG_TYPE_FILENAME, GC_BACKEND_DIRMNGR },
{ "Monitor",
GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC,
"gnupg", N_("Options controlling the diagnostic output") },
{ "verbose", GC_OPT_FLAG_LIST, GC_LEVEL_BASIC,
"dirmngr", "verbose",
GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR },
{ "quiet", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
"dirmngr", "be somewhat more quiet",
GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR },
{ "no-greeting", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE,
NULL, NULL,
GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR },
{ "Format",
GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC,
"gnupg", N_("Options controlling the format of the output") },
{ "sh", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
"dirmngr", "sh-style command output",
GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR },
{ "csh", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
"dirmngr", "csh-style command output",
GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR },
{ "Configuration",
GC_OPT_FLAG_GROUP, GC_LEVEL_EXPERT,
"gnupg", N_("Options controlling the configuration") },
{ "options", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT,
"dirmngr", "|FILE|read options from FILE",
GC_ARG_TYPE_FILENAME, GC_BACKEND_DIRMNGR },
{ "resolver-timeout", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE,
NULL, NULL,
GC_ARG_TYPE_INT32, GC_BACKEND_DIRMNGR },
{ "nameserver", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE,
NULL, NULL,
GC_ARG_TYPE_STRING, GC_BACKEND_DIRMNGR },
{ "Debug",
GC_OPT_FLAG_GROUP, GC_LEVEL_ADVANCED,
"gnupg", N_("Options useful for debugging") },
{ "debug-level", GC_OPT_FLAG_ARG_OPT, GC_LEVEL_ADVANCED,
"dirmngr", "|LEVEL|set the debugging level to LEVEL",
GC_ARG_TYPE_STRING, GC_BACKEND_DIRMNGR },
{ "no-detach", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
"dirmngr", "do not detach from the console",
GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR },
{ "log-file", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
"dirmngr", N_("|FILE|write server mode logs to FILE"),
GC_ARG_TYPE_FILENAME, GC_BACKEND_DIRMNGR },
{ "debug-wait", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE,
NULL, NULL,
GC_ARG_TYPE_UINT32, GC_BACKEND_DIRMNGR },
{ "faked-system-time", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE,
NULL, NULL,
GC_ARG_TYPE_UINT32, GC_BACKEND_DIRMNGR },
{ "Enforcement",
GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC,
"gnupg", N_("Options controlling the interactivity and enforcement") },
{ "batch", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
"dirmngr", "run without asking a user",
GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR },
{ "force", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
"dirmngr", "force loading of outdated CRLs",
GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR },
{ "allow-version-check", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
"dirmngr", "allow online software version check",
GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR },
{ "Tor",
GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC,
"gnupg", N_("Options controlling the use of Tor") },
{ "use-tor", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
- "dirmngr", "route all network traffic via TOR",
+ "dirmngr", "route all network traffic via Tor",
GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR },
{ "Keyserver",
GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC,
"gnupg", N_("Configuration for Keyservers") },
{ "keyserver", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
"gnupg", N_("|URL|use keyserver at URL"),
GC_ARG_TYPE_STRING, GC_BACKEND_DIRMNGR },
{ "HTTP",
GC_OPT_FLAG_GROUP, GC_LEVEL_ADVANCED,
"gnupg", N_("Configuration for HTTP servers") },
{ "disable-http", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
"dirmngr", "inhibit the use of HTTP",
GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR },
{ "ignore-http-dp", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
"dirmngr", "ignore HTTP CRL distribution points",
GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR },
{ "http-proxy", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
"dirmngr", "|URL|redirect all HTTP requests to URL",
GC_ARG_TYPE_STRING, GC_BACKEND_DIRMNGR },
{ "honor-http-proxy", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
"gnupg", N_("use system's HTTP proxy setting"),
GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR },
{ "LDAP",
GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC,
"gnupg", N_("Configuration of LDAP servers to use") },
{ "disable-ldap", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
"dirmngr", "inhibit the use of LDAP",
GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR },
{ "ignore-ldap-dp", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
"dirmngr", "ignore LDAP CRL distribution points",
GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR },
{ "ldap-proxy", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
"dirmngr", "|HOST|use HOST for LDAP queries",
GC_ARG_TYPE_STRING, GC_BACKEND_DIRMNGR },
{ "only-ldap-proxy", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
"dirmngr", "do not use fallback hosts with --ldap-proxy",
GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR },
{ "add-servers", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
"dirmngr", "add new servers discovered in CRL distribution points"
" to serverlist", GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR },
{ "ldaptimeout", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
"dirmngr", "|N|set LDAP timeout to N seconds",
GC_ARG_TYPE_UINT32, GC_BACKEND_DIRMNGR },
/* The following entry must not be removed, as it is required for
the GC_BACKEND_DIRMNGR_LDAP_SERVER_LIST. */
{ "ldapserverlist-file",
GC_OPT_FLAG_NONE, GC_LEVEL_INTERNAL,
"dirmngr", "|FILE|read LDAP server list from FILE",
GC_ARG_TYPE_FILENAME, GC_BACKEND_DIRMNGR },
/* This entry must come after at least one entry for
GC_BACKEND_DIRMNGR in this component, so that the entry for
"ldapserverlist-file will be initialized before this one. */
{ "LDAP Server", GC_OPT_FLAG_ARG_OPT|GC_OPT_FLAG_LIST, GC_LEVEL_BASIC,
"gnupg", N_("LDAP server list"),
GC_ARG_TYPE_LDAP_SERVER, GC_BACKEND_DIRMNGR_LDAP_SERVER_LIST },
{ "max-replies", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
"dirmngr", "|N|do not return more than N items in one query",
GC_ARG_TYPE_UINT32, GC_BACKEND_DIRMNGR },
{ "OCSP",
GC_OPT_FLAG_GROUP, GC_LEVEL_ADVANCED,
"gnupg", N_("Configuration for OCSP") },
{ "allow-ocsp", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
"dirmngr", "allow sending OCSP requests",
GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR },
{ "ignore-ocsp-service-url", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
"dirmngr", "ignore certificate contained OCSP service URLs",
GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR },
{ "ocsp-responder", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
"dirmngr", "|URL|use OCSP responder at URL",
GC_ARG_TYPE_STRING, GC_BACKEND_DIRMNGR },
{ "ocsp-signer", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
"dirmngr", "|FPR|OCSP response signed by FPR",
GC_ARG_TYPE_STRING, GC_BACKEND_DIRMNGR },
GC_OPTION_NULL
};
#endif /*BUILD_WITH_DIRMNGR*/
/* The options of the GC_COMPONENT_PINENTRY component. */
static gc_option_t gc_options_pinentry[] =
{
/* A dummy option to allow gc_component_list_components to find the
pinentry backend. Needs to be a conf file. */
{ GPGCONF_NAME"-pinentry.conf",
GC_OPT_FLAG_NONE, GC_LEVEL_INTERNAL,
NULL, NULL, GC_ARG_TYPE_FILENAME, GC_BACKEND_PINENTRY },
GC_OPTION_NULL
};
/* The information associated with each component. */
static const struct
{
/* The name of this component. Must not contain a colon (':')
character. */
const char *name;
/* The gettext domain for the description DESC. If this is NULL,
then the description is not translated. */
const char *desc_domain;
/* The description for this domain. */
const char *desc;
/* The list of options for this component, terminated by
GC_OPTION_NULL. */
gc_option_t *options;
} gc_component[] =
{
{ "gpg", "gnupg", N_("OpenPGP"), gc_options_gpg },
{ "gpg-agent","gnupg", N_("Private Keys"), gc_options_gpg_agent },
{ "scdaemon", "gnupg", N_("Smartcards"), gc_options_scdaemon },
{ "gpgsm", "gnupg", N_("S/MIME"), gc_options_gpgsm },
{ "dirmngr", "gnupg", N_("Network"), gc_options_dirmngr },
{ "pinentry", "gnupg", N_("Passphrase Entry"), gc_options_pinentry }
};
/* Structure used to collect error output of the backend programs. */
struct error_line_s;
typedef struct error_line_s *error_line_t;
struct error_line_s
{
error_line_t next; /* Link to next item. */
const char *fname; /* Name of the config file (points into BUFFER). */
unsigned int lineno; /* Line number of the config file. */
const char *errtext; /* Text of the error message (points into BUFFER). */
char buffer[1]; /* Helper buffer. */
};
/* Initialization and finalization. */
static void
gc_option_free (gc_option_t *o)
{
if (o == NULL || o->name == NULL)
return;
xfree (o->value);
gc_option_free (o + 1);
}
static void
gc_components_free (void)
{
int i;
for (i = 0; i < DIM (gc_component); i++)
gc_option_free (gc_component[i].options);
}
void
gc_components_init (void)
{
atexit (gc_components_free);
}
/* Engine specific support. */
static void
gpg_agent_runtime_change (int killflag)
{
gpg_error_t err = 0;
const char *pgmname;
const char *argv[5];
pid_t pid = (pid_t)(-1);
char *abs_homedir = NULL;
int i = 0;
pgmname = gnupg_module_name (GNUPG_MODULE_NAME_CONNECT_AGENT);
if (!gnupg_default_homedir_p ())
{
- abs_homedir = make_absfilename_try (gnupg_homedir (), NULL);
- if (!abs_homedir)
- err = gpg_error_from_syserror ();
-
argv[i++] = "--homedir";
- argv[i++] = abs_homedir;
+ argv[i++] = gnupg_homedir ();
}
argv[i++] = "--no-autostart";
argv[i++] = killflag? "KILLAGENT" : "RELOADAGENT";
argv[i++] = NULL;
if (!err)
err = gnupg_spawn_process_fd (pgmname, argv, -1, -1, -1, &pid);
if (!err)
err = gnupg_wait_process (pgmname, pid, 1, NULL);
if (err)
gc_error (0, 0, "error running '%s %s': %s",
pgmname, argv[1], gpg_strerror (err));
gnupg_release_process (pid);
xfree (abs_homedir);
}
static void
scdaemon_runtime_change (int killflag)
{
gpg_error_t err = 0;
const char *pgmname;
const char *argv[9];
pid_t pid = (pid_t)(-1);
char *abs_homedir = NULL;
int i = 0;
(void)killflag; /* For scdaemon kill and reload are synonyms. */
/* We use "GETINFO app_running" to see whether the agent is already
running and kill it only in this case. This avoids an explicit
starting of the agent in case it is not yet running. There is
obviously a race condition but that should not harm too much. */
pgmname = gnupg_module_name (GNUPG_MODULE_NAME_CONNECT_AGENT);
if (!gnupg_default_homedir_p ())
{
- abs_homedir = make_absfilename_try (gnupg_homedir (), NULL);
- if (!abs_homedir)
- err = gpg_error_from_syserror ();
-
argv[i++] = "--homedir";
- argv[i++] = abs_homedir;
+ argv[i++] = gnupg_homedir ();
}
argv[i++] = "-s";
argv[i++] = "--no-autostart";
argv[i++] = "GETINFO scd_running";
argv[i++] = "/if ${! $?}";
argv[i++] = "scd killscd";
argv[i++] = "/end";
argv[i++] = NULL;
if (!err)
err = gnupg_spawn_process_fd (pgmname, argv, -1, -1, -1, &pid);
if (!err)
err = gnupg_wait_process (pgmname, pid, 1, NULL);
if (err)
gc_error (0, 0, "error running '%s %s': %s",
pgmname, argv[4], gpg_strerror (err));
gnupg_release_process (pid);
xfree (abs_homedir);
}
static void
dirmngr_runtime_change (int killflag)
{
gpg_error_t err = 0;
const char *pgmname;
const char *argv[6];
pid_t pid = (pid_t)(-1);
char *abs_homedir = NULL;
pgmname = gnupg_module_name (GNUPG_MODULE_NAME_CONNECT_AGENT);
argv[0] = "--no-autostart";
argv[1] = "--dirmngr";
argv[2] = killflag? "KILLDIRMNGR" : "RELOADDIRMNGR";
if (gnupg_default_homedir_p ())
argv[3] = NULL;
else
{
- abs_homedir = make_absfilename_try (gnupg_homedir (), NULL);
- if (!abs_homedir)
- err = gpg_error_from_syserror ();
-
argv[3] = "--homedir";
- argv[4] = abs_homedir;
+ argv[4] = gnupg_homedir ();
argv[5] = NULL;
}
if (!err)
err = gnupg_spawn_process_fd (pgmname, argv, -1, -1, -1, &pid);
if (!err)
err = gnupg_wait_process (pgmname, pid, 1, NULL);
if (err)
gc_error (0, 0, "error running '%s %s': %s",
pgmname, argv[2], gpg_strerror (err));
gnupg_release_process (pid);
xfree (abs_homedir);
}
/* Launch the gpg-agent or the dirmngr if not already running. */
gpg_error_t
gc_component_launch (int component)
{
gpg_error_t err;
const char *pgmname;
- const char *argv[3];
+ const char *argv[5];
int i;
pid_t pid;
if (component < 0)
{
err = gc_component_launch (GC_COMPONENT_GPG_AGENT);
if (!err)
err = gc_component_launch (GC_COMPONENT_DIRMNGR);
return err;
}
if (!(component == GC_COMPONENT_GPG_AGENT
|| component == GC_COMPONENT_DIRMNGR))
{
- es_fputs (_("Component not suitable for launching"), es_stderr);
- es_putc ('\n', es_stderr);
+ log_error ("%s\n", _("Component not suitable for launching"));
+ gpgconf_failure (0);
+ }
+
+ if (gc_component_check_options (component, NULL, NULL))
+ {
+ log_error (_("Configuration file of component %s is broken\n"),
+ gc_component[component].name);
+ if (!opt.quiet)
+ log_info (_("Note: Use the command \"%s%s\" to get details.\n"),
+ "gpgconf --check-options ", gc_component[component].name);
gpgconf_failure (0);
}
pgmname = gnupg_module_name (GNUPG_MODULE_NAME_CONNECT_AGENT);
i = 0;
+ if (!gnupg_default_homedir_p ())
+ {
+ argv[i++] = "--homedir";
+ argv[i++] = gnupg_homedir ();
+ }
if (component == GC_COMPONENT_DIRMNGR)
argv[i++] = "--dirmngr";
argv[i++] = "NOP";
argv[i] = NULL;
err = gnupg_spawn_process_fd (pgmname, argv, -1, -1, -1, &pid);
if (!err)
err = gnupg_wait_process (pgmname, pid, 1, NULL);
if (err)
gc_error (0, 0, "error running '%s%s%s': %s",
pgmname,
component == GC_COMPONENT_DIRMNGR? " --dirmngr":"",
" NOP",
gpg_strerror (err));
gnupg_release_process (pid);
return err;
}
/* Unconditionally restart COMPONENT. */
void
gc_component_kill (int component)
{
int runtime[GC_BACKEND_NR];
gc_option_t *option;
gc_backend_t backend;
/* Set a flag for the backends to be reloaded. */
for (backend = 0; backend < GC_BACKEND_NR; backend++)
runtime[backend] = 0;
if (component < 0)
{
for (component = 0; component < GC_COMPONENT_NR; component++)
{
option = gc_component[component].options;
for (; option && option->name; option++)
runtime[option->backend] = 1;
}
}
else
{
assert (component < GC_COMPONENT_NR);
option = gc_component[component].options;
for (; option && option->name; option++)
runtime[option->backend] = 1;
}
/* Do the restart for the selected backends. */
- for (backend = 0; backend < GC_BACKEND_NR; backend++)
+ for (backend = GC_BACKEND_NR-1; backend; backend--)
{
if (runtime[backend] && gc_backend[backend].runtime_change)
(*gc_backend[backend].runtime_change) (1);
}
}
/* Unconditionally reload COMPONENT or all components if COMPONENT is -1. */
void
gc_component_reload (int component)
{
int runtime[GC_BACKEND_NR];
gc_option_t *option;
gc_backend_t backend;
/* Set a flag for the backends to be reloaded. */
for (backend = 0; backend < GC_BACKEND_NR; backend++)
runtime[backend] = 0;
if (component < 0)
{
for (component = 0; component < GC_COMPONENT_NR; component++)
{
option = gc_component[component].options;
for (; option && option->name; option++)
runtime[option->backend] = 1;
}
}
else
{
assert (component < GC_COMPONENT_NR);
option = gc_component[component].options;
for (; option && option->name; option++)
runtime[option->backend] = 1;
}
/* Do the reload for all selected backends. */
for (backend = 0; backend < GC_BACKEND_NR; backend++)
{
if (runtime[backend] && gc_backend[backend].runtime_change)
(*gc_backend[backend].runtime_change) (0);
}
}
/* More or less Robust version of dgettext. It has the side effect of
switching the codeset to utf-8 because this is what we want to
output. In theory it is possible to keep the original code set and
switch back for regular disgnostic output (redefine "_(" for that)
but given the natur of this tool, being something invoked from
other pograms, it does not make much sense. */
static const char *
my_dgettext (const char *domain, const char *msgid)
{
#ifdef USE_SIMPLE_GETTEXT
if (domain)
{
static int switched_codeset;
char *text;
if (!switched_codeset)
{
switched_codeset = 1;
gettext_use_utf8 (1);
}
if (!strcmp (domain, "gnupg"))
domain = PACKAGE_GT;
/* FIXME: we have no dgettext, thus we can't switch. */
text = (char*)gettext (msgid);
return text ? text : msgid;
}
else
return msgid;
#elif defined(ENABLE_NLS)
if (domain)
{
static int switched_codeset;
char *text;
if (!switched_codeset)
{
switched_codeset = 1;
bind_textdomain_codeset (PACKAGE_GT, "utf-8");
bindtextdomain (DIRMNGR_NAME, LOCALEDIR);
bind_textdomain_codeset (DIRMNGR_NAME, "utf-8");
}
/* Note: This is a hack to actually use the gnupg2 domain as
long we are in a transition phase where gnupg 1.x and 1.9 may
coexist. */
if (!strcmp (domain, "gnupg"))
domain = PACKAGE_GT;
text = dgettext (domain, msgid);
return text ? text : msgid;
}
else
return msgid;
#else
(void)domain;
return msgid;
#endif
}
/* Percent-Escape special characters. The string is valid until the
next invocation of the function. */
char *
gc_percent_escape (const char *src)
{
static char *esc_str;
static int esc_str_len;
int new_len = 3 * strlen (src) + 1;
char *dst;
if (esc_str_len < new_len)
{
char *new_esc_str = realloc (esc_str, new_len);
if (!new_esc_str)
gc_error (1, errno, "can not escape string");
esc_str = new_esc_str;
esc_str_len = new_len;
}
dst = esc_str;
while (*src)
{
if (*src == '%')
{
*(dst++) = '%';
*(dst++) = '2';
*(dst++) = '5';
}
else if (*src == ':')
{
/* The colon is used as field separator. */
*(dst++) = '%';
*(dst++) = '3';
*(dst++) = 'a';
}
else if (*src == ',')
{
/* The comma is used as list separator. */
*(dst++) = '%';
*(dst++) = '2';
*(dst++) = 'c';
}
else if (*src == '\n')
{
/* The newline is problematic in a line-based format. */
*(dst++) = '%';
*(dst++) = '0';
*(dst++) = 'a';
}
else
*(dst++) = *(src);
src++;
}
*dst = '\0';
return esc_str;
}
/* Percent-Deescape special characters. The string is valid until the
next invocation of the function. */
static char *
percent_deescape (const char *src)
{
static char *str;
static int str_len;
int new_len = 3 * strlen (src) + 1;
char *dst;
if (str_len < new_len)
{
char *new_str = realloc (str, new_len);
if (!new_str)
gc_error (1, errno, "can not deescape string");
str = new_str;
str_len = new_len;
}
dst = str;
while (*src)
{
if (*src == '%')
{
int val = hextobyte (src + 1);
if (val < 0)
gc_error (1, 0, "malformed end of string %s", src);
*(dst++) = (char) val;
src += 3;
}
else
*(dst++) = *(src++);
}
*dst = '\0';
return str;
}
/* List all components that are available. */
void
gc_component_list_components (estream_t out)
{
gc_component_t component;
gc_option_t *option;
gc_backend_t backend;
int backend_seen[GC_BACKEND_NR];
const char *desc;
const char *pgmname;
for (component = 0; component < GC_COMPONENT_NR; component++)
{
option = gc_component[component].options;
if (option)
{
for (backend = 0; backend < GC_BACKEND_NR; backend++)
backend_seen[backend] = 0;
pgmname = "";
for (; option && option->name; option++)
{
if ((option->flags & GC_OPT_FLAG_GROUP))
continue;
backend = option->backend;
if (backend_seen[backend])
continue;
backend_seen[backend] = 1;
assert (backend != GC_BACKEND_ANY);
if (gc_backend[backend].program
&& !gc_backend[backend].module_name)
continue;
pgmname = gnupg_module_name (gc_backend[backend].module_name);
break;
}
desc = gc_component[component].desc;
desc = my_dgettext (gc_component[component].desc_domain, desc);
es_fprintf (out, "%s:%s:",
gc_component[component].name, gc_percent_escape (desc));
es_fprintf (out, "%s\n", gc_percent_escape (pgmname));
}
}
}
static int
all_digits_p (const char *p, size_t len)
{
if (!len)
return 0; /* No. */
for (; len; len--, p++)
if (!isascii (*p) || !isdigit (*p))
return 0; /* No. */
return 1; /* Yes. */
}
/* Collect all error lines from stream FP. Only lines prefixed with
TAG are considered. Returns a list of error line items (which may
be empty). There is no error return. */
static error_line_t
collect_error_output (estream_t fp, const char *tag)
{
char buffer[1024];
char *p, *p2, *p3;
int c, cont_line;
unsigned int pos;
error_line_t eitem, errlines, *errlines_tail;
size_t taglen = strlen (tag);
errlines = NULL;
errlines_tail = &errlines;
pos = 0;
cont_line = 0;
while ((c=es_getc (fp)) != EOF)
{
buffer[pos++] = c;
if (pos >= sizeof buffer - 5 || c == '\n')
{
buffer[pos - (c == '\n')] = 0;
if (cont_line)
; /*Ignore continuations of previous line. */
else if (!strncmp (buffer, tag, taglen) && buffer[taglen] == ':')
{
/* "gpgsm: foo:4: bla" */
/* Yep, we are interested in this line. */
p = buffer + taglen + 1;
while (*p == ' ' || *p == '\t')
p++;
trim_trailing_spaces (p); /* Get rid of extra CRs. */
if (!*p)
; /* Empty lines are ignored. */
else if ( (p2 = strchr (p, ':')) && (p3 = strchr (p2+1, ':'))
&& all_digits_p (p2+1, p3 - (p2+1)))
{
/* Line in standard compiler format. */
p3++;
while (*p3 == ' ' || *p3 == '\t')
p3++;
eitem = xmalloc (sizeof *eitem + strlen (p));
eitem->next = NULL;
strcpy (eitem->buffer, p);
eitem->fname = eitem->buffer;
eitem->buffer[p2-p] = 0;
eitem->errtext = eitem->buffer + (p3 - p);
/* (we already checked that there are only ascii
digits followed by a colon) */
eitem->lineno = 0;
for (p2++; isdigit (*p2); p2++)
eitem->lineno = eitem->lineno*10 + (*p2 - '0');
*errlines_tail = eitem;
errlines_tail = &eitem->next;
}
else
{
/* Other error output. */
eitem = xmalloc (sizeof *eitem + strlen (p));
eitem->next = NULL;
strcpy (eitem->buffer, p);
eitem->fname = NULL;
eitem->errtext = eitem->buffer;
eitem->lineno = 0;
*errlines_tail = eitem;
errlines_tail = &eitem->next;
}
}
pos = 0;
/* If this was not a complete line mark that we are in a
continuation. */
cont_line = (c != '\n');
}
}
/* We ignore error lines not terminated by a LF. */
return errlines;
}
-/* Check the options of a single component. Returns 0 if everything
- is OK. */
+/* Check the options of a single component. If CONF_FILE is NULL the
+ * standard config file is used. If OUT is not NULL the output is
+ * written to that stream. Returns 0 if everything is OK. */
int
gc_component_check_options (int component, estream_t out, const char *conf_file)
{
gpg_error_t err;
unsigned int result;
int backend_seen[GC_BACKEND_NR];
gc_backend_t backend;
gc_option_t *option;
const char *pgmname;
const char *argv[4];
int i;
pid_t pid;
int exitcode;
estream_t errfp;
error_line_t errlines;
for (backend = 0; backend < GC_BACKEND_NR; backend++)
backend_seen[backend] = 0;
option = gc_component[component].options;
for (; option && option->name; option++)
{
if ((option->flags & GC_OPT_FLAG_GROUP))
continue;
backend = option->backend;
if (backend_seen[backend])
continue;
backend_seen[backend] = 1;
assert (backend != GC_BACKEND_ANY);
if (!gc_backend[backend].program)
continue;
if (!gc_backend[backend].module_name)
continue;
break;
}
if (! option || ! option->name)
return 0;
pgmname = gnupg_module_name (gc_backend[backend].module_name);
i = 0;
if (conf_file)
{
argv[i++] = "--options";
argv[i++] = conf_file;
}
if (component == GC_COMPONENT_PINENTRY)
argv[i++] = "--version";
else
argv[i++] = "--gpgconf-test";
argv[i++] = NULL;
result = 0;
errlines = NULL;
err = gnupg_spawn_process (pgmname, argv, NULL, NULL, 0,
NULL, NULL, &errfp, &pid);
if (err)
result |= 1; /* Program could not be run. */
else
{
errlines = collect_error_output (errfp,
gc_component[component].name);
if (gnupg_wait_process (pgmname, pid, 1, &exitcode))
{
if (exitcode == -1)
result |= 1; /* Program could not be run or it
terminated abnormally. */
result |= 2; /* Program returned an error. */
}
gnupg_release_process (pid);
es_fclose (errfp);
}
/* If the program could not be run, we can't tell whether
the config file is good. */
if (result & 1)
result |= 2;
if (out)
{
const char *desc;
error_line_t errptr;
desc = gc_component[component].desc;
desc = my_dgettext (gc_component[component].desc_domain, desc);
es_fprintf (out, "%s:%s:",
gc_component[component].name, gc_percent_escape (desc));
es_fputs (gc_percent_escape (pgmname), out);
es_fprintf (out, ":%d:%d:", !(result & 1), !(result & 2));
for (errptr = errlines; errptr; errptr = errptr->next)
{
if (errptr != errlines)
es_fputs ("\n:::::", out); /* Continuation line. */
if (errptr->fname)
es_fputs (gc_percent_escape (errptr->fname), out);
es_putc (':', out);
if (errptr->fname)
es_fprintf (out, "%u", errptr->lineno);
es_putc (':', out);
es_fputs (gc_percent_escape (errptr->errtext), out);
es_putc (':', out);
}
es_putc ('\n', out);
}
while (errlines)
{
error_line_t tmp = errlines->next;
xfree (errlines);
errlines = tmp;
}
return result;
}
/* Check all components that are available. */
void
gc_check_programs (estream_t out)
{
gc_component_t component;
for (component = 0; component < GC_COMPONENT_NR; component++)
gc_component_check_options (component, out, NULL);
}
/* Find the component with the name NAME. Returns -1 if not
found. */
int
gc_component_find (const char *name)
{
gc_component_t idx;
for (idx = 0; idx < GC_COMPONENT_NR; idx++)
{
if (gc_component[idx].options
&& !strcmp (name, gc_component[idx].name))
return idx;
}
return -1;
}
/* List the option OPTION. */
static void
list_one_option (const gc_option_t *option, estream_t out)
{
const char *desc = NULL;
char *arg_name = NULL;
if (option->desc)
{
desc = my_dgettext (option->desc_domain, option->desc);
if (*desc == '|')
{
const char *arg_tail = strchr (&desc[1], '|');
if (arg_tail)
{
int arg_len = arg_tail - &desc[1];
arg_name = xmalloc (arg_len + 1);
memcpy (arg_name, &desc[1], arg_len);
arg_name[arg_len] = '\0';
desc = arg_tail + 1;
}
}
}
/* YOU MUST NOT REORDER THE FIELDS IN THIS OUTPUT, AS THEIR ORDER IS
PART OF THE EXTERNAL INTERFACE. YOU MUST NOT REMOVE ANY
FIELDS. */
/* The name field. */
es_fprintf (out, "%s", option->name);
/* The flags field. */
es_fprintf (out, ":%lu", option->flags);
if (opt.verbose)
{
es_putc (' ', out);
if (!option->flags)
es_fprintf (out, "none");
else
{
unsigned long flags = option->flags;
unsigned long flag = 0;
unsigned long first = 1;
while (flags)
{
if (flags & 1)
{
if (first)
first = 0;
else
es_putc (',', out);
es_fprintf (out, "%s", gc_flag[flag].name);
}
flags >>= 1;
flag++;
}
}
}
/* The level field. */
es_fprintf (out, ":%u", option->level);
if (opt.verbose)
es_fprintf (out, " %s", gc_level[option->level].name);
/* The description field. */
es_fprintf (out, ":%s", desc ? gc_percent_escape (desc) : "");
/* The type field. */
es_fprintf (out, ":%u", option->arg_type);
if (opt.verbose)
es_fprintf (out, " %s", gc_arg_type[option->arg_type].name);
/* The alternate type field. */
es_fprintf (out, ":%u", gc_arg_type[option->arg_type].fallback);
if (opt.verbose)
es_fprintf (out, " %s",
gc_arg_type[gc_arg_type[option->arg_type].fallback].name);
/* The argument name field. */
es_fprintf (out, ":%s", arg_name ? gc_percent_escape (arg_name) : "");
xfree (arg_name);
/* The default value field. */
es_fprintf (out, ":%s", option->default_value ? option->default_value : "");
/* The default argument field. */
es_fprintf (out, ":%s", option->default_arg ? option->default_arg : "");
/* The value field. */
if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_NONE
&& (option->flags & GC_OPT_FLAG_LIST)
&& option->value)
/* The special format "1,1,1,1,...,1" is converted to a number
here. */
es_fprintf (out, ":%u", (unsigned int)((strlen (option->value) + 1) / 2));
else
es_fprintf (out, ":%s", option->value ? option->value : "");
/* ADD NEW FIELDS HERE. */
es_putc ('\n', out);
}
/* List all options of the component COMPONENT. */
void
gc_component_list_options (int component, estream_t out)
{
const gc_option_t *option = gc_component[component].options;
while (option && option->name)
{
/* Do not output unknown or internal options. */
if (!(option->flags & GC_OPT_FLAG_GROUP)
&& (!option->active || option->level == GC_LEVEL_INTERNAL))
{
option++;
continue;
}
if (option->flags & GC_OPT_FLAG_GROUP)
{
const gc_option_t *group_option = option + 1;
gc_expert_level_t level = GC_LEVEL_NR;
/* The manual states that the group level is always the
minimum of the levels of all contained options. Due to
different active options, and because it is hard to
maintain manually, we calculate it here. The value in
the global static table is ignored. */
while (group_option->name)
{
if (group_option->flags & GC_OPT_FLAG_GROUP)
break;
if (group_option->level < level)
level = group_option->level;
group_option++;
}
/* Check if group is empty. */
if (level != GC_LEVEL_NR)
{
gc_option_t opt_copy;
/* Fix up the group level. */
memcpy (&opt_copy, option, sizeof (opt_copy));
opt_copy.level = level;
list_one_option (&opt_copy, out);
}
}
else
list_one_option (option, out);
option++;
}
}
/* Find the option NAME in component COMPONENT, for the backend
BACKEND. If BACKEND is GC_BACKEND_ANY, any backend will match. */
static gc_option_t *
find_option (gc_component_t component, const char *name,
gc_backend_t backend)
{
gc_option_t *option = gc_component[component].options;
while (option->name)
{
if (!(option->flags & GC_OPT_FLAG_GROUP)
&& !strcmp (option->name, name)
&& (backend == GC_BACKEND_ANY || option->backend == backend))
break;
option++;
}
return option->name ? option : NULL;
}
/* Determine the configuration filename for the component COMPONENT
and backend BACKEND. */
static char *
get_config_filename (gc_component_t component, gc_backend_t backend)
{
char *filename = NULL;
gc_option_t *option = find_option
(component, gc_backend[backend].option_config_filename, GC_BACKEND_ANY);
assert (option);
assert (option->arg_type == GC_ARG_TYPE_FILENAME);
assert (!(option->flags & GC_OPT_FLAG_LIST));
if (!option->active || !option->default_value)
gc_error (1, 0, "Option %s, needed by backend %s, was not initialized",
gc_backend[backend].option_config_filename,
gc_backend[backend].name);
if (option->value && *option->value)
filename = percent_deescape (&option->value[1]);
else if (option->default_value && *option->default_value)
filename = percent_deescape (&option->default_value[1]);
else
filename = "";
#if HAVE_W32CE_SYSTEM
if (!(filename[0] == '/' || filename[0] == '\\'))
#elif defined(HAVE_DOSISH_SYSTEM)
if (!(filename[0]
&& filename[1] == ':'
&& (filename[2] == '/' || filename[2] == '\\')) /* x:\ or x:/ */
&& !((filename[0] == '\\' && filename[1] == '\\')
|| (filename[0] == '/' && filename[1] == '/'))) /* \\server */
#else
if (filename[0] != '/')
#endif
gc_error (1, 0, "Option %s, needed by backend %s, is not absolute",
gc_backend[backend].option_config_filename,
gc_backend[backend].name);
return filename;
}
/* Retrieve the options for the component COMPONENT from backend
* BACKEND, which we already know is a program-type backend. With
* ONLY_INSTALLED set components which are not installed are silently
* ignored. */
static void
retrieve_options_from_program (gc_component_t component, gc_backend_t backend,
int only_installed)
{
gpg_error_t err;
const char *pgmname;
const char *argv[2];
estream_t outfp;
int exitcode;
pid_t pid;
char *line = NULL;
size_t line_len = 0;
ssize_t length;
estream_t config;
char *config_filename;
pgmname = (gc_backend[backend].module_name
? gnupg_module_name (gc_backend[backend].module_name)
: gc_backend[backend].program );
argv[0] = "--gpgconf-list";
argv[1] = NULL;
if (only_installed && access (pgmname, X_OK))
{
return; /* The component is not installed. */
}
err = gnupg_spawn_process (pgmname, argv, NULL, NULL, 0,
NULL, &outfp, NULL, &pid);
if (err)
{
gc_error (1, 0, "could not gather active options from '%s': %s",
pgmname, gpg_strerror (err));
}
while ((length = es_read_line (outfp, &line, &line_len, NULL)) > 0)
{
gc_option_t *option;
char *linep;
unsigned long flags = 0;
char *default_value = NULL;
/* Strip newline and carriage return, if present. */
while (length > 0
&& (line[length - 1] == '\n' || line[length - 1] == '\r'))
line[--length] = '\0';
linep = strchr (line, ':');
if (linep)
*(linep++) = '\0';
/* Extract additional flags. Default to none. */
if (linep)
{
char *end;
char *tail;
end = strchr (linep, ':');
if (end)
*(end++) = '\0';
gpg_err_set_errno (0);
flags = strtoul (linep, &tail, 0);
if (errno)
gc_error (1, errno, "malformed flags in option %s from %s",
line, pgmname);
if (!(*tail == '\0' || *tail == ':' || *tail == ' '))
gc_error (1, 0, "garbage after flags in option %s from %s",
line, pgmname);
linep = end;
}
/* Extract default value, if present. Default to empty if
not. */
if (linep)
{
char *end;
end = strchr (linep, ':');
if (end)
*(end++) = '\0';
if (flags & GC_OPT_FLAG_DEFAULT)
default_value = linep;
linep = end;
}
/* Look up the option in the component and install the
configuration data. */
option = find_option (component, line, backend);
if (option)
{
if (option->active)
gc_error (1, errno, "option %s returned twice from %s",
line, pgmname);
option->active = 1;
option->flags |= flags;
if (default_value && *default_value)
option->default_value = xstrdup (default_value);
}
}
if (length < 0 || es_ferror (outfp))
gc_error (1, errno, "error reading from %s", pgmname);
if (es_fclose (outfp))
gc_error (1, errno, "error closing %s", pgmname);
err = gnupg_wait_process (pgmname, pid, 1, &exitcode);
if (err)
gc_error (1, 0, "running %s failed (exitcode=%d): %s",
pgmname, exitcode, gpg_strerror (err));
gnupg_release_process (pid);
/* At this point, we can parse the configuration file. */
config_filename = get_config_filename (component, backend);
config = es_fopen (config_filename, "r");
if (!config)
{
if (errno != ENOENT)
gc_error (0, errno, "warning: can not open config file %s",
config_filename);
}
else
{
while ((length = es_read_line (config, &line, &line_len, NULL)) > 0)
{
char *name;
char *value;
gc_option_t *option;
name = line;
while (*name == ' ' || *name == '\t')
name++;
if (!*name || *name == '#' || *name == '\r' || *name == '\n')
continue;
value = name;
while (*value && *value != ' ' && *value != '\t'
&& *value != '#' && *value != '\r' && *value != '\n')
value++;
if (*value == ' ' || *value == '\t')
{
char *end;
*(value++) = '\0';
while (*value == ' ' || *value == '\t')
value++;
end = value;
while (*end && *end != '#' && *end != '\r' && *end != '\n')
end++;
while (end > value && (end[-1] == ' ' || end[-1] == '\t'))
end--;
*end = '\0';
}
else
*value = '\0';
/* Look up the option in the component and install the
configuration data. */
option = find_option (component, line, backend);
if (option)
{
char *opt_value;
if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_NONE)
{
if (*value)
gc_error (0, 0,
"warning: ignoring argument %s for option %s",
value, name);
opt_value = xstrdup ("1");
}
else if (gc_arg_type[option->arg_type].fallback
== GC_ARG_TYPE_STRING)
opt_value = xasprintf ("\"%s", gc_percent_escape (value));
else
{
/* FIXME: Verify that the number is sane. */
opt_value = xstrdup (value);
}
/* Now enter the option into the table. */
if (!(option->flags & GC_OPT_FLAG_LIST))
{
if (option->value)
xfree (option->value);
option->value = opt_value;
}
else
{
if (!option->value)
option->value = opt_value;
else
{
char *old = option->value;
option->value = xasprintf ("%s,%s", old, opt_value);
xfree (old);
xfree (opt_value);
}
}
}
}
if (length < 0 || es_ferror (config))
gc_error (1, errno, "error reading from %s", config_filename);
if (es_fclose (config))
gc_error (1, errno, "error closing %s", config_filename);
}
xfree (line);
}
/* Retrieve the options for the component COMPONENT from backend
BACKEND, which we already know is of type file list. */
static void
retrieve_options_from_file (gc_component_t component, gc_backend_t backend)
{
gc_option_t *list_option;
gc_option_t *config_option;
char *list_filename;
gpgrt_stream_t list_file;
char *line = NULL;
size_t line_len = 0;
ssize_t length;
char *list = NULL;
list_option = find_option (component,
gc_backend[backend].option_name, GC_BACKEND_ANY);
assert (list_option);
assert (!list_option->active);
list_filename = get_config_filename (component, backend);
list_file = gpgrt_fopen (list_filename, "r");
if (!list_file)
gc_error (0, errno, "warning: can not open list file %s", list_filename);
else
{
while ((length = gpgrt_read_line (list_file, &line, &line_len, NULL)) > 0)
{
char *start;
char *end;
char *new_list;
start = line;
while (*start == ' ' || *start == '\t')
start++;
if (!*start || *start == '#' || *start == '\r' || *start == '\n')
continue;
end = start;
while (*end && *end != '#' && *end != '\r' && *end != '\n')
end++;
/* Walk back to skip trailing white spaces. Looks evil, but
works because of the conditions on START and END imposed
at this point (END is at least START + 1, and START is
not a whitespace character). */
while (*(end - 1) == ' ' || *(end - 1) == '\t')
end--;
*end = '\0';
/* FIXME: Oh, no! This is so lame! Should use realloc and
really append. */
if (list)
{
new_list = xasprintf ("%s,\"%s", list, gc_percent_escape (start));
xfree (list);
list = new_list;
}
else
list = xasprintf ("\"%s", gc_percent_escape (start));
}
if (length < 0 || gpgrt_ferror (list_file))
gc_error (1, errno, "can not read list file %s", list_filename);
}
list_option->active = 1;
list_option->value = list;
/* Fix up the read-only flag. */
config_option = find_option
(component, gc_backend[backend].option_config_filename, GC_BACKEND_ANY);
if (config_option->flags & GC_OPT_FLAG_NO_CHANGE)
list_option->flags |= GC_OPT_FLAG_NO_CHANGE;
if (list_file && gpgrt_fclose (list_file))
gc_error (1, errno, "error closing %s", list_filename);
xfree (line);
}
/* Retrieve the currently active options and their defaults from all
involved backends for this component. Using -1 for component will
retrieve all options from all installed components. */
void
gc_component_retrieve_options (int component)
{
int process_all = 0;
int backend_seen[GC_BACKEND_NR];
gc_backend_t backend;
gc_option_t *option;
for (backend = 0; backend < GC_BACKEND_NR; backend++)
backend_seen[backend] = 0;
if (component == -1)
{
process_all = 1;
component = 0;
assert (component < GC_COMPONENT_NR);
}
do
{
if (component == GC_COMPONENT_PINENTRY)
continue; /* Skip this dummy component. */
option = gc_component[component].options;
while (option && option->name)
{
if (!(option->flags & GC_OPT_FLAG_GROUP))
{
backend = option->backend;
if (backend_seen[backend])
{
option++;
continue;
}
backend_seen[backend] = 1;
assert (backend != GC_BACKEND_ANY);
if (gc_backend[backend].program)
retrieve_options_from_program (component, backend,
process_all);
else
retrieve_options_from_file (component, backend);
}
option++;
}
}
while (process_all && ++component < GC_COMPONENT_NR);
}
/* Perform a simple validity check based on the type. Return in
* NEW_VALUE_NR the value of the number in NEW_VALUE if OPTION is of
* type GC_ARG_TYPE_NONE. If VERBATIM is set the profile parsing mode
* is used. */
static void
option_check_validity (gc_option_t *option, unsigned long flags,
char *new_value, unsigned long *new_value_nr,
int verbatim)
{
char *arg;
if (!option->active)
gc_error (1, 0, "option %s not supported by backend %s",
option->name, gc_backend[option->backend].name);
if (option->new_flags || option->new_value)
gc_error (1, 0, "option %s already changed", option->name);
if (flags & GC_OPT_FLAG_DEFAULT)
{
if (*new_value)
gc_error (1, 0, "argument %s provided for deleted option %s",
new_value, option->name);
return;
}
/* GC_ARG_TYPE_NONE options have special list treatment. */
if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_NONE)
{
char *tail;
gpg_err_set_errno (0);
*new_value_nr = strtoul (new_value, &tail, 0);
if (errno)
gc_error (1, errno, "invalid argument for option %s",
option->name);
if (*tail)
gc_error (1, 0, "garbage after argument for option %s",
option->name);
if (!(option->flags & GC_OPT_FLAG_LIST))
{
if (*new_value_nr != 1)
gc_error (1, 0, "argument for non-list option %s of type 0 "
"(none) must be 1", option->name);
}
else
{
if (*new_value_nr == 0)
gc_error (1, 0, "argument for option %s of type 0 (none) "
"must be positive", option->name);
}
return;
}
arg = new_value;
do
{
if (*arg == '\0' || (*arg == ',' && !verbatim))
{
if (!(option->flags & GC_OPT_FLAG_ARG_OPT))
gc_error (1, 0, "argument required for option %s", option->name);
if (*arg == ',' && !verbatim && !(option->flags & GC_OPT_FLAG_LIST))
gc_error (1, 0, "list found for non-list option %s", option->name);
}
else if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_STRING)
{
if (*arg != '"' && !verbatim)
gc_error (1, 0, "string argument for option %s must begin "
"with a quote (\") character", option->name);
/* FIXME: We do not allow empty string arguments for now, as
we do not quote arguments in configuration files, and
thus no argument is indistinguishable from the empty
string. */
if (arg[1] == '\0' || (arg[1] == ',' && !verbatim))
gc_error (1, 0, "empty string argument for option %s is "
"currently not allowed. Please report this!",
option->name);
}
else if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_INT32)
{
long res;
gpg_err_set_errno (0);
res = strtol (arg, &arg, 0);
(void) res;
if (errno)
gc_error (1, errno, "invalid argument for option %s",
option->name);
if (*arg != '\0' && (*arg != ',' || verbatim))
gc_error (1, 0, "garbage after argument for option %s",
option->name);
}
else if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_UINT32)
{
unsigned long res;
gpg_err_set_errno (0);
res = strtoul (arg, &arg, 0);
(void) res;
if (errno)
gc_error (1, errno, "invalid argument for option %s",
option->name);
if (*arg != '\0' && (*arg != ',' || verbatim))
gc_error (1, 0, "garbage after argument for option %s",
option->name);
}
arg = verbatim? strchr (arg, ',') : NULL;
if (arg)
arg++;
}
while (arg && *arg);
}
#ifdef HAVE_W32_SYSTEM
int
copy_file (const char *src_name, const char *dst_name)
{
#define BUF_LEN 4096
char buffer[BUF_LEN];
int len;
gpgrt_stream_t src;
gpgrt_stream_t dst;
src = gpgrt_fopen (src_name, "r");
if (src == NULL)
return -1;
dst = gpgrt_fopen (dst_name, "w");
if (dst == NULL)
{
int saved_err = errno;
gpgrt_fclose (src);
gpg_err_set_errno (saved_err);
return -1;
}
do
{
int written;
len = gpgrt_fread (buffer, 1, BUF_LEN, src);
if (len == 0)
break;
written = gpgrt_fwrite (buffer, 1, len, dst);
if (written != len)
break;
}
while (! gpgrt_feof (src) && ! gpgrt_ferror (src) && ! gpgrt_ferror (dst));
if (gpgrt_ferror (src) || gpgrt_ferror (dst) || ! gpgrt_feof (src))
{
int saved_errno = errno;
gpgrt_fclose (src);
gpgrt_fclose (dst);
unlink (dst_name);
gpg_err_set_errno (saved_errno);
return -1;
}
if (gpgrt_fclose (dst))
gc_error (1, errno, "error closing %s", dst_name);
if (gpgrt_fclose (src))
gc_error (1, errno, "error closing %s", src_name);
return 0;
}
#endif /* HAVE_W32_SYSTEM */
/* Create and verify the new configuration file for the specified
* backend and component. Returns 0 on success and -1 on error. This
* function may store pointers to malloced strings in SRC_FILENAMEP,
* DEST_FILENAMEP, and ORIG_FILENAMEP. Those must be freed by the
* caller. The strings refer to three versions of the configuration
* file:
*
* SRC_FILENAME: The updated configuration is written to this file.
* DEST_FILENAME: Name of the configuration file read by the
* component.
* ORIG_FILENAME: A backup of the previous configuration file.
*
* To apply the configuration change, rename SRC_FILENAME to
* DEST_FILENAME. To revert to the previous configuration, rename
* ORIG_FILENAME to DEST_FILENAME. */
static int
change_options_file (gc_component_t component, gc_backend_t backend,
char **src_filenamep, char **dest_filenamep,
char **orig_filenamep)
{
static const char marker[] = "###+++--- " GPGCONF_DISP_NAME " ---+++###";
/* True if we are within the marker in the config file. */
int in_marker = 0;
gc_option_t *option;
char *line = NULL;
size_t line_len;
ssize_t length;
int res;
int fd;
gpgrt_stream_t src_file = NULL;
gpgrt_stream_t dest_file = NULL;
char *src_filename;
char *dest_filename;
char *orig_filename;
char *arg;
char *cur_arg = NULL;
option = find_option (component,
gc_backend[backend].option_name, GC_BACKEND_ANY);
assert (option);
assert (option->active);
assert (gc_arg_type[option->arg_type].fallback != GC_ARG_TYPE_NONE);
/* FIXME. Throughout the function, do better error reporting. */
/* Note that get_config_filename() calls percent_deescape(), so we
call this before processing the arguments. */
dest_filename = xstrdup (get_config_filename (component, backend));
src_filename = xasprintf ("%s.%s.%i.new",
dest_filename, GPGCONF_NAME, (int)getpid ());
orig_filename = xasprintf ("%s.%s.%i.bak",
dest_filename, GPGCONF_NAME, (int)getpid ());
arg = option->new_value;
if (arg && arg[0] == '\0')
arg = NULL;
else if (arg)
{
char *end;
arg++;
end = strchr (arg, ',');
if (end)
*end = '\0';
cur_arg = percent_deescape (arg);
if (end)
{
*end = ',';
arg = end + 1;
}
else
arg = NULL;
}
#ifdef HAVE_W32_SYSTEM
res = copy_file (dest_filename, orig_filename);
#else
res = link (dest_filename, orig_filename);
#endif
if (res < 0 && errno != ENOENT)
{
xfree (dest_filename);
xfree (src_filename);
xfree (orig_filename);
return -1;
}
if (res < 0)
{
xfree (orig_filename);
orig_filename = NULL;
}
/* We now initialize the return strings, so the caller can do the
cleanup for us. */
*src_filenamep = src_filename;
*dest_filenamep = dest_filename;
*orig_filenamep = orig_filename;
/* Use open() so that we can use O_EXCL. */
fd = open (src_filename, O_CREAT | O_EXCL | O_WRONLY, 0644);
if (fd < 0)
return -1;
src_file = gpgrt_fdopen (fd, "w");
res = errno;
if (!src_file)
{
gpg_err_set_errno (res);
return -1;
}
/* Only if ORIG_FILENAME is not NULL did the configuration file
exist already. In this case, we will copy its content into the
new configuration file, changing it to our liking in the
process. */
if (orig_filename)
{
dest_file = gpgrt_fopen (dest_filename, "r");
if (!dest_file)
goto change_file_one_err;
while ((length = gpgrt_read_line (dest_file, &line, &line_len, NULL)) > 0)
{
int disable = 0;
char *start;
if (!strncmp (marker, line, sizeof (marker) - 1))
{
if (!in_marker)
in_marker = 1;
else
break;
}
start = line;
while (*start == ' ' || *start == '\t')
start++;
if (*start && *start != '\r' && *start != '\n' && *start != '#')
{
char *end;
char *endp;
char saved_end;
endp = start;
end = endp;
/* Search for the end of the line. */
while (*endp && *endp != '#' && *endp != '\r' && *endp != '\n')
{
endp++;
if (*endp && *endp != ' ' && *endp != '\t'
&& *endp != '\r' && *endp != '\n' && *endp != '#')
end = endp + 1;
}
saved_end = *end;
*end = '\0';
if ((option->new_flags & GC_OPT_FLAG_DEFAULT)
|| !cur_arg || strcmp (start, cur_arg))
disable = 1;
else
{
/* Find next argument. */
if (arg)
{
char *arg_end;
arg++;
arg_end = strchr (arg, ',');
if (arg_end)
*arg_end = '\0';
cur_arg = percent_deescape (arg);
if (arg_end)
{
*arg_end = ',';
arg = arg_end + 1;
}
else
arg = NULL;
}
else
cur_arg = NULL;
}
*end = saved_end;
}
if (disable)
{
if (!in_marker)
{
gpgrt_fprintf (src_file,
"# %s disabled this option here at %s\n",
GPGCONF_DISP_NAME, asctimestamp (gnupg_get_time ()));
if (gpgrt_ferror (src_file))
goto change_file_one_err;
gpgrt_fprintf (src_file, "# %s", line);
if (gpgrt_ferror (src_file))
goto change_file_one_err;
}
}
else
{
gpgrt_fprintf (src_file, "%s", line);
if (gpgrt_ferror (src_file))
goto change_file_one_err;
}
}
if (length < 0 || gpgrt_ferror (dest_file))
goto change_file_one_err;
}
if (!in_marker)
{
/* There was no marker. This is the first time we edit the
file. We add our own marker at the end of the file and
proceed. Note that we first write a newline, this guards us
against files which lack the newline at the end of the last
line, while it doesn't hurt us in all other cases. */
gpgrt_fprintf (src_file, "\n%s\n", marker);
if (gpgrt_ferror (src_file))
goto change_file_one_err;
}
/* At this point, we have copied everything up to the end marker
into the new file, except for the arguments we are going to add.
Now, dump the new arguments and write the end marker, possibly
followed by the rest of the original file. */
while (cur_arg)
{
gpgrt_fprintf (src_file, "%s\n", cur_arg);
/* Find next argument. */
if (arg)
{
char *end;
arg++;
end = strchr (arg, ',');
if (end)
*end = '\0';
cur_arg = percent_deescape (arg);
if (end)
{
*end = ',';
arg = end + 1;
}
else
arg = NULL;
}
else
cur_arg = NULL;
}
gpgrt_fprintf (src_file, "%s %s\n", marker, asctimestamp (gnupg_get_time ()));
if (gpgrt_ferror (src_file))
goto change_file_one_err;
if (!in_marker)
{
gpgrt_fprintf (src_file, "# %s edited this configuration file.\n",
GPGCONF_DISP_NAME);
if (gpgrt_ferror (src_file))
goto change_file_one_err;
gpgrt_fprintf (src_file, "# It will disable options before this marked "
"block, but it will\n");
if (gpgrt_ferror (src_file))
goto change_file_one_err;
gpgrt_fprintf (src_file, "# never change anything below these lines.\n");
if (gpgrt_ferror (src_file))
goto change_file_one_err;
}
if (dest_file)
{
while ((length = gpgrt_read_line (dest_file, &line, &line_len, NULL)) > 0)
{
gpgrt_fprintf (src_file, "%s", line);
if (gpgrt_ferror (src_file))
goto change_file_one_err;
}
if (length < 0 || gpgrt_ferror (dest_file))
goto change_file_one_err;
}
xfree (line);
line = NULL;
res = gpgrt_fclose (src_file);
if (res)
{
res = errno;
close (fd);
if (dest_file)
gpgrt_fclose (dest_file);
gpg_err_set_errno (res);
return -1;
}
close (fd);
if (dest_file)
{
res = gpgrt_fclose (dest_file);
if (res)
return -1;
}
return 0;
change_file_one_err:
xfree (line);
res = errno;
if (src_file)
{
gpgrt_fclose (src_file);
close (fd);
}
if (dest_file)
gpgrt_fclose (dest_file);
gpg_err_set_errno (res);
return -1;
}
/* Create and verify the new configuration file for the specified
* backend and component. Returns 0 on success and -1 on error. If
* VERBATIM is set the profile mode is used. This function may store
* pointers to malloced strings in SRC_FILENAMEP, DEST_FILENAMEP, and
* ORIG_FILENAMEP. Those must be freed by the caller. The strings
* refer to three versions of the configuration file:
*
* SRC_FILENAME: The updated configuration is written to this file.
* DEST_FILENAME: Name of the configuration file read by the
* component.
* ORIG_FILENAME: A backup of the previous configuration file.
*
* To apply the configuration change, rename SRC_FILENAME to
* DEST_FILENAME. To revert to the previous configuration, rename
* ORIG_FILENAME to DEST_FILENAME. */
static int
change_options_program (gc_component_t component, gc_backend_t backend,
char **src_filenamep, char **dest_filenamep,
char **orig_filenamep,
int verbatim)
{
static const char marker[] = "###+++--- " GPGCONF_DISP_NAME " ---+++###";
/* True if we are within the marker in the config file. */
int in_marker = 0;
gc_option_t *option;
char *line = NULL;
size_t line_len;
ssize_t length;
int res;
int fd;
gpgrt_stream_t src_file = NULL;
gpgrt_stream_t dest_file = NULL;
char *src_filename;
char *dest_filename;
char *orig_filename;
/* Special hack for gpg, see below. */
int utf8strings_seen = 0;
/* FIXME. Throughout the function, do better error reporting. */
dest_filename = xstrdup (get_config_filename (component, backend));
src_filename = xasprintf ("%s.%s.%i.new",
dest_filename, GPGCONF_NAME, (int)getpid ());
orig_filename = xasprintf ("%s.%s.%i.bak",
dest_filename, GPGCONF_NAME, (int)getpid ());
#ifdef HAVE_W32_SYSTEM
res = copy_file (dest_filename, orig_filename);
#else
res = link (dest_filename, orig_filename);
#endif
if (res < 0 && errno != ENOENT)
{
xfree (dest_filename);
xfree (src_filename);
xfree (orig_filename);
return -1;
}
if (res < 0)
{
xfree (orig_filename);
orig_filename = NULL;
}
/* We now initialize the return strings, so the caller can do the
cleanup for us. */
*src_filenamep = src_filename;
*dest_filenamep = dest_filename;
*orig_filenamep = orig_filename;
/* Use open() so that we can use O_EXCL. */
fd = open (src_filename, O_CREAT | O_EXCL | O_WRONLY, 0644);
if (fd < 0)
return -1;
src_file = gpgrt_fdopen (fd, "w");
res = errno;
if (!src_file)
{
gpg_err_set_errno (res);
return -1;
}
/* Only if ORIG_FILENAME is not NULL did the configuration file
exist already. In this case, we will copy its content into the
new configuration file, changing it to our liking in the
process. */
if (orig_filename)
{
dest_file = gpgrt_fopen (dest_filename, "r");
if (!dest_file)
goto change_one_err;
while ((length = gpgrt_read_line (dest_file, &line, &line_len, NULL)) > 0)
{
int disable = 0;
char *start;
if (!strncmp (marker, line, sizeof (marker) - 1))
{
if (!in_marker)
in_marker = 1;
else
break;
}
else if (backend == GC_BACKEND_GPG && in_marker
&& ! strcmp ("utf8-strings\n", line))
{
/* Strip duplicated entries. */
if (utf8strings_seen)
disable = 1;
else
utf8strings_seen = 1;
}
start = line;
while (*start == ' ' || *start == '\t')
start++;
if (*start && *start != '\r' && *start != '\n' && *start != '#')
{
char *end;
char saved_end;
end = start;
while (*end && *end != ' ' && *end != '\t'
&& *end != '\r' && *end != '\n' && *end != '#')
end++;
saved_end = *end;
*end = '\0';
option = find_option (component, start, backend);
*end = saved_end;
if (option && ((option->new_flags & GC_OPT_FLAG_DEFAULT)
|| option->new_value))
disable = 1;
}
if (disable)
{
if (!in_marker)
{
gpgrt_fprintf (src_file,
"# %s disabled this option here at %s\n",
GPGCONF_DISP_NAME, asctimestamp (gnupg_get_time ()));
if (gpgrt_ferror (src_file))
goto change_one_err;
gpgrt_fprintf (src_file, "# %s", line);
if (gpgrt_ferror (src_file))
goto change_one_err;
}
}
else
{
gpgrt_fprintf (src_file, "%s", line);
if (gpgrt_ferror (src_file))
goto change_one_err;
}
}
if (length < 0 || gpgrt_ferror (dest_file))
goto change_one_err;
}
if (!in_marker)
{
/* There was no marker. This is the first time we edit the
file. We add our own marker at the end of the file and
proceed. Note that we first write a newline, this guards us
against files which lack the newline at the end of the last
line, while it doesn't hurt us in all other cases. */
gpgrt_fprintf (src_file, "\n%s\n", marker);
if (gpgrt_ferror (src_file))
goto change_one_err;
}
/* At this point, we have copied everything up to the end marker
into the new file, except for the options we are going to change.
Now, dump the changed options (except for those we are going to
revert to their default), and write the end marker, possibly
followed by the rest of the original file. */
/* We have to turn on UTF8 strings for GnuPG. */
if (backend == GC_BACKEND_GPG && ! utf8strings_seen)
gpgrt_fprintf (src_file, "utf8-strings\n");
option = gc_component[component].options;
while (option->name)
{
if (!(option->flags & GC_OPT_FLAG_GROUP)
&& option->backend == backend
&& option->new_value)
{
char *arg = option->new_value;
do
{
if (*arg == '\0' || *arg == ',')
{
gpgrt_fprintf (src_file, "%s\n", option->name);
if (gpgrt_ferror (src_file))
goto change_one_err;
}
else if (gc_arg_type[option->arg_type].fallback
== GC_ARG_TYPE_NONE)
{
assert (*arg == '1');
gpgrt_fprintf (src_file, "%s\n", option->name);
if (gpgrt_ferror (src_file))
goto change_one_err;
arg++;
}
else if (gc_arg_type[option->arg_type].fallback
== GC_ARG_TYPE_STRING)
{
char *end;
if (!verbatim)
{
log_assert (*arg == '"');
arg++;
end = strchr (arg, ',');
if (end)
*end = '\0';
}
else
end = NULL;
gpgrt_fprintf (src_file, "%s %s\n", option->name,
verbatim? arg : percent_deescape (arg));
if (gpgrt_ferror (src_file))
goto change_one_err;
if (end)
*end = ',';
arg = end;
}
else
{
char *end;
end = strchr (arg, ',');
if (end)
*end = '\0';
gpgrt_fprintf (src_file, "%s %s\n", option->name, arg);
if (gpgrt_ferror (src_file))
goto change_one_err;
if (end)
*end = ',';
arg = end;
}
assert (arg == NULL || *arg == '\0' || *arg == ',');
if (arg && *arg == ',')
arg++;
}
while (arg && *arg);
}
option++;
}
gpgrt_fprintf (src_file, "%s %s\n", marker, asctimestamp (gnupg_get_time ()));
if (gpgrt_ferror (src_file))
goto change_one_err;
if (!in_marker)
{
gpgrt_fprintf (src_file, "# %s edited this configuration file.\n",
GPGCONF_DISP_NAME);
if (gpgrt_ferror (src_file))
goto change_one_err;
gpgrt_fprintf (src_file, "# It will disable options before this marked "
"block, but it will\n");
if (gpgrt_ferror (src_file))
goto change_one_err;
gpgrt_fprintf (src_file, "# never change anything below these lines.\n");
if (gpgrt_ferror (src_file))
goto change_one_err;
}
if (dest_file)
{
while ((length = gpgrt_read_line (dest_file, &line, &line_len, NULL)) > 0)
{
gpgrt_fprintf (src_file, "%s", line);
if (gpgrt_ferror (src_file))
goto change_one_err;
}
if (length < 0 || gpgrt_ferror (dest_file))
goto change_one_err;
}
xfree (line);
line = NULL;
res = gpgrt_fclose (src_file);
if (res)
{
res = errno;
close (fd);
if (dest_file)
gpgrt_fclose (dest_file);
gpg_err_set_errno (res);
return -1;
}
close (fd);
if (dest_file)
{
res = gpgrt_fclose (dest_file);
if (res)
return -1;
}
return 0;
change_one_err:
xfree (line);
res = errno;
if (src_file)
{
gpgrt_fclose (src_file);
close (fd);
}
if (dest_file)
gpgrt_fclose (dest_file);
gpg_err_set_errno (res);
return -1;
}
/* Common code for gc_component_change_options and
* gc_process_gpgconf_conf. If VERBATIM is set the profile parsing
* mode is used. */
static void
change_one_value (gc_option_t *option, int *runtime,
unsigned long flags, char *new_value, int verbatim)
{
unsigned long new_value_nr = 0;
option_check_validity (option, flags, new_value, &new_value_nr, verbatim);
if (option->flags & GC_OPT_FLAG_RUNTIME)
runtime[option->backend] = 1;
option->new_flags = flags;
if (!(flags & GC_OPT_FLAG_DEFAULT))
{
if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_NONE
&& (option->flags & GC_OPT_FLAG_LIST))
{
char *str;
/* We convert the number to a list of 1's for convenient
list handling. */
assert (new_value_nr > 0);
option->new_value = xmalloc ((2 * (new_value_nr - 1) + 1) + 1);
str = option->new_value;
*(str++) = '1';
while (--new_value_nr > 0)
{
*(str++) = ',';
*(str++) = '1';
}
*(str++) = '\0';
}
else
option->new_value = xstrdup (new_value);
}
}
/* Read the modifications from IN and apply them. If IN is NULL the
modifications are expected to already have been set to the global
table. If VERBATIM is set the profile mode is used. */
void
gc_component_change_options (int component, estream_t in, estream_t out,
int verbatim)
{
int err = 0;
int block = 0;
int runtime[GC_BACKEND_NR];
char *src_filename[GC_BACKEND_NR];
char *dest_filename[GC_BACKEND_NR];
char *orig_filename[GC_BACKEND_NR];
gc_backend_t backend;
gc_option_t *option;
char *line = NULL;
size_t line_len = 0;
ssize_t length;
if (component == GC_COMPONENT_PINENTRY)
return; /* Dummy component for now. */
for (backend = 0; backend < GC_BACKEND_NR; backend++)
{
runtime[backend] = 0;
src_filename[backend] = NULL;
dest_filename[backend] = NULL;
orig_filename[backend] = NULL;
}
if (in)
{
/* Read options from the file IN. */
while ((length = es_read_line (in, &line, &line_len, NULL)) > 0)
{
char *linep;
unsigned long flags = 0;
char *new_value = "";
/* Strip newline and carriage return, if present. */
while (length > 0
&& (line[length - 1] == '\n' || line[length - 1] == '\r'))
line[--length] = '\0';
linep = strchr (line, ':');
if (linep)
*(linep++) = '\0';
/* Extract additional flags. Default to none. */
if (linep)
{
char *end;
char *tail;
end = strchr (linep, ':');
if (end)
*(end++) = '\0';
gpg_err_set_errno (0);
flags = strtoul (linep, &tail, 0);
if (errno)
gc_error (1, errno, "malformed flags in option %s", line);
if (!(*tail == '\0' || *tail == ':' || *tail == ' '))
gc_error (1, 0, "garbage after flags in option %s", line);
linep = end;
}
/* Don't allow setting of the no change flag. */
flags &= ~GC_OPT_FLAG_NO_CHANGE;
/* Extract default value, if present. Default to empty if not. */
if (linep)
{
char *end;
end = strchr (linep, ':');
if (end)
*(end++) = '\0';
new_value = linep;
linep = end;
}
option = find_option (component, line, GC_BACKEND_ANY);
if (!option)
gc_error (1, 0, "unknown option %s", line);
if ((option->flags & GC_OPT_FLAG_NO_CHANGE))
{
gc_error (0, 0, "ignoring new value for option %s",
option->name);
continue;
}
change_one_value (option, runtime, flags, new_value, 0);
}
if (length < 0 || gpgrt_ferror (in))
gc_error (1, errno, "error reading stream 'in'");
}
/* Now that we have collected and locally verified the changes,
write them out to new configuration files, verify them
externally, and then commit them. */
option = gc_component[component].options;
while (option && option->name)
{
/* Go on if we have already seen this backend, or if there is
nothing to do. */
if (src_filename[option->backend]
|| !(option->new_flags || option->new_value))
{
option++;
continue;
}
if (gc_backend[option->backend].program)
{
err = change_options_program (component, option->backend,
&src_filename[option->backend],
&dest_filename[option->backend],
&orig_filename[option->backend],
verbatim);
if (! err)
{
/* External verification. */
err = gc_component_check_options (component, out,
src_filename[option->backend]);
if (err)
{
gc_error (0, 0,
_("External verification of component %s failed"),
gc_component[component].name);
gpg_err_set_errno (EINVAL);
}
}
}
else
err = change_options_file (component, option->backend,
&src_filename[option->backend],
&dest_filename[option->backend],
&orig_filename[option->backend]);
if (err)
break;
option++;
}
/* We are trying to atomically commit all changes. Unfortunately,
we cannot rely on gnupg_rename_file to manage the signals for us,
doing so would require us to pass NULL as BLOCK to any subsequent
call to it. Instead, we just manage the signal handling
manually. */
block = 1;
gnupg_block_all_signals ();
if (! err && ! opt.dry_run)
{
int i;
for (i = 0; i < GC_BACKEND_NR; i++)
{
if (src_filename[i])
{
/* FIXME: Make a verification here. */
assert (dest_filename[i]);
if (orig_filename[i])
err = gnupg_rename_file (src_filename[i], dest_filename[i], NULL);
else
{
#ifdef HAVE_W32_SYSTEM
/* We skip the unlink if we expect the file not to
be there. */
err = gnupg_rename_file (src_filename[i], dest_filename[i], NULL);
#else /* HAVE_W32_SYSTEM */
/* This is a bit safer than rename() because we
expect DEST_FILENAME not to be there. If it
happens to be there, this will fail. */
err = link (src_filename[i], dest_filename[i]);
if (!err)
err = unlink (src_filename[i]);
#endif /* !HAVE_W32_SYSTEM */
}
if (err)
break;
xfree (src_filename[i]);
src_filename[i] = NULL;
}
}
}
if (err || opt.dry_run)
{
int i;
int saved_errno = errno;
/* An error occurred or a dry-run is requested. */
for (i = 0; i < GC_BACKEND_NR; i++)
{
if (src_filename[i])
{
/* The change was not yet committed. */
unlink (src_filename[i]);
if (orig_filename[i])
unlink (orig_filename[i]);
}
else
{
/* The changes were already committed. FIXME: This is a
tad dangerous, as we don't know if we don't overwrite
a version of the file that is even newer than the one
we just installed. */
if (orig_filename[i])
gnupg_rename_file (orig_filename[i], dest_filename[i], NULL);
else
unlink (dest_filename[i]);
}
}
if (err)
gc_error (1, saved_errno, "could not commit changes");
/* Fall-through for dry run. */
goto leave;
}
/* If it all worked, notify the daemons of the changes. */
if (opt.runtime)
for (backend = 0; backend < GC_BACKEND_NR; backend++)
{
if (runtime[backend] && gc_backend[backend].runtime_change)
(*gc_backend[backend].runtime_change) (0);
}
/* Move the per-process backup file into its place. */
for (backend = 0; backend < GC_BACKEND_NR; backend++)
if (orig_filename[backend])
{
char *backup_filename;
assert (dest_filename[backend]);
backup_filename = xasprintf ("%s.%s.bak",
dest_filename[backend], GPGCONF_NAME);
gnupg_rename_file (orig_filename[backend], backup_filename, NULL);
xfree (backup_filename);
}
leave:
if (block)
gnupg_unblock_all_signals ();
xfree (line);
for (backend = 0; backend < GC_BACKEND_NR; backend++)
{
xfree (src_filename[backend]);
xfree (dest_filename[backend]);
xfree (orig_filename[backend]);
}
}
/* Check whether USER matches the current user of one of its group.
This function may change USER. Returns true is there is a
match. */
static int
key_matches_user_or_group (char *user)
{
char *group;
if (*user == '*' && user[1] == 0)
return 1; /* A single asterisk matches all users. */
group = strchr (user, ':');
if (group)
*group++ = 0;
#ifdef HAVE_W32_SYSTEM
/* Under Windows we don't support groups. */
if (group && *group)
gc_error (0, 0, _("Note that group specifications are ignored\n"));
#ifndef HAVE_W32CE_SYSTEM
if (*user)
{
static char *my_name;
if (!my_name)
{
char tmp[1];
DWORD size = 1;
GetUserNameA (tmp, &size);
my_name = xmalloc (size);
if (!GetUserNameA (my_name, &size))
gc_error (1,0, "error getting current user name: %s",
w32_strerror (-1));
}
if (!strcmp (user, my_name))
return 1; /* Found. */
}
#endif /*HAVE_W32CE_SYSTEM*/
#else /*!HAVE_W32_SYSTEM*/
/* First check whether the user matches. */
if (*user)
{
static char *my_name;
if (!my_name)
{
struct passwd *pw = getpwuid ( getuid () );
if (!pw)
gc_error (1, errno, "getpwuid failed for current user");
my_name = xstrdup (pw->pw_name);
}
if (!strcmp (user, my_name))
return 1; /* Found. */
}
/* If that failed, check whether a group matches. */
if (group && *group)
{
static char *my_group;
static char **my_supgroups;
int n;
if (!my_group)
{
struct group *gr = getgrgid ( getgid () );
if (!gr)
gc_error (1, errno, "getgrgid failed for current user");
my_group = xstrdup (gr->gr_name);
}
if (!strcmp (group, my_group))
return 1; /* Found. */
if (!my_supgroups)
{
int ngids;
gid_t *gids;
ngids = getgroups (0, NULL);
gids = xcalloc (ngids+1, sizeof *gids);
ngids = getgroups (ngids, gids);
if (ngids < 0)
gc_error (1, errno, "getgroups failed for current user");
my_supgroups = xcalloc (ngids+1, sizeof *my_supgroups);
for (n=0; n < ngids; n++)
{
struct group *gr = getgrgid ( gids[n] );
if (!gr)
gc_error (1, errno, "getgrgid failed for supplementary group");
my_supgroups[n] = xstrdup (gr->gr_name);
}
xfree (gids);
}
for (n=0; my_supgroups[n]; n++)
if (!strcmp (group, my_supgroups[n]))
return 1; /* Found. */
}
#endif /*!HAVE_W32_SYSTEM*/
return 0; /* No match. */
}
/* Read and process the global configuration file for gpgconf. This
optional file is used to update our internal tables at runtime and
may also be used to set new default values. If FNAME is NULL the
default name will be used. With UPDATE set to true the internal
tables are actually updated; if not set, only a syntax check is
done. If DEFAULTS is true the global options are written to the
configuration files. If LISTFP is set, no changes are done but the
configuration file is printed to LISTFP in a colon separated format.
Returns 0 on success or if the config file is not present; -1 is
returned on error. */
int
gc_process_gpgconf_conf (const char *fname_arg, int update, int defaults,
estream_t listfp)
{
int result = 0;
char *line = NULL;
size_t line_len = 0;
ssize_t length;
gpgrt_stream_t config;
int lineno = 0;
int in_rule = 0;
int got_match = 0;
int runtime[GC_BACKEND_NR];
int backend_id, component_id;
char *fname;
if (fname_arg)
fname = xstrdup (fname_arg);
else
fname = make_filename (gnupg_sysconfdir (), GPGCONF_NAME EXTSEP_S "conf",
NULL);
for (backend_id = 0; backend_id < GC_BACKEND_NR; backend_id++)
runtime[backend_id] = 0;
config = gpgrt_fopen (fname, "r");
if (!config)
{
/* Do not print an error if the file is not available, except
when running in syntax check mode. */
if (errno != ENOENT || !update)
{
gc_error (0, errno, "can not open global config file '%s'", fname);
result = -1;
}
xfree (fname);
return result;
}
while ((length = gpgrt_read_line (config, &line, &line_len, NULL)) > 0)
{
char *key, *component, *option, *flags, *value;
char *empty;
gc_option_t *option_info = NULL;
char *p;
int is_continuation;
lineno++;
key = line;
while (*key == ' ' || *key == '\t')
key++;
if (!*key || *key == '#' || *key == '\r' || *key == '\n')
continue;
is_continuation = (key != line);
/* Parse the key field. */
if (!is_continuation && got_match)
break; /* Finish after the first match. */
else if (!is_continuation)
{
in_rule = 0;
for (p=key+1; *p && !strchr (" \t\r\n", *p); p++)
;
if (!*p)
{
gc_error (0, 0, "missing rule at '%s', line %d", fname, lineno);
result = -1;
gpgconf_write_status (STATUS_WARNING,
"gpgconf.conf %d file '%s' line %d "
"missing rule",
GPG_ERR_SYNTAX, fname, lineno);
continue;
}
*p++ = 0;
component = p;
}
else if (!in_rule)
{
gc_error (0, 0, "continuation but no rule at '%s', line %d",
fname, lineno);
result = -1;
continue;
}
else
{
component = key;
key = NULL;
}
in_rule = 1;
/* Parse the component. */
while (*component == ' ' || *component == '\t')
component++;
for (p=component; *p && !strchr (" \t\r\n", *p); p++)
;
if (p == component)
{
gc_error (0, 0, "missing component at '%s', line %d",
fname, lineno);
gpgconf_write_status (STATUS_WARNING,
"gpgconf.conf %d file '%s' line %d "
" missing component",
GPG_ERR_NO_NAME, fname, lineno);
result = -1;
continue;
}
empty = p;
*p++ = 0;
option = p;
component_id = gc_component_find (component);
if (component_id < 0)
{
gc_error (0, 0, "unknown component at '%s', line %d",
fname, lineno);
gpgconf_write_status (STATUS_WARNING,
"gpgconf.conf %d file '%s' line %d "
"unknown component",
GPG_ERR_UNKNOWN_NAME, fname, lineno);
result = -1;
}
/* Parse the option name. */
while (*option == ' ' || *option == '\t')
option++;
for (p=option; *p && !strchr (" \t\r\n", *p); p++)
;
if (p == option)
{
gc_error (0, 0, "missing option at '%s', line %d",
fname, lineno);
gpgconf_write_status (STATUS_WARNING,
"gpgconf.conf %d file '%s' line %d "
"missing option",
GPG_ERR_INV_NAME, fname, lineno);
result = -1;
continue;
}
*p++ = 0;
flags = p;
if ( component_id != -1)
{
option_info = find_option (component_id, option, GC_BACKEND_ANY);
if (!option_info)
{
gc_error (0, 0, "unknown option at '%s', line %d",
fname, lineno);
gpgconf_write_status (STATUS_WARNING,
"gpgconf.conf %d file '%s' line %d "
"unknown option",
GPG_ERR_UNKNOWN_OPTION, fname, lineno);
result = -1;
}
}
/* Parse the optional flags. */
while (*flags == ' ' || *flags == '\t')
flags++;
if (*flags == '[')
{
flags++;
p = strchr (flags, ']');
if (!p)
{
gc_error (0, 0, "syntax error in rule at '%s', line %d",
fname, lineno);
gpgconf_write_status (STATUS_WARNING,
"gpgconf.conf %d file '%s' line %d "
"syntax error in rule",
GPG_ERR_SYNTAX, fname, lineno);
result = -1;
continue;
}
*p++ = 0;
value = p;
}
else /* No flags given. */
{
value = flags;
flags = NULL;
}
/* Parse the optional value. */
while (*value == ' ' || *value == '\t')
value++;
for (p=value; *p && !strchr ("\r\n", *p); p++)
;
if (p == value)
value = empty; /* No value given; let it point to an empty string. */
else
{
/* Strip trailing white space. */
*p = 0;
for (p--; p > value && (*p == ' ' || *p == '\t'); p--)
*p = 0;
}
/* Check flag combinations. */
if (!flags)
;
else if (!strcmp (flags, "default"))
{
if (*value)
{
gc_error (0, 0, "flag \"default\" may not be combined "
"with a value at '%s', line %d",
fname, lineno);
result = -1;
}
}
else if (!strcmp (flags, "change"))
;
else if (!strcmp (flags, "no-change"))
;
else
{
gc_error (0, 0, "unknown flag at '%s', line %d",
fname, lineno);
result = -1;
}
/* In list mode we print out all records. */
if (listfp && !result)
{
/* If this is a new ruleset, print a key record. */
if (!is_continuation)
{
char *group = strchr (key, ':');
if (group)
{
*group++ = 0;
if ((p = strchr (group, ':')))
*p = 0; /* We better strip any extra stuff. */
}
es_fprintf (listfp, "k:%s:", gc_percent_escape (key));
es_fprintf (listfp, "%s\n", group? gc_percent_escape (group):"");
}
/* All other lines are rule records. */
es_fprintf (listfp, "r:::%s:%s:%s:",
gc_component[component_id].name,
option_info->name? option_info->name : "",
flags? flags : "");
if (value != empty)
es_fprintf (listfp, "\"%s", gc_percent_escape (value));
es_putc ('\n', listfp);
}
/* Check whether the key matches but do this only if we are not
running in syntax check mode. */
if ( update
&& !result && !listfp
&& (got_match || (key && key_matches_user_or_group (key))) )
{
int newflags = 0;
got_match = 1;
/* Apply the flags from gpgconf.conf. */
if (!flags)
;
else if (!strcmp (flags, "default"))
newflags |= GC_OPT_FLAG_DEFAULT;
else if (!strcmp (flags, "no-change"))
option_info->flags |= GC_OPT_FLAG_NO_CHANGE;
else if (!strcmp (flags, "change"))
option_info->flags &= ~GC_OPT_FLAG_NO_CHANGE;
if (defaults)
{
/* Here we explicitly allow updating the value again. */
if (newflags)
{
option_info->new_flags = 0;
}
if (*value)
{
xfree (option_info->new_value);
option_info->new_value = NULL;
}
change_one_value (option_info, runtime, newflags, value, 0);
}
}
}
if (length < 0 || gpgrt_ferror (config))
{
gc_error (0, errno, "error reading from '%s'", fname);
result = -1;
}
if (gpgrt_fclose (config))
gc_error (0, errno, "error closing '%s'", fname);
xfree (line);
/* If it all worked, process the options. */
if (!result && update && defaults && !listfp)
{
/* We need to switch off the runtime update, so that we can do
it later all at once. */
int save_opt_runtime = opt.runtime;
opt.runtime = 0;
for (component_id = 0; component_id < GC_COMPONENT_NR; component_id++)
{
gc_component_change_options (component_id, NULL, NULL, 0);
}
opt.runtime = save_opt_runtime;
if (opt.runtime)
{
for (backend_id = 0; backend_id < GC_BACKEND_NR; backend_id++)
if (runtime[backend_id] && gc_backend[backend_id].runtime_change)
(*gc_backend[backend_id].runtime_change) (0);
}
}
xfree (fname);
return result;
}
/*
* Apply the profile FNAME to all known configure files.
*/
gpg_error_t
gc_apply_profile (const char *fname)
{
gpg_error_t err;
char *fname_buffer = NULL;
char *line = NULL;
size_t line_len = 0;
ssize_t length;
estream_t fp;
int lineno = 0;
int runtime[GC_BACKEND_NR];
int backend_id;
int component_id = -1;
int skip_section = 0;
int error_count = 0;
int newflags;
if (!fname)
fname = "-";
for (backend_id = 0; backend_id < GC_BACKEND_NR; backend_id++)
runtime[backend_id] = 0;
if (!(!strcmp (fname, "-")
|| strchr (fname, '/')
#ifdef HAVE_W32_SYSTEM
|| strchr (fname, '\\')
#endif
|| strchr (fname, '.')))
{
/* FNAME looks like a standard profile name. Check whether one
* is installed and use that instead of the given file name. */
fname_buffer = xstrconcat (gnupg_datadir (), DIRSEP_S,
fname, ".prf", NULL);
if (!access (fname_buffer, F_OK))
fname = fname_buffer;
}
fp = !strcmp (fname, "-")? es_stdin : es_fopen (fname, "r");
if (!fp)
{
err = gpg_error_from_syserror ();
log_error ("can't open '%s': %s\n", fname, gpg_strerror (err));
return err;
}
if (opt.verbose)
log_info ("applying profile '%s'\n", fname);
err = 0;
while ((length = es_read_line (fp, &line, &line_len, NULL)) > 0)
{
char *name, *flags, *value;
gc_option_t *option_info = NULL;
char *p;
lineno++;
name = line;
while (*name == ' ' || *name == '\t')
name++;
if (!*name || *name == '#' || *name == '\r' || *name == '\n')
continue;
trim_trailing_spaces (name);
/* Check whether this is a new section. */
if (*name == '[')
{
name++;
skip_section = 0;
/* New section: Get the name of the component. */
p = strchr (name, ']');
if (!p)
{
error_count++;
log_info ("%s:%d:%d: error: syntax error in section tag\n",
fname, lineno, (int)(name - line));
skip_section = 1;
continue;
}
*p++ = 0;
if (*p)
log_info ("%s:%d:%d: warning: garbage after section tag\n",
fname, lineno, (int)(p - line));
trim_spaces (name);
component_id = gc_component_find (name);
if (component_id < 0)
{
log_info ("%s:%d:%d: warning: skipping unknown section '%s'\n",
fname, lineno, (int)(name - line), name );
skip_section = 1;
}
continue;
}
if (skip_section)
continue;
if (component_id < 0)
{
error_count++;
log_info ("%s:%d:%d: error: not in a valid section\n",
fname, lineno, (int)(name - line));
skip_section = 1;
continue;
}
/* Parse the option name. */
for (p = name; *p && !spacep (p); p++)
;
*p++ = 0;
value = p;
option_info = find_option (component_id, name, GC_BACKEND_ANY);
if (!option_info)
{
error_count++;
log_info ("%s:%d:%d: error: unknown option '%s' in section '%s'\n",
fname, lineno, (int)(name - line),
name, gc_component[component_id].name);
continue;
}
/* Parse the optional flags. */
trim_spaces (value);
flags = value;
if (*flags == '[')
{
flags++;
p = strchr (flags, ']');
if (!p)
{
log_info ("%s:%d:%d: warning: invalid flag specification\n",
fname, lineno, (int)(p - line));
continue;
}
*p++ = 0;
value = p;
trim_spaces (value);
}
else /* No flags given. */
flags = NULL;
/* Set required defaults. */
if (gc_arg_type[option_info->arg_type].fallback == GC_ARG_TYPE_NONE
&& !*value)
value = "1";
/* Check and save this option. */
newflags = 0;
if (flags && !strcmp (flags, "default"))
newflags |= GC_OPT_FLAG_DEFAULT;
if (newflags)
option_info->new_flags = 0;
if (*value)
{
xfree (option_info->new_value);
option_info->new_value = NULL;
}
change_one_value (option_info, runtime, newflags, value, 1);
}
if (length < 0 || es_ferror (fp))
{
err = gpg_error_from_syserror ();
error_count++;
log_error (_("%s:%u: read error: %s\n"),
fname, lineno, gpg_strerror (err));
}
if (es_fclose (fp))
log_error (_("error closing '%s'\n"), fname);
if (error_count)
log_error (_("error parsing '%s'\n"), fname);
xfree (line);
/* If it all worked, process the options. */
if (!err)
{
/* We need to switch off the runtime update, so that we can do
it later all at once. */
int save_opt_runtime = opt.runtime;
opt.runtime = 0;
for (component_id = 0; component_id < GC_COMPONENT_NR; component_id++)
{
gc_component_change_options (component_id, NULL, NULL, 1);
}
opt.runtime = save_opt_runtime;
if (opt.runtime)
{
for (backend_id = 0; backend_id < GC_BACKEND_NR; backend_id++)
if (runtime[backend_id] && gc_backend[backend_id].runtime_change)
(*gc_backend[backend_id].runtime_change) (0);
}
}
xfree (fname_buffer);
return err;
}
diff --git a/tools/gpgconf.c b/tools/gpgconf.c
index b67125b89..a0cd97f60 100644
--- a/tools/gpgconf.c
+++ b/tools/gpgconf.c
@@ -1,909 +1,910 @@
/* gpgconf.c - Configuration utility for GnuPG
* Copyright (C) 2003, 2007, 2009, 2011 Free Software Foundation, Inc.
* Copyright (C) 2016 g10 Code GmbH.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "gpgconf.h"
#include "../common/i18n.h"
#include "../common/sysutils.h"
#include "../common/init.h"
#include "../common/status.h"
/* Constants to identify the commands and options. */
enum cmd_and_opt_values
{
aNull = 0,
oDryRun = 'n',
oOutput = 'o',
oQuiet = 'q',
oVerbose = 'v',
oRuntime = 'r',
oComponent = 'c',
oNull = '0',
oNoVerbose = 500,
oHomedir,
oBuilddir,
oStatusFD,
oShowSocket,
aListComponents,
aCheckPrograms,
aListOptions,
aChangeOptions,
aCheckOptions,
aApplyDefaults,
aListConfig,
aCheckConfig,
aQuerySWDB,
aListDirs,
aLaunch,
aKill,
aCreateSocketDir,
aRemoveSocketDir,
aApplyProfile,
aReload
};
/* The list of commands and options. */
static ARGPARSE_OPTS opts[] =
{
{ 300, NULL, 0, N_("@Commands:\n ") },
{ aListComponents, "list-components", 256, N_("list all components") },
{ aCheckPrograms, "check-programs", 256, N_("check all programs") },
{ aListOptions, "list-options", 256, N_("|COMPONENT|list options") },
{ aChangeOptions, "change-options", 256, N_("|COMPONENT|change options") },
{ aCheckOptions, "check-options", 256, N_("|COMPONENT|check options") },
{ aApplyDefaults, "apply-defaults", 256,
N_("apply global default values") },
{ aApplyProfile, "apply-profile", 256,
N_("|FILE|update configuration files using FILE") },
{ aListDirs, "list-dirs", 256,
N_("get the configuration directories for @GPGCONF@") },
{ aListConfig, "list-config", 256,
N_("list global configuration file") },
{ aCheckConfig, "check-config", 256,
N_("check global configuration file") },
{ aQuerySWDB, "query-swdb", 256,
N_("query the software version database") },
{ aReload, "reload", 256, N_("reload all or a given component")},
{ aLaunch, "launch", 256, N_("launch a given component")},
{ aKill, "kill", 256, N_("kill a given component")},
{ aCreateSocketDir, "create-socketdir", 256, "@"},
{ aRemoveSocketDir, "remove-socketdir", 256, "@"},
{ 301, NULL, 0, N_("@\nOptions:\n ") },
{ oOutput, "output", 2, N_("use as output file") },
{ oVerbose, "verbose", 0, N_("verbose") },
{ oQuiet, "quiet", 0, N_("quiet") },
{ oDryRun, "dry-run", 0, N_("do not make any changes") },
{ oRuntime, "runtime", 0, N_("activate changes at runtime, if possible") },
ARGPARSE_s_i (oStatusFD, "status-fd", N_("|FD|write status info to this FD")),
/* hidden options */
{ oHomedir, "homedir", 2, "@" },
{ oBuilddir, "build-prefix", 2, "@" },
{ oNull, "null", 0, "@" },
{ oNoVerbose, "no-verbose", 0, "@"},
ARGPARSE_s_n (oShowSocket, "show-socket", "@"),
ARGPARSE_end(),
};
/* The stream to output the status information. Status Output is disabled if
* this is NULL. */
static estream_t statusfp;
/* Print usage information and provide strings for help. */
static const char *
my_strusage( int level )
{
const char *p;
switch (level)
{
case 11: p = "@GPGCONF@ (@GNUPG@)";
break;
case 13: p = VERSION; break;
case 17: p = PRINTABLE_OS_NAME; break;
case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break;
case 1:
case 40: p = _("Usage: @GPGCONF@ [options] (-h for help)");
break;
case 41:
p = _("Syntax: @GPGCONF@ [options]\n"
"Manage configuration options for tools of the @GNUPG@ system\n");
break;
default: p = NULL; break;
}
return p;
}
/* Return the fp for the output. This is usually stdout unless
--output has been used. In the latter case this function opens
that file. */
static estream_t
get_outfp (estream_t *fp)
{
if (!*fp)
{
if (opt.outfile)
{
*fp = es_fopen (opt.outfile, "w");
if (!*fp)
gc_error (1, errno, "can not open '%s'", opt.outfile);
}
else
*fp = es_stdout;
}
return *fp;
}
/* Set the status FD. */
static void
set_status_fd (int fd)
{
static int last_fd = -1;
if (fd != -1 && last_fd == fd)
return;
if (statusfp && statusfp != es_stdout && statusfp != es_stderr)
es_fclose (statusfp);
statusfp = NULL;
if (fd == -1)
return;
if (fd == 1)
statusfp = es_stdout;
else if (fd == 2)
statusfp = es_stderr;
else
statusfp = es_fdopen (fd, "w");
if (!statusfp)
{
log_fatal ("can't open fd %d for status output: %s\n",
fd, gpg_strerror (gpg_error_from_syserror ()));
}
last_fd = fd;
}
/* Write a status line with code NO followed by the output of the
* printf style FORMAT. The caller needs to make sure that LFs and
* CRs are not printed. */
void
gpgconf_write_status (int no, const char *format, ...)
{
va_list arg_ptr;
if (!statusfp)
return; /* Not enabled. */
es_fputs ("[GNUPG:] ", statusfp);
es_fputs (get_status_string (no), statusfp);
if (format)
{
es_putc (' ', statusfp);
va_start (arg_ptr, format);
es_vfprintf (statusfp, format, arg_ptr);
va_end (arg_ptr);
}
es_putc ('\n', statusfp);
}
static void
list_dirs (estream_t fp, char **names)
{
static struct {
const char *name;
const char *(*fnc)(void);
const char *extra;
} list[] = {
{ "sysconfdir", gnupg_sysconfdir, NULL },
{ "bindir", gnupg_bindir, NULL },
{ "libexecdir", gnupg_libexecdir, NULL },
{ "libdir", gnupg_libdir, NULL },
{ "datadir", gnupg_datadir, NULL },
{ "localedir", gnupg_localedir, NULL },
{ "socketdir", gnupg_socketdir, NULL },
{ "dirmngr-socket", dirmngr_socket_name, NULL,},
{ "agent-ssh-socket", gnupg_socketdir, GPG_AGENT_SSH_SOCK_NAME },
{ "agent-extra-socket", gnupg_socketdir, GPG_AGENT_EXTRA_SOCK_NAME },
{ "agent-browser-socket",gnupg_socketdir, GPG_AGENT_BROWSER_SOCK_NAME },
{ "agent-socket", gnupg_socketdir, GPG_AGENT_SOCK_NAME },
{ "homedir", gnupg_homedir, NULL }
};
int idx, j;
char *tmp;
const char *s;
for (idx = 0; idx < DIM (list); idx++)
{
s = list[idx].fnc ();
if (list[idx].extra)
{
tmp = make_filename (s, list[idx].extra, NULL);
s = tmp;
}
else
tmp = NULL;
if (!names)
es_fprintf (fp, "%s:%s\n", list[idx].name, gc_percent_escape (s));
else
{
for (j=0; names[j]; j++)
if (!strcmp (names[j], list[idx].name))
{
es_fputs (s, fp);
es_putc (opt.null? '\0':'\n', fp);
}
}
xfree (tmp);
}
}
/* Check whether NAME is valid argument for query_swdb(). Valid names
* start with a letter and contain only alphanumeric characters or an
* underscore. */
static int
valid_swdb_name_p (const char *name)
{
if (!name || !*name || !alphap (name))
return 0;
for (name++; *name; name++)
if (!alnump (name) && *name != '_')
return 0;
return 1;
}
/* Query the SWDB file. If necessary and possible this functions asks
* the dirmngr to load an updated version of that file. The caller
* needs to provide the NAME to query (e.g. "gnupg", "libgcrypt") and
* optional the currently installed version in CURRENT_VERSION. The
* output written to OUT is a colon delimited line with these fields:
*
* name :: The name of the package
* curvers:: The installed version if given.
* status :: This value tells the status of the software package
* '-' :: No information available
* (error or CURRENT_VERSION not given)
* '?' :: Unknown NAME
* 'u' :: Update available
* 'c' :: The version is Current
* 'n' :: The current version is already Newer than the
* available one.
* urgency :: If the value is greater than zero an urgent update is required.
* error :: 0 on success or an gpg_err_code_t
* Common codes seen:
* GPG_ERR_TOO_OLD :: The SWDB file is to old to be used.
* GPG_ERR_ENOENT :: The SWDB file is not available.
* GPG_ERR_BAD_SIGNATURE :: Currupted SWDB file.
* filedate:: Date of the swdb file (yyyymmddThhmmss)
* verified:: Date we checked the validity of the file (yyyyymmddThhmmss)
* version :: The version string from the swdb.
* reldate :: Release date of that version (yyyymmddThhmmss)
* size :: Size of the package in bytes.
* hash :: SHA-2 hash of the package.
*
*/
static void
query_swdb (estream_t out, const char *name, const char *current_version)
{
gpg_error_t err;
const char *search_name;
char *fname = NULL;
estream_t fp = NULL;
char *line = NULL;
char *self_version = NULL;
size_t length_of_line = 0;
size_t maxlen;
ssize_t len;
char *fields[2];
char *p;
gnupg_isotime_t filedate = {0};
gnupg_isotime_t verified = {0};
char *value_ver = NULL;
gnupg_isotime_t value_date = {0};
char *value_size = NULL;
char *value_sha2 = NULL;
unsigned long value_size_ul = 0;
int status, i;
if (!valid_swdb_name_p (name))
{
log_error ("error in package name '%s': %s\n",
name, gpg_strerror (GPG_ERR_INV_NAME));
goto leave;
}
if (!strcmp (name, "gnupg"))
search_name = GNUPG_SWDB_TAG;
else if (!strcmp (name, "gnupg1"))
search_name = "gnupg1";
else
search_name = name;
if (!current_version && !strcmp (name, "gnupg"))
{
/* Use our own version but string a possible beta string. */
self_version = xstrdup (PACKAGE_VERSION);
p = strchr (self_version, '-');
if (p)
*p = 0;
current_version = self_version;
}
if (current_version && (strchr (current_version, ':')
|| compare_version_strings (current_version, NULL)))
{
log_error ("error in version string '%s': %s\n",
current_version, gpg_strerror (GPG_ERR_INV_ARG));
goto leave;
}
fname = make_filename (gnupg_homedir (), "swdb.lst", NULL);
fp = es_fopen (fname, "r");
if (!fp)
{
err = gpg_error_from_syserror ();
es_fprintf (out, "%s:%s:-::%u:::::::\n",
name,
current_version? current_version : "",
gpg_err_code (err));
if (gpg_err_code (err) != GPG_ERR_ENOENT)
log_error (_("error opening '%s': %s\n"), fname, gpg_strerror (err));
goto leave;
}
/* Note that the parser uses the first occurrence of a matching
* values and ignores possible duplicated values. */
maxlen = 2048; /* Set limit. */
while ((len = es_read_line (fp, &line, &length_of_line, &maxlen)) > 0)
{
if (!maxlen)
{
err = gpg_error (GPG_ERR_LINE_TOO_LONG);
log_error (_("error reading '%s': %s\n"), fname, gpg_strerror (err));
goto leave;
}
/* Strip newline and carriage return, if present. */
while (len > 0 && (line[len - 1] == '\n' || line[len - 1] == '\r'))
line[--len] = '\0';
if (split_fields (line, fields, DIM (fields)) < DIM(fields))
continue; /* Skip empty lines and names w/o a value. */
if (*fields[0] == '#')
continue; /* Skip comments. */
/* Record the meta data. */
if (!*filedate && !strcmp (fields[0], ".filedate"))
{
string2isotime (filedate, fields[1]);
continue;
}
if (!*verified && !strcmp (fields[0], ".verified"))
{
string2isotime (verified, fields[1]);
continue;
}
/* Tokenize the name. */
p = strrchr (fields[0], '_');
if (!p)
continue; /* Name w/o an underscore. */
*p++ = 0;
/* Wait for the requested name. */
if (!strcmp (fields[0], search_name))
{
if (!strcmp (p, "ver") && !value_ver)
value_ver = xstrdup (fields[1]);
else if (!strcmp (p, "date") && !*value_date)
string2isotime (value_date, fields[1]);
else if (!strcmp (p, "size") && !value_size)
value_size = xstrdup (fields[1]);
else if (!strcmp (p, "sha2") && !value_sha2)
value_sha2 = xstrdup (fields[1]);
}
}
if (len < 0 || es_ferror (fp))
{
err = gpg_error_from_syserror ();
log_error (_("error reading '%s': %s\n"), fname, gpg_strerror (err));
goto leave;
}
if (!*filedate || !*verified)
{
err = gpg_error (GPG_ERR_INV_TIME);
es_fprintf (out, "%s:%s:-::%u:::::::\n",
name,
current_version? current_version : "",
gpg_err_code (err));
goto leave;
}
if (!value_ver)
{
es_fprintf (out, "%s:%s:?:::::::::\n",
name,
current_version? current_version : "");
goto leave;
}
if (value_size)
{
gpg_err_set_errno (0);
value_size_ul = strtoul (value_size, &p, 10);
if (errno)
value_size_ul = 0;
else if (*p == 'k')
value_size_ul *= 1024;
}
err = 0;
status = '-';
if (compare_version_strings (value_ver, NULL))
err = gpg_error (GPG_ERR_INV_VALUE);
else if (!current_version)
;
else if (!(i = compare_version_strings (value_ver, current_version)))
status = 'c';
else if (i > 0)
status = 'u';
else
status = 'n';
es_fprintf (out, "%s:%s:%c::%d:%s:%s:%s:%s:%lu:%s:\n",
name,
current_version? current_version : "",
status,
err,
filedate,
verified,
value_ver,
value_date,
value_size_ul,
value_sha2? value_sha2 : "");
leave:
xfree (value_ver);
xfree (value_size);
xfree (value_sha2);
xfree (line);
es_fclose (fp);
xfree (fname);
xfree (self_version);
}
/* gpgconf main. */
int
main (int argc, char **argv)
{
gpg_error_t err;
ARGPARSE_ARGS pargs;
const char *fname;
int no_more_options = 0;
enum cmd_and_opt_values cmd = 0;
estream_t outfp = NULL;
int show_socket = 0;
early_system_init ();
gnupg_reopen_std (GPGCONF_NAME);
set_strusage (my_strusage);
log_set_prefix (GPGCONF_NAME, GPGRT_LOG_WITH_PREFIX);
/* Make sure that our subsystems are ready. */
i18n_init();
init_common_subsystems (&argc, &argv);
gc_components_init ();
/* Parse the command line. */
pargs.argc = &argc;
pargs.argv = &argv;
pargs.flags = 1; /* Do not remove the args. */
while (!no_more_options && optfile_parse (NULL, NULL, NULL, &pargs, opts))
{
switch (pargs.r_opt)
{
case oOutput: opt.outfile = pargs.r.ret_str; break;
case oQuiet: opt.quiet = 1; break;
case oDryRun: opt.dry_run = 1; break;
case oRuntime:
opt.runtime = 1;
break;
case oVerbose: opt.verbose++; break;
case oNoVerbose: opt.verbose = 0; break;
case oHomedir: gnupg_set_homedir (pargs.r.ret_str); break;
case oBuilddir: gnupg_set_builddir (pargs.r.ret_str); break;
case oNull: opt.null = 1; break;
case oStatusFD:
set_status_fd (translate_sys2libc_fd_int (pargs.r.ret_int, 1));
break;
case oShowSocket: show_socket = 1; break;
case aListDirs:
case aListComponents:
case aCheckPrograms:
case aListOptions:
case aChangeOptions:
case aCheckOptions:
case aApplyDefaults:
case aApplyProfile:
case aListConfig:
case aCheckConfig:
case aQuerySWDB:
case aReload:
case aLaunch:
case aKill:
case aCreateSocketDir:
case aRemoveSocketDir:
cmd = pargs.r_opt;
break;
default: pargs.err = 2; break;
}
}
if (log_get_errorcount (0))
gpgconf_failure (GPG_ERR_USER_2);
/* Print a warning if an argument looks like an option. */
if (!opt.quiet && !(pargs.flags & ARGPARSE_FLAG_STOP_SEEN))
{
int i;
for (i=0; i < argc; i++)
if (argv[i][0] == '-' && argv[i][1] == '-')
log_info (_("Note: '%s' is not considered an option\n"), argv[i]);
}
fname = argc ? *argv : NULL;
switch (cmd)
{
case aListComponents:
default:
/* List all components. */
gc_component_list_components (get_outfp (&outfp));
break;
case aCheckPrograms:
/* Check all programs. */
gc_check_programs (get_outfp (&outfp));
break;
case aListOptions:
case aChangeOptions:
case aCheckOptions:
if (!fname)
{
es_fprintf (es_stderr, _("usage: %s [options] "), GPGCONF_NAME);
es_putc ('\n', es_stderr);
es_fputs (_("Need one component argument"), es_stderr);
es_putc ('\n', es_stderr);
gpgconf_failure (GPG_ERR_USER_2);
}
else
{
int idx = gc_component_find (fname);
if (idx < 0)
{
es_fputs (_("Component not found"), es_stderr);
es_putc ('\n', es_stderr);
gpgconf_failure (0);
}
if (cmd == aCheckOptions)
gc_component_check_options (idx, get_outfp (&outfp), NULL);
else
{
gc_component_retrieve_options (idx);
if (gc_process_gpgconf_conf (NULL, 1, 0, NULL))
gpgconf_failure (0);
if (cmd == aListOptions)
gc_component_list_options (idx, get_outfp (&outfp));
else if (cmd == aChangeOptions)
gc_component_change_options (idx, es_stdin,
get_outfp (&outfp), 0);
}
}
break;
case aLaunch:
case aKill:
if (!fname)
{
es_fprintf (es_stderr, _("usage: %s [options] "), GPGCONF_NAME);
es_putc ('\n', es_stderr);
es_fputs (_("Need one component argument"), es_stderr);
es_putc ('\n', es_stderr);
gpgconf_failure (GPG_ERR_USER_2);
}
else if (!strcmp (fname, "all"))
{
if (cmd == aLaunch)
{
if (gc_component_launch (-1))
gpgconf_failure (0);
}
else
{
gc_component_kill (-1);
}
}
else
{
/* Launch/Kill a given component. */
int idx;
idx = gc_component_find (fname);
if (idx < 0)
{
es_fputs (_("Component not found"), es_stderr);
es_putc ('\n', es_stderr);
gpgconf_failure (0);
}
else if (cmd == aLaunch)
{
err = gc_component_launch (idx);
if (show_socket)
{
char *names[2];
if (idx == GC_COMPONENT_GPG_AGENT)
names[0] = "agent-socket";
else if (idx == GC_COMPONENT_DIRMNGR)
names[0] = "dirmngr-socket";
else
names[0] = NULL;
names[1] = NULL;
get_outfp (&outfp);
list_dirs (outfp, names);
}
if (err)
gpgconf_failure (0);
}
else
{
/* We don't error out if the kill failed because this
command should do nothing if the component is not
running. */
gc_component_kill (idx);
}
}
break;
case aReload:
if (!fname || !strcmp (fname, "all"))
{
/* Reload all. */
gc_component_reload (-1);
}
else
{
/* Reload given component. */
int idx;
idx = gc_component_find (fname);
if (idx < 0)
{
es_fputs (_("Component not found"), es_stderr);
es_putc ('\n', es_stderr);
gpgconf_failure (0);
}
else
{
gc_component_reload (idx);
}
}
break;
case aListConfig:
if (gc_process_gpgconf_conf (fname, 0, 0, get_outfp (&outfp)))
gpgconf_failure (0);
break;
case aCheckConfig:
if (gc_process_gpgconf_conf (fname, 0, 0, NULL))
gpgconf_failure (0);
break;
case aApplyDefaults:
if (fname)
{
es_fprintf (es_stderr, _("usage: %s [options] "), GPGCONF_NAME);
es_putc ('\n', es_stderr);
es_fputs (_("No argument allowed"), es_stderr);
es_putc ('\n', es_stderr);
gpgconf_failure (GPG_ERR_USER_2);
}
gc_component_retrieve_options (-1);
if (gc_process_gpgconf_conf (NULL, 1, 1, NULL))
gpgconf_failure (0);
break;
case aApplyProfile:
gc_component_retrieve_options (-1);
if (gc_apply_profile (fname))
gpgconf_failure (0);
break;
case aListDirs:
/* Show the system configuration directories for gpgconf. */
get_outfp (&outfp);
list_dirs (outfp, argc? argv : NULL);
break;
case aQuerySWDB:
/* Query the software version database. */
if (!fname || argc > 2)
{
es_fprintf (es_stderr, "usage: %s --query-swdb NAME [VERSION]\n",
GPGCONF_NAME);
gpgconf_failure (GPG_ERR_USER_2);
}
get_outfp (&outfp);
query_swdb (outfp, fname, argc > 1? argv[1] : NULL);
break;
case aCreateSocketDir:
{
char *socketdir;
unsigned int flags;
/* Make sure that the top /run/user/UID/gnupg dir has been
* created. */
gnupg_socketdir ();
/* Check the /var/run dir. */
socketdir = _gnupg_socketdir_internal (1, &flags);
if ((flags & 64) && !opt.dry_run)
{
/* No sub dir - create it. */
if (gnupg_mkdir (socketdir, "-rwx"))
gc_error (1, errno, "error creating '%s'", socketdir);
/* Try again. */
xfree (socketdir);
socketdir = _gnupg_socketdir_internal (1, &flags);
}
/* Give some info. */
if ( (flags & ~32) || opt.verbose || opt.dry_run)
{
log_info ("socketdir is '%s'\n", socketdir);
if ((flags & 1)) log_info ("\tgeneral error\n");
if ((flags & 2)) log_info ("\tno /run/user dir\n");
if ((flags & 4)) log_info ("\tbad permissions\n");
if ((flags & 8)) log_info ("\tbad permissions (subdir)\n");
if ((flags & 16)) log_info ("\tmkdir failed\n");
if ((flags & 32)) log_info ("\tnon-default homedir\n");
if ((flags & 64)) log_info ("\tno such subdir\n");
if ((flags & 128)) log_info ("\tusing homedir as fallback\n");
}
if ((flags & ~32) && !opt.dry_run)
gc_error (1, 0, "error creating socket directory");
xfree (socketdir);
}
break;
case aRemoveSocketDir:
{
char *socketdir;
unsigned int flags;
/* Check the /var/run dir. */
socketdir = _gnupg_socketdir_internal (1, &flags);
if ((flags & 128))
log_info ("ignoring request to remove non /run/user socket dir\n");
else if (opt.dry_run)
;
else if (rmdir (socketdir))
{
/* If the director is not empty we first try to delete
* socket files. */
err = gpg_error_from_syserror ();
if (gpg_err_code (err) == GPG_ERR_ENOTEMPTY
|| gpg_err_code (err) == GPG_ERR_EEXIST)
{
static const char * const names[] = {
GPG_AGENT_SOCK_NAME,
GPG_AGENT_EXTRA_SOCK_NAME,
GPG_AGENT_BROWSER_SOCK_NAME,
GPG_AGENT_SSH_SOCK_NAME,
SCDAEMON_SOCK_NAME,
DIRMNGR_SOCK_NAME
};
int i;
char *p;
for (i=0; i < DIM(names); i++)
{
p = strconcat (socketdir , "/", names[i], NULL);
if (p)
gnupg_remove (p);
xfree (p);
}
if (rmdir (socketdir))
gc_error (1, 0, "error removing '%s': %s",
socketdir, gpg_strerror (err));
}
else if (gpg_err_code (err) == GPG_ERR_ENOENT)
gc_error (0, 0, "warning: removing '%s' failed: %s",
socketdir, gpg_strerror (err));
else
gc_error (1, 0, "error removing '%s': %s",
socketdir, gpg_strerror (err));
}
xfree (socketdir);
}
break;
}
if (outfp != es_stdout)
if (es_fclose (outfp))
gc_error (1, errno, "error closing '%s'", opt.outfile);
if (log_get_errorcount (0))
gpgconf_failure (0);
else
gpgconf_write_status (STATUS_SUCCESS, NULL);
return 0;
}
void
gpgconf_failure (gpg_error_t err)
{
+ log_flush ();
if (!err)
err = gpg_error (GPG_ERR_GENERAL);
gpgconf_write_status
(STATUS_FAILURE, "- %u",
gpg_err_code (err) == GPG_ERR_USER_2? GPG_ERR_EINVAL : err);
exit (gpg_err_code (err) == GPG_ERR_USER_2? 2 : 1);
}
diff --git a/tools/rfc822parse.c b/tools/rfc822parse.c
index f1e95bd34..ac6ecb17c 100644
--- a/tools/rfc822parse.c
+++ b/tools/rfc822parse.c
@@ -1,1331 +1,1349 @@
/* rfc822parse.c - Simple mail and MIME parser
* Copyright (C) 1999, 2000 Werner Koch, Duesseldorf
* Copyright (C) 2003, 2004 g10 Code GmbH
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This file 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
/* According to RFC822 binary zeroes are allowed at many places. We do
* not handle this correct especially in the field parsing code. It
* should be easy to fix and the API provides a interfaces which
* returns the length but in addition makes sure that returned strings
* are always ended by a \0.
*
* Furthermore, the case of field names is changed and thus it is not
* always a good idea to use these modified header
* lines (e.g. signatures may break).
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdarg.h>
#include <assert.h>
#include "rfc822parse.h"
/* All valid characters in a header name. */
#define HEADER_NAME_CHARS ("abcdefghijklmnopqrstuvwxyz" \
"ABCDEFGHIJKLMNOPQRSTUVWXYZ" \
"-01234567890")
enum token_type
{
tSPACE,
tATOM,
tQUOTED,
tDOMAINLIT,
tSPECIAL
};
/* For now we directly use our TOKEN as the parse context */
typedef struct rfc822parse_field_context *TOKEN;
struct rfc822parse_field_context
{
TOKEN next;
enum token_type type;
struct {
unsigned int cont:1;
unsigned int lowered:1;
} flags;
/*TOKEN owner_pantry; */
char data[1];
};
struct hdr_line
{
struct hdr_line *next;
int cont; /* This is a continuation of the previous line. */
unsigned char line[1];
};
typedef struct hdr_line *HDR_LINE;
struct part
{
struct part *right; /* The next part. */
struct part *down; /* A contained part. */
HDR_LINE hdr_lines; /* Header lines os that part. */
HDR_LINE *hdr_lines_tail; /* Helper for adding lines. */
char *boundary; /* Only used in the first part. */
};
typedef struct part *part_t;
struct rfc822parse_context
{
rfc822parse_cb_t callback;
void *callback_value;
int callback_error;
int in_body;
int in_preamble; /* Whether we are before the first boundary. */
part_t parts; /* The tree of parts. */
part_t current_part; /* Whom we are processing (points into parts). */
const char *boundary; /* Current boundary. */
};
static HDR_LINE find_header (rfc822parse_t msg, const char *name,
int which, HDR_LINE * rprev);
static size_t
length_sans_trailing_ws (const unsigned char *line, size_t len)
{
const unsigned char *p, *mark;
size_t n;
for (mark=NULL, p=line, n=0; n < len; n++, p++)
{
if (strchr (" \t\r\n", *p ))
{
if( !mark )
mark = p;
}
else
mark = NULL;
}
if (mark)
return mark - line;
return len;
}
static void
lowercase_string (unsigned char *string)
{
for (; *string; string++)
if (*string >= 'A' && *string <= 'Z')
*string = *string - 'A' + 'a';
}
static int
my_toupper (int c)
{
if (c >= 'a' && c <= 'z')
c &= ~0x20;
return c;
}
/* This is the same as ascii_strcasecmp. */
static int
my_strcasecmp (const char *a, const char *b)
{
if (a == b)
return 0;
for (; *a && *b; a++, b++)
{
if (*a != *b && my_toupper(*a) != my_toupper(*b))
break;
}
return *a == *b? 0 : (my_toupper (*a) - my_toupper (*b));
}
#ifndef HAVE_STPCPY
static char *
my_stpcpy (char *a,const char *b)
{
while (*b)
*a++ = *b++;
*a = 0;
return (char*)a;
}
#define stpcpy my_stpcpy
#endif
/* If a callback has been registered, call it for the event of type
EVENT. */
static int
do_callback (rfc822parse_t msg, rfc822parse_event_t event)
{
int rc;
if (!msg->callback || msg->callback_error)
return 0;
rc = msg->callback (msg->callback_value, event, msg);
if (rc)
msg->callback_error = rc;
return rc;
}
static part_t
new_part (void)
{
part_t part;
part = calloc (1, sizeof *part);
if (part)
{
part->hdr_lines_tail = &part->hdr_lines;
}
return part;
}
static void
release_part (part_t part)
{
part_t tmp;
HDR_LINE hdr, hdr2;
for (; part; part = tmp)
{
tmp = part->right;
if (part->down)
release_part (part->down);
for (hdr = part->hdr_lines; hdr; hdr = hdr2)
{
hdr2 = hdr->next;
free (hdr);
}
free (part->boundary);
free (part);
}
}
static void
release_handle_data (rfc822parse_t msg)
{
release_part (msg->parts);
msg->parts = NULL;
msg->current_part = NULL;
msg->boundary = NULL;
}
/* Check that the header name is valid. We allow all lower and
* uppercase letters and, except for the first character, digits and
* the dash. The check stops at the first colon or at string end.
* Returns true if the name is valid. */
int
rfc822_valid_header_name_p (const char *name)
{
const char *s;
size_t namelen;
if ((s=strchr (name, ':')))
namelen = s - name;
else
namelen = strlen (name);
if (!namelen
|| strspn (name, HEADER_NAME_CHARS) != namelen
|| strchr ("-0123456789", *name))
return 0;
return 1;
}
/* Transform a header NAME into a standard capitalized format.
* Conversion stops at the colon. */
void
rfc822_capitalize_header_name (char *name)
{
unsigned char *p = name;
int first = 1;
/* Special cases first. */
if (!my_strcasecmp (name, "MIME-Version"))
{
strcpy (name, "MIME-Version");
return;
}
/* Regular cases. */
for (; *p && *p != ':'; p++)
{
if (*p == '-')
first = 1;
else if (first)
{
if (*p >= 'a' && *p <= 'z')
*p = *p - 'a' + 'A';
first = 0;
}
else if (*p >= 'A' && *p <= 'Z')
*p = *p - 'A' + 'a';
}
}
/* Create a new parsing context for an entire rfc822 message and
return it. CB and CB_VALUE may be given to callback for certain
events. NULL is returned on error with errno set appropriately. */
rfc822parse_t
rfc822parse_open (rfc822parse_cb_t cb, void *cb_value)
{
rfc822parse_t msg = calloc (1, sizeof *msg);
if (msg)
{
msg->parts = msg->current_part = new_part ();
if (!msg->parts)
{
free (msg);
msg = NULL;
}
else
{
msg->callback = cb;
msg->callback_value = cb_value;
if (do_callback (msg, RFC822PARSE_OPEN))
{
release_handle_data (msg);
free (msg);
msg = NULL;
}
}
}
return msg;
}
void
rfc822parse_cancel (rfc822parse_t msg)
{
if (msg)
{
do_callback (msg, RFC822PARSE_CANCEL);
release_handle_data (msg);
free (msg);
}
}
void
rfc822parse_close (rfc822parse_t msg)
{
if (msg)
{
do_callback (msg, RFC822PARSE_CLOSE);
release_handle_data (msg);
free (msg);
}
}
static part_t
find_parent (part_t tree, part_t target)
{
part_t part;
for (part = tree->down; part; part = part->right)
{
if (part == target)
return tree; /* Found. */
if (part->down)
{
part_t tmp = find_parent (part, target);
if (tmp)
return tmp;
}
}
return NULL;
}
static void
set_current_part_to_parent (rfc822parse_t msg)
{
part_t parent;
assert (msg->current_part);
parent = find_parent (msg->parts, msg->current_part);
if (!parent)
return; /* Already at the top. */
#ifndef NDEBUG
{
part_t part;
for (part = parent->down; part; part = part->right)
if (part == msg->current_part)
break;
assert (part);
}
#endif
msg->current_part = parent;
parent = find_parent (msg->parts, parent);
msg->boundary = parent? parent->boundary: NULL;
}
/****************
* We have read in all header lines and are about to receive the body
* part. The delimiter line has already been processed.
*
* FIXME: we's better return an error in case of memory failures.
*/
static int
transition_to_body (rfc822parse_t msg)
{
rfc822parse_field_t ctx;
int rc;
rc = do_callback (msg, RFC822PARSE_T2BODY);
if (!rc)
{
/* Store the boundary if we have multipart type. */
ctx = rfc822parse_parse_field (msg, "Content-Type", -1);
if (ctx)
{
const char *s;
s = rfc822parse_query_media_type (ctx, NULL);
if (s && !strcmp (s,"multipart"))
{
s = rfc822parse_query_parameter (ctx, "boundary", 0);
if (s)
{
- assert (!msg->current_part->boundary);
+ if (msg->current_part->boundary)
+ {
+ errno = ENOENT;
+ return -1;
+ }
+
msg->current_part->boundary = malloc (strlen (s) + 1);
if (msg->current_part->boundary)
{
part_t part;
strcpy (msg->current_part->boundary, s);
msg->boundary = msg->current_part->boundary;
part = new_part ();
if (!part)
{
int save_errno = errno;
rfc822parse_release_field (ctx);
errno = save_errno;
return -1;
}
rc = do_callback (msg, RFC822PARSE_LEVEL_DOWN);
- assert (!msg->current_part->down);
+ if (msg->current_part->down)
+ {
+ errno = ENOENT;
+ return -1;
+ }
msg->current_part->down = part;
msg->current_part = part;
msg->in_preamble = 1;
}
}
}
rfc822parse_release_field (ctx);
}
}
return rc;
}
/* We have just passed a MIME boundary and need to prepare for new part.
headers. */
static int
transition_to_header (rfc822parse_t msg)
{
part_t part;
- assert (msg->current_part);
- assert (!msg->current_part->right);
+ if (!(msg->current_part
+ && !msg->current_part->right))
+ {
+ errno = ENOENT;
+ return -1;
+ }
part = new_part ();
if (!part)
return -1;
msg->current_part->right = part;
msg->current_part = part;
return 0;
}
static int
insert_header (rfc822parse_t msg, const unsigned char *line, size_t length)
{
HDR_LINE hdr;
- assert (msg->current_part);
+ if (!msg->current_part)
+ {
+ errno = ENOENT;
+ return -1;
+ }
+
if (!length)
{
msg->in_body = 1;
return transition_to_body (msg);
}
if (!msg->current_part->hdr_lines)
do_callback (msg, RFC822PARSE_BEGIN_HEADER);
length = length_sans_trailing_ws (line, length);
hdr = malloc (sizeof (*hdr) + length);
if (!hdr)
return -1;
hdr->next = NULL;
hdr->cont = (*line == ' ' || *line == '\t');
memcpy (hdr->line, line, length);
hdr->line[length] = 0; /* Make it a string. */
/* Transform a field name into canonical format. */
if (!hdr->cont && strchr (line, ':'))
rfc822_capitalize_header_name (hdr->line);
*msg->current_part->hdr_lines_tail = hdr;
msg->current_part->hdr_lines_tail = &hdr->next;
/* Lets help the caller to prevent mail loops and issue an event for
* every Received header. */
if (length >= 9 && !memcmp (line, "Received:", 9))
do_callback (msg, RFC822PARSE_RCVD_SEEN);
return 0;
}
/****************
* Note: We handle the body transparent to allow binary zeroes in it.
*/
static int
insert_body (rfc822parse_t msg, const unsigned char *line, size_t length)
{
int rc = 0;
if (length > 2 && *line == '-' && line[1] == '-' && msg->boundary)
{
size_t blen = strlen (msg->boundary);
if (length == blen + 2
&& !memcmp (line+2, msg->boundary, blen))
{
rc = do_callback (msg, RFC822PARSE_BOUNDARY);
msg->in_body = 0;
if (!rc && !msg->in_preamble)
rc = transition_to_header (msg);
msg->in_preamble = 0;
}
else if (length == blen + 4
&& line[length-2] =='-' && line[length-1] == '-'
&& !memcmp (line+2, msg->boundary, blen))
{
rc = do_callback (msg, RFC822PARSE_LAST_BOUNDARY);
msg->boundary = NULL; /* No current boundary anymore. */
set_current_part_to_parent (msg);
/* Fixme: The next should actually be send right before the
next boundary, so that we can mark the epilogue. */
if (!rc)
rc = do_callback (msg, RFC822PARSE_LEVEL_UP);
}
}
if (msg->in_preamble && !rc)
rc = do_callback (msg, RFC822PARSE_PREAMBLE);
return rc;
}
/* Insert the next line into the parser. Return 0 on success or true
on error with errno set appropriately. */
int
rfc822parse_insert (rfc822parse_t msg, const unsigned char *line, size_t length)
{
return (msg->in_body
? insert_body (msg, line, length)
: insert_header (msg, line, length));
}
/* Tell the parser that we have finished the message. */
int
rfc822parse_finish (rfc822parse_t msg)
{
return do_callback (msg, RFC822PARSE_FINISH);
}
/****************
* Get a copy of a header line. The line is returned as one long
* string with LF to separate the continuation line. Caller must free
* the return buffer. WHICH may be used to enumerate over all lines.
* Wildcards are allowed. This function works on the current headers;
* i.e. the regular mail headers or the MIME headers of the current
* part.
*
* WHICH gives the mode:
* -1 := Take the last occurrence
* n := Take the n-th one.
*
* Returns a newly allocated buffer or NULL on error. errno is set in
* case of a memory failure or set to 0 if the requested field is not
* available.
*
* If VALUEOFF is not NULL it will receive the offset of the first non
* space character in the value part of the line (i.e. after the first
* colon).
*/
char *
rfc822parse_get_field (rfc822parse_t msg, const char *name, int which,
size_t *valueoff)
{
HDR_LINE h, h2;
char *buf, *p;
size_t n;
h = find_header (msg, name, which, NULL);
if (!h)
{
errno = 0;
return NULL; /* no such field */
}
n = strlen (h->line) + 1;
for (h2 = h->next; h2 && h2->cont; h2 = h2->next)
n += strlen (h2->line) + 1;
buf = p = malloc (n);
if (buf)
{
p = stpcpy (p, h->line);
*p++ = '\n';
for (h2 = h->next; h2 && h2->cont; h2 = h2->next)
{
p = stpcpy (p, h2->line);
*p++ = '\n';
}
p[-1] = 0;
}
if (valueoff)
{
p = strchr (buf, ':');
if (!p)
*valueoff = 0; /* Oops: should never happen. */
else
{
p++;
while (*p == ' ' || *p == '\t' || *p == '\r' || *p == '\n')
p++;
*valueoff = p - buf;
}
}
return buf;
}
/****************
* Enumerate all header. Caller has to provide the address of a pointer
* which has to be initialized to NULL, the caller should then never change this
* pointer until he has closed the enumeration by passing again the address
* of the pointer but with msg set to NULL.
* The function returns pointers to all the header lines or NULL when
* all lines have been enumerated or no headers are available.
*/
const char *
rfc822parse_enum_header_lines (rfc822parse_t msg, void **context)
{
HDR_LINE l;
if (!msg) /* Close. */
return NULL;
if (*context == msg || !msg->current_part)
return NULL;
l = *context ? (HDR_LINE) *context : msg->current_part->hdr_lines;
if (l)
{
*context = l->next ? (void *) (l->next) : (void *) msg;
return l->line;
}
*context = msg; /* Mark end of list. */
return NULL;
}
/****************
* Find a header field. If the Name does end in an asterisk this is meant
* to be a wildcard.
*
* which -1 : Retrieve the last field
* >0 : Retrieve the n-th field
* RPREV may be used to return the predecessor of the returned field;
* which may be NULL for the very first one. It has to be initialized
* to either NULL in which case the search start at the first header line,
* or it may point to a headerline, where the search should start
*/
static HDR_LINE
find_header (rfc822parse_t msg, const char *name, int which, HDR_LINE *rprev)
{
HDR_LINE hdr, prev = NULL, mark = NULL;
unsigned char *p;
size_t namelen, n;
int found = 0;
int glob = 0;
if (!msg->current_part)
return NULL;
namelen = strlen (name);
if (namelen && name[namelen - 1] == '*')
{
namelen--;
glob = 1;
}
hdr = msg->current_part->hdr_lines;
if (rprev && *rprev)
{
/* spool forward to the requested starting place.
* we cannot simply set this as we have to return
* the previous list element too */
for (; hdr && hdr != *rprev; prev = hdr, hdr = hdr->next)
;
}
for (; hdr; prev = hdr, hdr = hdr->next)
{
if (hdr->cont)
continue;
if (!(p = strchr (hdr->line, ':')))
continue; /* invalid header, just skip it. */
n = p - hdr->line;
if (!n)
continue; /* invalid name */
if ((glob ? (namelen <= n) : (namelen == n))
&& !memcmp (hdr->line, name, namelen))
{
found++;
if (which == -1)
mark = hdr;
else if (found == which)
{
if (rprev)
*rprev = prev;
return hdr;
}
}
}
if (mark && rprev)
*rprev = prev;
return mark;
}
static const char *
skip_ws (const char *s)
{
while (*s == ' ' || *s == '\t' || *s == '\r' || *s == '\n')
s++;
return s;
}
static void
release_token_list (TOKEN t)
{
while (t)
{
TOKEN t2 = t->next;
/* fixme: If we have owner_pantry, put the token back to
* this pantry so that it can be reused later */
free (t);
t = t2;
}
}
static TOKEN
new_token (enum token_type type, const char *buf, size_t length)
{
TOKEN t;
/* fixme: look through our pantries to find a suitable
* token for reuse */
t = malloc (sizeof *t + length);
if (t)
{
t->next = NULL;
t->type = type;
memset (&t->flags, 0, sizeof (t->flags));
t->data[0] = 0;
if (buf)
{
memcpy (t->data, buf, length);
t->data[length] = 0; /* Make sure it is a C string. */
}
else
t->data[0] = 0;
}
return t;
}
static TOKEN
append_to_token (TOKEN old, const char *buf, size_t length)
{
size_t n = strlen (old->data);
TOKEN t;
t = malloc (sizeof *t + n + length);
if (t)
{
t->next = old->next;
t->type = old->type;
t->flags = old->flags;
memcpy (t->data, old->data, n);
memcpy (t->data + n, buf, length);
t->data[n + length] = 0;
old->next = NULL;
release_token_list (old);
}
return t;
}
/*
Parse a field into tokens as defined by rfc822.
*/
static TOKEN
parse_field (HDR_LINE hdr)
{
static const char specials[] = "<>@.,;:\\[]\"()";
static const char specials2[] = "<>@.,;:";
static const char tspecials[] = "/?=<>@,;:\\[]\"()";
static const char tspecials2[] = "/?=<>@.,;:"; /* FIXME: really
include '.'?*/
static struct
{
const unsigned char *name;
size_t namelen;
} tspecial_header[] = {
{ "Content-Type", 12},
{ "Content-Transfer-Encoding", 25},
{ "Content-Disposition", 19},
{ NULL, 0}
};
const char *delimiters;
const char *delimiters2;
const unsigned char *line, *s, *s2;
size_t n;
int i, invalid = 0;
TOKEN t, tok, *tok_tail;
errno = 0;
if (!hdr)
return NULL;
tok = NULL;
tok_tail = &tok;
line = hdr->line;
if (!(s = strchr (line, ':')))
return NULL; /* oops */
n = s - line;
if (!n)
return NULL; /* oops: invalid name */
delimiters = specials;
delimiters2 = specials2;
for (i = 0; tspecial_header[i].name; i++)
{
if (n == tspecial_header[i].namelen
&& !memcmp (line, tspecial_header[i].name, n))
{
delimiters = tspecials;
delimiters2 = tspecials2;
break;
}
}
s++; /* Move over the colon. */
for (;;)
{
while (!*s)
{
if (!hdr->next || !hdr->next->cont)
return tok; /* Ready. */
/* Next item is a header continuation line. */
hdr = hdr->next;
s = hdr->line;
}
if (*s == '(')
{
int level = 1;
int in_quote = 0;
invalid = 0;
for (s++;; s++)
{
while (!*s)
{
if (!hdr->next || !hdr->next->cont)
goto oparen_out;
/* Next item is a header continuation line. */
hdr = hdr->next;
s = hdr->line;
}
if (in_quote)
{
if (*s == '\"')
in_quote = 0;
else if (*s == '\\' && s[1]) /* what about continuation? */
s++;
}
else if (*s == ')')
{
if (!--level)
break;
}
else if (*s == '(')
level++;
else if (*s == '\"')
in_quote = 1;
}
oparen_out:
if (!*s)
; /* Actually this is an error, but we don't care about it. */
else
s++;
}
else if (*s == '\"' || *s == '[')
{
/* We do not check for non-allowed nesting of domainliterals */
int term = *s == '\"' ? '\"' : ']';
invalid = 0;
s++;
t = NULL;
for (;;)
{
for (s2 = s; *s2; s2++)
{
if (*s2 == term)
break;
else if (*s2 == '\\' && s2[1]) /* what about continuation? */
s2++;
}
t = (t
? append_to_token (t, s, s2 - s)
: new_token (term == '\"'? tQUOTED : tDOMAINLIT, s, s2 - s));
if (!t)
goto failure;
if (*s2 || !hdr->next || !hdr->next->cont)
break;
/* Next item is a header continuation line. */
hdr = hdr->next;
s = hdr->line;
}
*tok_tail = t;
tok_tail = &t->next;
s = s2;
if (*s)
s++; /* skip the delimiter */
}
else if ((s2 = strchr (delimiters2, *s)))
{ /* Special characters which are not handled above. */
invalid = 0;
t = new_token (tSPECIAL, s, 1);
if (!t)
goto failure;
*tok_tail = t;
tok_tail = &t->next;
s++;
}
else if (*s == ' ' || *s == '\t' || *s == '\r' || *s == '\n')
{
invalid = 0;
s = skip_ws (s + 1);
}
else if (*s > 0x20 && !(*s & 128))
{ /* Atom. */
invalid = 0;
for (s2 = s + 1; *s2 > 0x20
&& !(*s2 & 128) && !strchr (delimiters, *s2); s2++)
;
t = new_token (tATOM, s, s2 - s);
if (!t)
goto failure;
*tok_tail = t;
tok_tail = &t->next;
s = s2;
}
else
{ /* Invalid character. */
if (!invalid)
{ /* For parsing we assume only one space. */
t = new_token (tSPACE, NULL, 0);
if (!t)
goto failure;
*tok_tail = t;
tok_tail = &t->next;
invalid = 1;
}
s++;
}
}
/*NOTREACHED*/
failure:
{
int save = errno;
release_token_list (tok);
errno = save;
}
return NULL;
}
/****************
* Find and parse a header field.
* WHICH indicates what to do if there are multiple instance of the same
* field (like "Received"); the following value are defined:
* -1 := Take the last occurrence
* 0 := Reserved
* n := Take the n-th one.
* Returns a handle for further operations on the parse context of the field
* or NULL if the field was not found.
*/
rfc822parse_field_t
rfc822parse_parse_field (rfc822parse_t msg, const char *name, int which)
{
HDR_LINE hdr;
if (!which)
return NULL;
hdr = find_header (msg, name, which, NULL);
if (!hdr)
return NULL;
return parse_field (hdr);
}
void
rfc822parse_release_field (rfc822parse_field_t ctx)
{
if (ctx)
release_token_list (ctx);
}
/****************
* Check whether T points to a parameter.
* A parameter starts with a semicolon and it is assumed that t
* points to exactly this one.
*/
static int
is_parameter (TOKEN t)
{
t = t->next;
if (!t || t->type != tATOM)
return 0;
t = t->next;
if (!t || !(t->type == tSPECIAL && t->data[0] == '='))
return 0;
t = t->next;
if (!t)
return 1; /* We assume that an non existing value is an empty one. */
return t->type == tQUOTED || t->type == tATOM;
}
/*
Some header (Content-type) have a special syntax where attribute=value
pairs are used after a leading semicolon. The parse_field code
knows about these fields and changes the parsing to the one defined
in RFC2045.
Returns a pointer to the value which is valid as long as the
parse context is valid; NULL is returned in case that attr is not
defined in the header, a missing value is reppresented by an empty string.
With LOWER_VALUE set to true, a matching field value will be
lowercased.
Note, that ATTR should be lowercase.
*/
const char *
rfc822parse_query_parameter (rfc822parse_field_t ctx, const char *attr,
int lower_value)
{
TOKEN t, a;
for (t = ctx; t; t = t->next)
{
/* skip to the next semicolon */
for (; t && !(t->type == tSPECIAL && t->data[0] == ';'); t = t->next)
;
if (!t)
return NULL;
if (is_parameter (t))
{ /* Look closer. */
a = t->next; /* We know that this is an atom */
if ( !a->flags.lowered )
{
lowercase_string (a->data);
a->flags.lowered = 1;
}
if (!strcmp (a->data, attr))
{ /* found */
t = a->next->next;
/* Either T is now an atom, a quoted string or NULL in
* which case we return an empty string. */
if ( lower_value && t && !t->flags.lowered )
{
lowercase_string (t->data);
t->flags.lowered = 1;
}
return t ? t->data : "";
}
}
}
return NULL;
}
/****************
* This function may be used for the Content-Type header to figure out
* the media type and subtype. Note, that the returned strings are
* guaranteed to be lowercase as required by MIME.
*
* Returns: a pointer to the media type and if subtype is not NULL,
* a pointer to the subtype.
*/
const char *
rfc822parse_query_media_type (rfc822parse_field_t ctx, const char **subtype)
{
TOKEN t = ctx;
const char *type;
if (t->type != tATOM)
return NULL;
if (!t->flags.lowered)
{
lowercase_string (t->data);
t->flags.lowered = 1;
}
type = t->data;
t = t->next;
if (!t || t->type != tSPECIAL || t->data[0] != '/')
return NULL;
t = t->next;
if (!t || t->type != tATOM)
return NULL;
if (subtype)
{
if (!t->flags.lowered)
{
lowercase_string (t->data);
t->flags.lowered = 1;
}
*subtype = t->data;
}
return type;
}
#ifdef TESTING
/* Internal debug function to print the structure of the message. */
static void
dump_structure (rfc822parse_t msg, part_t part, int indent)
{
if (!part)
{
printf ("*** Structure of this message:\n");
part = msg->parts;
}
for (; part; part = part->right)
{
rfc822parse_field_t ctx;
part_t save_part; /* ugly hack - we should have a function to
get part information. */
const char *s;
save_part = msg->current_part;
msg->current_part = part;
ctx = rfc822parse_parse_field (msg, "Content-Type", -1);
msg->current_part = save_part;
if (ctx)
{
const char *s1, *s2;
s1 = rfc822parse_query_media_type (ctx, &s2);
if (s1)
printf ("*** %*s %s/%s", indent*2, "", s1, s2);
else
printf ("*** %*s [not found]", indent*2, "");
s = rfc822parse_query_parameter (ctx, "boundary", 0);
if (s)
printf (" (boundary=\"%s\")", s);
rfc822parse_release_field (ctx);
}
else
printf ("*** %*s text/plain [assumed]", indent*2, "");
putchar('\n');
if (part->down)
dump_structure (msg, part->down, indent + 1);
}
}
static void
show_param (rfc822parse_field_t ctx, const char *name)
{
const char *s;
if (!ctx)
return;
s = rfc822parse_query_parameter (ctx, name, 0);
if (s)
printf ("*** %s: '%s'\n", name, s);
}
static void
show_event (rfc822parse_event_t event)
{
const char *s;
switch (event)
{
case RFC822PARSE_OPEN: s= "Open"; break;
case RFC822PARSE_CLOSE: s= "Close"; break;
case RFC822PARSE_CANCEL: s= "Cancel"; break;
case RFC822PARSE_T2BODY: s= "T2Body"; break;
case RFC822PARSE_FINISH: s= "Finish"; break;
case RFC822PARSE_RCVD_SEEN: s= "Rcvd_Seen"; break;
case RFC822PARSE_LEVEL_DOWN: s= "Level_Down"; break;
case RFC822PARSE_LEVEL_UP: s= "Level_Up"; break;
case RFC822PARSE_BOUNDARY: s= "Boundary"; break;
case RFC822PARSE_LAST_BOUNDARY: s= "Last_Boundary"; break;
case RFC822PARSE_BEGIN_HEADER: s= "Begin_Header"; break;
case RFC822PARSE_PREAMBLE: s= "Preamble"; break;
case RFC822PARSE_EPILOGUE: s= "Epilogue"; break;
default: s= "***invalid event***"; break;
}
printf ("*** got RFC822 event %s\n", s);
}
static int
msg_cb (void *dummy_arg, rfc822parse_event_t event, rfc822parse_t msg)
{
show_event (event);
if (event == RFC822PARSE_T2BODY)
{
rfc822parse_field_t ctx;
void *ectx;
const char *line;
for (ectx=NULL; (line = rfc822parse_enum_header_lines (msg, &ectx)); )
{
printf ("*** HDR: %s\n", line);
}
rfc822parse_enum_header_lines (NULL, &ectx); /* Close enumerator. */
ctx = rfc822parse_parse_field (msg, "Content-Type", -1);
if (ctx)
{
const char *s1, *s2;
s1 = rfc822parse_query_media_type (ctx, &s2);
if (s1)
printf ("*** media: '%s/%s'\n", s1, s2);
else
printf ("*** media: [not found]\n");
show_param (ctx, "boundary");
show_param (ctx, "protocol");
rfc822parse_release_field (ctx);
}
else
printf ("*** media: text/plain [assumed]\n");
}
return 0;
}
int
main (int argc, char **argv)
{
char line[5000];
size_t length;
rfc822parse_t msg;
msg = rfc822parse_open (msg_cb, NULL);
if (!msg)
abort ();
while (fgets (line, sizeof (line), stdin))
{
length = strlen (line);
if (length && line[length - 1] == '\n')
line[--length] = 0;
if (length && line[length - 1] == '\r')
line[--length] = 0;
if (rfc822parse_insert (msg, line, length))
abort ();
}
dump_structure (msg, NULL, 0);
rfc822parse_close (msg);
return 0;
}
#endif
/*
Local Variables:
compile-command: "gcc -Wall -Wno-pointer-sign -g -DTESTING -o rfc822parse rfc822parse.c"
End:
*/
diff --git a/tools/wks-receive.c b/tools/wks-receive.c
index e67da628d..e5d2ed4b2 100644
--- a/tools/wks-receive.c
+++ b/tools/wks-receive.c
@@ -1,534 +1,534 @@
/* wks-receive.c - Receive a WKS mail
* Copyright (C) 2016 g10 Code GmbH
* Copyright (C) 2016 Bundesamt für Sicherheit in der Informationstechnik
*
* This file is part of GnuPG.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This file 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "../common/util.h"
#include "../common/ccparray.h"
#include "../common/exectool.h"
#include "gpg-wks.h"
#include "rfc822parse.h"
#include "mime-parser.h"
/* Limit of acceptable signed data. */
#define MAX_SIGNEDDATA 10000
/* Limit of acceptable signature. */
#define MAX_SIGNATURE 10000
/* Limit of acceptable encrypted data. */
#define MAX_ENCRYPTED 100000
/* Data for a received object. */
struct receive_ctx_s
{
mime_parser_t parser;
estream_t encrypted;
estream_t plaintext;
estream_t signeddata;
estream_t signature;
estream_t key_data;
estream_t wkd_data;
unsigned int collect_key_data:1;
unsigned int collect_wkd_data:1;
unsigned int draft_version_2:1; /* This is a draft version 2 request. */
unsigned int multipart_mixed_seen:1;
};
typedef struct receive_ctx_s *receive_ctx_t;
static void
decrypt_data_status_cb (void *opaque, const char *keyword, char *args)
{
receive_ctx_t ctx = opaque;
(void)ctx;
if (DBG_CRYPTO)
log_debug ("gpg status: %s %s\n", keyword, args);
}
/* Decrypt the collected data. */
static void
decrypt_data (receive_ctx_t ctx)
{
gpg_error_t err;
ccparray_t ccp;
const char **argv;
int c;
es_rewind (ctx->encrypted);
if (!ctx->plaintext)
ctx->plaintext = es_fopenmem (0, "w+b");
if (!ctx->plaintext)
{
err = gpg_error_from_syserror ();
log_error ("error allocating space for plaintext: %s\n",
gpg_strerror (err));
return;
}
ccparray_init (&ccp, 0);
ccparray_put (&ccp, "--no-options");
/* We limit the output to 64 KiB to avoid DoS using compression
* tricks. A regular client will anyway only send a minimal key;
* that is one w/o key signatures and attribute packets. */
- ccparray_put (&ccp, "--max-output=0xf0000"); /*FIXME: Change s/F/1/ */
+ ccparray_put (&ccp, "--max-output=0x10000");
ccparray_put (&ccp, "--batch");
if (opt.verbose)
ccparray_put (&ccp, "--verbose");
ccparray_put (&ccp, "--always-trust");
ccparray_put (&ccp, "--decrypt");
ccparray_put (&ccp, "--");
ccparray_put (&ccp, NULL);
argv = ccparray_get (&ccp, NULL);
if (!argv)
{
err = gpg_error_from_syserror ();
goto leave;
}
err = gnupg_exec_tool_stream (opt.gpg_program, argv, ctx->encrypted,
NULL, ctx->plaintext,
decrypt_data_status_cb, ctx);
if (err)
{
log_error ("decryption failed: %s\n", gpg_strerror (err));
goto leave;
}
if (DBG_CRYPTO)
{
es_rewind (ctx->plaintext);
log_debug ("plaintext: '");
while ((c = es_getc (ctx->plaintext)) != EOF)
log_printf ("%c", c);
log_printf ("'\n");
}
es_rewind (ctx->plaintext);
leave:
xfree (argv);
}
static void
verify_signature_status_cb (void *opaque, const char *keyword, char *args)
{
receive_ctx_t ctx = opaque;
(void)ctx;
if (DBG_CRYPTO)
log_debug ("gpg status: %s %s\n", keyword, args);
}
/* Verify the signed data. */
static void
verify_signature (receive_ctx_t ctx)
{
gpg_error_t err;
ccparray_t ccp;
const char **argv;
log_assert (ctx->signeddata);
log_assert (ctx->signature);
es_rewind (ctx->signeddata);
es_rewind (ctx->signature);
ccparray_init (&ccp, 0);
ccparray_put (&ccp, "--no-options");
ccparray_put (&ccp, "--batch");
if (opt.verbose)
ccparray_put (&ccp, "--verbose");
ccparray_put (&ccp, "--enable-special-filenames");
ccparray_put (&ccp, "--status-fd=2");
ccparray_put (&ccp, "--always-trust"); /* To avoid trustdb checks. */
ccparray_put (&ccp, "--verify");
ccparray_put (&ccp, "--");
ccparray_put (&ccp, "-&@INEXTRA@");
ccparray_put (&ccp, "-");
ccparray_put (&ccp, NULL);
argv = ccparray_get (&ccp, NULL);
if (!argv)
{
err = gpg_error_from_syserror ();
goto leave;
}
err = gnupg_exec_tool_stream (opt.gpg_program, argv, ctx->signeddata,
ctx->signature, NULL,
verify_signature_status_cb, ctx);
if (err)
{
log_error ("verification failed: %s\n", gpg_strerror (err));
goto leave;
}
log_debug ("Fixme: Verification result is not used\n");
leave:
xfree (argv);
}
static gpg_error_t
collect_encrypted (void *cookie, const char *data)
{
receive_ctx_t ctx = cookie;
if (!ctx->encrypted)
if (!(ctx->encrypted = es_fopenmem (MAX_ENCRYPTED, "w+b,samethread")))
return gpg_error_from_syserror ();
if (data)
es_fputs (data, ctx->encrypted);
if (es_ferror (ctx->encrypted))
return gpg_error_from_syserror ();
if (!data)
{
decrypt_data (ctx);
}
return 0;
}
static gpg_error_t
collect_signeddata (void *cookie, const char *data)
{
receive_ctx_t ctx = cookie;
if (!ctx->signeddata)
if (!(ctx->signeddata = es_fopenmem (MAX_SIGNEDDATA, "w+b,samethread")))
return gpg_error_from_syserror ();
if (data)
es_fputs (data, ctx->signeddata);
if (es_ferror (ctx->signeddata))
return gpg_error_from_syserror ();
return 0;
}
static gpg_error_t
collect_signature (void *cookie, const char *data)
{
receive_ctx_t ctx = cookie;
if (!ctx->signature)
if (!(ctx->signature = es_fopenmem (MAX_SIGNATURE, "w+b,samethread")))
return gpg_error_from_syserror ();
if (data)
es_fputs (data, ctx->signature);
if (es_ferror (ctx->signature))
return gpg_error_from_syserror ();
if (!data)
{
verify_signature (ctx);
}
return 0;
}
/* The callback for the transition from header to body. We use it to
* look at some header values. */
static gpg_error_t
t2body (void *cookie, int level)
{
receive_ctx_t ctx = cookie;
rfc822parse_t msg;
char *value;
size_t valueoff;
log_info ("t2body for level %d\n", level);
if (!level)
{
/* This is the outermost header. */
msg = mime_parser_rfc822parser (ctx->parser);
if (msg)
{
value = rfc822parse_get_field (msg, "Wks-Draft-Version",
-1, &valueoff);
if (value)
{
if (atoi(value+valueoff) >= 2 )
ctx->draft_version_2 = 1;
free (value);
}
}
}
return 0;
}
static gpg_error_t
new_part (void *cookie, const char *mediatype, const char *mediasubtype)
{
receive_ctx_t ctx = cookie;
gpg_error_t err = 0;
ctx->collect_key_data = 0;
ctx->collect_wkd_data = 0;
if (!strcmp (mediatype, "application")
&& !strcmp (mediasubtype, "pgp-keys"))
{
log_info ("new '%s/%s' message part\n", mediatype, mediasubtype);
if (ctx->key_data)
{
log_error ("we already got a key - ignoring this part\n");
err = gpg_error (GPG_ERR_FALSE);
}
else
{
ctx->key_data = es_fopenmem (0, "w+b");
if (!ctx->key_data)
{
err = gpg_error_from_syserror ();
log_error ("error allocating space for key: %s\n",
gpg_strerror (err));
}
else
{
ctx->collect_key_data = 1;
err = gpg_error (GPG_ERR_TRUE); /* We want the part decoded. */
}
}
}
else if (!strcmp (mediatype, "application")
&& !strcmp (mediasubtype, "vnd.gnupg.wks"))
{
log_info ("new '%s/%s' message part\n", mediatype, mediasubtype);
if (ctx->wkd_data)
{
log_error ("we already got a wkd part - ignoring this part\n");
err = gpg_error (GPG_ERR_FALSE);
}
else
{
ctx->wkd_data = es_fopenmem (0, "w+b");
if (!ctx->wkd_data)
{
err = gpg_error_from_syserror ();
log_error ("error allocating space for key: %s\n",
gpg_strerror (err));
}
else
{
ctx->collect_wkd_data = 1;
err = gpg_error (GPG_ERR_TRUE); /* We want the part decoded. */
}
}
}
else if (!strcmp (mediatype, "multipart")
&& !strcmp (mediasubtype, "mixed"))
{
ctx->multipart_mixed_seen = 1;
}
else if (!strcmp (mediatype, "text"))
{
/* Check that we receive a text part only after a
* application/mixed. This is actually a too simple test and we
* should eventually employ a strict MIME structure check. */
if (!ctx->multipart_mixed_seen)
err = gpg_error (GPG_ERR_UNEXPECTED_MSG);
}
else
{
log_error ("unexpected '%s/%s' message part\n", mediatype, mediasubtype);
err = gpg_error (GPG_ERR_FALSE); /* We do not want the part. */
}
return err;
}
static gpg_error_t
part_data (void *cookie, const void *data, size_t datalen)
{
receive_ctx_t ctx = cookie;
if (data)
{
if (DBG_MIME)
log_debug ("part_data: '%.*s'\n", (int)datalen, (const char*)data);
if (ctx->collect_key_data)
{
if (es_write (ctx->key_data, data, datalen, NULL)
|| es_fputs ("\n", ctx->key_data))
return gpg_error_from_syserror ();
}
if (ctx->collect_wkd_data)
{
if (es_write (ctx->wkd_data, data, datalen, NULL)
|| es_fputs ("\n", ctx->wkd_data))
return gpg_error_from_syserror ();
}
}
else
{
if (DBG_MIME)
log_debug ("part_data: finished\n");
ctx->collect_key_data = 0;
ctx->collect_wkd_data = 0;
}
return 0;
}
/* Receive a WKS mail from FP and process it accordingly. On success
* the RESULT_CB is called with the mediatype and a stream with the
* decrypted data. */
gpg_error_t
wks_receive (estream_t fp,
gpg_error_t (*result_cb)(void *opaque,
const char *mediatype,
estream_t data,
unsigned int flags),
void *cb_data)
{
gpg_error_t err;
receive_ctx_t ctx;
mime_parser_t parser;
estream_t plaintext = NULL;
int c;
unsigned int flags = 0;
ctx = xtrycalloc (1, sizeof *ctx);
if (!ctx)
return gpg_error_from_syserror ();
err = mime_parser_new (&parser, ctx);
if (err)
goto leave;
if (DBG_PARSER)
mime_parser_set_verbose (parser, 1);
mime_parser_set_t2body (parser, t2body);
mime_parser_set_new_part (parser, new_part);
mime_parser_set_part_data (parser, part_data);
mime_parser_set_collect_encrypted (parser, collect_encrypted);
mime_parser_set_collect_signeddata (parser, collect_signeddata);
mime_parser_set_collect_signature (parser, collect_signature);
ctx->parser = parser;
err = mime_parser_parse (parser, fp);
if (err)
goto leave;
if (ctx->key_data)
log_info ("key data found\n");
if (ctx->wkd_data)
log_info ("wkd data found\n");
if (ctx->draft_version_2)
{
log_info ("draft version 2 requested\n");
flags |= WKS_RECEIVE_DRAFT2;
}
if (ctx->plaintext)
{
if (opt.verbose)
log_info ("parsing decrypted message\n");
plaintext = ctx->plaintext;
ctx->plaintext = NULL;
if (ctx->encrypted)
es_rewind (ctx->encrypted);
if (ctx->signeddata)
es_rewind (ctx->signeddata);
if (ctx->signature)
es_rewind (ctx->signature);
err = mime_parser_parse (parser, plaintext);
if (err)
return err;
}
if (!ctx->key_data && !ctx->wkd_data)
{
log_error ("no suitable data found in the message\n");
err = gpg_error (GPG_ERR_NO_DATA);
goto leave;
}
if (ctx->key_data)
{
if (DBG_MIME)
{
es_rewind (ctx->key_data);
log_debug ("Key: '");
log_printf ("\n");
while ((c = es_getc (ctx->key_data)) != EOF)
log_printf ("%c", c);
log_printf ("'\n");
}
if (result_cb)
{
es_rewind (ctx->key_data);
err = result_cb (cb_data, "application/pgp-keys",
ctx->key_data, flags);
if (err)
goto leave;
}
}
if (ctx->wkd_data)
{
if (DBG_MIME)
{
es_rewind (ctx->wkd_data);
log_debug ("WKD: '");
log_printf ("\n");
while ((c = es_getc (ctx->wkd_data)) != EOF)
log_printf ("%c", c);
log_printf ("'\n");
}
if (result_cb)
{
es_rewind (ctx->wkd_data);
err = result_cb (cb_data, "application/vnd.gnupg.wks",
ctx->wkd_data, flags);
if (err)
goto leave;
}
}
leave:
es_fclose (plaintext);
mime_parser_release (parser);
ctx->parser = NULL;
es_fclose (ctx->encrypted);
es_fclose (ctx->plaintext);
es_fclose (ctx->signeddata);
es_fclose (ctx->signature);
es_fclose (ctx->key_data);
es_fclose (ctx->wkd_data);
xfree (ctx);
return err;
}
diff --git a/tools/wks-util.c b/tools/wks-util.c
index 1459045ef..1c1ac8c0b 100644
--- a/tools/wks-util.c
+++ b/tools/wks-util.c
@@ -1,1091 +1,1147 @@
/* wks-utils.c - Common helper functions for wks tools
* Copyright (C) 2016 g10 Code GmbH
* Copyright (C) 2016 Bundesamt für Sicherheit in der Informationstechnik
*
* This file is part of GnuPG.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This file 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 Lesser General Public License for more details.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "../common/util.h"
#include "../common/status.h"
#include "../common/ccparray.h"
#include "../common/exectool.h"
#include "../common/zb32.h"
#include "../common/userids.h"
#include "../common/mbox-util.h"
#include "../common/sysutils.h"
#include "mime-maker.h"
#include "send-mail.h"
#include "gpg-wks.h"
/* The stream to output the status information. Output is disabled if
this is NULL. */
static estream_t statusfp;
/* Set the status FD. */
void
wks_set_status_fd (int fd)
{
static int last_fd = -1;
if (fd != -1 && last_fd == fd)
return;
if (statusfp && statusfp != es_stdout && statusfp != es_stderr)
es_fclose (statusfp);
statusfp = NULL;
if (fd == -1)
return;
if (fd == 1)
statusfp = es_stdout;
else if (fd == 2)
statusfp = es_stderr;
else
statusfp = es_fdopen (fd, "w");
if (!statusfp)
{
log_fatal ("can't open fd %d for status output: %s\n",
fd, gpg_strerror (gpg_error_from_syserror ()));
}
last_fd = fd;
}
/* Write a status line with code NO followed by the output of the
* printf style FORMAT. The caller needs to make sure that LFs and
* CRs are not printed. */
void
wks_write_status (int no, const char *format, ...)
{
va_list arg_ptr;
if (!statusfp)
return; /* Not enabled. */
es_fputs ("[GNUPG:] ", statusfp);
es_fputs (get_status_string (no), statusfp);
if (format)
{
es_putc (' ', statusfp);
va_start (arg_ptr, format);
es_vfprintf (statusfp, format, arg_ptr);
va_end (arg_ptr);
}
es_putc ('\n', statusfp);
}
/* Append UID to LIST and return the new item. On success LIST is
* updated. On error ERRNO is set and NULL returned. */
static uidinfo_list_t
append_to_uidinfo_list (uidinfo_list_t *list, const char *uid, time_t created)
{
uidinfo_list_t r, sl;
sl = xtrymalloc (sizeof *sl + strlen (uid));
if (!sl)
return NULL;
strcpy (sl->uid, uid);
sl->created = created;
sl->mbox = mailbox_from_userid (uid, 0);
sl->next = NULL;
if (!*list)
*list = sl;
else
{
for (r = *list; r->next; r = r->next )
;
r->next = sl;
}
return sl;
}
/* Free the list of uid infos at LIST. */
void
free_uidinfo_list (uidinfo_list_t list)
{
while (list)
{
uidinfo_list_t tmp = list->next;
xfree (list->mbox);
xfree (list);
list = tmp;
}
}
struct get_key_status_parm_s
{
const char *fpr;
int found;
int count;
};
static void
get_key_status_cb (void *opaque, const char *keyword, char *args)
{
struct get_key_status_parm_s *parm = opaque;
/*log_debug ("%s: %s\n", keyword, args);*/
if (!strcmp (keyword, "EXPORTED"))
{
parm->count++;
if (!ascii_strcasecmp (args, parm->fpr))
parm->found = 1;
}
}
/* Get a key by fingerprint from gpg's keyring and make sure that the
* mail address ADDRSPEC is included in the key. If EXACT is set the
* returned user id must match Addrspec exactly and not just in the
* addr-spec (mailbox) part. The key is returned as a new memory
* stream at R_KEY. */
gpg_error_t
wks_get_key (estream_t *r_key, const char *fingerprint, const char *addrspec,
int exact)
{
gpg_error_t err;
ccparray_t ccp;
const char **argv = NULL;
estream_t key = NULL;
struct get_key_status_parm_s parm;
char *filterexp = NULL;
memset (&parm, 0, sizeof parm);
*r_key = NULL;
key = es_fopenmem (0, "w+b");
if (!key)
{
err = gpg_error_from_syserror ();
log_error ("error allocating memory buffer: %s\n", gpg_strerror (err));
goto leave;
}
/* Prefix the key with the MIME content type. */
es_fputs ("Content-Type: application/pgp-keys\n"
"\n", key);
filterexp = es_bsprintf ("keep-uid=%s= %s", exact? "uid":"mbox", addrspec);
if (!filterexp)
{
err = gpg_error_from_syserror ();
log_error ("error allocating memory buffer: %s\n", gpg_strerror (err));
goto leave;
}
ccparray_init (&ccp, 0);
ccparray_put (&ccp, "--no-options");
if (!opt.verbose)
ccparray_put (&ccp, "--quiet");
else if (opt.verbose > 1)
ccparray_put (&ccp, "--verbose");
ccparray_put (&ccp, "--batch");
ccparray_put (&ccp, "--status-fd=2");
ccparray_put (&ccp, "--always-trust");
ccparray_put (&ccp, "--armor");
ccparray_put (&ccp, "--export-options=export-minimal");
ccparray_put (&ccp, "--export-filter");
ccparray_put (&ccp, filterexp);
ccparray_put (&ccp, "--export");
ccparray_put (&ccp, "--");
ccparray_put (&ccp, fingerprint);
ccparray_put (&ccp, NULL);
argv = ccparray_get (&ccp, NULL);
if (!argv)
{
err = gpg_error_from_syserror ();
goto leave;
}
parm.fpr = fingerprint;
err = gnupg_exec_tool_stream (opt.gpg_program, argv, NULL,
NULL, key,
get_key_status_cb, &parm);
if (!err && parm.count > 1)
err = gpg_error (GPG_ERR_TOO_MANY);
else if (!err && !parm.found)
err = gpg_error (GPG_ERR_NOT_FOUND);
if (err)
{
log_error ("export failed: %s\n", gpg_strerror (err));
goto leave;
}
es_rewind (key);
*r_key = key;
key = NULL;
leave:
es_fclose (key);
xfree (argv);
xfree (filterexp);
return err;
}
/* Helper for wks_list_key and wks_filter_uid. */
static void
key_status_cb (void *opaque, const char *keyword, char *args)
{
(void)opaque;
if (DBG_CRYPTO)
log_debug ("gpg status: %s %s\n", keyword, args);
}
/* Run gpg on KEY and store the primary fingerprint at R_FPR and the
* list of mailboxes at R_MBOXES. Returns 0 on success; on error NULL
* is stored at R_FPR and R_MBOXES and an error code is returned.
* R_FPR may be NULL if the fingerprint is not needed. */
gpg_error_t
wks_list_key (estream_t key, char **r_fpr, uidinfo_list_t *r_mboxes)
{
gpg_error_t err;
ccparray_t ccp;
const char **argv;
estream_t listing;
char *line = NULL;
size_t length_of_line = 0;
size_t maxlen;
ssize_t len;
char **fields = NULL;
int nfields;
int lnr;
char *fpr = NULL;
uidinfo_list_t mboxes = NULL;
if (r_fpr)
*r_fpr = NULL;
*r_mboxes = NULL;
/* Open a memory stream. */
listing = es_fopenmem (0, "w+b");
if (!listing)
{
err = gpg_error_from_syserror ();
log_error ("error allocating memory buffer: %s\n", gpg_strerror (err));
return err;
}
ccparray_init (&ccp, 0);
ccparray_put (&ccp, "--no-options");
if (!opt.verbose)
ccparray_put (&ccp, "--quiet");
else if (opt.verbose > 1)
ccparray_put (&ccp, "--verbose");
ccparray_put (&ccp, "--batch");
ccparray_put (&ccp, "--status-fd=2");
ccparray_put (&ccp, "--always-trust");
ccparray_put (&ccp, "--with-colons");
ccparray_put (&ccp, "--dry-run");
ccparray_put (&ccp, "--import-options=import-minimal,import-show");
ccparray_put (&ccp, "--import");
ccparray_put (&ccp, NULL);
argv = ccparray_get (&ccp, NULL);
if (!argv)
{
err = gpg_error_from_syserror ();
goto leave;
}
err = gnupg_exec_tool_stream (opt.gpg_program, argv, key,
NULL, listing,
key_status_cb, NULL);
if (err)
{
log_error ("import failed: %s\n", gpg_strerror (err));
goto leave;
}
es_rewind (listing);
lnr = 0;
maxlen = 2048; /* Set limit. */
while ((len = es_read_line (listing, &line, &length_of_line, &maxlen)) > 0)
{
lnr++;
if (!maxlen)
{
log_error ("received line too long\n");
err = gpg_error (GPG_ERR_LINE_TOO_LONG);
goto leave;
}
/* Strip newline and carriage return, if present. */
while (len > 0
&& (line[len - 1] == '\n' || line[len - 1] == '\r'))
line[--len] = '\0';
/* log_debug ("line '%s'\n", line); */
xfree (fields);
fields = strtokenize (line, ":");
if (!fields)
{
err = gpg_error_from_syserror ();
log_error ("strtokenize failed: %s\n", gpg_strerror (err));
goto leave;
}
for (nfields = 0; fields[nfields]; nfields++)
;
if (!nfields)
{
err = gpg_error (GPG_ERR_INV_ENGINE);
goto leave;
}
if (!strcmp (fields[0], "sec"))
{
/* gpg may return "sec" as the first record - but we do not
* accept secret keys. */
err = gpg_error (GPG_ERR_NO_PUBKEY);
goto leave;
}
if (lnr == 1 && strcmp (fields[0], "pub"))
{
/* First record is not a public key. */
err = gpg_error (GPG_ERR_INV_ENGINE);
goto leave;
}
if (lnr > 1 && !strcmp (fields[0], "pub"))
{
/* More than one public key. */
err = gpg_error (GPG_ERR_TOO_MANY);
goto leave;
}
if (!strcmp (fields[0], "sub") || !strcmp (fields[0], "ssb"))
break; /* We can stop parsing here. */
if (!strcmp (fields[0], "fpr") && nfields > 9 && !fpr)
{
fpr = xtrystrdup (fields[9]);
if (!fpr)
{
err = gpg_error_from_syserror ();
goto leave;
}
}
else if (!strcmp (fields[0], "uid") && nfields > 9)
{
/* Fixme: Unescape fields[9] */
if (!append_to_uidinfo_list (&mboxes, fields[9],
parse_timestamp (fields[5], NULL)))
{
err = gpg_error_from_syserror ();
goto leave;
}
}
}
if (len < 0 || es_ferror (listing))
{
err = gpg_error_from_syserror ();
log_error ("error reading memory stream\n");
goto leave;
}
if (!fpr)
{
err = gpg_error (GPG_ERR_NO_PUBKEY);
goto leave;
}
if (r_fpr)
{
*r_fpr = fpr;
fpr = NULL;
}
*r_mboxes = mboxes;
mboxes = NULL;
leave:
xfree (fpr);
free_uidinfo_list (mboxes);
xfree (fields);
es_free (line);
xfree (argv);
es_fclose (listing);
return err;
}
/* Run gpg as a filter on KEY and write the output to a new stream
* stored at R_NEWKEY. The new key will contain only the user id UID.
* Returns 0 on success. Only one key is expected in KEY. If BINARY
* is set the resulting key is returned as a binary (non-armored)
* keyblock. */
gpg_error_t
wks_filter_uid (estream_t *r_newkey, estream_t key, const char *uid,
int binary)
{
gpg_error_t err;
ccparray_t ccp;
const char **argv = NULL;
estream_t newkey;
char *filterexp = NULL;
*r_newkey = NULL;
/* Open a memory stream. */
newkey = es_fopenmem (0, "w+b");
if (!newkey)
{
err = gpg_error_from_syserror ();
log_error ("error allocating memory buffer: %s\n", gpg_strerror (err));
return err;
}
/* Prefix the key with the MIME content type. */
if (!binary)
es_fputs ("Content-Type: application/pgp-keys\n"
"\n", newkey);
filterexp = es_bsprintf ("keep-uid=uid= %s", uid);
if (!filterexp)
{
err = gpg_error_from_syserror ();
log_error ("error allocating memory buffer: %s\n", gpg_strerror (err));
goto leave;
}
ccparray_init (&ccp, 0);
ccparray_put (&ccp, "--no-options");
if (!opt.verbose)
ccparray_put (&ccp, "--quiet");
else if (opt.verbose > 1)
ccparray_put (&ccp, "--verbose");
ccparray_put (&ccp, "--batch");
ccparray_put (&ccp, "--status-fd=2");
ccparray_put (&ccp, "--always-trust");
if (!binary)
ccparray_put (&ccp, "--armor");
ccparray_put (&ccp, "--import-options=import-export");
ccparray_put (&ccp, "--import-filter");
ccparray_put (&ccp, filterexp);
ccparray_put (&ccp, "--import");
ccparray_put (&ccp, NULL);
argv = ccparray_get (&ccp, NULL);
if (!argv)
{
err = gpg_error_from_syserror ();
goto leave;
}
err = gnupg_exec_tool_stream (opt.gpg_program, argv, key,
NULL, newkey,
key_status_cb, NULL);
if (err)
{
log_error ("import/export failed: %s\n", gpg_strerror (err));
goto leave;
}
es_rewind (newkey);
*r_newkey = newkey;
newkey = NULL;
leave:
xfree (filterexp);
xfree (argv);
es_fclose (newkey);
return err;
}
/* Helper to write mail to the output(s). */
gpg_error_t
wks_send_mime (mime_maker_t mime)
{
gpg_error_t err;
estream_t mail;
/* Without any option we take a short path. */
if (!opt.use_sendmail && !opt.output)
{
es_set_binary (es_stdout);
return mime_maker_make (mime, es_stdout);
}
mail = es_fopenmem (0, "w+b");
if (!mail)
{
err = gpg_error_from_syserror ();
return err;
}
err = mime_maker_make (mime, mail);
if (!err && opt.output)
{
es_rewind (mail);
err = send_mail_to_file (mail, opt.output);
}
if (!err && opt.use_sendmail)
{
es_rewind (mail);
err = send_mail (mail);
}
es_fclose (mail);
return err;
}
/* Parse the policy flags by reading them from STREAM and storing them
* into FLAGS. If IGNORE_UNKNOWN is set unknown keywords are
* ignored. */
gpg_error_t
wks_parse_policy (policy_flags_t flags, estream_t stream, int ignore_unknown)
{
enum tokens {
TOK_SUBMISSION_ADDRESS,
TOK_MAILBOX_ONLY,
TOK_DANE_ONLY,
TOK_AUTH_SUBMIT,
TOK_MAX_PENDING,
TOK_PROTOCOL_VERSION
};
static struct {
const char *name;
enum tokens token;
} keywords[] = {
{ "submission-address", TOK_SUBMISSION_ADDRESS },
{ "mailbox-only", TOK_MAILBOX_ONLY },
{ "dane-only", TOK_DANE_ONLY },
{ "auth-submit", TOK_AUTH_SUBMIT },
{ "max-pending", TOK_MAX_PENDING },
{ "protocol-version", TOK_PROTOCOL_VERSION }
};
gpg_error_t err = 0;
int lnr = 0;
char line[1024];
char *p, *keyword, *value;
int i, n;
memset (flags, 0, sizeof *flags);
while (es_fgets (line, DIM(line)-1, stream) )
{
lnr++;
n = strlen (line);
if (!n || line[n-1] != '\n')
{
err = gpg_error (*line? GPG_ERR_LINE_TOO_LONG
: GPG_ERR_INCOMPLETE_LINE);
break;
}
trim_trailing_spaces (line);
/* Skip empty and comment lines. */
for (p=line; spacep (p); p++)
;
if (!*p || *p == '#')
continue;
if (*p == ':')
{
err = gpg_error (GPG_ERR_SYNTAX);
break;
}
keyword = p;
value = NULL;
if ((p = strchr (p, ':')))
{
/* Colon found: Keyword with value. */
*p++ = 0;
for (; spacep (p); p++)
;
if (!*p)
{
err = gpg_error (GPG_ERR_MISSING_VALUE);
break;
}
value = p;
}
for (i=0; i < DIM (keywords); i++)
if (!ascii_strcasecmp (keywords[i].name, keyword))
break;
if (!(i < DIM (keywords)))
{
if (ignore_unknown)
continue;
err = gpg_error (GPG_ERR_INV_NAME);
break;
}
switch (keywords[i].token)
{
case TOK_SUBMISSION_ADDRESS:
if (!value || !*value)
{
err = gpg_error (GPG_ERR_SYNTAX);
goto leave;
}
xfree (flags->submission_address);
flags->submission_address = xtrystrdup (value);
if (!flags->submission_address)
{
err = gpg_error_from_syserror ();
goto leave;
}
break;
case TOK_MAILBOX_ONLY: flags->mailbox_only = 1; break;
case TOK_DANE_ONLY: flags->dane_only = 1; break;
case TOK_AUTH_SUBMIT: flags->auth_submit = 1; break;
case TOK_MAX_PENDING:
if (!value)
{
err = gpg_error (GPG_ERR_SYNTAX);
goto leave;
}
/* FIXME: Define whether these are seconds, hours, or days
* and decide whether to allow other units. */
flags->max_pending = atoi (value);
break;
case TOK_PROTOCOL_VERSION:
if (!value)
{
err = gpg_error (GPG_ERR_SYNTAX);
goto leave;
}
flags->protocol_version = atoi (value);
break;
}
}
if (!err && !es_feof (stream))
err = gpg_error_from_syserror ();
leave:
if (err)
log_error ("error reading '%s', line %d: %s\n",
es_fname_get (stream), lnr, gpg_strerror (err));
return err;
}
void
wks_free_policy (policy_flags_t policy)
{
if (policy)
{
xfree (policy->submission_address);
memset (policy, 0, sizeof *policy);
}
}
/* Write the content of SRC to the new file FNAME. */
static gpg_error_t
write_to_file (estream_t src, const char *fname)
{
gpg_error_t err;
estream_t dst;
char buffer[4096];
size_t nread, written;
dst = es_fopen (fname, "wb");
if (!dst)
return gpg_error_from_syserror ();
do
{
nread = es_fread (buffer, 1, sizeof buffer, src);
if (!nread)
break;
written = es_fwrite (buffer, 1, nread, dst);
if (written != nread)
break;
}
while (!es_feof (src) && !es_ferror (src) && !es_ferror (dst));
if (!es_feof (src) || es_ferror (src) || es_ferror (dst))
{
err = gpg_error_from_syserror ();
es_fclose (dst);
gnupg_remove (fname);
return err;
}
if (es_fclose (dst))
{
err = gpg_error_from_syserror ();
log_error ("error closing '%s': %s\n", fname, gpg_strerror (err));
return err;
}
return 0;
}
/* Return the filename and optionally the addrspec for USERID at
- * R_FNAME and R_ADDRSPEC. R_ADDRSPEC might also be set on error. */
+ * R_FNAME and R_ADDRSPEC. R_ADDRSPEC might also be set on error. If
+ * HASH_ONLY is set only the has is returned at R_FNAME and no file is
+ * created. */
gpg_error_t
-wks_fname_from_userid (const char *userid, char **r_fname, char **r_addrspec)
+wks_fname_from_userid (const char *userid, int hash_only,
+ char **r_fname, char **r_addrspec)
{
gpg_error_t err;
char *addrspec = NULL;
const char *domain;
char *hash = NULL;
const char *s;
char shaxbuf[32]; /* Used for SHA-1 and SHA-256 */
*r_fname = NULL;
if (r_addrspec)
*r_addrspec = NULL;
addrspec = mailbox_from_userid (userid, 0);
if (!addrspec)
{
- if (opt.verbose)
+ if (opt.verbose || hash_only)
log_info ("\"%s\" is not a proper mail address\n", userid);
err = gpg_error (GPG_ERR_INV_USER_ID);
goto leave;
}
domain = strchr (addrspec, '@');
log_assert (domain);
domain++;
/* Hash user ID and create filename. */
s = strchr (addrspec, '@');
log_assert (s);
gcry_md_hash_buffer (GCRY_MD_SHA1, shaxbuf, addrspec, s - addrspec);
hash = zb32_encode (shaxbuf, 8*20);
if (!hash)
{
err = gpg_error_from_syserror ();
goto leave;
}
- *r_fname = make_filename_try (opt.directory, domain, "hu", hash, NULL);
- if (!*r_fname)
- err = gpg_error_from_syserror ();
+ if (hash_only)
+ {
+ *r_fname = hash;
+ hash = NULL;
+ err = 0;
+ }
else
- err = 0;
+ {
+ *r_fname = make_filename_try (opt.directory, domain, "hu", hash, NULL);
+ if (!*r_fname)
+ err = gpg_error_from_syserror ();
+ else
+ err = 0;
+ }
leave:
if (r_addrspec && addrspec)
*r_addrspec = addrspec;
else
xfree (addrspec);
xfree (hash);
return err;
}
/* Compute the the full file name for the key with ADDRSPEC and return
* it at R_FNAME. */
gpg_error_t
wks_compute_hu_fname (char **r_fname, const char *addrspec)
{
gpg_error_t err;
char *hash;
const char *domain;
char sha1buf[20];
char *fname;
struct stat sb;
*r_fname = NULL;
domain = strchr (addrspec, '@');
if (!domain || !domain[1] || domain == addrspec)
return gpg_error (GPG_ERR_INV_ARG);
domain++;
gcry_md_hash_buffer (GCRY_MD_SHA1, sha1buf, addrspec, domain - addrspec - 1);
hash = zb32_encode (sha1buf, 8*20);
if (!hash)
return gpg_error_from_syserror ();
/* Try to create missing directories below opt.directory. */
fname = make_filename_try (opt.directory, domain, NULL);
if (fname && stat (fname, &sb)
&& gpg_err_code_from_syserror () == GPG_ERR_ENOENT)
if (!gnupg_mkdir (fname, "-rwxr--r--") && opt.verbose)
log_info ("directory '%s' created\n", fname);
xfree (fname);
fname = make_filename_try (opt.directory, domain, "hu", NULL);
if (fname && stat (fname, &sb)
&& gpg_err_code_from_syserror () == GPG_ERR_ENOENT)
if (!gnupg_mkdir (fname, "-rwxr--r--") && opt.verbose)
log_info ("directory '%s' created\n", fname);
xfree (fname);
/* Create the filename. */
fname = make_filename_try (opt.directory, domain, "hu", hash, NULL);
err = fname? 0 : gpg_error_from_syserror ();
if (err)
xfree (fname);
else
*r_fname = fname; /* Okay. */
xfree (hash);
return err;
}
/* Helper form wks_cmd_install_key. */
static gpg_error_t
install_key_from_spec_file (const char *fname)
{
gpg_error_t err;
estream_t fp;
char *line = NULL;
size_t linelen = 0;
size_t maxlen = 2048;
char *fields[2];
unsigned int lnr = 0;
if (!fname || !strcmp (fname, ""))
fp = es_stdin;
else
fp = es_fopen (fname, "rb");
if (!fp)
{
err = gpg_error_from_syserror ();
log_error ("error reading '%s': %s\n", fname, gpg_strerror (err));
goto leave;
}
while (es_read_line (fp, &line, &linelen, &maxlen) > 0)
{
if (!maxlen)
{
err = gpg_error (GPG_ERR_LINE_TOO_LONG);
log_error ("error reading '%s': %s\n", fname, gpg_strerror (err));
goto leave;
}
lnr++;
trim_spaces (line);
if (!*line || *line == '#')
continue;
if (split_fields (line, fields, DIM(fields)) < 2)
{
log_error ("error reading '%s': syntax error at line %u\n",
fname, lnr);
continue;
}
err = wks_cmd_install_key (fields[0], fields[1]);
if (err)
goto leave;
}
if (es_ferror (fp))
{
err = gpg_error_from_syserror ();
log_error ("error reading '%s': %s\n", fname, gpg_strerror (err));
goto leave;
}
leave:
if (fp != es_stdin)
es_fclose (fp);
es_free (line);
return err;
}
/* Install a single key into the WKD by reading FNAME and extracting
* USERID. If USERID is NULL FNAME is expected to be a list of fpr
* mbox lines and for each line the respective key will be
* installed. */
gpg_error_t
wks_cmd_install_key (const char *fname, const char *userid)
{
gpg_error_t err;
KEYDB_SEARCH_DESC desc;
estream_t fp = NULL;
char *addrspec = NULL;
char *fpr = NULL;
uidinfo_list_t uidlist = NULL;
uidinfo_list_t uid, thisuid;
time_t thistime;
char *huname = NULL;
int any;
if (!userid)
return install_key_from_spec_file (fname);
addrspec = mailbox_from_userid (userid, 0);
if (!addrspec)
{
log_error ("\"%s\" is not a proper mail address\n", userid);
err = gpg_error (GPG_ERR_INV_USER_ID);
goto leave;
}
if (!classify_user_id (fname, &desc, 1)
&& desc.mode == KEYDB_SEARCH_MODE_FPR)
{
/* FNAME looks like a fingerprint. Get the key from the
* standard keyring. */
err = wks_get_key (&fp, fname, addrspec, 0);
if (err)
{
log_error ("error getting key '%s' (uid='%s'): %s\n",
fname, addrspec, gpg_strerror (err));
goto leave;
}
}
else /* Take it from the file */
{
fp = es_fopen (fname, "rb");
if (!fp)
{
err = gpg_error_from_syserror ();
log_error ("error reading '%s': %s\n", fname, gpg_strerror (err));
goto leave;
}
}
/* List the key so that we can figure out the newest UID with the
* requested addrspec. */
err = wks_list_key (fp, &fpr, &uidlist);
if (err)
{
log_error ("error parsing key: %s\n", gpg_strerror (err));
err = gpg_error (GPG_ERR_NO_PUBKEY);
goto leave;
}
thistime = 0;
thisuid = NULL;
any = 0;
for (uid = uidlist; uid; uid = uid->next)
{
if (!uid->mbox)
continue; /* Should not happen anyway. */
if (ascii_strcasecmp (uid->mbox, addrspec))
continue; /* Not the requested addrspec. */
any = 1;
if (uid->created > thistime)
{
thistime = uid->created;
thisuid = uid;
}
}
if (!thisuid)
thisuid = uidlist; /* This is the case for a missing timestamp. */
if (!any)
{
log_error ("public key in '%s' has no mail address '%s'\n",
fname, addrspec);
err = gpg_error (GPG_ERR_INV_USER_ID);
goto leave;
}
if (opt.verbose)
log_info ("using key with user id '%s'\n", thisuid->uid);
{
estream_t fp2;
es_rewind (fp);
err = wks_filter_uid (&fp2, fp, thisuid->uid, 1);
if (err)
{
log_error ("error filtering key: %s\n", gpg_strerror (err));
err = gpg_error (GPG_ERR_NO_PUBKEY);
goto leave;
}
es_fclose (fp);
fp = fp2;
}
/* Hash user ID and create filename. */
err = wks_compute_hu_fname (&huname, addrspec);
if (err)
goto leave;
/* Publish. */
err = write_to_file (fp, huname);
if (err)
{
log_error ("copying key to '%s' failed: %s\n", huname,gpg_strerror (err));
goto leave;
}
/* Make sure it is world readable. */
if (gnupg_chmod (huname, "-rwxr--r--"))
log_error ("can't set permissions of '%s': %s\n",
huname, gpg_strerror (gpg_err_code_from_syserror()));
if (!opt.quiet)
log_info ("key %s published for '%s'\n", fpr, addrspec);
leave:
xfree (huname);
free_uidinfo_list (uidlist);
xfree (fpr);
xfree (addrspec);
es_fclose (fp);
return err;
}
/* Remove the key with mail address in USERID. */
gpg_error_t
wks_cmd_remove_key (const char *userid)
{
gpg_error_t err;
char *addrspec = NULL;
char *fname = NULL;
- err = wks_fname_from_userid (userid, &fname, &addrspec);
+ err = wks_fname_from_userid (userid, 0, &fname, &addrspec);
if (err)
goto leave;
if (gnupg_remove (fname))
{
err = gpg_error_from_syserror ();
if (gpg_err_code (err) == GPG_ERR_ENOENT)
{
if (!opt.quiet)
log_info ("key for '%s' is not installed\n", addrspec);
log_inc_errorcount ();
err = 0;
}
else
log_error ("error removing '%s': %s\n", fname, gpg_strerror (err));
goto leave;
}
if (opt.verbose)
log_info ("key for '%s' removed\n", addrspec);
err = 0;
leave:
xfree (fname);
xfree (addrspec);
return err;
}
+
+
+/* Print the WKD hash for the user id to stdout. */
+gpg_error_t
+wks_cmd_print_wkd_hash (const char *userid)
+{
+ gpg_error_t err;
+ char *addrspec, *fname;
+
+ err = wks_fname_from_userid (userid, 1, &fname, &addrspec);
+ if (err)
+ return err;
+
+ es_printf ("%s %s\n", fname, addrspec);
+
+ xfree (fname);
+ xfree (addrspec);
+ return err;
+}
+
+
+/* Print the WKD URL for the user id to stdout. */
+gpg_error_t
+wks_cmd_print_wkd_url (const char *userid)
+{
+ gpg_error_t err;
+ char *addrspec, *fname;
+ char *domain;
+
+ err = wks_fname_from_userid (userid, 1, &fname, &addrspec);
+ if (err)
+ return err;
+
+ domain = strchr (addrspec, '@');
+ if (domain)
+ *domain++ = 0;
+
+ es_printf ("https://openpgpkey.%s/.well-known/openpgpkey/%s/hu/%s?l=%s\n",
+ domain, domain, fname, addrspec);
+
+ xfree (fname);
+ xfree (addrspec);
+ return err;
+}

File Metadata

Mime Type
application/octet-stream
Expires
Thu, May 9, 2:25 AM (2 d)
Storage Engine
chunks
Storage Format
Chunks
Storage Handle
kgNO9.NpfFqC

Event Timeline