Matching Rules for Constructed Syntaxes

"Steven Legg" <steven.legg@adacel.com.au> Mon, 28 August 2000 02:01 UTC

Received: from ns.secondary.com (ns.secondary.com [208.184.76.39]) by ietf.org (8.9.1a/8.9.1a) with ESMTP id WAA14758 for <pkix-archive@odin.ietf.org>; Sun, 27 Aug 2000 22:01:17 -0400 (EDT)
Received: from localhost (daemon@localhost) by ns.secondary.com (8.9.3/8.9.3) with SMTP id SAA21747; Sun, 27 Aug 2000 18:59:51 -0700 (PDT)
Received: by mail.imc.org (bulk_mailer v1.12); Sun, 27 Aug 2000 18:58:58 -0700
Received: from arnie.adacel.com.au (arnie.adacel.com.au [203.36.26.147]) by ns.secondary.com (8.9.3/8.9.3) with SMTP id SAA21590 for <ietf-pkix@imc.org>; Sun, 27 Aug 2000 18:58:40 -0700 (PDT)
Received: (qmail 31425 invoked from network); 28 Aug 2000 02:59:25 -0000
Received: from unknown (HELO osmium) (203.8.85.176) by arnie.adacel.com.au with SMTP; 28 Aug 2000 02:59:25 -0000
Reply-To: steven.legg@adacel.com.au
From: Steven Legg <steven.legg@adacel.com.au>
To: ietf-ldapext@netscape.com, ietf-pkix@imc.org
Subject: Matching Rules for Constructed Syntaxes
Date: Mon, 28 Aug 2000 12:58:31 +1000
Message-ID: <000301c0109b$d9bf6bb0$b05508cb@osmium.adacel.com.au>
MIME-Version: 1.0
Content-Type: text/plain; charset="iso-8859-1"
Content-Transfer-Encoding: 7bit
X-Priority: 3 (Normal)
X-MSMail-Priority: Normal
X-Mailer: Microsoft Outlook 8.5, Build 4.71.2377.0
X-Mimeole: Produced By Microsoft MimeOLE V4.72.2120.0
Importance: Normal
Precedence: bulk
List-Archive: http://www.imc.org/ietf-pkix/mail-archive/
List-ID: <ietf-pkix.imc.org>
List-Unsubscribe: mailto:ietf-pkix-request@imc.org?body=unsubscribe
Content-Transfer-Encoding: 7bit

LDAPers and PKIXers,

The LDAP ACI model and PKIX both have to deal with attribute syntaxes
that are complex constructed types. Matching rules for those syntaxes
have recently been discussion topics so it seems like a good time to
describe some ideas for matching rules that have been kicking around
in the back of my head for a while. First I'll address the question of
an equality matching rule for ldapACI and then go on to describe some
generic matching rules for complex syntaxes that would be useful to
both LDAP access control and PKIX, among other things.

One approach for ldapACI is the X.500 solution where a DirectoryString
label has been made a component of the ACI and the
directoryStringFirstComponentMatch matching rule is used to decide the
equality of two ACIs. This satisfies some basic requirements to be
able to match ACIs but the equality of two ACI values doesn't really
say anything about them at the semantic level.

Back when I was with Telstra we defined a catch-all equality matching rule
that could do a comparison of two attribute values of any arbitrary
ASN.1 syntax. This rule, or something like it, could be used as the
equality matching rule for the ldapACI attribute. The X.500 style
definition is simply this:

asn1Match MATCHING-RULE ::= {
	ID			{ 1 3 32 0 1 13 0 } }

Since no assertion syntax is specified, the syntax is inherited at
run time from the attribute type to which the matching rule is applied.
So the assertion syntax would be LdapACI if asn1Match is used to match
against the ldapACI attribute.

I have a slight problem defining this matching rule in LDAP terms because
the string encoding for MatchingRuleDescription doesn't allow an absent
SYNTAX.

This matching rule evaluates to true if and only if the attribute value
and the assertion value have the same components with the same values.

If the syntax is a SEQUENCE or SET type then each component in
the assertion value must match each component of the attribute value.
Optional components must be either both present or both absent.

If the syntax is a SEQUENCE OF then the attribute value and the
assertion value must have the same instances (members)
in the same order.

If the syntax is a SET OF then the attribute value and the assertion
value must have the same instances (and the same duplicates),
but order doesn't matter.

If the syntax is a CHOICE then the attribute value and the assertion value
must use the same CHOICE alternative.

The rules apply recursively to nested constructed types.

The primitive types like OBJECT IDENTIFIER, BOOLEAN, INTEGER, etc
match as one would expect. The string types are just matched character
for character.


