Nuances of MIT Kerberos prompting

classic Classic list List threaded Threaded
7 messages Options
Reply | Threaded
Open this post in threaded view
|

Nuances of MIT Kerberos prompting

Russ Allbery-2
I'm finally revisiting PKINIT prompting in pam-krb5 and am trying to wrap
my head around some subtle nuances of behavior to be sure I'm doing
reasonable things.  Thus, a few questions:

1. The normal prompter interface has a mechanism to send a "name" and a
   "banner".  Neither of these are very well-documented, but the current
   PAM module behavior is to output them both (name first, then banner) as
   PAM_TEXT_INFO.

   I don't see any equivalent in the responder API.  How does that work?
   Is the prompter called with the name and banner but an empty list of
   prompts and the responder called with the actual questions, if both are
   given?  In what order?  I naively would have expected some way to use
   the responder interface to completely replace the prompter interface,
   but that doesn't seem to be possible because name and banner aren't
   supported.

2. Revisiting this message from many years ago:

   http://mailman.mit.edu/pipermail/kerberos/2013-May/018973.html

   the suggestion for use_pkinit was to have a prompter that declined to
   respond to password questions and only passed along preauth questions.
   This would be an alternative approach to using the responder API.
   However, this prompts a question: how does a prompter decline to
   respond to a question?  Should the prompter return failure if
   KRB5_PROMPT_TYPE_PASSWORD appears anywhere in krb5_get_prompt_types,
   presumably without displaying the name or banner?  If so, what status
   code?

3. How can I trigger MIT Kerberos to send a PKINIT prompt with multiple
   identities so that I can test the code that prompts the user to select
   one?  The code seems determined to prevent me from specifying multiple
   pkinit_identities.  Only the first one in krb5.conf appears to be
   honored and any subsequent ones are ignored, and kinit doesn't allow
   X509_user_identity to be set more than once.  Do I *have* to have a
   PKCS11 module to get to that code path, and I cannot access it with
   FILE or PKCS12 identities?

Finally, more a bug report than a question, but MIT Kerberos 1.17, when
given a PKCS12 identity file that's protected with a password (which is
what I'm using to test PIN prompting) prompts for the password twice.  The
second time appears to be during the krb5_verify_init_creds call.  What's
going on here?  The user experience is a little odd.  kinit only prompts
once, but I think that's because kinit never calls krb5_verify_init_creds.

--
Russ Allbery ([hidden email])             <https://www.eyrie.org/~eagle/>
________________________________________________
Kerberos mailing list           [hidden email]
https://mailman.mit.edu/mailman/listinfo/kerberos
Reply | Threaded
Open this post in threaded view
|

Re: Nuances of MIT Kerberos prompting

Greg Hudson
On 3/3/20 1:33 AM, Russ Allbery wrote:

> 1. The normal prompter interface has a mechanism to send a "name" and a
>    "banner".  Neither of these are very well-documented, but the current
>    PAM module behavior is to output them both (name first, then banner) as
>    PAM_TEXT_INFO.
>
>    I don't see any equivalent in the responder API.  How does that work?
>    Is the prompter called with the name and banner but an empty list of
>    prompts and the responder called with the actual questions, if both are
>    given?  In what order?  I naively would have expected some way to use
>    the responder interface to completely replace the prompter interface,
>    but that doesn't seem to be possible because name and banner aren't
>    supported.

The responder interface is targeted at an application that wants to have
knowledge of particular preauth mechanisms and present its own UI.  So
the responder question doesn't tell the application what text to present
to the user; instead, it presents a semantic question in terms of
concepts specific to the preauth mech.

The prompter acts as a fallback to the responder.  So if the responder
is present but does not answer a question the preauth mech needs to
proceed, the prompter is invoked (if present).

So, the responder doesn't strictly subsume the prompter; a caller who
wants to be told what textual questions to ask the user, or who doesn't
want to have specific knowledge of preauth mechanisms, must continue to
use the prompter.

