Subject: [kitten] Clarification of gss_add_cred() behavior

From: Nico Williams <[email-masked]>
Date: Wed, 18 Mar 2015 21:52:02 -0500

I need a clarification of RFCs 2743 and 2744 as to GSS_add_cred().

    Is it really the case that calling GSS_Add_cred() with no
    input_cred_handle (GSS_C_NO_CREDENTIAL) must ignore desired_name
    even when it is not the GSS_C_NO_NAME??

I think the answer is that this text in RFC2743 section 2.1.4, page 39:

   If GSS_C_NO_CREDENTIAL is specified as input_cred_handle, a non-NULL
   output_cred_handle must be supplied.  For the case of
   GSS_C_NO_CREDENTIAL as input_cred_handle, GSS_Add_cred() will create
   the credential referenced by its output_cred_handle based on default
   behavior.  That is, the call will have the same effect as if the
   caller had previously called GSS_Acquire_cred(), specifying the same
   usage and passing GSS_C_NO_NAME as the desired_name parameter
   (thereby obtaining an explicit credential handle corresponding to
   default behavior), had passed that credential handle to
   GSS_Add_cred(), and had finally called GSS_Release_cred() on the
   credential handle received from GSS_Acquire_cred().

and the corresponding and matching text in RFC2744 section 5.3, are
subtly wrong.  This text should have read thusly:

   If GSS_C_NO_CREDENTIAL is specified as input_cred_handle, a non-NULL
   output_cred_handle must be supplied.  For the case of
   GSS_C_NO_CREDENTIAL as input_cred_handle and desired_name as
   GSS_C_NO_NAME, GSS_Add_cred() will create
   the credential referenced by its output_cred_handle based on default
   behavior.  ...

That is, it should be possible to call GSS_Add_cred() with
input_cred_handle == GSS_C_NO_CREDENTIAL and a
desired_name != GSS_C_NO_NAME, and get a CREDENTIAL HANDLE for that
desired_name.

Otherwise GSS_Add_cred() is mostly useless.

I think this must have been a drafting error.  It's obvious to me, but
maybe someone has a very different opinion as to this (Martin?).

Nico
--

---

From: Benjamin Kaduk <[email-masked]>
Date: Thu, 19 Mar 2015 14:48:44 -0400 (EDT)

> maybe someone has a very different opinion as to this (Martin?).

But a GSS credential can only represent a single identity ("entity", see
1.1.1.1)!  Identities are represented by (non-mechanism) names, so it is
not clear to me that this is a drafting error.  If you know what name you
want credentials for, you should use it.

-Ben

---

From: Nico Williams <[email-masked]>
Date: Thu, 19 Mar 2015 14:16:41 -0500

> want credentials for, you should use it.

That's not what this is about.  I'm not asking for multiple elements for
the same mech but different desired_names.

The problem is the spec effectivel says that when input_cred_handle ==
GSS_C_NO_CREDENTIAL then desired_name is ignored.  This seems like a
mistake.

Nico
--

---

From: Viktor Dukhovni <[email-masked]>
Date: Thu, 19 Mar 2015 19:40:02 +0000

> want credentials for, you should use it.

The situation as I understand it is as follows.

    1. gss_add_cred() is intended to build a credential handle
       *incrementally*, adding credentials one mechanism at a time.
       Either for a default name, or for an explicit name that
       agrees with any previous credentials in the input handle.

    2. For this interface to be useful, the initial state one must
       be able to start with an empty credential handle and build up
       from there.  This presumably is specified via a NULL input
       credential, and still the same name for which one wants
       creds.

    3. So how are the desired "name" and "mech" used when going
       from no creds (NULL input handle) to a handle with a first
       credential element?

    4.  The text says that a default credential handle is implicitly
	constructed with GSS_C_NO_NAME, and we add to that!  But what
	if the acquired default credential has a conflicting name, and
	potentially already has elements for the same or other mechanisms?

	What is that text really trying to say, and why?

-- 
	Viktor.

---

From: Benjamin Kaduk <[email-masked]>
Date: Thu, 19 Mar 2015 15:50:22 -0400 (EDT)

>        agrees with any previous credentials in the input handle.

Well, I am not sure that it makes sense to try to do this for the default
name (but see below).