The asn1Match rule would be fine as the equality matching rule for the
ldapACI attribute and would have much the same effect as was intended
by the original use of caseIgnoreMatch and DirectoryString syntax.
However, where there is a complex constructed syntax there is usually
a desire to selectively match only some of the components, e.g. to
match ACIs with a specific subject, or with specific rights.
What usually happens is that a bunch of new matching rules are defined
to cover the anticipated needs. Just look at X.509 and PKI for examples.

Rather than going down this path I would like to suggest a generic
way of selectively matching components in arbitrary syntaxes.
The following matching rule is something I've often thought about
implementing but haven't as yet.

componentMatch MATCHING-RULE ::= {
	SYNTAX		ComponentAssertion
	ID			<to be supplied>
}

ComponentAssertion ::= SEQUENCE {
	component	[0] PrintableString,
	rule		[1] MATCHING-RULE.&id, -- MatchingRuleId
	value		[2] MATCHING-RULE.&AssertionType -- AssertionValue
}

The idea here is that the component field identifies which part of a
value of a constructed syntax is to be matched, the rule indicates how that
part is to be matched, and the value is what that part is matched against.

X.680, section 12 already describes a way to reference the components
of an arbitrary ASN.1 type but it hasn't got a BER encoding so I'm just
stuffing it as text into a PrintableString.

Basically the component referencing string is a series of "." separated
componentIds where a componentId is usually the ASN.1 identifier for
the component. The componentId can also be a positive number to reference
a particular instance in a SET OF or SEQUENCE OF, "*" to reference
all the instances in a SET OF or SEQUENCE OF, or the number zero,
which is a conceptual count of the number of instances.

An example should make this clearer. First, here is the ASN.1 definition
of the syntax of the objectClasses schema attribute (not to be confused
with the objectClass attribute), so that I have something reasonably
familiar to base examples on.

ObjectClassDescription ::= SEQUENCE {
	identifier		OBJECT-CLASS.&id,
	name			SET OF DirectoryString { ub-schema } OPTIONAL,
	description		DirectoryString { ub-schema } OPTIONAL,
	obsolete		BOOLEAN DEFAULT FALSE,
	information	[0]	ObjectClassInformation
}

ObjectClassInformation	::=	SEQUENCE {
	subclassOf		SET OF OBJECT-CLASS.&id OPTIONAL,
	kind			ObjectClassKind DEFAULT structural,
	mandatories	[3]	SET OF ATTRIBUTE.&id OPTIONAL,
	optionals	[4]	SET OF ATTRIBUTE.&id OPTIONAL
}

OBJECT-CLASS.&id and ATTRIBUTE.&id are equivalent to the OBJECT IDENTIFIER
ASN.1 type.

So for a given value with the ObjectClassDescription syntax (i.e. a
value of the objectClasses attribute) ...

"identifier" references the OID of the object class description,
"description" references the text description of the object class,
"name.*" references all of the object class's names,
"name.0" gives the number of names defined for the object class,
"name.1" references the first of those names,
"information" references the whole ObjectClassInformation nested SEQUENCE,
"information.kind" references the ObjectClassKind in the nested SEQUENCE,
"information.mandatories.*" references all the mandatory attribute types
	for the object class

... and so on.

Each referenced component has a specific ASN.1 type. For example,
"identifier" is of type OBJECT IDENTIFIER and "name.1" is of type
DirectoryString. The type of "information.mandatories" is
SET OF OBJECT IDENTIFIER but the corresponding type for
"information.mandatories.*" I'll take to be OBJECT IDENTIFIER
(the instance type of the SET OF). The ".0" instance counts I'll assume
to be INTEGER.

Now that I've got a way to reference components I need to say how they
are matched. I could specify a new bunch of rules for different kinds
of matching on different ASN.1 types but it is convenient to just use
existing attribute matching rules. Each matching rule has a notional
set of attribute syntaxes (typically one), defined as ASN.1 types, to
which it may be applied. When embedded in a ComponentAssertion these
matching rules apply to the same ASN.1 types, only in this context the
types aren't necessarily attribute syntaxes. So the "identifier"
component of an objectClasses attribute value could be matched with
objectIdentifierMatch. The "name.*" component (instances) could be
matched with caseIgnoreMatch, caseExactMatch, caseIgnoreOrderingMatch,
or even caseIgnoreSubstringMatch. Any component, regardless of its
ASN.1 type could be matched by the asn1Match rule. The assertion value
that goes with the chosen matching rule will be of the syntax required
by that rule.

Because constructed types can have CHOICEs and OPTIONAL fields it is
possible that the referenced component does not exist in an attribute value.
In these cases the matching rule would evaluate to false.


Now for a few example filters using the componentMatch matching rule.
I don't want to go to the trouble of defining complete ABNF right
now so I'm using a string encoding that is a mix of ASN.1 value notation,
which I trust is verbose enough to be sufficiently clear, and LDAP string
encodings for well known ASN.1 types. I've put in spacing and line breaks
only as an aid to readability. The example search filters are mostly
single extensible match filter items, though of course there is no
reason why componentMatch can't be used in more complicated search filters.

