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 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.
We define a new concept "matching principal", which is a principal of type KRB5_NT_SRV_HOST with an empty realm and/or second principal component (hostname).
When we create a krb5 mechanism name from a host-based GSS name, we should record the service name and hostname (if given).
If the name is used on the initiator path (as an initiator name or target name) or is exported, we should convert from the recorded form to the principal we would construct with today's code, using krb5_sname_to_principal. (UPDATE: it proved to be too difficult to calculate the krb5_sname_to_principal result only when it is needed. Instead, we record the passed-on service and hostname, but also call krb5_sname_to_principal at mechanism name import time, even though in some cases the result won't be used.)
If the name is used in an acceptor cred, we should construct a matching principal service/hostname@ (empty realm) or service/@ (empty hostname and realm) depending on whether a name was recorded. We should scan the keytab to make sure the matching principal is matched by a keytab entry. If the keytab has no iteration support, we should just skip this check.
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 matching principal. krb5_rd_req should treat matching principals as restrictions on the keytab scan it already performs when no principal is passed in. krb5_rd_req should ignore the hostname component of a matching principal if the ignore_acceptor_hostname profile variable is set. If the keytab has no iterator function, then krb5_rd_req should check that the principal asserted by the client matches the matching principal, and look up that principal in the keytab.
We will need a new krb5 library interface for matching a principal against a matching principal, following the same rules as krb5_rd_req will apply. The proposed API for this function is:
krb5_boolean krb5_sname_match(krb5_context context, krb5_const_principal matching, krb5_const_principal princ);
If a server imports a host-based name containing a hostname, we could try the uncanonicalized, forward-canonicalized, and reverse-canonicalized forms of the hostname. Upon discussion of this idea, we decided that it would add significant complexity to the code, and we would be better off encouraging GSS server applications to import just the service name (without the local hostname attached).
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 flag stored in the ccache.
The changes proposed in this project should further that goal. By recording the original hostname when a host-based name is imported into the mechanism, we make it possible for the GSSAPI initiator code to use that information if the ccache indicates that it should.
Python tests and a simple GSS initiator/acceptor C harness will be created to test each aspect of the new behavior.