>        credential, and still the same name for which one wants

There is GSS_Acquire_cred to obtain the first credential element.

>        from no creds (NULL input handle) to a handle with a first

A GSS_C_NO_CREDENTIAL input handle is not a representation of no creds,
but rather a request to start from the default credentials.  The default
credentials must have some name associated with them, but we don't
(necessarily?) know what that name is.  My reading is that the only
reliable way to request the name associated with the default credentials
is to pass GSS_C_NO_NAME and not try to explicitly name the default
identity.  Now, maybe implementing this by completely ignoring the
supplied desired_name is not very friendly, but it makes some amount of
sense to me.

> 	potentially already has elements for the same or other mechanisms?

I think that the attempt to add the new credential must fail.

In general, if one is going to be doing anything with the default
credentials, one has to only use the default credentials.  If there are
other credentials in play, only the specific (known) credentials should be
used, to the exclusion of the default credentials.

> 	What is that text really trying to say, and why?

I guess my reading has the issue that if my reading is correct, why would
they have mentioned the GSS_C_NO_CREDENTIAL case at all for GSS_Add_cred.

> GSS_C_NO_CREDENTIAL then desired_name is ignored.  This seems like a

My reply is that GSS_C_NO_CREDENTIAL means the default credentials which
in turn means the default name.  There's no use for an explcit name when
the default name is being used.

-Ben

---

From: Nico Williams <[email-masked]>
Date: Thu, 19 Mar 2015 14:50:47 -0500

Here's what I want (and which I always thought was a possible way to use
gss_add_cred()):

    /*
     * Build a cred handle one elment-at-a-time.
     *
     * Here some_name is a gss_name_t value other than GSS_C_NO_NAME,
     * and some_mech is a gss_OID value other than GSS_C_NO_OID.
     */
    maj = gss_add_cred(&min, GSS_C_NO_CREDENTIAL,
                       some_name, some_mech,
                       GSS_C_INITIATE, GSS_C_INDEFINITE, 0,
                       &new_cred,
                       NULL, NULL, NULL);
    if (maj == GSS_S_COMPLETE) {
        /* Yes!  I have a cred handle for {some_mech, some_name}! */
        ...

        /*
         * Now I can gss_add_cred() more elements to the brand-new
         * new_cred, one element-at-a-time.
         */
        maj = gss_add_cred(&min, new_cred, some_name, other_mech,
                           GSS_C_INITIATE, GSS_C_INDEFINITE, 0,
                           NULL, NULL, NULL, NULL);
        ...
    }

This would be a handy use-case when the caller wants to initiate a
security context as the same initiator principal as a previous time, and
with the same mechanism (think: SSHv2 re-key exchange with GSS), which
is useful when one wants to re-delegate a fresh credential for the same
initiator principal to some target.

Now, the text in RFCs 2743 and 2744 as to what happens when
input_cred_handle == GSS_C_NO_CREDENTIAL means that my some_name
desired_name must get ignored in the above code, which makes the above
not feasible at all.

But it's worse than that: I can't even use gss_add_cred() to get a
single-mechanism element credential handle if I don't already have one.
It means I can't really use gss_add_cred() without also first using
gss_acquire_cred().  Using gss_acquire_cred() at all is obnoxious when
you have a single mechanism that you want a credential for.

With the text as-is the only real use of gss_add_cred() is to function
as a would-be gss_duplicate_cred() when you ask for an
output_cred_handle.  But it's a rather difficult-to-use cred handle dup
function...

Nico
-- 

On Wed, Mar 18, 2015 at 09:52:02PM -0500, Nico Williams wrote:
> subtly wrong.  [...]

---

From: Nico Williams <[email-masked]>
Date: Thu, 19 Mar 2015 14:56:06 -0500

> the default name is being used.

But I have a desired_name, and if it is the GSS_C_NO_NAME, then sure,
that means "default", but if it's not, then gss_add_cred() is useless.

Think about what the quoted RFC text means...  First GSS will acquire a
default credential (for what mechanisms?  not stated!).  THEN it will
try to add an element for the desired_mech... which element should
already be present, therefore GSS_Add_cred() with input_cred_handle ==
GSS_C_NO_CREDENTIAL -> same as GSS_Acquire_cred() with desired_name ==
GSS_C_NO_NAME.  But then I can never use GSS_Add_cred() to acquire a
signle-element credential, and I must always use GSS_Acquire_cred() for
that (which is lame for several reasons).