To find the object class definition for the object class called
foobar use the filter:

(objectClasses:componentMatch:={
	component "names.*",
	rule caseIgnoreMatch,
	value "foobar" })

An object class definition can have multiple names and the above filter
will match an objectClasses value if any one of the names is "foobar".

To find all obsolete object classes use:

(objectClasses:componentMatch:={
	component "obsolete",
	rule booleanMatch,
	value TRUE })

To find all subclasses of "person" use:

(objectClasses:componentMatch:={
	component "information.subclassOf.*",
	rule objectIdentifierMatch,
	value person })

To find the object class definition for 2.5.6.18 use:

(objectClasses:componentMatch:={
	component "identifier",
	rule objectIdentifierMatch,
	value 2.5.6.18 })

A match on the "identifier" component of objectClasses values
is equivalent to the objectIdentifierFirstComponentMatch matching
rule applied to this attribute type. The componentMatch matching rule
subsumes the objectIdentifierFirstComponentMatch, integerFirstComponentMatch
and directoryStringFirstComponentMatch matching rules.

To find all values of the ldapACI attribute that have my DN in the subject
field I could use (refer to draft-ietf-ldapext-acl-model-06.txt for the
ASN.1 type definition of the ldapACI syntax):

(ldapACI:componentMatch:={
	component "subject.dn",
	rule distinguishedNameMatch,
	value "cn=Steven Legg, o=Adacel, c=au" })

The subject component is a CHOICE of which "dn" is one of several
possibilities. Those values with a different choice won't match the
filter item.

To find all ACIs that apply to the commonName attribute:

(ldapACI:componentMatch:=
	{ component "attr.attributes.*", rule objectIdentifierMatch, value cn })


The componentMatch rule allows any one component in a constructed value
to be matched but there are often good reasons to want to match more than
one component in the same constructed value. This is particularly true
for the matching of certificates and related constructs in PKIX.
Most, if not all, of the desired functionality could be achieved
with the following matching rule whose assertion syntax is a filter
of component matches.

componentFilterMatch MATCHING-RULE ::= {
	SYNTAX		ComponentFilter
	ID			<to be supplied>
}

ComponentFilter ::= CHOICE {
	item		[0] ComponentAssertion,
	and			[1] SEQUENCE OF ComponentFilter,
	or			[2] SEQUENCE OF ComponentFilter,
	not			[3] ComponentFilter
}

The component filter is evaluated over a single attribute value rather
than a whole entry. Note that a matching rule like this gives us the same
capability for matching components of a constructed syntax that a regular
search filter would give us had the constructed syntax been exploded out
as a set of attributes of simpler syntaxes in a subordinate entry.

The observant will notice that componentMatch is a special case of
componentFilterMatch so we don't really need componentMatch.

Here are some example search filters using the componentFilterMatch
matching rule.

To find all object class definitions that reference the commonName
AND surname attributes as mandatory attributes use:

(objectClasses:componentFilterMatch:=and:{
		item:{ component "information.mandatories.*",
			rule objectIdentifierMatch, value cn },
		item:{ component "information.mandatories.*",
			rule objectIdentifierMatch, value sn } })

To find all object class definitions that reference the commonName
AND surname attributes as either mandatory OR optional attributes:

(objectClasses:componentFilterMatch:=and:{
	or:{
		item:{ component "information.mandatories.*",
			rule objectIdentifierMatch, value cn },
		item:{ component "information.optionals.*",
			rule objectIdentifierMatch, value cn }
	},
	or:{
		item:{ component "information.mandatories.*",
			rule objectIdentifierMatch, value sn },
		item:{ component "information.optionals.*",
			rule objectIdentifierMatch, value sn }
	}
})

The next example is inspired by Sean Mullan.

Sean Mullan wrote:
> What happens if you want to define a matching rule to only retrieve certs
> that contain a specific basic constraints value AND a certain issuer name?
In
> this case, you would define 2 matching rules and specify both of them in
> the valuesReturnFilter control. But then you would get all certs with the
> requested issuer name and any basic constraints value and all certs with
> any issuer name and the requested basic constraints value. That's not
> what you wanted. I think we may want to enhance the valuesReturnFilter
> control (through a flag, perhaps) to allow the user to apply all of the
> elements of the filter to each attribute value. Let's discuss this more
> offline, if you like.

Here is a component filter match on a specific value of basic constraints
AND a certain issuer name:

(userCertificate:componentFilterMatch:=and:{
	item:{ component "extensions.*", rule asn1Match,
		value { extnId 2.5.29.19, extnValue '3003020102'H } },
	item:{ component "issuer.rdnSequence", rule distinguishedNameMatch,
		value "cn=CA, o=Adacel, c=au" } })

