Subject: Re: [kitten] gss_oid_equal and null equality

From: Simon Josefsson <[email-masked]>
Date: Sun, 17 Apr 2011 10:00:37 +0200

[email-masked] writes:

> I just had occasion to look at the specification of gss_oid_equal() in
> http://tools.ietf.org/html/draft-josefsson-gss-capsulate-04, and I ran
> into this text:
>
>   "The value GSS_C_NO_OID will not match any OID, including
>   GSS_C_NO_OID itself."
>
> That seems peculiar to me; I'm not familiar with any other comparison
> operator for a nullable type in any language which makes null not
> equal to itself.  Is there a specific reason why this behavior is
> convenient?

I don't recall a strong rationale.  If it was intentional, it was as a
pre-caution to make sure the function would not return true if you had
received GSS_C_NO_OID from two different code paths and were expecting
them to be real OIDs with some security-important meaning and wanted to
compare them for equality.  For example two security policy OIDs or
something.  If the function would return true for two GSS_C_NO_OID
inputs, the caller would have to compare the parameters against
GSS_C_NO_OID manually to make sure it wasn't in a "don't know" state
which would typically not be OK.

The same argument could be made the other way around too, but since it
is easy and inexpensive to compare two NULL OIDs (as oppoosed to
comparing two non-NULL OIDs which has the possibility of BER encoding
differences) the behaviour for NULL inputs doesn't seem like a major
issue to me.

/Simon

---

From: Nico Williams <[email-masked]>
Date: Sun, 17 Apr 2011 23:57:02 -0500

> which would typically not be OK.

I think it's a fair fail-safe feature.  Note that gss_display_name()
is allowed to return GSS_C_NO_OID as the output name type in some
cases, and so far this is the only case that RFCs 2743 and 2744
envision where the API can return GSS_C_NO_OID:

   If input_name was created by a call to gss_import_name, specifying
   GSS_C_NO_OID as the name-type, implementations that employ lazy
   conversion between name types may return GSS_C_NO_OID via the
   output_name_type parameter.

(And, of course, apps should not really be comparing non-MNs... but it
could happen.)

But future extensions might add new cases.  And then there's APIs
layered above the GSS-API.  Having an OID comparison function work
like SQL's three-valued logic (where (NULL = NULL) is NULL, not true)
seems like the right thing to do.

> differences) the behaviour for NULL inputs doesn't seem like a major