This totally conflicts with the stated purpose of GSS_Add_cred():

   GSS_Add_cred() enables callers to construct credentials iteratively
   by adding credential elements in successive operations, corresponding
   to different mechanisms.  This offers particular value in multi-
   mechanism environments, as the major_status and minor_status values
   returned on each iteration are individually visible and can therefore
   be interpreted unambiguously on a per-mechanism basis. A credential
   element is identified by the name of the principal to which it
   refers.  [...]

Nico
--

---

From: Viktor Dukhovni <[email-masked]>
Date: Thu, 19 Mar 2015 19:57:20 +0000

> There is GSS_Acquire_cred to obtain the first credential element.

So I believe that you're saying that gss_add_cred() not intended to
be able to add the *first* credential element.  That is:

    1. Always call gss_acquire_cred() with a singleton mechanism
       set (to get the first mechanism) and desired name.

    2. Then, as appropriate, call gss_add_cred() for any *additional*
       mechanisms.

This makes the API more unpleasant to use, but sounds plausibly
consistent with the text...

-- 
	Viktor.

---

From: Nico Williams <[email-masked]>
Date: Thu, 19 Mar 2015 16:04:57 -0500

On Thu, Mar 19, 2015 at 07:57:20PM +0000, Viktor Dukhovni wrote:
> This makes the API more unpleasant to use, [...]

Decidedly more unpleasant.

BTW, nowhere in RFCs 2743 and 2744 does it say that the minor_status
output of GSS_Acquire_cred() corresponds to the one mechanism in
desired_mechs when there is just one mechanism in it.

But clarifying which mech the minor_status is for is one of the reasons
given for GSS_Add_cred()'s very existence.

Another reason given for GSS_Add_cred() is to support iterative
construction of credential handles, but no mention is made of having to
use GSS_Acquire_cred() to acquire a seed handle.

>                                     [...], but sounds plausibly

I'm not sold on that yet.  First, see above as to spec self-inconsistencies.

Second, implementations seem to disagree:

 - MIT -> input_cred_handle == default -> allocates a new cred, honors desired_name;
 - Illumos -> same as MIT;
 - Heimdal -> input_cred_handle == default -> error;
 - GNU GSS -> not implemented, returns GSS_S_UNAVAILABLE;
 - gsskrb5 (Martin's SSPI shim) -> not implemented, returns GSS_S_FAILURE;

 - Illumos kgss (for RPCSEC_GSS) implements an unused kgss_add_cred() by calling
   up to gssd, which calls the one described above;

 - FreeBSD kgss (ditto) does not implement gss_add_cred() (nor kgss_add_cred());

 - Linux kernel gss: does not implement any GSS functions dealing with creds,

but clearly the behavior you (Viktor) and I prefer cannot be surprising:
because if gss_add_cred() does anything other than fail, then it's doing
what you and I want.

Second, the "pleasant" behavior is clearly better.  Sure, one can't rely
on it, but then, neither could one rely on the default credential
behavior, and that behavior always errors out in Heimdal, GNU, and
gsskrb5.

Incidentally, are there other implementations to check?  I don't think
so.  I think I've checked all the open-source ones (even "PGSSAPI",
which also doesn't implement gss_add_cred()).  I know of none that are
not open source other than Solaris' (whose lineage is the same as
Illumos').

Nico
--

---

From: Nico Williams <[email-masked]>
Date: Thu, 19 Mar 2015 16:47:02 -0500

From: Nico Williams <[email-masked]>
To: [email-masked]
Cc: 
Bcc: 
Subject: Re: [kitten] Clarification of gss_add_cred() behavior
Message-ID: <20150319214213.GO8099@localhost>
Reply-To: 
In-Reply-To: <20150319210456.GN8099@localhost>

On the strength of what all the implementations I could check do, I'm
going to make Heimdal match MIT and leave it at that.  If someone wants
to update the RFCs, that's fine with me.  (I think it could be an
erratum, but only if we agree that the RFC is either horribly broken or
self-inconsistent, which we might not.)

---