> 2. Revisiting this message from many years ago:
>
>    http://mailman.mit.edu/pipermail/kerberos/2013-May/018973.html
>
>    the suggestion for use_pkinit was to have a prompter that declined to
>    respond to password questions and only passed along preauth questions.
>    This would be an alternative approach to using the responder API.
>    However, this prompts a question: how does a prompter decline to
>    respond to a question?  Should the prompter return failure if
>    KRB5_PROMPT_TYPE_PASSWORD appears anywhere in krb5_get_prompt_types,
>    presumably without displaying the name or banner?  If so, what status
>    code?

I think that would work.  Any error code should work to disable a
preauth mech which requires the password (encrypted timestamp, encrypted
challenge, SPAKE).  I'll suggest EIO, which is the error code returned
by krb5_get_as_key_password() if no prompter is present.

> 3. How can I trigger MIT Kerberos to send a PKINIT prompt with multiple
>    identities so that I can test the code that prompts the user to select
>    one?  The code seems determined to prevent me from specifying multiple
>    pkinit_identities.  Only the first one in krb5.conf appears to be
>    honored and any subsequent ones are ignored, and kinit doesn't allow
>    X509_user_identity to be set more than once.  Do I *have* to have a
>    PKCS11 module to get to that code path, and I cannot access it with
>    FILE or PKCS12 identities?

I don't have a ready answer to this question; I can look into it if it's
still relevant given the other answers.

> Finally, more a bug report than a question, but MIT Kerberos 1.17, when
> given a PKCS12 identity file that's protected with a password (which is
> what I'm using to test PIN prompting) prompts for the password twice.  The
> second time appears to be during the krb5_verify_init_creds call.  What's
> going on here?  The user experience is a little odd.  kinit only prompts
> once, but I think that's because kinit never calls krb5_verify_init_creds.

