Page MenuHome GnuPG

Prevent Smartcard from caching PIN when cache-ttl is set accordingly
Open, WishlistPublic

Description

One can configure gpg-agent to stop caching of passphrases after some time (max-chache-ttl etc.).

As this does not apply for smartcards yet, it would be nice to add this feature. The smartcard is caching the PIN by itself until it is powered down. Therefore a possible solution would be to power down the card after the time specified in max-cache-ttl to make sure, that the PIN is not cached anymore. At least this is the behaviour a user would probably expect when setting this configuration.

See https://lists.gt.net/gnupg/users/81257 for problem description.

Details

Version
gpg (GnuPG) 2.1.23

Event Timeline

werner triaged this task as Wishlist priority.Aug 23 2017, 10:52 AM
werner added a subscriber: werner.

Smartcards and on-disk keys are very different things and handled by different processes.

You may use "card-timeout" in scdaemon.conf to achieve. However we would need to fully implement that option. Thus I change this to a feature request. Please be so kind and explain your use case for this request.

Using a smartcard it should be possible to set a cache-ttl value so that not only on-disk keys but also the PIN used for unlocking the key on the smartcard is not cached longer than the given period in cache-ttl. Until now you have to plug out and in the card by yourself to get this working. Alternatively you theoretically could set a config in scdaemon to power off the card after some time ("card-timeout). It could be a solution to set this config automatically if cache-ttl option is used.

Please note that the card-timeout value in scdaemon.conf does not seem to work as once intended ("the current version of Scdaemon the card is powered down immediately at the next timer tick for any value of n other than 0. ") so that this may is not the best option other than stated above.

I am not sure if this is really a good explanation, sorry. I hope it helps anyway.

Unfortunately, not all OpenPGPcard implementations support command to unauthorize use of keys.

Gnuk supports that with VERIFY command with NULL. I proposed this feature for newer OpenPGPcard (>= 3.x).
So, for some OpenPGPcard implementations, scdaemon can support this feature.

To support this feature for other OpenPGP implementations, all that we can do is reset the card (power-off + power-on), perhaps.

The problem of power-off/power-on is that it is somehow risky operation for physical smartcard. For virtual card (like Gnuk or JavaCard implementation), it works always. For physical smartcard + card reader, it has a possibility to fail. That's difficult.

The idea with the smartcard is that you can limit the time of exposure
of the key. Leaving the card accessible to the host is thus not a good
idea. Malware can simply snoop the PIN from the last operation and
then, at its own discretion, use the keys of the card. This can only be
avoided by using a smartcard reader equipped with a pinpad and able to
filter commands so that it is not possible to bypass the pinpad (which
is easy for the host).

Yes. For the use case of GnuPG, it is better to support disabling (unauthorize) use of keys.
On the other hand, IIUC, the original OpenPGPcard implementation is designed/implemented under the influence of other smartcard usages.

In a typical usage of smartcard for PC, which is also used for login authentication, once it is authenticated, it keeps that state until a user remove it to lock the screen.

If we can focus on (major) use case of GnuPG, I think that gpg-agent compatible timeout of authentication state is better, and easier to understand for GnuPG user.

For the record, the authentication status reset by VERIFY command was introduced in OpenPGPcard specification V2.2.
I think V3 card supports that.
Gnuk 1.2 supports this reset feature.

So, this is VERIFY reset allows the host to implement the "force" flag we always had in the card for the first key. At least kind of, because malware can still suppress the VERIFY reset ;-). The integrated "force" flag requires the admin PIN, which is malware should have more problems to snoop.

@werner , I understand your poiont.

So, the best approach would be:
(1) Define some DO (Data-Object) or attribute/flag per key to control timeout or "force" by the card itself.
(2) Modify scdaemon so that it always ask authentication state to the card before doing crypto operation.
(3) Modify gpg frontend so that it shows those attribute/flag and setup.

Then, it is the card itself to control timeout or "force".

I hope I did not choose inappropriate action in commenting here that I also would highly appreciate a cache timeout for OpenPGP Cards to reduce the exposure time of already unlocked card's keys. Would be great to get such an option

@werner , I understand your poiont.

So, the best approach would be:
(1) Define some DO (Data-Object) or attribute/flag per key to control timeout or "force" by the card itself.
(2) Modify scdaemon so that it always ask authentication state to the card before doing crypto operation.
(3) Modify gpg frontend so that it shows those attribute/flag and setup.

Then, it is the card itself to control timeout or "force".

Is this plan going to be implemented any time soon? Implementation of this plan will definitely contribute to making smartcard usage safer.

There is not a lot of demand for this, thus we have not continued to think about it.

@gniibe: We could implement this on the card by extending our ugly hacks on the login-data DO, which are currently:

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.

A new 'C' flag maybe?

There is not a lot of demand for this, thus we have not continued to think about it.