2.5.29.19 is the OID for the basicConstraints extension. Unfortunately
the extension value (extnValue) is defined to be a DER encoding wrapped
in an OCTET STRING (ugh!).

In the above example it was particularly convenient to use the
asn1Match rule to match the whole extension, i.e.
{ extnId 2.5.29.19, extnValue '3003020102'H }, in one hit, instead
of writing a nested "and" filter of each of the components of
the extension. The asn1Match rule is essentially a short hand
for an "and" component filter specifying every component.


I can imagine situations where it would be useful to test not only if
a *specific* value of a particular component is present but whether *any*
value of a particular component is present. Hence the following
matching rule:

presentMatch MATCHING-RULE ::= {
	SYNTAX		NULL
	ID			<to be supplied>
}

When used on an attribute in a extensible match filter item it behaves
like the present case of a regular search filter. In a ComponentAssertion
it evaluates to true if and only if the referenced component exists.

To find object class definitions that have descriptions use:

(objectClasses:componentMatch:=
	{ component "description", rule presentMatch, NULL })

To find object class definitions that don't have descriptions use:

(objectClasses:componentFilterMatch:=
	not:item:{ component "description", rule presentMatch, NULL })

To find the object class definitions that are not marked as obsolete
(the "obsolete" component is absent, or is present and set to FALSE):

(objectClasses:componentFilterMatch:=or:{
	not:item:{ component "obsolete", rule presentMatch, NULL },
	item:{ component "obsolete", rule booleanMatch, FALSE } })


The DistinguishedName and RelativeDistinguishedName ASN.1 types are
also constructed types so the component matching rules could be
applied to them.

For convenience, here is the stripped down ASN.1 type for DNs.

DistinguishedName ::= SEQUENCE OF RelativeDistinguishedName

RelativeDistinguishedName ::= SET OF AttributeTypeAndValue

AttributeTypeAndValue ::= SEQUENCE {
	type		AttributeType, -- OBJECT IDENTIFIER
	value		AttributeValue -- ANY }

To find all seeAlso attributes (DN syntax) containing the RDN "o=Adacel"
use:

(seeAlso:componentMatch:=
	{ component "*", rule componentFilterMatch, value
		and:{
			item:{ component "0", rule integerMatch, value 1 },
			item:{ component "*", rule asn1Match, value
				{ type o, value "Adacel" } }
		}
	})

This assertion tests each RDN (referenced by the first "*") to see if
it has exactly one AVA (the count is given by "0") with attribute type
organization and value "Adacel".

Since RDNs appear all over the place it makes sense to define a
matching rule that can make RDN matching more concise.

rdnMatch MATCHING-RULE ::= {
	SYNTAX		RelativeDistinguishedName
	ID			<to be supplied>
}

The rdnMatch rule evaluates to true if the component value and assertion
value are the same RDN.

Now the above example to find all seeAlso attributes containing the
RDN "o=Adacel" becomes:

(seeAlso:componentMatch:={ component "*", rule rdnMatch, value "o=Adacel" })

The rdnMatch can be useful wherever DNs appear. To find all values of the
ldapACI attribute that have "o=Adacel" somewhere in the DN in the subject
field use:

(ldapACI:componentMatch:=
	{ component "subject.dn.*", rule rdnMatch, value "o=Adacel" })

To find all seeAlso values with "o=Adacel, c=au" as a "suffix"
(noting that the string encoding of LDAPDN puts the RDNs in
reverse order) use:

(seeAlso:componentFilterMatch:=and:{
	item:{ component "1", rule rdnMatch, value "c=au" },
	item:{ component "2", rule rdnMatch, value "o=Adacel" } })

There's a case for being able to specify a match against the last component,
or second last component, etc, but we'd have to invent a syntax for that.


Okay, so what do people think ? The value of componentFilterMatch and
friends is that they implicitly define a bunch of useful matching rule
semantics for a new attribute syntax the moment the ASN.1 type is
written down, and are automatically extended if the ASN.1 type is later
extended. We might never need to define another matching rule for a
constructed syntax. What would be missing though is a definition of
the string encoding, so having a predictable way of generating the
ABNF from the ASN.1 type would be particularly valuable. For the examples
above I didn't formally define the string encoding for ComponentAssertion
and ComponentFilter, themselves constructed types, but I was consistently
following a set of rules. Those rules would be my preferred solution
for algorithmically derived string encodings for new attribute and assertion
syntaxes.

Since the componentFilterMatch is generally useful, and can be applied
to any ASN.1 type, it makes more sense for me to write it up as a separate
Internet draft under the auspices of LDAPEXT, rather than incorporating
its definition into either the LDAP ACL model or the PKIX LDAP schema.
So is there support out there for me to go ahead and write this up as
an ID ? Will LDAP access control and PKIX make use of it ?

Regards,
Steven