Any one OID has one and only one possible encoding in DER (and even
BER).  If a pair of OIDs' encodings differ it's because the OIDs
themselves differ.  (Though I've not sat down to figure out if
memcmp() happens to define a useful collation for encoded OIDs...  But
I'd not bet on it.  If you want to sort OIDs by canonical order then I
bet you'll need to decode arcs as you compare.)

Nico
--

---

From: Martin Rex <[email-masked]>
Date: Mon, 18 Apr 2011 17:29:09 +0200 (MEST)

Nico Williams wrote:
> > I don't recall a strong rationale. Â If it was intentional, it was as a
> > compare them for equality. Â For example two security policy OIDs or
> > something. Â If the function would return true for two GSS_C_NO_OID
> I think it's a fair fail-safe feature.

I do have two different implementations of a compare_oid function in two
different projects of mine.  The 16-year old implementation returns
FALSE when comparing two GSS_C_NO_OIDs, the other (part of gsskrb5.dll)
returns TRUE.  I don't see a need to change either of them.

So looking at my own implementations, I probably think the result
of comparing two GSS_C_NO_OID should be implementation defined.

>
>                                          Note that gss_display_name()

Comparing the outputs of gss_display_name() is not a good idea,
and while it may work most of the time with Kerberos-based gssapi
mechanisms, it often fails with PKI/X.509 based gssapi mechanisms:

  http://tools.ietf.org/html/rfc2743#page-14

   GSS_Import_name() implementations can, where appropriate, support
   more than one printable syntax corresponding to a given namespace
   (e.g., alternative printable representations for X.500 Distinguished
   Names), allowing flexibility for their callers to select among
   alternative representations. GSS_Display_name() implementations
   output a printable syntax selected as appropriate to their
   operational environments; this selection is a local matter. Callers
   desiring portability across alternative printable syntaxes should
   refrain from implementing comparisons based on printable name forms
   and should instead use the GSS_Compare_name()  call to determine
   whether or not one internal-format name matches another.

> themselves differ.

In theory, the ASN.1 BER encoding rules require canonical encodings
for Object Identifiers Elements (X.690, Section 8.19.2 last sentence):

     The subidentifier shall be encoded in the fewest possible octets,
     that is, the leading octet of the subidentifier shall not have
     the value 80(base16)

In practice, ASN.1 decoders do not necessarily implement this as a hard
failure and apps might, under certain circumstances (depending on the internal
representation of OIDs created by the ASN.1 decoder), treat the
following OIDs as equal (in other places than GSS-API gss_OID):

  rfc-1964 Kerberos (BER&DER)
       {  9, "\x2a\x86\x48\x86\xf7\x12\x01\x02\x02" }
  (non-canonical)
       { 10, "\x2a\x86\x48\x86\xf7\x12\x01\x02\x80\x82" }

The problem is much with non-canonical UTF8 in strcmp() comparisons,

  ASCII&canonical UTF8  "\x41\x42\x43"     = "ABC"
  non-canonical UTF8    "\xc1\x81\x42\x43" = "ABC"

-Martin

---

From: Martin Rex <[email-masked]>
Date: Mon, 18 Apr 2011 17:33:13 +0200 (MEST)

Ooops, sorry.
Typo in the non-canonical (not correctly formed ASN.1 DER) oid
(corrected below, final octet 0x02 insted of 0x82).

Martin Rex wrote:
>        { 10, "\x2a\x86\x48\x86\xf7\x12\x01\x02\x80\x02" }
> 
> 
> The problem is much worse with non-canonical UTF8 in strcmp() comparisons:
>

---

From: Simon Josefsson <[email-masked]>
Date: Mon, 18 Apr 2011 18:24:42 +0200

> of comparing two GSS_C_NO_OID should be implementation defined.

Oh please no!  What's the point in that?  An application would then have
to always check the GSS_C_NO_OID case specially, regardless of which
behaviour it wanted.

/Simon

---

From: Martin Rex <[email-masked]>
Date: Mon, 18 Apr 2011 18:42:31 +0200 (MEST)

> to always check the GSS_C_NO_OID case specially, regardless of which

What does ISO-C say about  memcmp(NULL,NULL,0) or memcmp(NULL, something, 0)
Having to special-case the absence of some value is not unusual.

In GSS-API GSS_C_NO_OID indicates the _absence_ of an OID value.

-Martin

---

From: Simon Josefsson <[email-masked]>
Date: Mon, 18 Apr 2011 18:55:58 +0200

> Having to special-case the absence of some value is not unusual.

That doesn't necessarily make it a good idea.

ISO C leaves some things to implementers because traditional
implementations were behaving differently and the standard came later
and couldn't rule existing implementations non-compliant -- but we are
not in that situation.  The implementation-specific stuff in ISO C has
big costs associated with it if you try to write portable code -- for
example, you can't assume signed integers are two-complement, that there
aren't "holes" in the integer values, or that NULL is an all-0 object.

> In GSS-API GSS_C_NO_OID indicates the _absence_ of an OID value.

Right.  But we can still specify what it means to compare something
against that value.

/Simon

---

From: Martin Rex <[email-masked]>
Date: Mon, 18 Apr 2011 22:13:17 +0200 (MEST)

> Right.  But we can still specify what it means to compare something

I am not convinced that it is sensible to define this, because the
"correct" answer is context sensitive and even there potentially debatable.
As I said, I implemented the answer to this question twice, the answers
differ, and I don't feel a need to change either implementation.

-Martin

---

From: Nico Williams <[email-masked]>
Date: Mon, 18 Apr 2011 18:05:49 -0500

> differ, and I don't feel a need to change either implementation.

I understand the desire to avoid changing either implementation that you
have.  A reasonable compromise would be to say that this I-D documents a
historically available function and to pick one behavior as RECOMMENDED
(SHOULD) instead of REQUIRED.

I would much prefer REQUIREing a single behavior though.

Similay regarding memcmp() vs decoding the arcs and properly implementing an
OID collation.  I understand it's much simpler to memcmp()...  I'd rather we
have a REQUIRED behavior, but in a pinch will settle for RECOMMENDED --
after all, we can all write autoconf macros, but surely we all hate having
to!  (Dont't forget, it's the apps developers who will have to put up with
choices of convenience for implementors.)

Nico
--

---

From: Simon Josefsson <[email-masked]>
Date: Tue, 19 Apr 2011 08:39:43 +0200

> differ, and I don't feel a need to change either implementation.

You don't have to -- presumably you didn't call your function
gss_oid_equal and exported it.  (Which implementation is this, btw?  Is
it possible to evaluate it?)

If the application needs to do some comparison itself in order to get a
well-defined result, I'd almost rather have it do all comparison (and
don't standardize this function) than having a standard function that
doesn't have well-defined meaning.

/Simon

---

From: Greg Hudson <[email-masked]>
Date: Tue, 19 Apr 2011 11:59:06 -0400

> doesn't have well-defined meaning.

I agree with Simon.

If there are multiple shipping GSSAPI implementations with exported
symbols named gss_oid_equal() with different behaviors, then we should
pick a new name.  If there aren't, we should go with the behavior in
Heimdal/Gnu GSS.  Either way, the behavior should be fully specified if
the arguments are valid OID values or GSS_C_NO_OID.