The goal of this project is to improve the likelihood that a GSS server application will be able to use the correct entries from a keytab when accepting authentication. Specifically:
- We will stop using the domain_realm map or default realm to determine the realm of host-based GSS acceptor names. Instead, we will allow any keytab entry matching the other constraints of the host-based name, regardless of realm.
- If the host-based acceptor name contains a service name but no hostname, we will allow any keytab entry matching the service name, regardless of its hostname or realm.
- If the host-based acceptor name contains a service@hostname pair, we will allow a keytab entry matching the service name and either the original or canonicalized hostname. (XXX should we also allow the forward-canonicalized hostname? That has significant implications for the implementation design.)
- If the new profile variable ignore_acceptor_hostname is set, we will disregard the hostname part of a service@hostname pair and allow any keytab entry matching the service name.
It is not required that this new flexibility extend to non-GSS krb5 servers, although it is acceptable if it flows naturally from the implementation.
A GSSAPI server may invoke gss_accept_context with GSS_C_NO_CREDENTIAL, or with a credential acquired with GSS_C_NO_NAME, then we place no restrictions on the keytab entry used to decrypt the sender's ticket; we scan each keytab entry until we find one which can decrypt it. No changes are proposed in this case.
If a server calls gss_import_name with the name type GSS_C_NT_HOSTBASED_SERVICE, it supplies a service name and optionally a hostname. When a credential is acquired with this name, the mechglue will invoke krb5_gss_import_name() to create the mechanism name. This function calls krb5_sname_to_principal() to construct the principal, passing NULL for the hostname if none was supplied. krb5_sname_to_principal() does the following:
- If the hostname is NULL, calls gethostname() to get an initial value for the hostname.
- Canonicalizes the hostname using getaddrinfo() and possibly getnameinfo().
- Attempts to map the hostname to a realm using krb5_get_host_realm(). This will consult the profile domain_realm map, and will return the referral (empty) realm if no mapping exists.
- Constructs a principal servicename/canonicalized-hostname@realm.
When acceptor credentials are acquired using the mechanism name, acquire_accept_cred() looks up the principal in the host's keytab, and returns an error if it is not found. The keytab lookup code translates from the empty realm to the default realm.
When gss_accept_sec_context() is invoked, krb5_rd_req() looks up the principal in the host's keytab again, and uses it to decrypt the ticket.
Note that the principal resulting from krb5_sname_to_principal() may be used in the initiator code path as either an initiator or target name. If used as an initiator name, it is looked up in the credential cache, or passed to krb5_get_init_creds_password(). If used as a target name, it is passed to krb5_get_credentials() as the server name.
The principal resulting from krb5_sname_to_principal() may also be used by gss_export_name(). RFC 2744 section 5.5 mandates that gss_canonicalize_name and gss_export_name produce the same result as would be produced by using the name as an initiator name, establishing a security context, and exporting the authenticated name on the acceptor. There is no requirement that canonicalize/export match the result of accepting a security context with an acceptor name and querying the acceptor name of the resulting context.
When we create a krb5 mechanism name from a host-based GSS name, we should record the service name and hostname (if given). If ignore_acceptor_hostname is set in the profile, we should discard the hostname at this time. If a hostname is given and not ignored, we should attempt to canonicalize it and record the canonicalized name as well. (NOTE: this will be the first time the gss-krb5 code directly consults the profile.)
If the name is used on the initiator path (as an initiator name or target name), we should convert from the recorded form to the principal we would construct with today's code.
If the name is imported for an acceptor cred, we should construct one or two partial principals: service/@ (empty hostname and realm) if no hostname was given, service/orighostname@ (empty realm) and service/canonhostname@ (empty realm) if one was given. We should scan the keytab to make sure one of the partial principals is matched by a keytab entry.
When an acceptor cred with an imported host-based name is used to accept a security context, we should try krb5_rd_req with the first partial principal and, if that fails, try again with the second (if there is one). (If the keytab has no iterator function, we should probably just skip this check.)
krb5_rd_req should treat partial host-based principals as restrictions on the keytab scan it already performs when no principal is passed in. (If the keytab has no iterator function, then krb5_rd_req should check that the principal asserted by the client matches the partial host-based principal, and look up that principal in the keytab.)
We will need new krb5 library interfaces for:
- Canonicalizing a hostname according to the sn2princ rules, honoring the profile "rdns" setting.
- Building a principal according to the sn2princ rules, with the canonicalized hostname already known. (sn2princ will be reimplemented in terms of this and the previous interface.)
- Matching a principal against a partial principal, following the same rules as krb5_rd_req will apply.
Proposed APIs for these functions:
krb5_error_code krb5_sname_canonhost(krb5_context context, const char *host, const char **canonhost_out);
krb5_error_code krb5_sname_to_princ_canon(krb5_context context, const char *canonhost, const char *sname, krb5_int32 type, krb5_principal *princ_out);
krb5_error_code krb5_sname_match(krb5_context context, krb5_const_principal partial, krb5_const_principal full);
For security reasons, we have long desired to get away from canonicalizing hostnames since it generally uses insecure DNS, but we have been unwilling to break existing deployments by simply turning it off. One possibility is to allow a KDC to assert that it should be responsible for host canonicalization, communicated as a ticket flag which would be stored in the credentials cache.
The implementation design of this project could, as a side effect, allow a GSSAPI initiator to honor this ticket flag. The initiator would consult the ticket cache and simply avoid calling krb5_sname_canonhost() before calling