[dm-crypt] cryptsetup - 2FA feature request

Stephen Feyrer <[email protected]> Fri, 27 Aug 2021 12:29:11 +0100
Newsgroups de.saout.dm-crypt
Message-ID <[email protected]>
This is a multi-part message in MIME format.
--===============9112068530392265905==
Content-Type: multipart/alternative;
 boundary="------------AAB2992DEA670393A15ECAAC"
Content-Language: en-GB

This is a multi-part message in MIME format.
--------------AAB2992DEA670393A15ECAAC
Content-Type: text/plain; charset=utf-8; format=flowed
Content-Transfer-Encoding: quoted-printable

Hi Everyone,

Before I start, I'd like to offer some caveats as I've had a week to=20
think about this.=C2=A0 This is a topic which I imagine has already been=20
covered ad nauseam (so I may be re-opening a can of worms - sorry). You=20
may also consider what I have to say as being out of scope for=20
cryptsetup or just overly complicated.=C2=A0 There may be details which I=
'm=20
not aware of or haven't given proper consideration.=C2=A0 So, take a deep=
=20
breath and lets dive in.

The rationale behind what I'm suggesting, is that I am working on using=20
a Yubikey as a second factor when decrypting my filesystem. To do this I=20
have an unencrypted partition where my kernel and initrd etc. are kept.=C2=
=A0=20
A script (that I am writing) will run and this will present a password=20
to cryptsetup.=C2=A0 It is the function of this script which creates the=20
password for which I suggest that cryptsetup take ownership.

My script picks up a JSON file with a structure like:
[
 =C2=A0=C2=A0=C2=A0 {"timeout": <TIME IN MILLISECONDS> }
 =C2=A0=C2=A0=C2=A0 {<SERIAL#.id1>:
 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 {"slot 1": {
 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 "password chall=
enge timeout": <TIME IN MILLISECONDS>,
 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0 =C2=A0 =C2=A0=C2=A0 "password required":=
 TRUE,
 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0 =C2=A0 =C2=A0=C2=A0 "Enabled": TRUE,
 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0 =C2=A0=C2=A0 =C2=A0 "password hint": "My=
 Password is..."
 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 "seed": <ENCRYP=
TED CHALLENGE VALUE>
 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 }
 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 },
 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 {"slot 2": {
 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 "password chall=
enge timeout": <TIME IN MILLISECONDS>,
 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0 =C2=A0=C2=A0 =C2=A0 "password required":=
 FALSE,
 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0 =C2=A0 =C2=A0=C2=A0 "Enabled": FALSE,
 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0 =C2=A0=C2=A0 =C2=A0 "password hint": "la=
zy but quick"
 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 "seed": <UNIQUE=
 CHALLENGE VALUE>
 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0 =C2=A0 =C2=A0=C2=A0 }
 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 }
 =C2=A0=C2=A0=C2=A0 },
 =C2=A0=C2=A0=C2=A0 {<SERIAL#.id2>:
 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 {"slot 1": {
 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 "password chall=
enge timeout": <TIME IN MILLISECONDS>,
 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 "password requi=
red": FALSE,
 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0 =C2=A0 =C2=A0=C2=A0 "Enabled": TRUE,
 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 "password hint"=
: "Backup Key Forgotten Password"
 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 "seed": <UNIQUE=
 CHALLENGE VALUE>
 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 }
 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 },
 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 {"slot 2": {
 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 "password chall=
enge timeout": <TIME IN MILLISECONDS>,
 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 "password requi=
red": FALSE,
 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0 =C2=A0 =C2=A0=C2=A0 "Enabled": FALSE,
 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 "password hint"=
: "Not Set Up"
 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 "seed": <UNUSED=
 VALUE>
 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 }
 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 }
 =C2=A0=C2=A0=C2=A0 }
]

That should give you an idea of what the script does but I'll give a=20
brief explanation for clarity:

 1. First it reads this file and gets the time out.
 2. The script enters a loop waiting for a USB Yubikey to be detected.
 3. Then when it detects a key, the serial number is checked and the
    data for that key is read.
 4. The script chooses the enabled slot on the Yubikey.=C2=A0 If both slo=
ts
    are enabled then it would choose the first slot, which if it fails
    then disable that slot and fail to boot.
 5. The password hint is printed as a re-assurance to the user that
    their key has been recognised.
 6. If a password is required (recommended), ask the user to enter their
    password and store in a variable (not the most secure but this is my
    first pass at the script).
 7. Call GPG to decrypt the seed value using the stored password.
 8. Pipe the decrypted value to ykchalresp using the selected slot
 9. Pipe the returned value into cryptsetup to open the desired device.
