Optional backwards-compatible anon-pkinit behaviour
* Anonymous pkinit responses from the KDC where the name type is not well-known (as issued by 7.5 KDCs and earlier) are accepted by the client. There is no need for the client to strictly enforce the name type. * With historical_anon_pkinit = true, the kinit(1) client's "--anonymous" option only performs anon pkinit, and does not require an '@' prefix for the realm argument. * With historical_anon_realm = true, the KDC issues anon pkinit tickets with the legacy pre-7.0 "real" realm.
This commit is contained in:

committed by
Viktor Dukhovni

parent
f40d393c83
commit
fae8df3839
@@ -59,6 +59,7 @@ krb5_kdc_get_config(krb5_context context, krb5_kdc_configuration **config)
|
||||
c->check_ticket_addresses = TRUE;
|
||||
c->allow_null_ticket_addresses = TRUE;
|
||||
c->allow_anonymous = FALSE;
|
||||
c->historical_anon_realm = FALSE;
|
||||
c->strict_nametypes = FALSE;
|
||||
c->trpolicy = TRPOLICY_ALWAYS_CHECK;
|
||||
c->enable_pkinit = FALSE;
|
||||
@@ -162,6 +163,12 @@ krb5_kdc_get_config(krb5_context context, krb5_kdc_configuration **config)
|
||||
"kdc",
|
||||
"allow-anonymous", NULL);
|
||||
|
||||
c->historical_anon_realm =
|
||||
krb5_config_get_bool_default(context, NULL,
|
||||
c->historical_anon_realm,
|
||||
"kdc",
|
||||
"historical_anon_realm", NULL);
|
||||
|
||||
c->strict_nametypes =
|
||||
krb5_config_get_bool_default(context, NULL,
|
||||
c->strict_nametypes,
|
||||
|
13
kdc/kdc.8
13
kdc/kdc.8
@@ -141,6 +141,19 @@ Permit tickets with no addresses.
|
||||
This option is only relevant when check-ticket-addresses is TRUE.
|
||||
.It Li allow-anonymous = Va boolean
|
||||
Permit anonymous tickets with no addresses.
|
||||
.It Li historical_anon_realm = Va boolean
|
||||
Enables pre-7.0 non-RFC-comformant KDC behavior.
|
||||
With this option set to
|
||||
.Li true
|
||||
the client realm in anonymous pkinit AS replies will be the requested realm,
|
||||
rather than the RFC-conformant
|
||||
.Li WELLKNOWN:ANONYMOUS
|
||||
realm.
|
||||
This can have a security impact on servers that expect to grant access to
|
||||
anonymous-but-authenticated to the KDC users of the realm in question:
|
||||
they would also grant access to unauthenticated anonymous users.
|
||||
As such, it is not recommend to set this option to
|
||||
.Li true.
|
||||
.It Li max-kdc-datagram-reply-length = Va number
|
||||
Maximum packet size the UDP rely that the KDC will transmit, instead
|
||||
the KDC sends back a reply telling the client to use TCP instead.
|
||||
|
@@ -69,6 +69,7 @@ typedef struct krb5_kdc_configuration {
|
||||
krb5_boolean check_ticket_addresses;
|
||||
krb5_boolean allow_null_ticket_addresses;
|
||||
krb5_boolean allow_anonymous;
|
||||
krb5_boolean historical_anon_realm;
|
||||
krb5_boolean strict_nametypes;
|
||||
enum krb5_kdc_trpolicy trpolicy;
|
||||
|
||||
|
@@ -117,10 +117,10 @@ is_default_salt_p(const krb5_salt *default_salt, const Key *key)
|
||||
}
|
||||
|
||||
|
||||
static krb5_boolean
|
||||
is_anon_as_request_p(kdc_request_t r)
|
||||
krb5_boolean
|
||||
_kdc_is_anon_request(const KDC_REQ *req)
|
||||
{
|
||||
KDC_REQ_BODY *b = &r->req.req_body;
|
||||
const KDC_REQ_BODY *b = &req->req_body;
|
||||
|
||||
/*
|
||||
* Versions of Heimdal from 0.9rc1 through 1.50 use bit 14 instead
|
||||
@@ -464,7 +464,7 @@ pa_enc_chal_validate(kdc_request_t r, const PA_DATA *pa)
|
||||
|
||||
heim_assert(r->armor_crypto != NULL, "ENC-CHAL called for non FAST");
|
||||
|
||||
if (is_anon_as_request_p(r)) {
|
||||
if (_kdc_is_anon_request(&r->req)) {
|
||||
ret = KRB5KRB_AP_ERR_BAD_INTEGRITY;
|
||||
kdc_log(r->context, r->config, 0, "ENC-CHALL doesn't support anon");
|
||||
return ret;
|
||||
@@ -1795,7 +1795,7 @@ _kdc_as_rep(kdc_request_t r,
|
||||
*/
|
||||
|
||||
if (_kdc_is_anonymous(context, r->client_princ) &&
|
||||
!is_anon_as_request_p(r)) {
|
||||
!_kdc_is_anon_request(&r->req)) {
|
||||
kdc_log(context, config, 0, "Anonymous client w/o anonymous flag");
|
||||
ret = KRB5KDC_ERR_BADOPTION;
|
||||
goto out;
|
||||
@@ -1969,7 +1969,7 @@ _kdc_as_rep(kdc_request_t r,
|
||||
* send requre preauth is its required or anon is requested,
|
||||
* anon is today only allowed via preauth mechanisms.
|
||||
*/
|
||||
if (require_preauth_p(r) || is_anon_as_request_p(r)) {
|
||||
if (require_preauth_p(r) || _kdc_is_anon_request(&r->req)) {
|
||||
ret = KRB5KDC_ERR_PREAUTH_REQUIRED;
|
||||
_kdc_set_e_text(r, "Need to use PA-ENC-TIMESTAMP/PA-PK-AS-REQ");
|
||||
goto out;
|
||||
@@ -2002,7 +2002,7 @@ _kdc_as_rep(kdc_request_t r,
|
||||
if(ret)
|
||||
goto out;
|
||||
|
||||
if (is_anon_as_request_p(r)) {
|
||||
if (_kdc_is_anon_request(&r->req)) {
|
||||
ret = _kdc_check_anon_policy(context, config, r->client, r->server);
|
||||
if (ret) {
|
||||
_kdc_set_e_text(r, "Anonymous ticket requests are disabled");
|
||||
@@ -2036,7 +2036,8 @@ _kdc_as_rep(kdc_request_t r,
|
||||
rep.pvno = 5;
|
||||
rep.msg_type = krb_as_rep;
|
||||
|
||||
if (_kdc_is_anonymous(context, r->client_princ)) {
|
||||
if (!config->historical_anon_realm &&
|
||||
_kdc_is_anonymous(context, r->client_princ)) {
|
||||
Realm anon_realm = KRB5_ANON_REALM;
|
||||
ret = copy_Realm(&anon_realm, &rep.crealm);
|
||||
} else if (f.canonicalize || r->client->entry.flags.force_canonicalize)
|
||||
|
@@ -623,7 +623,8 @@ _kdc_pk_rd_padata(krb5_context context,
|
||||
hx509_certs signer_certs;
|
||||
int flags = HX509_CMS_VS_ALLOW_DATA_OID_MISMATCH; /* BTMM */
|
||||
|
||||
if (_kdc_is_anonymous(context, client->entry.principal))
|
||||
if (_kdc_is_anonymous(context, client->entry.principal)
|
||||
|| (config->historical_anon_realm && _kdc_is_anon_request(req)))
|
||||
flags |= HX509_CMS_VS_ALLOW_ZERO_SIGNER;
|
||||
|
||||
ret = hx509_cms_verify_signed(context->hx509ctx,
|
||||
@@ -1676,7 +1677,8 @@ _kdc_pk_check_client(krb5_context context,
|
||||
size_t i;
|
||||
|
||||
if (cp->cert == NULL) {
|
||||
if (!_kdc_is_anonymous(context, client->entry.principal))
|
||||
if (!_kdc_is_anonymous(context, client->entry.principal)
|
||||
&& !config->historical_anon_realm)
|
||||
return KRB5KDC_ERR_BADOPTION;
|
||||
|
||||
*subject_name = strdup("<unauthenticated anonymous client>");
|
||||
|
@@ -166,11 +166,21 @@ in
|
||||
.It Fl A , Fl Fl no-addresses
|
||||
Request a ticket with no addresses.
|
||||
.It Fl n , Fl Fl anonymous
|
||||
Request an anonymous ticket. If the principal is specified as @REALM, then
|
||||
Request an anonymous ticket.
|
||||
With the default (false) setting of the
|
||||
.Ar historical_anon_pkinit
|
||||
configuration parameter, if the principal is specified as @REALM, then
|
||||
anonymous PKINIT will be used to acquire an unauthenticated anonymous ticket
|
||||
and both the client name and realm in the returned ticket will be anonymized.
|
||||
and both the client name and (with fully RFC-comformant KDCs) realm in the
|
||||
returned ticket will be anonymized.
|
||||
Otherwise, authentication proceeds as normal and the anonymous ticket will have
|
||||
only the client name anonymized.
|
||||
With
|
||||
.Ar historical_anon_pkinit
|
||||
set to
|
||||
.Li true ,
|
||||
the principal is interpreted as a realm even without an at-sign prefix, and it
|
||||
is not possible to obtain authenticated anonymized tickets.
|
||||
.It Fl Fl enterprise
|
||||
Parse principal as a enterprise (KRB5-NT-ENTERPRISE) name. Enterprise
|
||||
names are email like principals that are stored in the name part of
|
||||
|
@@ -669,7 +669,7 @@ get_new_tickets(krb5_context context,
|
||||
}
|
||||
} else if (pk_user_id || ent_user_id ||
|
||||
krb5_principal_is_anonymous(context, principal, KRB5_ANON_MATCH_ANY)) {
|
||||
|
||||
/* nop */;
|
||||
} else if (!interactive && passwd[0] == '\0') {
|
||||
static int already_warned = 0;
|
||||
|
||||
@@ -1272,6 +1272,7 @@ main(int argc, char **argv)
|
||||
struct sigaction sa;
|
||||
#endif
|
||||
krb5_boolean unique_ccache = FALSE;
|
||||
krb5_boolean historical_anon_pkinit = FALSE;
|
||||
int anonymous_pkinit = FALSE;
|
||||
|
||||
setprogname(argv[0]);
|
||||
@@ -1300,6 +1301,9 @@ main(int argc, char **argv)
|
||||
argc -= optidx;
|
||||
argv += optidx;
|
||||
|
||||
krb5_appdefault_boolean(context, "kinit", NULL, "historical_anon_pkinit",
|
||||
FALSE, &historical_anon_pkinit);
|
||||
|
||||
/*
|
||||
* Open the keytab now, we use the keytab to determine the principal's
|
||||
* realm when the requested principal has no realm.
|
||||
@@ -1332,6 +1336,16 @@ main(int argc, char **argv)
|
||||
krb5_err(context, 1, ret, "krb5_make_principal");
|
||||
krb5_principal_set_type(context, principal, KRB5_NT_WELLKNOWN);
|
||||
anonymous_pkinit = TRUE;
|
||||
} else if (anonymous_flag && historical_anon_pkinit) {
|
||||
char *realm = argc == 0 ? get_default_realm(context) :
|
||||
argv[0][0] == '@' ? &argv[0][1] : argv[0];
|
||||
|
||||
ret = krb5_make_principal(context, &principal, realm,
|
||||
KRB5_WELLKNOWN_NAME, KRB5_ANON_NAME, NULL);
|
||||
if (ret)
|
||||
krb5_err(context, 1, ret, "krb5_make_principal");
|
||||
krb5_principal_set_type(context, principal, KRB5_NT_WELLKNOWN);
|
||||
anonymous_pkinit = TRUE;
|
||||
} else if (use_keytab || keytab_str) {
|
||||
get_princ_kt(context, &principal, argv[0]);
|
||||
} else {
|
||||
|
@@ -148,6 +148,19 @@ Forward credentials to remote host (for
|
||||
.Xr rsh 1 ,
|
||||
.Xr telnet 1 ,
|
||||
etc).
|
||||
.It Li historical_anon_pkinit = Va boolean
|
||||
Enable legacy anonymous pkinit command-line syntax.
|
||||
With this option set to
|
||||
.Li true,
|
||||
the
|
||||
.Xr kinit 1
|
||||
.Fl Fl anonymous
|
||||
command with no principal argument specified will request an anonymous pkinit
|
||||
ticket from the default realm.
|
||||
If a principal argument is specified, it is used as an explicit realm name for
|
||||
anonymous pkinit even without an
|
||||
.Li @
|
||||
prefix.
|
||||
.El
|
||||
.It Li [libdefaults]
|
||||
.Bl -tag -width "xxx" -offset indent
|
||||
@@ -654,8 +667,21 @@ Allow address-less tickets.
|
||||
.\" XXX
|
||||
.It Li allow-anonymous = Va BOOL
|
||||
If the kdc is allowed to hand out anonymous tickets.
|
||||
.It Li historical_anon_realm = Va boolean
|
||||
Enables pre-7.0 non-RFC-comformant KDC behavior.
|
||||
With this option set to
|
||||
.Li true
|
||||
the client realm in anonymous pkinit AS replies will be the requested realm,
|
||||
rather than the RFC-conformant
|
||||
.Li WELLKNOWN:ANONYMOUS
|
||||
realm.
|
||||
This can have a security impact on servers that expect to grant access to
|
||||
anonymous-but-authenticated to the KDC users of the realm in question:
|
||||
they would also grant access to unauthenticated anonymous users.
|
||||
As such, it is not recommend to set this option to
|
||||
.Li true.
|
||||
.It Li encode_as_rep_as_tgs_rep = Va BOOL
|
||||
Encode as-rep as tgs-rep tobe compatible with mistakes older DCE secd did.
|
||||
Encode as-rep as tgs-rep to be compatible with mistakes older DCE secd did.
|
||||
.\" XXX
|
||||
.It Li kdc_warn_pwexpire = Va TIME
|
||||
The time before expiration that the user should be warned that her
|
||||
|
@@ -960,8 +960,11 @@ typedef struct krb5_name_canon_iterator_data *krb5_name_canon_iterator;
|
||||
*/
|
||||
#define KRB5_ANON_MATCH_AUTHENTICATED 1 /* authenticated with anon flag */
|
||||
#define KRB5_ANON_MATCH_UNAUTHENTICATED 2 /* anonymous PKINIT */
|
||||
#define KRB5_ANON_MATCH_ANY ( KRB5_ANON_MATCH_AUTHENTICATED | KRB5_ANON_MATCH_UNAUTHENTICATED )
|
||||
|
||||
#define KRB5_ANON_IGNORE_NAME_TYPE 4 /* don't check the name type */
|
||||
#define KRB5_ANON_MATCH_ANY ( KRB5_ANON_MATCH_AUTHENTICATED | \
|
||||
KRB5_ANON_MATCH_UNAUTHENTICATED )
|
||||
#define KRB5_ANON_MATCH_ANY_NONT ( KRB5_ANON_MATCH_ANY | \
|
||||
KRB5_ANON_IGNORE_NAME_TYPE )
|
||||
|
||||
/*
|
||||
*
|
||||
|
@@ -1258,19 +1258,43 @@ krb5_principal_is_anonymous(krb5_context context,
|
||||
krb5_const_principal p,
|
||||
unsigned int flags)
|
||||
{
|
||||
int anon_realm;
|
||||
/*
|
||||
* Heimdal versions 7.5 and below left the name-type at KRB5_NT_PRINCIPAL
|
||||
* even with anonymous pkinit responses. To retain interoperability with
|
||||
* legacy KDCs, the name-type is not checked by the client after requesting
|
||||
* a fully anonymous ticket.
|
||||
*/
|
||||
if (!(flags & KRB5_ANON_IGNORE_NAME_TYPE) &&
|
||||
p->name.name_type != KRB5_NT_WELLKNOWN &&
|
||||
p->name.name_type != KRB5_NT_UNKNOWN)
|
||||
return FALSE;
|
||||
|
||||
if ((p->name.name_type != KRB5_NT_WELLKNOWN &&
|
||||
p->name.name_type != KRB5_NT_UNKNOWN) ||
|
||||
p->name.name_string.len != 2 ||
|
||||
if (p->name.name_string.len != 2 ||
|
||||
strcmp(p->name.name_string.val[0], KRB5_WELLKNOWN_NAME) != 0 ||
|
||||
strcmp(p->name.name_string.val[1], KRB5_ANON_NAME) != 0)
|
||||
return FALSE;
|
||||
|
||||
anon_realm = strcmp(p->realm, KRB5_ANON_REALM) == 0;
|
||||
/*
|
||||
* While unauthenticated clients SHOULD get "WELLKNOWN:ANONYMOUS" as their
|
||||
* realm, Heimdal KDCs prior to 7.0 returned the requested realm. While
|
||||
* such tickets might lead *servers* to unwittingly grant access to fully
|
||||
* anonymous clients, trusting that the client was authenticated to the
|
||||
* realm in question, doing it right is the KDC's job, the client should
|
||||
* not refuse such a ticket.
|
||||
*
|
||||
* If we ever do decide to enforce WELLKNOWN:ANONYMOUS for unauthenticated
|
||||
* clients, it is essential that calls that pass KRB5_ANON_MATCH_ANY still
|
||||
* ignore the realm, as in that case either case matches one of the two
|
||||
* possible conditions.
|
||||
*/
|
||||
if (flags & KRB5_ANON_MATCH_UNAUTHENTICATED)
|
||||
return TRUE;
|
||||
|
||||
return ((flags & KRB5_ANON_MATCH_AUTHENTICATED) && !anon_realm) ||
|
||||
((flags & KRB5_ANON_MATCH_UNAUTHENTICATED) && anon_realm);
|
||||
/*
|
||||
* Finally, authenticated clients that asked to be only anonymized do
|
||||
* legitimately expect a non-anon realm.
|
||||
*/
|
||||
return strcmp(p->realm, KRB5_ANON_REALM) != 0;
|
||||
}
|
||||
|
||||
static int
|
||||
|
@@ -541,10 +541,22 @@ check_client_anonymous(krb5_context context,
|
||||
if (!rep->enc_part.flags.anonymous)
|
||||
return KRB5KDC_ERR_BADOPTION;
|
||||
|
||||
/*
|
||||
* Here we must validate that the AS returned a ticket of the expected type
|
||||
* for either a fully anonymous request, or authenticated request for an
|
||||
* anonymous ticket. If this is a TGS request, we're done. Then if the
|
||||
* 'requested' principal was anonymous, we'll check the 'mapped' principal
|
||||
* accordingly (without enforcing the name type and perhaps the realm).
|
||||
* Finally, if the 'requested' principal was not anonymous, well check
|
||||
* that the 'mapped' principal has an anonymous name and type, in a
|
||||
* non-anonymous realm. (Should we also be checking for a realm match
|
||||
* between the request and the mapped name in this case?)
|
||||
*/
|
||||
if (is_tgs_rep)
|
||||
flags = KRB5_ANON_MATCH_ANY;
|
||||
else if (krb5_principal_is_anonymous(context, requested, KRB5_ANON_MATCH_ANY))
|
||||
flags = KRB5_ANON_MATCH_UNAUTHENTICATED;
|
||||
flags = KRB5_ANON_MATCH_ANY_NONT;
|
||||
else if (krb5_principal_is_anonymous(context, requested,
|
||||
KRB5_ANON_MATCH_ANY_NONT))
|
||||
flags = KRB5_ANON_MATCH_UNAUTHENTICATED | KRB5_ANON_IGNORE_NAME_TYPE;
|
||||
else
|
||||
flags = KRB5_ANON_MATCH_AUTHENTICATED;
|
||||
|
||||
@@ -566,7 +578,8 @@ check_client_mismatch(krb5_context context,
|
||||
krb5_keyblock const * key)
|
||||
{
|
||||
if (rep->enc_part.flags.anonymous) {
|
||||
if (!krb5_principal_is_anonymous(context, mapped, KRB5_ANON_MATCH_ANY)) {
|
||||
if (!krb5_principal_is_anonymous(context, mapped,
|
||||
KRB5_ANON_MATCH_ANY_NONT)) {
|
||||
krb5_set_error_message(context, KRB5KRB_AP_ERR_MODIFIED,
|
||||
N_("Anonymous ticket does not contain anonymous "
|
||||
"principal", ""));
|
||||
|
Reference in New Issue
Block a user