Discussion:
Adding support for optimistic preauth to kinit
Ken Hornstein
2021-04-04 00:09:06 UTC
Permalink
So during some recent testing, I realized that there is one bit of
functionality in kinit that is missing; the ability to set the optimistic
preauth list. By this, I mean a list of preauthentication types to try
first instead of falling back to a list based on the list from the
"preauth required" error code.

Why would you want to do this? Well, here's what we use that for:

- We have users that have a hardware preauth configured, but not required.
This allows them to use their password to access a subset of site
resources but optionally use their hardware token to access resources that
require the extra security of a hardware token.

- We have users that have multiple tokens configured (typically this is
a smartcard that is used with PKINIT and a one-time token that uses SAM2).
There are a number of times where you might want to use one or the
other (if one is non-functional, or you can't use a smartcard driver).
From an architectural standpoint, the hard work is already done; the
library supports this via an already-exposed API in the get_init_creds
interface (krb5_get_init_creds_opt_set_preauth_list()). It's just
that kinit has no way to set this list.

What I was thinking was a new flag lets you add preauth types to the
optimistic preauth list. I'm not sure what letter makes sense as a lot
are already taken. Maybe -U? I have no preference here. What this
would look like would be:

% kinit -U pkinit -U sam2 -U 191 ***@REALM

The idea here being you could pick from a small list of symbolic
names of preauth types, and if that wasn't sufficient you could add
a number to capture future types. You could add multiple options to
have multiple entries in the preauth list. I think this is relatively
straightforward to implement; what do people think of this idea? I'd be
glad to submit the code for it.

--Ken
_______________________________________________
krbdev mailing list ***@mit.edu
https://mailman.mit.edu/mailman/listinfo/krbdev
Greg Hudson
2021-04-04 05:18:05 UTC
Permalink
Post by Ken Hornstein
From an architectural standpoint, the hard work is already done; the
library supports this via an already-exposed API in the get_init_creds
interface (krb5_get_init_creds_opt_set_preauth_list()). It's just
that kinit has no way to set this list.
I think the problem is both harder and easier than this.

Each preauth mechanism works differently, and some lend themselves to
optimistic preauth better than others:

* For encrypted timestamp and its FAST variant encrypted challenge, the
KDC's initial method-data is empty, but is accompanied by etype-info
containing the enctype, salt, and s2kparams. The client can cache those
(but we don't any current mechanism for that) or try to guess them
(default salt, no s2kparams, the client's preferred enctype), but it
should be prepared to retry after getting the proper etype-info, and if
the KDC is doing account lockout for the user principal, the optimistic
failure will count as a strike.

* For PKINIT the KDC's initial method-data is empty and the etype-info
is not needed. The client needs some configuration and possibly a local
password to unlock its private key, but there's no difference
between optimistic PKINIT preauth and regular PKINIT preauth aside from
the decision to do it. If optimistic PKINIT fails, it should not be
retried as the method-data won't contain any new information. (There is
a flow where PKINIT continues on a KDC error containing TD_DH_PARAMETERS
or similar, but that's not the same as trying again using the KDC
initial method-data.)

* SPAKE is even simpler than PKINIT; the client needs no information or
configuration to send an initial negotiation message. I had actually
planned to make optimistic SPAKE happen by default until I realized it
would prioritize SPAKE over PKINIT when the KDC thinks both are options.

* For SAM-2, the KDC's initial method-data contains a challenge
(including a checksum that can be dictionary-attacked to determine the
password). The challenge cannot be guessed, so optimistic SAM-2 simply
can't happen.

* For FAST OTP, the KDC's initial method-data contains some challenge
information which is mostly unused as we implemented it (and I don't
think anyone else has implemented it). So it might be reasonable to do
optimistic FAST OTP with no challenge data if asked for by the user,
although the spec contains no guidance for doing so.

On the flip side, the problem you stated isn't about reducing the number
of round trips, so we don't necessarily need optimistic preauth. Giving
kinit a way to choose between multiple credentials could be done in
other ways, although we don't have any current gic_opt machinery for them.

A path of lower resistance is to add an option to force a particular
preauth mech (single choice, hard-failing if it isn't available or
doesn't work). clpreauth modules already declare names like "pkinit"
and "sam2" which could be matched against. This approach has the
notable failing of requiring users to know something about preauth
mechs--in particular, if the user wants to authenticate with just their
password as registered with kpasswd, they need to know which of three
different preauth mechs to use (encrypted_timestamp,
encrypted_challenge, and spake) based on specific knowledge of their
realm and client capabilities.

A path of higher resistance is to invent names for credential types like
"password", and add a way to query preauth mechs for whether they use
the selected credential type. Although this might seem more
user-friendly at first, I think it would unfortunately get muddled
fairly quickly. For instance, "token" could describe a PKCS11 token to
be used with PKINIT, but also a device displaying a second factor for
use with SAM-2 or FAST OTP or (in the possible future) SPAKE.
"password" could describe a password to be used via one of the three
s2k-based mechanisms, or a password that decrypts a PKCS12 private key
or logs into a PKCS11 token for PKINIT.

There is also the option of just having specific kinit options for
specific preauth scenarios; that is, this problem's solution doesn't
have to be runtime-extensible just because we have a runtime-extensible
clpreauth framework (which as far as I know has only ever been used to
replace the PKINIT module with another implementation). As a point of
reference, Heimdal has specific kinit options and GIC options for
PKINIT, although they don't necessarily seem to fit your problem statement.
_______________________________________________
krbdev mailing list ***@mit.edu
https://mailman.mit.edu/mailman/listinfo/krbdev
Ken Hornstein
2021-04-05 03:43:23 UTC
Permalink
Post by Greg Hudson
I think the problem is both harder and easier than this.
Each preauth mechanism works differently, and some lend themselves to
[...]
That's all fair, and I admit that this is kind of the intersection of
what's available in terms of the API and the complexity of the preauth
code.

Really, the REAL goal is to force a particular preauth mechanism. We have
patches to kinit that make it so when kinit is called as "pkinit", it
will try PKINIT by default. The way this is done is by setting the
optimistic preauth list. But that has the sub-optimal behavior of making
it that if PKINIT doesn't work, it falls back to a password prompt
(well, that happens if PKINIT fails in the context of loading the plugin
or some other initial setup fails).
Post by Greg Hudson
A path of lower resistance is to add an option to force a particular
preauth mech (single choice, hard-failing if it isn't available or
doesn't work). clpreauth modules already declare names like "pkinit"
and "sam2" which could be matched against. This approach has the
notable failing of requiring users to know something about preauth
mechs--in particular, if the user wants to authenticate with just their
password as registered with kpasswd, they need to know which of three
different preauth mechs to use (encrypted_timestamp,
encrypted_challenge, and spake) based on specific knowledge of their
realm and client capabilities.
Honestly, I'd be fine with this (although it does occur to me that you
might want to specify more than one preauth to use).

I am thinking there are two classes of users that would make use of this.

- Developers/admins/other "power users"; they'd already know the right
options or would have the ability to figure it out.

- Regular users who would simply be told what option they needed to add to
kinit to access particular resources. For these people, they wouldn't
really know or care what the option means; it could be "-Y bloopitybloop"
and they'd just add that to the kinit command line.

--Ken
_______________________________________________
krbdev mailing list ***@mit.edu
https://mailman.mit.edu/mailman/listinfo/krbdev
Greg Hudson
2021-04-06 05:12:23 UTC
Permalink
Post by Ken Hornstein
Really, the REAL goal is to force a particular preauth mechanism. We have
patches to kinit that make it so when kinit is called as "pkinit", it
will try PKINIT by default. The way this is done is by setting the
optimistic preauth list.
I'm curious what this was needed for. Our initial ticket code is pretty
aggressive about trying PKINIT if the KDC offers it. Not only do KDCs
tend to offer PKINIT first when they're configured for it, but even if
they don't, the client code sorts the PKINIT types to the front of the
list (overridable with [libdefaults] preferred_preauth_types). So,
putting PKINIT in the optimistic preauth list wouldn't seem to change
the behavior of kinit except to save a round trip.
Post by Ken Hornstein
Post by Greg Hudson
A path of lower resistance is to add an option to force a particular
preauth mech (single choice, hard-failing if it isn't available or
doesn't work). clpreauth modules already declare names like "pkinit"
and "sam2" which could be matched against.
Honestly, I'd be fine with this (although it does occur to me that you
might want to specify more than one preauth to use)
What use cases do you have in mind for specifying more than one?

I'd be willing to consider a patch in this direction, since my other
ideas aren't well-formed enough to pursue.
_______________________________________________
krbdev mailing list ***@mit.edu
https://mailman.mit.edu/mailman/listinfo/krbdev
Ken Hornstein
2021-04-06 14:39:09 UTC
Permalink
Post by Greg Hudson
I'm curious what this was needed for. Our initial ticket code is pretty
aggressive about trying PKINIT if the KDC offers it. Not only do KDCs
tend to offer PKINIT first when they're configured for it, but even if
they don't, the client code sorts the PKINIT types to the front of the
list (overridable with [libdefaults] preferred_preauth_types). So,
putting PKINIT in the optimistic preauth list wouldn't seem to change
the behavior of kinit except to save a round trip.
Well, SINCE you're asking ... we put this in our distributed krb5.conf:

preferred_preauth_types = 30,31

As you note, you can't do optimistic preauth with SAM2. Because of the
sorting with PKINIT, the default configuration means it will try PKINIT
first if the KDC offers it. But, effectively, this means that if there
is a problem with PKINIT then you're kind of locked out even if you have
another type of working hardware preauth mechanism. I realize that if
your client isn't configured for PKINIT at all then it will skip over
it, but typically we ARE configured for PKINIT but unfortunately there
are a large number of failure modes with PKINIT so it's very easy to
get into a situation where the default configuration doesn't work.

The way this works for us in practice is we ship a "kinit" program and a
"pkinit" program; the pkinit program sets the optimistic preauth list to
include PKINIT which has the effect of overriding the preferred preauth
list. This I believe was the only reasonable solution available to us
given the existing APIs without totally rototilling the preauth layer
which nobody wants to do (because as it has been noted here, it's very
complicated to get it right for all cases).

I realize that there isn't a wonderful no-configuration general-purpose
solution; it's tough to make it work right for all use cases. Typically,
in our environment a user has one of 3 configurations:

- Has a single hardware token configured and their principal is configured
so it is required they use it
- Has multiple hardware tokens configured and their principal is configured
that it is required they use one of them
- Has zero or more hardware tokens configured, but the use of the hardware
token is optional.

But we never are really in the situation a user wants to just use "the
best token available". They know "I have a smartcard" or "I have this
other type of token" and want to use that particular one. I mean, they
don't really know that smartcard == PKINIT and token == SAM2, they know
that when they use their smartcard they have to run "pkinit" and the
rest of the time they use "kinit".
Post by Greg Hudson
Post by Ken Hornstein
Post by Greg Hudson
A path of lower resistance is to add an option to force a particular
preauth mech (single choice, hard-failing if it isn't available or
doesn't work). clpreauth modules already declare names like "pkinit"
and "sam2" which could be matched against.
Honestly, I'd be fine with this (although it does occur to me that you
might want to specify more than one preauth to use)
What use cases do you have in mind for specifying more than one?
Hm ... good question! I'm not honestly sure. I guess I was thinking
that for no other reason than in development it sure would be nice to
test how things behave when one mechanism fails but others could still
potentially work. I'd like to have the capability in the API to open up
other potential uses that I couldn't think of right now.

--Ken
_______________________________________________
krbdev mailing list ***@mit.edu
https://mailman.mit.edu/mailman/listinfo/krbdev
Sam Hartman
2021-04-26 11:46:30 UTC
Permalink
Post by Greg Hudson
I think the problem is both harder and easier than this.
Each preauth mechanism works differently, and some lend
themselves to optimistic preauth better than others: [...]
Ken> That's all fair, and I admit that this is kind of the
Ken> intersection of what's available in terms of the API and the
Ken> complexity of the preauth code.

Ken> Really, the REAL goal is to force a particular preauth
Ken> mechanism. We have patches to kinit that make it so when kinit
Ken> is called as "pkinit", it will try PKINIT by default. The way
Ken> this is done is by setting the optimistic preauth list. But
Ken> that has the sub-optimal behavior of making it that if PKINIT
Ken> doesn't work, it falls back to a password prompt (well, that
Ken> happens if PKINIT fails in the context of loading the plugin or
Ken> some other initial setup fails).

As part of designing FAST and the preauth framework, we kind of accepted
that we were effectively killing off optimistic preauth. In the
generalized case you might need either the hint data or some other
information from the KDC to get started, and in the general case, once
you've started you cannot really restart (without completily restarting
over and possibly implementing lockout counters).

Also, one of the goals of FAST was to get to a point where after you got
the initial KDC reply you could present all the UI you would ever
present. Feedback we got on the GUI side was thet models like PAM
keyboard interactive didn't work welll.

I think that having a list of preauth mechanisms the client hopes to use
(or a single mechanism) makes sense,
but trying to reduce round trips is close to incompatible with other
design constraints that came up with FAST/the preauth framework.
I don't think we forbid anything in the spec, but we definitely
acknowledged that optimistic was going to be nearly impossible in the
general case.

--Sam
_______________________________________________
krbdev mailing list ***@mit.edu
https://mailman.mit.edu/mailman/listinfo/krbdev
Ken Hornstein
2021-04-26 15:35:53 UTC
Permalink
Post by Sam Hartman
As part of designing FAST and the preauth framework, we kind of accepted
that we were effectively killing off optimistic preauth. In the
generalized case you might need either the hint data or some other
information from the KDC to get started, and in the general case, once
you've started you cannot really restart (without completily restarting
over and possibly implementing lockout counters).
I hope I made it clear that optimistic preauth and/or reducing round trips
was not REALLY my goal; the goal was to permit the selection of a particular
preauth type the setting the optimistic preauth list was really the only
available API to do that.

I also fully acknowledge that the existing preauth framework is very
complicated and it's hard to make decisions which work universally for
everyone. It sounds like everyone agrees an API to set the preauth
list would be a good solution.

--Ken
_______________________________________________
krbdev mailing list ***@mit.edu
https://mailman.mit.edu/mailman/listinfo/krbdev

Loading...