Difference between revisions of "Projects/Initial creds improvements"
(→Stage 3: Pre-authentication) |
(→Background) |
||
(15 intermediate revisions by the same user not shown) | |||
Line 1: | Line 1: | ||
{{project-early}} |
{{project-early}} |
||
− | This project is |
+ | This project is mostly internal design changes to the initial creds API. The immediate user-visible impact should not be significant. |
==Background== |
==Background== |
||
Line 7: | Line 7: | ||
get_in_tkt.c implements the krb5_init_creds_step() API, which is responsible for processing a KDC reply and generating the next request (or terminating the exchange). Currently this function is divided into two helper functions: init_creds_step_reply(), which examines the KDC reply and alters the request state, and init_creds_step_request(), which generates a request based on the current state. |
get_in_tkt.c implements the krb5_init_creds_step() API, which is responsible for processing a KDC reply and generating the next request (or terminating the exchange). Currently this function is divided into two helper functions: init_creds_step_reply(), which examines the KDC reply and alters the request state, and init_creds_step_request(), which generates a request based on the current state. |
||
− | Several conditions can cause a "restart" of the exchange, discarding any existing KDC padata and FAST state: a realm referral, a KDC_ERR_PREAUTH_FAILED response when the client sent only informational padata, or a PA-FX-FAST padata element when the client did not use FAST but has an armor key. |
||
+ | The current code has the following limitations, most of which do not arise in current pre-authentication scenarios: |
||
− | Although the krb5_init_creds_context structure contains many state variables, they do not track a progression towards the end goal. As a result, an attacker impersonating a KDC can send replies to the client in non-sensical orders, which the client will attempt to interpret. This project will add stage-tracking so that the client can better manage request state. |
||
+ | * If two krb5_init_creds_step() operations are interleaved using the same krb5_context, they erroneously share per-request per-module data pointers, which may confuse clpreauth modules. (They also share the "tried" list which tracks previously tried mechanisms, but that list is not currently doing much.) {{bug|7877}} [now fixed] |
||
− | ==Proposed stages== |
||
+ | * Support for optimistic preauth is very limited. It must be initiated by the calling application, not by configuration, and there is no way to specify salt or s2kparams for producing the reply key. We currently retry optimistic preauth after realm referrals and other conversation restarts, which may be unnecessarily complicated. |
||
− | ===Stage 1: Optimistic pre-authentication=== |
||
+ | * If a preauth mechanism fails (even during optimistic preauth), we do not continue on to other mechanisms, unless it failed on the client side during method-data processing. |
||
− | If no optimistic pre-authentication information is available, and the client moves immediately to stage 2. In the future, we may implement optimistic PA-SPAKE, in which case there will always be an optimistic pre-authentication step. |
||
+ | * If we receive a PREAUTH_FAILED error from the KDC with e-data containing METHOD-DATA (as is recommended for KDCs in RFC 6113), we invoke clpreauth module tryagain methods, which is fruitless and can obscure error messages. |
||
− | We currently have minimal support for optimistic pre-authentication: the caller can specify a list of preauth types with krb5_get_init_creds_opt_set_preauth_list() to activate those mechanisms for the initial request; any parameters needed by the preauth mechs must be determined via gic options or profile variables, none of which we currently have. Optimistic pre-authentication is currently performed after restarts, but this property is not important; in the proposed new plan, only one optimistically pre-authenticated request will be generated. |
||
+ | * clpreauth modules do not get much information about the padata they are processing. It could be from optimistic preauth, from KDC METHOD-DATA in a PREAUTH_REQUIRED error, from KDC METHOD-DATA in a MORE_PREAUTH_DATA_REQUIRED error, or from an AS-REP. (Other KDC errors are handled via the tryagain method, so those are distinguishable.) |
||
− | If the KDC responds with KDC_ERR_PREAUTH_FAILED, the client should move to stage 3 if the error includes METHOD-DATA, or to stage 2 if it does not. If the KDC responds with KDC_ERR_MORE_PREAUTH_DATA_REQUIRED, it should respond as in stage 3. Otherwise, the client should respond as in stage 2. |
||
+ | * clpreauth modules cannot indicate that they expect additional padata values before the mechanism is complete. In the middle of a conversation, a KDC could issue an AS-REP with no padata and the client would try to decrypt it with whatever the reply key currently is. This limitation has no current security consequences, but could become important in the future. |
||
− | (XXX problem: if the KDC responds with PREAUTH_FAILED and a hint list, we should really move to stage 3, especially if we're doing optimistic PA-SPAKE all the time. But what if the KDC them comes back with a realm referral? The whole idea is not to backtrack stages. This needs more thought.) |
||
+ | * The preferred_preauth_types realm variable is a hack; the order of KDC padata should be the preferred order. However, it is important to keep trying PKINIT first by default as KDCs do not always put it before encrypted timestamp when they ought to. |
||
− | ===Stage 2: Unauthenticated request=== |
||
+ | * krb5_get_init_creds_get_error() cannot retrieve most intermediate errors. {{bug|8222}} |
||
− | In this stage, the client sends a request with no substantive padata and no cookie. It may send informational padata such as PA-REQ-ENC-PA-REP or PA-AS-FRESHNESS. |
||
+ | ==Improvements== |
||
− | If the KDC responds with a realm referral (KDC_ERR_WRONG_REALM or KDC_ERR_C_PRINCIPAL_UNKNOWN with a distinct crealm), the client should remain in stage 2 and retry in the new realm. |
||
+ | ===Proper scoping of per-request preauth data=== |
||
− | If the KDC responds with KDC_ERR_PREAUTH_FAILED, it is likely a pre-1.7 MIT krb5 KDC which does not ignore unknown client padata types. The client should remain in stage 2 and retry without informational padata. This retry should not be performed twice without changing realms. |
||
+ | The per-request krb5_clpreauth_modreq pointers for each module should be maintained in an object separate from krb5_preauth_context. This object should be linked from krb5_init_creds_context. |
||
− | If the KDC responds with an error containing padata, one of the padata elements is PA-FX-FAST, and the client did not use FAST but has an armor key, the client should remain in stage 2 and should retry using FAST. This retry should be exclusive with the previous retry, as pre-1.7 MIT krb5 KDCs do not support FAST. |
||
+ | The "tried" list is currently unused and should be removed. A list of preauthentication mechanisms which have failed should be maintained in the per-request preauth state. |
||
− | If the KDC responds with KDC_ERR_PREAUTH_REQUIRED, the client should move to stage 3. |
||
+ | [This work is now done; see commit b061f419cfc9653b7549b905e54fbbd78deea49e.] |
||
− | If the KDC responds with any other error, the client should abort the exchange. |
||
+ | ===Optimistic preauth=== |
||
− | If the KDC responds with an AS-REP, the client should move to stage 4. |
||
+ | TBD |
||
− | ===Stage 3: Pre-authentication=== |
||
+ | ===Continuing after failures=== |
||
− | In this stage, the client sends a pre-authenticated request using the METHOD-DATA from a previous KDC error, which we will call the "current hint list." The client iterates over the current hint list in the order supplied by the KDC (perhaps as re-sorted by preferred_preauth_types) and selects the first non-informational pre-authentication mechanism for which it can produce a padata value. If no padata value can be generated, the client should abort the exchange. |
||
+ | A preauth mechanism can fail in several ways: |
||
− | If the KDC responds with KDC_ERR_PREAUTH_REQUIRED, the client should remain in stage 3, replace the current hint list, and produce a new pre-authenticated message. This case should be unusual, the MIT KDC does it as part of the SAM-2 Duo integration. The client may restrict this case to the situation when the previous requested used SAM-2, and may restrict itself to using SAM-2 for subsequent requests, but it must be willing to perform multiple KDC_ERR_PREAUTH_REQUIRED -> PA-SAM-2 round trips. |
||
+ | * The clpreauth module's process method can fail to generate initial padata value during optimistic preauth or processing of KDC method-data. In this case we already continue on to the next mechanism. |
||
− | If the KDC responds with KDC_ERR_MORE_PREAUTH_REQUIRED, the client should remain in stage 3 and continue using the same pre-authentication mechanism, passing the padata for that type to the mechanism's handler to produce a new padata value. If the clpreauth module cannot continue, the client should move on to the next untried padata type in the current hint list. |
||
+ | * The KDC can reply with KDC_ERR_PREAUTH_FAILED, with or without METHOD-DATA. |
||
− | If the KDC responds with KDC_ERR_PREAUTH_FAILED, the client should remain in stage 3 and try the next untried padata type in the current hint list. |
||
+ | * The KDC can reply with KDC_ERR_MORE_PREAUTH_DATA_REQUIRED, and the process method can fail to generate a padata value. |
||
− | If the KDC responds with KDC_ERR_PREAUTH_EXPIRED, the client should discard the current hint list and move back to stage 2. (This is the only case where stage back-tracking occurs. There is no way to avoid it, as any previously received hint list may include a cookie which is now expired.) If the AS key was generated during a previous attempt, it should be retained. Any responder items should also be retained. |
||
+ | * The KDC can reply with another error containing e-data, and the tryagain method can fail to generate a padata value. |
||
− | If the KDC responds with any other error containing padata, the client should remain in stage 3 and try to produce a new padata value using the current mechanism's tryagain method. |
||
+ | If optimistic preauth results in KDC_ERR_PREAUTH_FAILED, we should discard per-request preauth state and begin anew. If the KDC included METHOD-DATA, we should use that; otherwise we should send an unauthenticated request in order to get a KDC_ERR_PREAUTH_REQUIRED error with METHOD-DATA. If optimistic preauth results in a different kind of failure, we should send an unauthenticated request. |
||
− | If the KDC responds with an AS-REP, the client should move to stage 4. |
||
+ | The first time we receive a KDC METHOD-DATA list, we should remember it for the purpose of continuing after failures. We should discard this list on a KDC_ERR_PREAUTH_EXPIRED error (as the METHOD-DATA list may include a stale cookie). If we receive multiple METHOD-DATA lists, we can either replace the initial list or ignore the subsequent lists; it should not be very important. However, the METHOD-DATA in a KDC_ERR_MORE_PREAUTH_DATA_REQUIRED error should be treated separately from this list, as it is part of a multi-hop conversation for a particular mechanism. |
||
− | ===Stage 4: AS-REP processing=== |
||
+ | We should maintain a list of preauth mechanisms which have failed so that we don't try them again. This list should be separate from the KDC METHOD-DATA and the preauth request state, as we should not retry a failed preauth mechanism after KDC_ERR_PREAUTH_EXPIRED. This list should be discarded on a realm referral, although a realm referral during a preauthentication conversation should be rare. A KDC_ERR_PREAUTH_EXPIRED error should not count as failing. Optimistic preauthentication failure should not count either, as it may only have failed for lack of information from the KDC. |
||
− | In this stage, the client processes the padata in the AS-REP, and either aborts or successfully terminates the exchange. |
||
+ | A preauth mechanism may set the reply key as an output. Currently we store this output in the same state variable as is used to cache the gak_fct result. We will need to use a separate variable to support continuing after a preauth mechanism fails, so that we can clear the reply key used by the previous mechanism without also invalidating the gak_fct cached result (and therefore perhaps asking for the password again). |
||
+ | |||
+ | ===Stage tracking for clpreauth modules=== |
||
+ | |||
+ | When processing a KDC reply or beginning an exchange, we should keep track of what we are doing: (1) optimistic preauth, (2) sending an unauthenticated message, (3) beginning a preauth exchange using KDC METHOD-DATA, (4) continuing a preauth exchange using the METHOD-DATA in a KDC_ERR_MORE_PREAUTH_DATA_REQUIRED error, (5) recovering from a KDC error with e-data, or (6) processing the pa-data in an AS-REP. This stage tracking may also help to organize the get_in_tkt.c logic as we add retries after mechanism failures. |
||
+ | |||
+ | The stage should be made available to clpreauth modules via a callback. During the process method, only stages 1, 3, 4, and 6 will be possible; during the tryagain method, only stage 5 will be possible. |
||
+ | |||
+ | Alternatively, we could create four new clpreauth methods corresponding to the four stages currently served by the process method. These new methods could have shorter signatures if we omit some of the parameters (opt, request, encoded_request_body, encoded_previous_request) and make them available via callbacks. If a module does not supply one of the new methods, preauth2.c would fall back to the process method. |
||
+ | |||
+ | ===clpreauth module control of mechanism termination=== |
||
+ | |||
+ | We can add a callback for clpreauth modules to indicate that the mechanism is unfinished; the module would then be responsible for unsetting the flag via the same callback when the mechanism is complete. If the mechanism is incomplete after AS-REP padata processing, the client would abort the exchange. |
Latest revision as of 23:05, 30 January 2017
This project is mostly internal design changes to the initial creds API. The immediate user-visible impact should not be significant.
Contents
Background
get_in_tkt.c implements the krb5_init_creds_step() API, which is responsible for processing a KDC reply and generating the next request (or terminating the exchange). Currently this function is divided into two helper functions: init_creds_step_reply(), which examines the KDC reply and alters the request state, and init_creds_step_request(), which generates a request based on the current state.
The current code has the following limitations, most of which do not arise in current pre-authentication scenarios:
- If two krb5_init_creds_step() operations are interleaved using the same krb5_context, they erroneously share per-request per-module data pointers, which may confuse clpreauth modules. (They also share the "tried" list which tracks previously tried mechanisms, but that list is not currently doing much.) [krbdev.mit.edu #7877] [now fixed]
- Support for optimistic preauth is very limited. It must be initiated by the calling application, not by configuration, and there is no way to specify salt or s2kparams for producing the reply key. We currently retry optimistic preauth after realm referrals and other conversation restarts, which may be unnecessarily complicated.
- If a preauth mechanism fails (even during optimistic preauth), we do not continue on to other mechanisms, unless it failed on the client side during method-data processing.
- If we receive a PREAUTH_FAILED error from the KDC with e-data containing METHOD-DATA (as is recommended for KDCs in RFC 6113), we invoke clpreauth module tryagain methods, which is fruitless and can obscure error messages.
- clpreauth modules do not get much information about the padata they are processing. It could be from optimistic preauth, from KDC METHOD-DATA in a PREAUTH_REQUIRED error, from KDC METHOD-DATA in a MORE_PREAUTH_DATA_REQUIRED error, or from an AS-REP. (Other KDC errors are handled via the tryagain method, so those are distinguishable.)
- clpreauth modules cannot indicate that they expect additional padata values before the mechanism is complete. In the middle of a conversation, a KDC could issue an AS-REP with no padata and the client would try to decrypt it with whatever the reply key currently is. This limitation has no current security consequences, but could become important in the future.
- The preferred_preauth_types realm variable is a hack; the order of KDC padata should be the preferred order. However, it is important to keep trying PKINIT first by default as KDCs do not always put it before encrypted timestamp when they ought to.
- krb5_get_init_creds_get_error() cannot retrieve most intermediate errors. [krbdev.mit.edu #8222]
Improvements
Proper scoping of per-request preauth data
The per-request krb5_clpreauth_modreq pointers for each module should be maintained in an object separate from krb5_preauth_context. This object should be linked from krb5_init_creds_context.
The "tried" list is currently unused and should be removed. A list of preauthentication mechanisms which have failed should be maintained in the per-request preauth state.
[This work is now done; see commit b061f419cfc9653b7549b905e54fbbd78deea49e.]
Optimistic preauth
TBD
Continuing after failures
A preauth mechanism can fail in several ways:
- The clpreauth module's process method can fail to generate initial padata value during optimistic preauth or processing of KDC method-data. In this case we already continue on to the next mechanism.
- The KDC can reply with KDC_ERR_PREAUTH_FAILED, with or without METHOD-DATA.
- The KDC can reply with KDC_ERR_MORE_PREAUTH_DATA_REQUIRED, and the process method can fail to generate a padata value.
- The KDC can reply with another error containing e-data, and the tryagain method can fail to generate a padata value.
If optimistic preauth results in KDC_ERR_PREAUTH_FAILED, we should discard per-request preauth state and begin anew. If the KDC included METHOD-DATA, we should use that; otherwise we should send an unauthenticated request in order to get a KDC_ERR_PREAUTH_REQUIRED error with METHOD-DATA. If optimistic preauth results in a different kind of failure, we should send an unauthenticated request.
The first time we receive a KDC METHOD-DATA list, we should remember it for the purpose of continuing after failures. We should discard this list on a KDC_ERR_PREAUTH_EXPIRED error (as the METHOD-DATA list may include a stale cookie). If we receive multiple METHOD-DATA lists, we can either replace the initial list or ignore the subsequent lists; it should not be very important. However, the METHOD-DATA in a KDC_ERR_MORE_PREAUTH_DATA_REQUIRED error should be treated separately from this list, as it is part of a multi-hop conversation for a particular mechanism.
We should maintain a list of preauth mechanisms which have failed so that we don't try them again. This list should be separate from the KDC METHOD-DATA and the preauth request state, as we should not retry a failed preauth mechanism after KDC_ERR_PREAUTH_EXPIRED. This list should be discarded on a realm referral, although a realm referral during a preauthentication conversation should be rare. A KDC_ERR_PREAUTH_EXPIRED error should not count as failing. Optimistic preauthentication failure should not count either, as it may only have failed for lack of information from the KDC.
A preauth mechanism may set the reply key as an output. Currently we store this output in the same state variable as is used to cache the gak_fct result. We will need to use a separate variable to support continuing after a preauth mechanism fails, so that we can clear the reply key used by the previous mechanism without also invalidating the gak_fct cached result (and therefore perhaps asking for the password again).
Stage tracking for clpreauth modules
When processing a KDC reply or beginning an exchange, we should keep track of what we are doing: (1) optimistic preauth, (2) sending an unauthenticated message, (3) beginning a preauth exchange using KDC METHOD-DATA, (4) continuing a preauth exchange using the METHOD-DATA in a KDC_ERR_MORE_PREAUTH_DATA_REQUIRED error, (5) recovering from a KDC error with e-data, or (6) processing the pa-data in an AS-REP. This stage tracking may also help to organize the get_in_tkt.c logic as we add retries after mechanism failures.
The stage should be made available to clpreauth modules via a callback. During the process method, only stages 1, 3, 4, and 6 will be possible; during the tryagain method, only stage 5 will be possible.
Alternatively, we could create four new clpreauth methods corresponding to the four stages currently served by the process method. These new methods could have shorter signatures if we omit some of the parameters (opt, request, encoded_request_body, encoded_previous_request) and make them available via callbacks. If a module does not supply one of the new methods, preauth2.c would fall back to the process method.
clpreauth module control of mechanism termination
We can add a callback for clpreauth modules to indicate that the mechanism is unfinished; the module would then be responsible for unsetting the flag via the same callback when the mechanism is complete. If the mechanism is incomplete after AS-REP padata processing, the client would abort the exchange.