From: Benjamin Kaduk <[email-masked]>
Date: Thu, 19 Mar 2015 20:20:18 -0400 (EDT)

> that (which is lame for several reasons).

Perhaps I'm just being slow today, but could you say more about what these
reasons for lameness are?

Thanks,

Ben

---

From: Viktor Dukhovni <[email-masked]>
Date: Fri, 20 Mar 2015 00:36:25 +0000

> reasons for lameness are?

    1.  Building a set of things via a different function for the
        first element than then the rest is ugly.

    2.  Calls to gss_acquire_cred() need a mechanism *set*, so one
	ends up building one-element sets for that, and disposing
	of those (though I'm working around that with automatic
	singleton mech sets):

		gss_OID mech = ...;
		gss_OID_set_desc onemech = { 1, mech };

		gss_acquire_cred(..., &onemech, ...);

	Naively, one would allocate an empty set, add the mech to
	that, handle errors, ... use the set, and deallocate it.

    3.  Unclear semantics with respect to error reporting from
	gss_acquire_cred.  Does it report the mechanism-specific
	minor code for the sole mechanism when oid set is a
	singleton?

    4.  The semantics of gss_add_cred() with GSS_C_NO_CREDENTIAL
	for the input cred are underspecified.  (Which mechs are
	use to construct the missing initial cred handle?  Is the
	desired_name ignored when the requested mech is "added",
	and in what sense is it "added" anyway, since other than
	name it would likely already be in that default credential?)

The interface would be simpler and more clear if gss_add_cred()
could just start from zero.

-- 
	Viktor.

---

From: Nico Williams <[email-masked]>
Date: Thu, 19 Mar 2015 19:45:25 -0500

> reasons for lameness are?

Sure:

 - the need to construct an OID set of one OID is lame (this is easy
   when one has only one or known-fixed-at-compile-time number of OIDs,
   but it's still lame)

 - minor_status is not associated with any mechanism per the specs,
   though one hopes that for the one-OID case minor_status is specific
   to that one mechanism, but if we're going to be as literal as we can
   in reading the spec, then this point is correct and
   GSS_Acquire_cred() is lame

 - fewer informative outputs (initiator and acceptor lifetime rec)

   (admittedly this one is not a big deal)

The second is the real killer.  It's one of the reasons given for the
addition of GSS_Add_cred().  And if we interpret the spec as you
propose, we can't have this feature.  This is one reason to think that
the spec is self-inconsistent.

Other reasons to think the spec is self-inconsistent:

 - No information or pseudo-code or code is given showing how to
   iteratively construct credential handles.  It probably wasn't thought
   through.

   But if the case of input_cred_handle == GSS_C_NO_CREDENTIAL &&
   desired_name != GSS_C_NO_NAME is interpreted as I propose, then it's
   trivial to iteratively construct a credential handle.

 - It makes no sense anyways to ignore a non-default desired_name when
   the input_cred_handle is GSS_C_NO_CREDENTIAL, not in light of the
   fact that GSS_C_NO_NAME is how one refers to the default credential
   in GSS_Acquire_cred() calls.

 - In some places input_cred_handle is described as optional.  Well, but
   GSS_C_NO_CREDENTIAL -> ignore desired_name means there's no real way
   to signal "I'm not providing an input cred handle", therefore it's
   either not optional, or the text I say is wrong is wrong.

The RFC text I quoted as broken does not reflect reality for an
implementation of the C bindings that I could check.  I don't think
anyone is proposing to implement the text I quoted.  That leaves three
choices:

 - file an erratum
 - publish an update
 - do nothing (anyone who runs into this will hopefully find this thread)

I will not be submitting an I-D to update the RFCs just for this.  If we
have no consensus for an erratum, and no one else volunteers for the
update, then it's the third choice: do nothing.

Nico
--

---

From: Benjamin Kaduk <[email-masked]>
Date: Fri, 20 Mar 2015 13:57:03 -0400 (EDT)

> the spec is self-inconsistent.

Thanks.
Yeah, that definitely counts as "lame" :)

>    either not optional, or the text I say is wrong is wrong.

I will need to find a chunk of time to digest all of the relevant pieces
of 2743; it may not happen until after Dallas.

> update, then it's the third choice: do nothing.

I really hope it doesn't end up as "do nothing", but time will tell.

-Ben