I can't account for this.  krb5_verify_init_creds() is only supposed to
make TGS requests, not AS requests, so it shouldn't be doing any preauth
(and therefore shouldn't be doing any prompting).

________________________________________________
Kerberos mailing list           [hidden email]
https://mailman.mit.edu/mailman/listinfo/kerberos
Reply | Threaded
Open this post in threaded view
|

Re: Nuances of MIT Kerberos prompting

Russ Allbery-2
Greg Hudson <[hidden email]> writes:

> So, the responder doesn't strictly subsume the prompter; a caller who
> wants to be told what textual questions to ask the user, or who doesn't
> want to have specific knowledge of preauth mechanisms, must continue to
> use the prompter.

I think the reason why I am confused by this is that Heimdal uses the
prompter to pass along informational messages such as "your principal is
about to expire," and I wasn't sure how MIT Kerberos would do the same
thing with the responder interface.  But maybe it doesn't present those
messages, or uses the prompter for them even if a responder is provided
and answers the actual questions?

> I think that would work.  Any error code should work to disable a
> preauth mech which requires the password (encrypted timestamp, encrypted
> challenge, SPAKE).  I'll suggest EIO, which is the error code returned
> by krb5_get_as_key_password() if no prompter is present.

I've been tentatively using KRB5_LIBOS_CANTREADPWD since that seems more
specific.

Okay, looking at this some more, I think I'm going to throw out my
responder implementation and go back to using a prompter, but instead
decline to reply if krb5_get_prompt_types() indicates that the prompt is
for a password.

> I don't have a ready answer to this question; I can look into it if it's
> still relevant given the other answers.

It won't be, at least for me.

>> Finally, more a bug report than a question, but MIT Kerberos 1.17, when
>> given a PKCS12 identity file that's protected with a password (which is
>> what I'm using to test PIN prompting) prompts for the password twice.
>> The second time appears to be during the krb5_verify_init_creds call.
>> What's going on here?  The user experience is a little odd.  kinit only
>> prompts once, but I think that's because kinit never calls
>> krb5_verify_init_creds.

> I can't account for this.  krb5_verify_init_creds() is only supposed to
> make TGS requests, not AS requests, so it shouldn't be doing any preauth
> (and therefore shouldn't be doing any prompting).

Yeah, I find it puzzling.  I think it's during verify because if I
configure the test suite to not respond to that second prompt at all,
everyting still succeeds, so failure to reply to the second prompt is
ignored.  But I'm not sure what's going on.

Here's the trace output, but it's not very useful since it seems to end
after the authentication and doesn't include the verify attempt.

1583711786.155739: Getting initial credentials for [hidden email]
1583711786.155741: Sending unauthenticated request
1583711786.155742: Sending request (199 bytes) to EYRIE.ORG
1583711786.155743: Resolving hostname kerberos.eyrie.org
1583711786.155744: Initiating TCP connection to stream 166.84.7.155:88
1583711786.155745: Sending TCP request to stream 166.84.7.155:88
1583711786.155746: Received answer (309 bytes) from stream 166.84.7.155:88
1583711786.155747: Terminating TCP connection to stream 166.84.7.155:88
1583711786.155748: Response was from master KDC
1583711786.155749: Received error from KDC: -1765328359/Additional pre-authentication required
1583711786.155752: Preauthenticating using KDC method data
1583711786.155753: Processing preauth types: PA-PK-AS-REQ (16), PA-PK-AS-REP_OLD (15), PA-PK-AS-REQ_OLD (14), PA-FX-FAST (136), PA-ETYPE-INFO2 (19), PA-PKINIT-KX (147), PA-ENC-TIMESTAMP (2), PA_AS_FRESHNESS (150), PA-FX-COOKIE (133)
1583711786.155754: Selected etype info: etype aes256-cts, salt "EYRIE.ORGthoron", params ""
1583711786.155755: Received cookie: MIT
1583711786.155756: PKINIT initial PKCS12_parse with no password failed
1583711786.155757: Preauth module pkinit (147) (info) returned: 0/Success
1583711786.155758: PKINIT client received freshness token from KDC
1583711786.155759: Preauth module pkinit (150) (info) returned: 0/Success
1583711786.155760: PKINIT initial PKCS12_parse with no password failed
1583711786.155761: PKINIT loading CA certs and CRLs from FILE
1583711786.155762: PKINIT client computed kdc-req-body checksum 9/5200E986ED922B591C567068EBF6AD19A276D73F
1583711786.155764: PKINIT client making DH request
1583711787.103188: Preauth module pkinit (16) (real) returned: 0/Success
1583711787.103189: Produced preauth for next request: PA-FX-COOKIE (133), PA-PK-AS-REQ (16)
1583711787.103190: Sending request (2650 bytes) to EYRIE.ORG
1583711787.103191: Resolving hostname kerberos.eyrie.org
1583711787.103192: Initiating TCP connection to stream 166.84.7.155:88
1583711787.103193: Sending TCP request to stream 166.84.7.155:88
1583711787.103194: Received answer (2372 bytes) from stream 166.84.7.155:88
1583711787.103195: Terminating TCP connection to stream 166.84.7.155:88
1583711787.103196: Response was from master KDC
1583711787.103197: Processing preauth types: PA-PK-AS-REP (17)
1583711787.103198: PKINIT client verified DH reply
1583711787.103199: PKINIT client found id-pkinit-san in KDC cert: krbtgt/[hidden email]
1583711787.103200: PKINIT client matched KDC principal krbtgt/[hidden email] against id-pkinit-san; no EKU check required
1583711787.103201: PKINIT client used KDF 2B06010502030602 to compute reply key aes256-cts/1BD3
1583711787.103202: Preauth module pkinit (17) (real) returned: 0/Success
1583711787.103203: Produced preauth for next request: (empty)
1583711787.103204: AS key determined by preauth: aes256-cts/1BD3
1583711787.103205: Decrypted AS reply; session key is: aes256-cts/8D62
1583711787.103206: FAST negotiation: available

--
Russ Allbery ([hidden email])             <https://www.eyrie.org/~eagle/>
________________________________________________
Kerberos mailing list           [hidden email]
https://mailman.mit.edu/mailman/listinfo/kerberos
Reply | Threaded
Open this post in threaded view
|

Re: Nuances of MIT Kerberos prompting

Greg Hudson
On 3/8/20 8:01 PM, Russ Allbery wrote:
> I think the reason why I am confused by this is that Heimdal uses the
> prompter to pass along informational messages such as "your principal is
> about to expire," and I wasn't sure how MIT Kerberos would do the same
> thing with the responder interface.  But maybe it doesn't present those
> messages, or uses the prompter for them even if a responder is provided
> and answers the actual questions?

In MIT krb5 you can set an expire callback
(krb5_get_init_creds_opt_set_expire_callback()); otherwise the prompter
is used if present, whether or not a responder is provided.

[Regarding the double prompt:]
> Here's the trace output, but it's not very useful since it seems to end
> after the authentication and doesn't include the verify attempt.

Yeah, I don't see an explanation there.  A PKINIT PKCS12 prompter call
should be preceded by a "PKINIT initial PKCS12_parse with no password
failed" message.  There are two such trace messages, but the first comes
during prep_questions(), when prompting is deferred (instead, the
identity is saved and a question for the responder is generated).
________________________________________________
Kerberos mailing list           [hidden email]
https://mailman.mit.edu/mailman/listinfo/kerberos
Reply | Threaded
Open this post in threaded view
|

Re: Nuances of MIT Kerberos prompting

Russ Allbery-2
Greg Hudson <[hidden email]> writes:
> On 3/8/20 8:01 PM, Russ Allbery wrote:

>> I think the reason why I am confused by this is that Heimdal uses the
>> prompter to pass along informational messages such as "your principal
>> is about to expire," and I wasn't sure how MIT Kerberos would do the
>> same thing with the responder interface.  But maybe it doesn't present
>> those messages, or uses the prompter for them even if a responder is
>> provided and answers the actual questions?

> In MIT krb5 you can set an expire callback
> (krb5_get_init_creds_opt_set_expire_callback()); otherwise the prompter
> is used if present, whether or not a responder is provided.

Oh!  Okay, that makes sense.  In this case, the prompter is called with
just a banner but no question?

> Yeah, I don't see an explanation there.  A PKINIT PKCS12 prompter call
> should be preceded by a "PKINIT initial PKCS12_parse with no password
> failed" message.  There are two such trace messages, but the first comes
> during prep_questions(), when prompting is deferred (instead, the
> identity is saved and a question for the responder is generated).

I was wrong -- it's not during verify creds.  (I finally realized that I
had enough information that I could set a break point exactly where this
happens and look at the call structure.)  It appears to be called twice
during krb5_get_init_creds_password.  Here are the Kerberos library
portions of the two backtraces (the prompt in both cases is identical).
This is MIT Kerberos 1.17.

The relevant difference seems to be in frame 4 and frame 5.  Source
embedded from the krb5-1.17-final tag.  In both cases, k5_preauth then
calls the responder.

#3  0x00007ffff7f2fc9b in k5_preauth (context=context@entry=0x555555594810,
    ctx=ctx@entry=0x555555595710, in_padata=0x555555570bd0,
    must_preauth=must_preauth@entry=1, padata_out=0x555555595928,
    pa_type_out=pa_type_out@entry=0x5555555958e8)
    at ../../../../src/lib/krb5/krb/preauth2.c:1061