@gniibe: We could implement this on the card by extending our ugly hacks on the login-data DO, which are currently:

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.

A new 'C' flag maybe?

Currently, I just disconnect my USB smartcard reader from my PC once I'm done using it. But this wears down the physical contacts for people that need to use their smartcard frequently throughout the day.

Having the ability to make sure smartcard authentication information is never cached (via a simple config option) seems like a reasonable solution to the wear issue.

The scdaemon option card-timeout (which, to my knowledge, is unimplemented) deals with powering down the card; however, I believe having a simple config option for scdaemon like never-cache-pin would be a cleaner solution because we wouldn't need to deal with smartcard reader power which can be risky (as already noted in the documentation).

There are quite a few subscribers to this issue, so there seems to be genuine interest in this matter.

Thanks for your quick reply.

I also don't want to leave my card in the reader authenticated for a full day, it just doesn't sound like a good practice to me. I also very often just forget about the card, so it just sits there, keys open for use.

I resolved this with very ugly hack:

  1. Run scdaemon with debug logging (debug-level basic; log-file <path-to-log-file>)
  2. Script monitoring the scdaemon log, started by cron every 5 minutes. This script will just take the last timestamp for the log and compare it to current time
  3. Reload scdaemon if inactive for more than 30 minutes

Next time I try to use the card, it will prompt me for PIN, so in effect this is pretty much what man scdaemon says for card-timeout.

Following @turkja 's advice, here's a python script I wrote that does exactly that:

#!/usr/bin/python3

# Copyright 2021, Louis-Philippe Véronneau <pollo@debian.org>
#
# This script 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 script 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 script. If not, see <http://www.gnu.org/licenses/>.

"""
This script restarts scdaemon after X minutes of inactivity to reset the PIN
cache. It is meant to be ran by cron each X/2 minutes.

This is needed because there is currently no way to set a cache time for
smartcards. See https://dev.gnupg.org/T3362#137811 for more details.
"""

import os
import sys
import subprocess

from datetime import datetime, timedelta
from argparse import ArgumentParser


p = ArgumentParser(description=__doc__)
p.add_argument('-l', '--log', default="/var/log/scdaemon.log",
               help='Path to the scdaemon log file.')
p.add_argument('-t', '--timeout', type=int, default="10",
               help=("Desired cache time in minutes."))
args = p.parse_args()


def get_last_line(scdaemon_log):
    """Returns the last line of the scdameon log file."""
    with open(scdaemon_log, 'rb') as f:
        f.seek(-2, os.SEEK_END)
        while f.read(1) != b'\n':
            f.seek(-2, os.SEEK_CUR)
        last_line = f.readline().decode()

    return last_line


def check_time(last_line, timeout):
    """Returns True if scdaemon hasn't been called since the defined timeout."""
    # We don't need to restart scdaemon if no gpg command has been run since
    # the last time it was restarted.
    should_restart = True
    if "OK closing connection" in last_line:
        should_restart = False
    else:
        last_time = datetime.strptime(last_line[:19], '%Y-%m-%d %H:%M:%S')
        now = datetime.now()
        delta = now - last_time
        if delta <= timedelta(minutes = timeout):
            should_restart = False

    return should_restart


def restart_scdaemon(scdaemon_log):
    """Restart scdaemon and verify the restart process was successful."""
    subprocess.run(['gpgconf', '--reload', 'scdaemon'], check=True)
    last_line = get_last_line(scdaemon_log)
    if "OK closing connection" not in last_line:
        sys.exit("Restarting scdameon has failed.")


def main():
    """Main function."""
    last_line = get_last_line(args.log)
    should_restart = check_time(last_line, args.timeout)
    if should_restart:
        restart_scdaemon(args.log)


if __name__ == "__main__":
    main()

It seems to work well and I call it using this cron: */5 * * * * my_user /usr/local/bin/restart-scdaemon

You'll need a scdaemon.conf file that looks like:

debug-level basic
log-file /var/log/scdaemon.log

Hopefully I won't need that script for too long :)

I was also somewhat surprised to see that the max-cache-ttl options were rendered ineffective my moving the keys to a card.

If some form of cache eviction or resetting is not possible, could the man page be updated to point to this fact? In other words, add something like "This option has no effect when a smartcard is in use".

It's odd that cards would do this. Users unplugging a card after each use seems like a fringe use case; most users will probably keep the cards plugged in while they're in front of the computer.

It's been a few years since this task has been active.

I just want to chime in and mention that I think this feature would be useful.

Allowing smartcards to remain authenticated indefinitely seems insecure and unintuitive.

I hope someone can work on this soon.

Any thoughts, @werner or @gniibe?

Well, see my very first comment.

  • We should make card-timeout work again
  • For OpenPGP cards we could extend our magic login data (scd/app-openpgp:parse_login_data) to introduce the timeout @gniibe suggested.