10. Generate a new raw seed value with uuid-gen.
11. Replace the cryptsetup password.
12. Call GPG with the stored password and encrypt the new seed.
13. Write the encrypted seed value over the original.
14. Pass control back to the system.

This doesn't take account for error handling or malefactors or setup,=20
its just an overview.

At this point I can already imagine 'Out of Scope' and 'What does this=20
have to do with cryptsetup?' oozing out of angry indignant emails.=C2=A0 =
It=20
seems I'm using my imagination a lot here because I'm also imagining=20
there are a lot of users wondering why cryptsetup doesn't support=20
Yubikey or another second factor natively and save them all a lot of=20
bother?=C2=A0 I'm one of them...

My first thought was to clone cryptsetup, make a branch and have a go.=C2=
=A0=20
Then I looked at the cryptsetup code and realised that my C skills are=20
woefully inadequate, sorry.=C2=A0 I don't mind having a go but I don't wa=
nt=20
to cause more problems than I'd solve.

Therefore I ask that you please consider this.=C2=A0 Create a Yubikey opt=
ion=20
in cryptsetup which roughly follows the workflow outlined above.=C2=A0=20
Albeit, hypothetically storing the json in an fs-block and using=20
cryptsetups password authentication mechanism in place of GPG.=C2=A0 Stor=
ing=20
the setup data of 8 keys could be recorded in one or more 4Kb block(s)=20
give or take (given that we have some free form text fields).=C2=A0 There=
 is=20
a Yubikey SDK at: https://developers.yubico.com
In fact, such an architecture might be able to be generalised to allow=20
2FA plug-ins.=C2=A0 In that case, the plug-in would only need two special=
=20
function calls into cryptsetup.=C2=A0 One to get the data "object" stored=
 on=20
the fs and the other to decrypt a token with a given password.=C2=A0=20
Otherwise the plug-in would call functions similar to lukFormat,=20
luksChangeKey and luksOpen, as means to perform the setup and house=20
keeping actions.

The *advantages *of such a scheme include;=C2=A0 It would enable a second=
=20
factor for unlocking the filesystem natively.=C2=A0 Assuming other extern=
al=20
second factor devices operate similarly it would allow for=20
generalisation.=C2=A0 There isn't a direct change to the way cryptsetup w=
orks=20
in terms of taking a password and decrypting a filesystem key.=C2=A0=20
Cryptsetup effectively retains control of the authentication process.=C2=A0=
=20
Initramfs tools have an established relationship with cryptsetup, while=20
Yubikey doesn't seem to me to really fit in the great initramfs scheme=20
of things as it's a second factor not a device to be initialised. =C2=A0 =
This=20
would be more secure than relying on some random shell script.

The *detractors *which I can imagine include;=C2=A0 Efficient systems don=
't=20
leave 4Kb blocks just lying around idly doing nothing.=C2=A0 In the end m=
ore=20
than one block might be needed.=C2=A0 There's no guarantee that even if t=
his=20
approach were taken that it would work as a generalised form.=C2=A0 No-bo=
dy=20
likes strangers off the internet waltzing up and asking them to do=20
work.=C2=A0 The Yubikey SDK maybe more complicated than I thought.=C2=A0 =
This may=20
be just out of the scope of cryptsetup.=C2=A0 The Yubikey's been around f=
or a=20
while and if the cryptsetup developers were going to do something about=20
it, they'd probably have done it by now.

I've tried to give a balanced view and hope I haven't made it too=20
complicated.=C2=A0 Or perhaps, I've over simplified it, you guys work on=20
cryptography after all...

Please let me know your thoughts?


Thanks,

Stephen.

--------------AAB2992DEA670393A15ECAAC
Content-Type: text/html; charset=utf-8
Content-Transfer-Encoding: quoted-printable

<html>
  <head>

    <meta http-equiv=3D"content-type" content=3D"text/html; charset=3DUTF=
-8">
  </head>
  <body>
    Hi Everyone,<br>
    <br>
    Before I start, I'd like to offer some caveats as I've had a week to
    think about this.=C2=A0 This is a topic which I imagine has already b=
een
    covered ad nauseam (so I may be re-opening a can of worms - sorry).=C2=