#4  0x00007ffff7f22397 in init_creds_step_request (out=0x7fffffffc8e0,
    ctx=0x555555595710, context=0x555555594810)
    at ../../../../src/lib/krb5/krb/get_in_tkt.c:1398

    if (ctx->request->padata == NULL && ctx->method_padata != NULL) {
        /* Retrying after KDC_ERR_PREAUTH_REQUIRED, or trying again with a
         * different mechanism after a failure. */
        TRACE_INIT_CREDS_PREAUTH(context);
        code = k5_preauth(context, ctx, ctx->method_padata, TRUE,
                          &ctx->request->padata, &ctx->selected_preauth_type);
        if (code) {
            if (save.code != 0)
                code = k5_restore_ctx_error(context, &save);
            goto cleanup;
        }
    }

#5  krb5_init_creds_step (flags=0x7fffffffc8d8, realm=0x7fffffffc900,
    out=0x7fffffffc8e0, in=<optimized out>, ctx=0x555555595710,
    context=0x555555594810) at ../../../../src/lib/krb5/krb/get_in_tkt.c:1767

    code = init_creds_step_request(context, ctx, out);
    if (code != 0)
        goto cleanup;

#6  krb5_init_creds_step (context=0x555555594810, ctx=0x555555595710,
    in=<optimized out>, out=0x7fffffffc8e0, realm=0x7fffffffc900,
    flags=0x7fffffffc8d8) at ../../../../src/lib/krb5/krb/get_in_tkt.c:1727
