Louis-Philippe Véronneau - gpghttps://veronneau.org/2021-03-13T00:00:00-05:00Preventing an OpenPGP Smartcard from caching the PIN eternally2021-03-13T00:00:00-05:002021-03-13T00:00:00-05:00Louis-Philippe Véronneautag:veronneau.org,2021-03-13:/preventing-an-openpgp-smartcard-from-caching-the-pin-eternally.html<p>While I'm overall very happy about <a href="https://veronneau.org/new-year-new-openpgp-key.html">my migration to an OpenPGP hardware
token</a>, the process wasn't entirely seamless and I had to hack around
some issues, for example the PIN caching behavior in GnuPG.</p>
<p>As described <a href="https://dev.gnupg.org/T3362">in this bug</a> the <code>cache-ttl</code> parameter in GnuPG is not
implemented and thus does …</p><p>While I'm overall very happy about <a href="https://veronneau.org/new-year-new-openpgp-key.html">my migration to an OpenPGP hardware
token</a>, the process wasn't entirely seamless and I had to hack around
some issues, for example the PIN caching behavior in GnuPG.</p>
<p>As described <a href="https://dev.gnupg.org/T3362">in this bug</a> the <code>cache-ttl</code> parameter in GnuPG is not
implemented and thus does nothing. This means once you type in your PIN, it is
cached for as long as the token is plugged.</p>
<p>Security-wise, this is not great. Instead of manually disconnecting the token
frequently, I've come up with a script that restarts <code>scdameon</code> if the token
hasn't been used during the last X minutes.</p>
<p>It seems to work well and I call it using this cron entry:</p>
<p><code>*/5 * * * * my_user /usr/local/bin/restart-scdaemon</code></p>
<p>To get a log from <code>scdaemon</code>, you'll need a <code>~/.gnupg/scdaemon.conf</code> file that
looks like this:</p>
<pre>
debug-level basic
log-file /var/log/scdaemon.log
</pre>
<p>Hopefully it can be useful to others!</p>
<div class="highlight"><pre><span></span><code><span class="ch">#!/usr/bin/python3</span>
<span class="c1"># Copyright 2021, Louis-Philippe Véronneau <pollo@debian.org></span>
<span class="c1">#</span>
<span class="c1"># This script is free software: you can redistribute it and/or modify it under</span>
<span class="c1"># the terms of the GNU General Public License as published by the Free Software</span>
<span class="c1"># Foundation, either version 3 of the License, or (at your option) any later</span>
<span class="c1"># version.</span>
<span class="c1"># </span>
<span class="c1"># This script is distributed in the hope that it will be useful, but WITHOUT</span>
<span class="c1"># ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS</span>
<span class="c1"># FOR A PARTICULAR PURPOSE. See the GNU General Public License for more</span>
<span class="c1"># details.</span>
<span class="c1"># </span>
<span class="c1"># You should have received a copy of the GNU General Public License along with</span>
<span class="c1"># this script. If not, see <http://www.gnu.org/licenses/>.</span>
<span class="sd">"""</span>
<span class="sd">This script restarts scdaemon after X minutes of inactivity to reset the PIN</span>
<span class="sd">cache. It is meant to be ran by cron each X/2 minutes.</span>
<span class="sd">This is needed because there is currently no way to set a cache time for</span>
<span class="sd">smartcards. See https://dev.gnupg.org/T3362#137811 for more details.</span>
<span class="sd">"""</span>
<span class="kn">import</span> <span class="nn">os</span>
<span class="kn">import</span> <span class="nn">sys</span>
<span class="kn">import</span> <span class="nn">subprocess</span>
<span class="kn">from</span> <span class="nn">datetime</span> <span class="kn">import</span> <span class="n">datetime</span><span class="p">,</span> <span class="n">timedelta</span>
<span class="kn">from</span> <span class="nn">argparse</span> <span class="kn">import</span> <span class="n">ArgumentParser</span>
<span class="n">p</span> <span class="o">=</span> <span class="n">ArgumentParser</span><span class="p">(</span><span class="n">description</span><span class="o">=</span><span class="vm">__doc__</span><span class="p">)</span>
<span class="n">p</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s1">'-l'</span><span class="p">,</span> <span class="s1">'--log'</span><span class="p">,</span> <span class="n">default</span><span class="o">=</span><span class="s2">"/var/log/scdaemon.log"</span><span class="p">,</span>
<span class="n">help</span><span class="o">=</span><span class="s1">'Path to the scdaemon log file.'</span><span class="p">)</span>
<span class="n">p</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s1">'-t'</span><span class="p">,</span> <span class="s1">'--timeout'</span><span class="p">,</span> <span class="nb">type</span><span class="o">=</span><span class="nb">int</span><span class="p">,</span> <span class="n">default</span><span class="o">=</span><span class="s2">"10"</span><span class="p">,</span>
<span class="n">help</span><span class="o">=</span><span class="p">(</span><span class="s2">"Desired cache time in minutes."</span><span class="p">))</span>
<span class="n">args</span> <span class="o">=</span> <span class="n">p</span><span class="o">.</span><span class="n">parse_args</span><span class="p">()</span>
<span class="k">def</span> <span class="nf">get_last_line</span><span class="p">(</span><span class="n">scdaemon_log</span><span class="p">):</span>
<span class="w"> </span><span class="sd">"""Returns the last line of the scdameon log file."""</span>
<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">scdaemon_log</span><span class="p">,</span> <span class="s1">'rb'</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
<span class="n">f</span><span class="o">.</span><span class="n">seek</span><span class="p">(</span><span class="o">-</span><span class="mi">2</span><span class="p">,</span> <span class="n">os</span><span class="o">.</span><span class="n">SEEK_END</span><span class="p">)</span>
<span class="k">while</span> <span class="n">f</span><span class="o">.</span><span class="n">read</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="o">!=</span> <span class="sa">b</span><span class="s1">'</span><span class="se">\n</span><span class="s1">'</span><span class="p">:</span>
<span class="n">f</span><span class="o">.</span><span class="n">seek</span><span class="p">(</span><span class="o">-</span><span class="mi">2</span><span class="p">,</span> <span class="n">os</span><span class="o">.</span><span class="n">SEEK_CUR</span><span class="p">)</span>
<span class="n">last_line</span> <span class="o">=</span> <span class="n">f</span><span class="o">.</span><span class="n">readline</span><span class="p">()</span><span class="o">.</span><span class="n">decode</span><span class="p">()</span>
<span class="k">return</span> <span class="n">last_line</span>
<span class="k">def</span> <span class="nf">check_time</span><span class="p">(</span><span class="n">last_line</span><span class="p">,</span> <span class="n">timeout</span><span class="p">):</span>
<span class="w"> </span><span class="sd">"""Returns True if scdaemon hasn't been called since the defined timeout."""</span>
<span class="c1"># We don't need to restart scdaemon if no gpg command has been run since</span>
<span class="c1"># the last time it was restarted.</span>
<span class="n">should_restart</span> <span class="o">=</span> <span class="kc">True</span>
<span class="k">if</span> <span class="s2">"OK closing connection"</span> <span class="ow">in</span> <span class="n">last_line</span><span class="p">:</span>
<span class="n">should_restart</span> <span class="o">=</span> <span class="kc">False</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">last_time</span> <span class="o">=</span> <span class="n">datetime</span><span class="o">.</span><span class="n">strptime</span><span class="p">(</span><span class="n">last_line</span><span class="p">[:</span><span class="mi">19</span><span class="p">],</span> <span class="s1">'%Y-%m-</span><span class="si">%d</span><span class="s1"> %H:%M:%S'</span><span class="p">)</span>
<span class="n">now</span> <span class="o">=</span> <span class="n">datetime</span><span class="o">.</span><span class="n">now</span><span class="p">()</span>
<span class="n">delta</span> <span class="o">=</span> <span class="n">now</span> <span class="o">-</span> <span class="n">last_time</span>
<span class="k">if</span> <span class="n">delta</span> <span class="o"><=</span> <span class="n">timedelta</span><span class="p">(</span><span class="n">minutes</span> <span class="o">=</span> <span class="n">timeout</span><span class="p">):</span>
<span class="n">should_restart</span> <span class="o">=</span> <span class="kc">False</span>
<span class="k">return</span> <span class="n">should_restart</span>
<span class="k">def</span> <span class="nf">restart_scdaemon</span><span class="p">(</span><span class="n">scdaemon_log</span><span class="p">):</span>
<span class="w"> </span><span class="sd">"""Restart scdaemon and verify the restart process was successful."""</span>
<span class="n">subprocess</span><span class="o">.</span><span class="n">run</span><span class="p">([</span><span class="s1">'gpgconf'</span><span class="p">,</span> <span class="s1">'--reload'</span><span class="p">,</span> <span class="s1">'scdaemon'</span><span class="p">],</span> <span class="n">check</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
<span class="n">last_line</span> <span class="o">=</span> <span class="n">get_last_line</span><span class="p">(</span><span class="n">scdaemon_log</span><span class="p">)</span>
<span class="k">if</span> <span class="s2">"OK closing connection"</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">last_line</span><span class="p">:</span>
<span class="n">sys</span><span class="o">.</span><span class="n">exit</span><span class="p">(</span><span class="s2">"Restarting scdameon has failed."</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
<span class="w"> </span><span class="sd">"""Main function."""</span>
<span class="n">last_line</span> <span class="o">=</span> <span class="n">get_last_line</span><span class="p">(</span><span class="n">args</span><span class="o">.</span><span class="n">log</span><span class="p">)</span>
<span class="n">should_restart</span> <span class="o">=</span> <span class="n">check_time</span><span class="p">(</span><span class="n">last_line</span><span class="p">,</span> <span class="n">args</span><span class="o">.</span><span class="n">timeout</span><span class="p">)</span>
<span class="k">if</span> <span class="n">should_restart</span><span class="p">:</span>
<span class="n">restart_scdaemon</span><span class="p">(</span><span class="n">args</span><span class="o">.</span><span class="n">log</span><span class="p">)</span>
<span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">"__main__"</span><span class="p">:</span>
<span class="n">main</span><span class="p">()</span>
</code></pre></div>New Year, New OpenPGP Key2021-03-07T00:00:00-05:002021-03-07T00:00:00-05:00Louis-Philippe Véronneautag:veronneau.org,2021-03-07:/new-year-new-openpgp-key.html<pre>
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA512
Sun, 07 Mar 2021 13:00:17 -0500
I've recently set up a new OpenPGP key and will be transitioning away from my
old one.
It is a chance for me to start using a OpenPGP hardware token and to transition
to a new …</pre><pre>
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA512
Sun, 07 Mar 2021 13:00:17 -0500
I've recently set up a new OpenPGP key and will be transitioning away from my
old one.
It is a chance for me to start using a OpenPGP hardware token and to transition
to a new personal email address (my main public contact is still my
`@debian.org` address).
Please note that I've partially redacted some email addresses from this
statement to minimise the amount of spam I receive. It shouldn't be hard for
actual humans to follow the instructions below to find the complete addresses.
The old key will continue to be valid for a few months, but will eventually be
revoked.
You might know my old OpenPGP certificate as:
pub rsa4096/0x7AEAC4EC6AAA0A97 2014-12-22 [expires: 2021-06-02]
Key fingerprint = 677F 54F1 FA86 81AD 8EC0 BCE6 7AEA C4EC 6AAA 0A97
uid Louis-Philippe Véronneau <REDACTED@riseup.net>
uid Louis-Philippe Véronneau (alias) <REDACTED@riseup.net>
uid Louis-Philippe Véronneau (debian) <REDACTED@debian.org>
My new OpenPGP certificate is:
pub ed25519/0xE1E5457C8BAD4113 2021-03-06 [expires: 2022-03-06]
Key fingerprint = F64D 61D3 21F3 CB48 9156 753D E1E5 457C 8BAD 4113
uid Louis-Philippe Véronneau <REDACTED@veronneau.org>
uid Louis-Philippe Véronneau <REDACTED@debian.org>
These days, I mostly use my key for Debian and to sign git commit. I don't
really expect you to sign my new key if you had signed my old one.
I've published the new certificate on keys.openpgp.org as well as on my
personal website. You can fetch it like this:
$ wget -O- https://veronneau.org/media/openpgp.key | gpg --import
-----BEGIN PGP SIGNATURE-----
iQIzBAEBCgAdFiEEZ39U8fqGga2OwLzmeurE7GqqCpcFAmBFFM8ACgkQeurE7Gqq
CpcuchAAscAeszdtA+TlCI4YvK5nlk+nJnCnNBSnl7Et+jiNjq8kB/Fud+dWMTXC
Zag8oJkalbbxub0BT0bEAn+BiBunu58E0gd0Xq4syTbqZ5o5IN17S/tfxCD0k1hf
ewrnYZ2l0i5g4YvHGKC+Xv4D+Z84BylnIRaXHqlUdluOVfVYDfLybOAqoktO/KUH
I+vQBwXj0Fr/QAtgiz5Nwh/YHFiU9xMSvr5ozRwAFs6+xfIqFHuVPRRkEN5iVo4D
kkMIz+kFfkoh4aWIP4dgAu39XnEgxwTR9J+4yE8TzCCMzO7xCK0X6vqgPAxYMPvb
RuP4FnGWOnGnlcudCUAUkOaryrwRi+dPQTnNICHTYsvVc7dg+W0EhVUkwEuuEwpI
qtcB/Y5AGhqK0Cc11uXiFjIQwLTgwcUez4F0xrGeqsTtAM5gyRup2w0jbocTuYSh
ZRv/2zwrq/S3xVrUYGqdT+L5odmkBzz9zOwY5WlU2H9CMFOdh71XOv9wWQXan9ou
hLRodeOQ8MinIBP+sX36ol1zg/aP7mCHvRRSBzWt7l3WhVxgZFpNwIfp/RZqU0R4
IEq48mntFhPvHJjFmAKLKK/ckzNMtSn+HWQPJV3HTInKCTu5PTNMU3SAvPHOHEps
V6WWSOPB+1Lm/tlIULDc+0SopWoiWO4NObCSs8zMZHlYPBk5x/KIdQQBFgoAHRYh
BMqnQAcHqBawIC/DzfQlelCyHPqFBQJgRRTPAAoJEPQlelCyHPqFFVEA/1qScaAk
O+eBEE4q0BaJDsqweCS1XCcuQGkQCKi5Zv6kAQChQ96Ve7cKbN/wRkT9pdIhmx01
+CmIsnp3k6N0ZYLLCg==
=onl0
-----END PGP SIGNATURE-----
</pre>