=A0
    You may also consider what I have to say as being out of scope for
    cryptsetup or just overly complicated.=C2=A0 There may be details whi=
ch
    I'm not aware of or haven't given proper consideration.=C2=A0 So, tak=
e a
    deep breath and lets dive in.<br>
    <br>
    The rationale behind what I'm suggesting, is that I am working on
    using a Yubikey as a second factor when decrypting my filesystem.=C2=A0
    To do this I have an unencrypted partition where my kernel and
    initrd etc. are kept.=C2=A0 A script (that I am writing) will run and
    this will present a password to cryptsetup.=C2=A0 It is the function =
of
    this script which creates the password for which I suggest that
    cryptsetup take ownership.<br>
    <br>
    My script picks up a JSON file with a structure like:<br>
    [<br>
    =C2=A0=C2=A0=C2=A0 {"timeout": &lt;TIME IN MILLISECONDS&gt; }<br>
    =C2=A0=C2=A0=C2=A0 {&lt;SERIAL#.id1&gt;:<br>
    =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 {"slot 1": {<br>
    =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 "password ch=
allenge timeout": &lt;TIME IN
    MILLISECONDS&gt;,<br>
    =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0 =C2=A0 =C2=A0=C2=A0 "password require=
d": TRUE,
    <br>
    =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0 =C2=A0 =C2=A0=C2=A0 "Enabled": TRUE,<=
br>
    =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0 =C2=A0=C2=A0 =C2=A0 "password hint": =
"My Password is..."<br>
    =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 "seed": &lt;=
ENCRYPTED CHALLENGE VALUE&gt;<br>
    =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 }<br>
    =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 },<br>
    =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 {"slot 2": {
    <br>
    =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 "password ch=
allenge timeout": &lt;TIME IN
    MILLISECONDS&gt;,<br>
    =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0 =C2=A0=C2=A0 =C2=A0 "password require=
d": FALSE,
    <br>
    =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0 =C2=A0 =C2=A0=C2=A0 "Enabled": FALSE,=
<br>
    =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0 =C2=A0=C2=A0 =C2=A0 "password hint": =
"lazy but quick"<br>
    =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 "seed": &lt;=
UNIQUE CHALLENGE VALUE&gt;<br>
    =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0 =C2=A0 =C2=A0=C2=A0 }<br>
    =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 }<br>
    =C2=A0=C2=A0=C2=A0 },<br>
    =C2=A0=C2=A0=C2=A0 {&lt;SERIAL#.id2&gt;:<br>
    =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 {"slot 1": {
    <br>
    =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 "password ch=
allenge timeout": &lt;TIME IN
    MILLISECONDS&gt;,<br>
    =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 "password re=
quired": FALSE,
    <br>
    =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0 =C2=A0 =C2=A0=C2=A0 "Enabled": TRUE,<=
br>
    =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 "password hi=
nt": "Backup Key Forgotten Password"<br>
    =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 "seed": &lt;=
UNIQUE CHALLENGE VALUE&gt;<br>
    =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 }<br>
    =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 },
    <br>
    =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 {"slot 2": {
    <br>
    =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 "password ch=
allenge timeout": &lt;TIME IN
    MILLISECONDS&gt;,<br>
    =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 "password re=
quired": FALSE,
    <br>
    =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0 =C2=A0 =C2=A0=C2=A0 "Enabled": FALSE,=
<br>
    =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 "password hi=
nt": "Not Set Up"<br>
    =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 "seed": &lt;=
UNUSED VALUE&gt;<br>
    =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 }<br>
    =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 }<br>
    =C2=A0=C2=A0=C2=A0 }<br>
    ]<br>
    <br>
    That should give you an idea of what the script does but I'll give a
    brief explanation for clarity:
    <ol>
      <li>First it reads this file and gets the time out.</li>
      <li>The script enters a loop waiting for a USB Yubikey to be
        detected.</li>
      <li>Then when it detects a key, the serial number is checked and
        the data for that key is read.</li>
      <li>The script chooses the enabled slot on the Yubikey.=C2=A0 If bo=
th
        slots are enabled then it would choose the first slot, which if
        it fails then disable that slot and fail to boot.<br>
      </li>
      <li>The password hint is printed as a re-assurance to the user
        that their key has been recognised.</li>
      <li>If a password is required (recommended), ask the user to enter
        their password and store in a variable (not the most secure but
        this is my first pass at the script).</li>
      <li>Call GPG to decrypt the seed value using the stored password.</=