#7  0x00007ffff7f230b9 in k5_init_creds_get (
    context=context@entry=0x555555594810, ctx=0x555555595710,
    use_master=use_master@entry=0x7fffffffca98)
    at ../../../../src/lib/krb5/krb/get_in_tkt.c:608
#8  0x00007ffff7f23214 in k5_get_init_creds (
    context=context@entry=0x555555594810, creds=creds@entry=0x555555594cc0,
    client=client@entry=0x555555596a30, prompter=prompter@entry=0x0,
    prompter_data=prompter_data@entry=0x555555592d30,
    start_time=start_time@entry=0, in_tkt_service=0x0, options=0x555555594d40,
    gak_fct=0x7ffff7f24750 <krb5_get_as_key_password>,
    gak_data=0x7fffffffcb00, use_master=0x7fffffffca98,
    as_reply=0x7fffffffcaa0) at ../../../../src/lib/krb5/krb/get_in_tkt.c:1833
#9  0x00007ffff7f24d39 in krb5_get_init_creds_password (
    context=0x555555594810, creds=0x555555594cc0, client=0x555555596a30,
    password=password@entry=0x0, prompter=prompter@entry=0x0,
    data=data@entry=0x555555592d30, start_time=0, in_tkt_service=0x0,
    options=0x555555594d40) at ../../../../src/lib/krb5/krb/gic_pwd.c:317


#3  0x00007ffff7f2fc9b in k5_preauth (context=context@entry=0x555555594810,
    ctx=ctx@entry=0x555555595710, in_padata=0x55555559b210, must_preauth=0,
    padata_out=padata_out@entry=0x7fffffffc800,
    pa_type_out=pa_type_out@entry=0x7fffffffc7f4)
    at ../../../../src/lib/krb5/krb/preauth2.c:1061
#4  0x00007ffff7f21cab in init_creds_step_reply (in=<optimized out>,
    ctx=0x555555595710, context=0x555555594810)
    at ../../../../src/lib/krb5/krb/get_in_tkt.c:1638

    /* Process the final reply padata.  Don't restrict the preauth types or
     * record a selected preauth type. */
    ctx->allowed_preauth_type = KRB5_PADATA_NONE;
    code = k5_preauth(context, ctx, ctx->reply->padata, FALSE, &kdc_padata,
                      &kdc_pa_type);
    if (code != 0)
        goto cleanup;

#5  krb5_init_creds_step (flags=0x7fffffffc8d8, realm=0x7fffffffc900,
    out=0x7fffffffc8e0, in=<optimized out>, ctx=0x555555595710,
    context=0x555555594810) at ../../../../src/lib/krb5/krb/get_in_tkt.c:1752

    if (in->length != 0) {
        code = init_creds_step_reply(context, ctx, in);
        if (code == KRB5KRB_ERR_RESPONSE_TOO_BIG) {
            code2 = krb5int_copy_data_contents(context,
                                               ctx->encoded_previous_request,
                                               out);

#6  krb5_init_creds_step (context=0x555555594810, ctx=0x555555595710,
    in=<optimized out>, out=0x7fffffffc8e0, realm=0x7fffffffc900,
    flags=0x7fffffffc8d8) at ../../../../src/lib/krb5/krb/get_in_tkt.c:1727
#7  0x00007ffff7f230b9 in k5_init_creds_get (
    context=context@entry=0x555555594810, ctx=0x555555595710,
    use_master=use_master@entry=0x7fffffffca98)
    at ../../../../src/lib/krb5/krb/get_in_tkt.c:608