li>
      <li>Pipe the decrypted value to ykchalresp using the selected slot<=
/li>
      <li>Pipe the returned value into cryptsetup to open the desired
        device.</li>
      <li>Generate a new raw seed value with uuid-gen.</li>
      <li>Replace the cryptsetup password.</li>
      <li>Call GPG with the stored password and encrypt the new seed.</li=
>
      <li>Write the encrypted seed value over the original.</li>
      <li>Pass control back to the system.</li>
    </ol>
    This doesn't take account for error handling or malefactors or
    setup, its just an overview.<br>
    <br>
    At this point I can already imagine 'Out of Scope' and 'What does
    this have to do with cryptsetup?' oozing out of angry indignant
    emails.=C2=A0 It seems I'm using my imagination a lot here because I'=
m
    also imagining there are a lot of users wondering why cryptsetup
    doesn't support Yubikey or another second factor natively and save
    them all a lot of bother?=C2=A0 I'm one of them...<br>
    <br>
    My first thought was to clone cryptsetup, make a branch and have a
    go.=C2=A0 Then I looked at the cryptsetup code and realised that my C
    skills are woefully inadequate, sorry.=C2=A0 I don't mind having a go=
 but
    I don't want to cause more problems than I'd solve.<br>
    <br>
    Therefore I ask that you please consider this.=C2=A0 Create a Yubikey
    option in cryptsetup which roughly follows the workflow outlined
    above.=C2=A0 Albeit, hypothetically storing the json in an fs-block a=
nd
    using cryptsetups password authentication mechanism in place of
    GPG.=C2=A0 Storing the setup data of 8 keys could be recorded in one =
or
    more 4Kb block(s) give or take (given that we have some free form
    text fields).=C2=A0 There is a Yubikey SDK at:=C2=A0
    <a class=3D"moz-txt-link-freetext" href=3D"https://developers.yubico.=
com">https://developers.yubico.com</a><br>
    In fact, such an architecture might be able to be generalised to
    allow 2FA plug-ins.=C2=A0 In that case, the plug-in would only need t=
wo
    special function calls into cryptsetup.=C2=A0 One to get the data
    "object" stored on the fs and the other to decrypt a token with a
    given password.=C2=A0 Otherwise the plug-in would call functions simi=
lar
    to lukFormat, luksChangeKey and luksOpen, as means to perform the
    setup and house keeping actions.<br>
    <br>
    The <b>advantages </b>of such a scheme include;=C2=A0 It would enable=
 a
    second factor for unlocking the filesystem natively.=C2=A0 Assuming o=
ther
    external second factor devices operate similarly it would allow for
    generalisation.=C2=A0 There isn't a direct change to the way cryptset=
up
    works in terms of taking a password and decrypting a filesystem
    key.=C2=A0 Cryptsetup effectively retains control of the authenticati=
on
    process.=C2=A0 Initramfs tools have an established relationship with
    cryptsetup, while Yubikey doesn't seem to me to really fit in the
    great initramfs scheme of things as it's a second factor not a
    device to be initialised. =C2=A0 This would be more secure than relyi=
ng
    on some random shell script.<br>
    <br>
    The <b>detractors </b>which I can imagine include;=C2=A0 Efficient
    systems don't leave 4Kb blocks just lying around idly doing
    nothing.=C2=A0 In the end more than one block might be needed.=C2=A0 =
There's
    no guarantee that even if this approach were taken that it would
    work as a generalised form.=C2=A0 No-body likes strangers off the
    internet waltzing up and asking them to do work.=C2=A0 The Yubikey SD=
K
    maybe more complicated than I thought.=C2=A0 This may be just out of =
the
    scope of cryptsetup.=C2=A0 The Yubikey's been around for a while and =
if
    the cryptsetup developers were going to do something about it,
    they'd probably have done it by now.<br>
    <br>
    I've tried to give a balanced view and hope I haven't made it too
    complicated.=C2=A0 Or perhaps, I've over simplified it, you guys work=
 on
    cryptography after all...<br>
    <br>
    Please let me know your thoughts?<br>
    <br>
    <br>
    Thanks,<br>
    <br>
    Stephen.<br>
  </body>
</html>

--------------AAB2992DEA670393A15ECAAC--

--===============9112068530392265905==
Content-Type: text/plain; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Content-Disposition: inline

_______________________________________________
dm-crypt mailing list -- [email protected]
To unsubscribe send an email to [email protected]

--===============9112068530392265905==--