#8  0x00007ffff7f23214 in k5_get_init_creds (
    context=context@entry=0x555555594810, creds=creds@entry=0x555555594cc0,
    client=client@entry=0x555555596a30, prompter=prompter@entry=0x0,
    prompter_data=prompter_data@entry=0x555555592d30,
    start_time=start_time@entry=0, in_tkt_service=0x0, options=0x555555594d40,
    gak_fct=0x7ffff7f24750 <krb5_get_as_key_password>,
    gak_data=0x7fffffffcb00, use_master=0x7fffffffca98,
    as_reply=0x7fffffffcaa0) at ../../../../src/lib/krb5/krb/get_in_tkt.c:1833
#9  0x00007ffff7f24d39 in krb5_get_init_creds_password (
    context=0x555555594810, creds=0x555555594cc0, client=0x555555596a30,
    password=password@entry=0x0, prompter=prompter@entry=0x0,
    data=data@entry=0x555555592d30, start_time=0, in_tkt_service=0x0,
    options=0x555555594d40) at ../../../../src/lib/krb5/krb/gic_pwd.c:317

It looks like it calls the responder at two different points during the
entire preauth exchange?

Note that this may soon be irrelevant to this particular code base since
I'm probably going to switch back to a prompter.

--
Russ Allbery ([hidden email])             <https://www.eyrie.org/~eagle/>
________________________________________________
Kerberos mailing list           [hidden email]
https://mailman.mit.edu/mailman/listinfo/kerberos
Reply | Threaded
Open this post in threaded view
|

Re: Nuances of MIT Kerberos prompting

Greg Hudson
On 3/9/20 1:32 AM, Russ Allbery wrote:
>> In MIT krb5 you can set an expire callback
>> (krb5_get_init_creds_opt_set_expire_callback()); otherwise the prompter
>> is used if present, whether or not a responder is provided.
>
> Oh!  Okay, that makes sense.  In this case, the prompter is called with
> just a banner but no question?

Yes.  For this prompter call, name is NULL, banner is the formatted
expiration warning, and num_prompts is 0.
> The relevant difference seems to be in frame 4 and frame 5.  Source
> embedded from the krb5-1.17-final tag.  In both cases, k5_preauth then
> calls the responder.

Ah, two responder calls, not two prompter calls.  I was looking at the
wrong code paths.

Now that I look a the PKINIT responder logic, I agree that there is a
bug.  In the second call to k5_preauth(), we are processing the KDC
PKINIT padata supplied alongside the issued ticket, in order to
authenticate the KDC response and set the correct reply key.  PKINIT
does not need access to client certificates at this stage, but
pkinit_client_prep_questions() re-asks questions for its recorded
identities without checking the padata type or any other state that
would indicate where it is in the process.  I will file a ticket.

(The real reason kinit isn't affected is that it doesn't use a responder
callback.)
________________________________________________
Kerberos mailing list           [hidden email]
https://mailman.mit.edu/mailman/listinfo/kerberos
Reply | Threaded
Open this post in threaded view
|

Re: Nuances of MIT Kerberos prompting

Russ Allbery-2
Greg Hudson <[hidden email]> writes:

> Yes.  For this prompter call, name is NULL, banner is the formatted
> expiration warning, and num_prompts is 0.

Thanks!

> Ah, two responder calls, not two prompter calls.  I was looking at the
> wrong code paths.

Oh, sorry, poor bug report on my part.

> Now that I look a the PKINIT responder logic, I agree that there is a
> bug.  In the second call to k5_preauth(), we are processing the KDC
> PKINIT padata supplied alongside the issued ticket, in order to
> authenticate the KDC response and set the correct reply key.  PKINIT
> does not need access to client certificates at this stage, but
> pkinit_client_prep_questions() re-asks questions for its recorded
> identities without checking the padata type or any other state that
> would indicate where it is in the process.  I will file a ticket.

Thanks!

> (The real reason kinit isn't affected is that it doesn't use a responder
> callback.)

Yes, that makes perfect sense in retrospect.  I should have started with
gdb before speculating.

--
Russ Allbery ([hidden email])             <https://www.eyrie.org/~eagle/>
________________________________________________
Kerberos mailing list           [hidden email]
https://mailman.mit.edu/mailman/listinfo/kerberos