Deviations and Nuances
Meerkat DSA deviates from the X.500 specifications in a few ways, often due to ambiguities in the specifications or creative leeway given by the specifications to DSA implementors. These deviations are noted below. Also noted below are nuances in Meerkat DSA:
- RFC 4512: "The root DSE SHALL NOT be included if the client performs a subtree search starting from the root." I could not find anywhere in the X.500 specifications where this behavior is required, however, it makes sense to me: a search that does not explicitly target the root DSE should not include it, because the root DSE is, in some sense, not a "real" entry in the DIT.
- Meerkat will not allow the creation of attribute values from the modifyDN operation. This is for security / integrity purposes.
- Meerkat DSA automatically sets the
SearchControlOptions.separateFamilyMembersoption when LDAP search requests are converted to DAP search requests. This does not violate the X.500 standards, but it is not mentioned. In fact, there is no specified behavior on how to translate an LDAP request into DAP--only the opposite. - Meerkat DSA will not throw a
noSuchAttributeOrValueerror during a compare operation. That is insecure because it reveals that the entry does not have an attribute of the asserted type. - The "Target Not Found" subprocedure defined in X.518 seems to imply that a single CR must be chosen from candidateRefs to be added to the NRContinuationList. Instead, Meerkat DSA adds all of them. Why not try all of them?
- Check Suitability of filter for a subtree search in a shadowed area is extremely complicated and guaranteed to be incorrect. However, it will function better when the attribute selection of replication and the filter is simpler.
- Check Suitability of selection for a search or read is not performed at all. The selection will return whatever attributes are requested and replicated.
- The X.500 specifications are not clear at all as to how the
uniqueIdentifierattribute is to be used for authentication, since it is multi-valued and user-modifiable, so, during bind, the firstuniqueIdentifier, if it exists, will be used as the boundNameAndOptionalUID. - How LDAP matching rule assertion syntax is obtained from
MatchingRuleDescription: it is not. It is obtained from the
ldapAssertionSyntaxproperty of matching rules. - Because LDAP schema values are converted to the equivalent X.500 types, extensions (fields starting with "X-") will be ignored and not preserved.
- ITU Recommendation X.511, Section 7.5.f is not clear in what it means by "behaves as though normal entries do not exist." The parent of a subentry is necessarily a normal entry. Does this mean that a subtree search can only return subentries immediately subordinate to the base object? Meerkat DSA behaves as if this were so.
administrativeRoleis automatically added to top-level DSEs when added if it is not present, making the entry an AAP.- The specification is not clear as to whether
pageNumberis zero-indexed or one-indexed. Meerkat DSA will treat this as zero-indexed. This means that, whether the parameter is 0 or not supplied in the request, a simple falsy check can inform the DSA as to whether it can ignore this parameter. If a user did not want to use paging, they should omit this value, rather than setting it to zero or one to indicate that they want the first page; for this reason, 1 will be treated as the "second" page. As another slight benefit, this also means that if clients differ in their behavior from this, it means that fewer entries will be returned. - Pagination may not be used when signing is required and chaining is not prohibited. This is because there is no way to merge results while preserving the signatures from other DSAs. If chaining is prohibited, there will only be results from the local DSA, which mean that the results can be paginated and signed.
- There are necessarily no access controls that can be applied to first-level
DSEs that do not yet exist. This begs the question: how do we control which
users can add first-level DSEs? Meerkat DSA does this by prohibiting entries
that do not have a
may_add_top_level_dseflag set. The first entry to have a password set will automatically get this flag set as well. After that first entry, any other entries that should have this permission will require direct database queries to get this flag set. To use this privilege, a user cannot be authenticated anonymously. This does not apply if there are no users with passwords set, or if theMEERKAT_OPEN_TOP_LEVELenvironment variable is set to1. - When sorting is used in LDAP requests, the response will always indicate a successful sort by including the sort response control with a success code. If ever in the future, sorting status can "trickle-up" to the LDAP response from the operation dispatcher, maybe this will change.
matchedValuesOnlykeeps only the matched values from all members of the returned family. This might not be incorrect, since the X.500 specifications do not clarify what the expected behavior is for non-contributing family members, but it is something to be aware of.- The
restrictedByalternative ofProtectedItemsis not supported for the purposes of access control. - Pending implementation: it will be slightly more efficient to use the user-first alternative for ACI Items, because the ACI Items can be "pre-filtered" to only retain the relevant ones.
DiscloseOnErrorpermissions do not apply to some operations, such as list and search when they return 0 results, or addEntry, because Meerkat DSA requires the target objects to be discoverable in the first place.- We do not check for
DiscloseOnErrorin modifyEntry and addEntry operations that add values, to determine when an attribute value already exists before it is added, because checks are already in place to determine if it can be added. The X.500 specifications permit the attribute value's existence to be disclosed if DiscloseOnError OR Add permissions exist for that value, but since Add permissions are a necessary pre-requisite before Meerkat DSA even checks for duplicates, there is no need to worry aboutDiscloseOnErrorpermissions. - The ITU specs do not explicitly say that an IDM client cannot make multiple subsequent bind attempts before the first one gets a response. If this is allowed, it opens the doors to brute-force attempts. Nefarious users can circumvent rate-limiting by submitting back-to-back bind requests without waiting for each one to succeed sequentially. Meerkat DSA aborts IDM connections with clients that attempt back-to-back binds.
- ITU X.511 (2016), Page 43, Footnote 2: This is because, apparently, the ACI
for a subordinate reference may not be available locally (see ITU X.518
(2016), Section 19.3.1.2.1, item 3). If this is the case, the DSA must chain
to let the subordinate DSA decide whether to reveal this entry. There is not
really a good way to know if the subordinate DSA actually informed the
superior DSA of relevant access control information, so Meerkat DSA will
assume that it has all of the ACI information necessary to make this
decision. If the subordinate does not inform the superior of ACI information,
it is the fault of the subordinate if its
subrentry is disclosed. For that matter, the subordinate DSA could use no access control or an unsupported access control scheme, for all the superior DSA knows. - The specification is not clear as to which member of search or list results,
including those within a compound entry or hierarchy selection, should have
partialNameset toTRUEwhen a name is partially-resolved. Meerkat DSA will only setpartialNamefor the base object, unlessseparateFamilyMembersis used, in which case all members of the family will be marked accordingly. - Permission to read the entry and the attribute types and values of the new RDN are required for renaming an entry. This prevents information disclosure where a nefarious user could attempt to discover values present in the entry by seeing which newRDN choices come back with a "no such values" error.
- The X.500 specifications demand that, if a change or removal of a subschema or DIT structural rule results in a change in the governing structural rule of any entry in the subschema, every entry within the subtree beneath that entry downwards until autonomous administrative points must have their governing structure rules recalculated. Meerkat DSA does not update governing structure rules automatically, because it could mean that potentially millions (or even billions, depending on how big the directory gets!) of entries would be affected. With Meerkat DSA, administrators must manually kick off a recalculation of an entry's governing structure rule. This can be done by performing a modifyDN operation that 'renames' an entry to its exact same name on every entry, starting from the immediate subordinates of any affected entry downwards until you reach autonomous administrative points or subschema administrative points. Note that, whenever an entry is made into an autonomous administrative point or subschema administrative point, or whenever such an administrative point has a DIT structure rule added, removed, or modified in its subschema, its governing structure rule will be automatically recalculated; this does not recurse downward automatically.
- When a subentry is added below an administrative point that is also a context prefix, the superior DSA's operational binding (if one exists) is updated.
- The Root DSE may not be modified.
- The information selection of a read or search operation is not evaluated against the selection of information that is shadowed for a shadow DSE. Meerkat DSA will simply return whatever it has.
- When using the
removeValueschange from themodifyEntryoperation, the presence of the values to be removed will not be checked. Whether they exist or not, this change will succeed. One benefit of this is that we do not have to worry about accidentally disclosing to users which values exist and do not exist for an entry by returning a different error when they do exist. - When using the
alterValueschange from themodifyEntryoperation,Modifypermission is also required for the values that are to be replaced. This is more strict than the specification. - Default context values are not used exactly as specified in X.501 (2016), Section 13.9.2. If the context is required by context use rules, and if the context of that type is not supplied, a default value can "fill the gap," but beyond that, default context values are not used. This is because the verbiage of the section 13.9.2 is unclear.
- ITU Recommendation X.501 (2016), Section 14.10 states that, when a hierarchical parent is removed, its children are to be removed from the hierarchical group. The specification does not make it clear whether they should now belong to separate hierarchical groups with themselves at the top or if we should recursively remove all hierarchical group attributes for all hierarchical descendants. Meerkat DSA puts the children in their own separate hierarchical groups. It is not clear whether this is a deviation from the specification at all. This was chosen because it is the most performant, easiest to implement, and preserves potentially a lot of work from accidental deletion.
- Meerkat DSA does not throw an error if a search or list operation returns a null result (a result with zero entries or RDNs).
- The way Meerkat DSA handles invalid signed operations is complicated and is described here.
- The
userPwdHistoryattribute returns password history items that have fallen out of history according to thepwdMaxTimeInHistoryoperational attribute. When passwords are changed, they are still evaluated against the current, valid password history.- This is done for performance reasons. There is no practical way to keep
these attributes synced up with
pwdMaxTimeInHistory.
- This is done for performance reasons. There is no practical way to keep
these attributes synced up with
- Password history is never deleted unless
administerPasswordis used and only if there are insufficient slots in theuserPwdHistoryto store the old and new passwords.- This is kind of a good thing: if you change
pwdMaxTimeInHistoryto a higher value, the history items that would have been truncated from history will flawlessly re-appear in history.
- This is kind of a good thing: if you change
- Meerkat DSA can enforce mandatory password resets as described (mentioned in
passing, really) in ITU Recommendation X.511. As it is unclear how this is to
be recorded in the X.500 specifications, Meerkat DSA uses the
pwdResetattribute defined here and commonly used in LDAP servers. In addition to this, the X.500 specifications say that the only allowed operation should bechangePassword, but Meerkat DSA is much more generous than this, allowingmodifyEntry(since it can also be used to change an entry's password),administerPassword(since theoretically, an administrator could administer their own password), andsearchandlist, since many DUA implementations might present an interactive "tree-like" representation of the directory that automatically performs these operations and which cease to work if these operations are unavailable. In LDAP, the only operations available when a password is pending a change aresearch,modifyEntry, and the LDAPchangePasswordextended request. - A
securityErrorwith aproblemofblockedCredentialswill never be returned, even if the account is blocked. This is for security reasons:- If the asserted credentials are invalid, it risks disclosing whether an
entry exists. A nefarious user could guess distinguished names until they
get a
blockedCredentialserror instead ofinvalidCredentials, which will reveal the existence of entries they were not supposed to know about. - If the asserted credentials are valid, it risks disclosing the correct
password for a locked account. Nefarious users could guess passwords for a
locked account until they receive a
blockedCredentialserror instead ofinvalidCredentials, which will reveal that they guessed the password that was in place prior to the block.
- If the asserted credentials are invalid, it risks disclosing whether an
entry exists. A nefarious user could guess distinguished names until they
get a
- Duplicates are only removed from search or list results in these
circumstances:
- When a local operation yields local results that are duplicates, and
- When paginated search results are requested and the
unmergedoption is left unused or explicitly set toFALSE.- This might not be a deviation at all, because the procedures for deduplicating entries are vague enough to lack details for handling the presence of multiple possibly-signed result sets. It is worth knowing, though: if you have a strong need for absolutely no duplicate entries in results, simply request pagination, even if you only read the first page.
- ITU Recommendation X.501 (2019), Section 10.3 outlines very specific ordering in which entries included as a result of hierarchical selection are to be returned in search results, but Meerkat DSA does not strictly obey this, solely because an implementation that did strictly obey this would be complicated, and for the small benefit of correct ordering. Instead, Meerkat DSA performs the hierarchy selection sub-searches in such an order that the returned entries are innately likely to appear in approximately the proper ordering.
- Relaxations and Tightenings do not apply to
extensibleMatch. This was partly due to difficulty in implementing this, but also because it seems like unintuitive behavior to replace matching rules that a user specifically requested. - Where a search rule is used to provide default values in a request attribute profile, filter evaluation does not check if the supertypes of that the asserted attribute type have request attribute profiles with default values.
- The
additionalControlcomponent in a search rule has no effect, because Meerkat DSA does not recognize any service control attributes. - The
matchingUsecomponent in a search rule'sinputAttributeTypeshas no effect because there are no matching restrictions defined anywhere and Meerkat DSA does not support any. - The
entryTypecomponent of a search rule's input attribute type'sdefaultValuesfield is unused, because the specification does not define how it is to be used. As such, Meerkat DSA simply joins all default values defined in each item in this set as though there were noentryTypefield at all. - When HOBs and NHOBs are terminated, the subordinate DSEs are not deleted from the subordinate DSA. This is so these entries can be "repatriated": reused for later.
- When the last subordinate entry underneath an NSSR is deleted, the NHOB will not be automatically terminated. This is so more entries could be added back to the subordinate DSA, if desired. This is NOT the case with a hierarchical operational binding (HOB).
- When an
addEntryoperation is chained, Meerkat DSA checks if its superior DSE is of typeimmSupr, and that the chained request came from one of the superior DSAs. If so, the new entry is marked as having typecp. This is so theaddEntry'stargetSystemparameter can be used to add new context prefixes beneath a Non-Specific Subordinate Reference (NSSR). Having entries of typecpbeneath an NSSR are essential for list and search operations to work correctly across NHOBs. - There is no way for a shadow supplier to indicate to a shadow consumer that it
is the master for the replicated area. There is a
masterfield in the shadowing agreement, but this is used generally just to indicate where the master DSA can be reached. As such, Meerkat DSA will assume that, if this field is present, the correspondent DSA is not the master. - The X.500 specifications state that access to a given entry is denied under
Rule-Based Access Control when access to all attribute values is denied.
However, enforcing this would be devastating from a performance perspective.
When performing a
listoperation, Meerkat DSA would have to check what might be thousands of attributes per entry. Instead, Meerkat DSA denies access to an entry if access to any of its distinguished values are denied. This is much faster, since usually only one single value is evaluated, and it is technically more strict from a security perspective.
The "Never Contributing" Bug
ITU Recommendation X.511 (2016), Section 7.13 states that:
If the filter used is the default filter (and : ), then all members of a family grouping shall be marked as participating members, but not as contributing members.
This is a problem, because familyReturn defaults to contributingEntriesOnly,
which means that nothing will be returned even though the compound entry as a
whole matches and:{}. In other words, if the default search filter and
selection are used, compound entries will be entirely hidden from results if the
X.500 specifications are observed strictly.
This was probably not intentional, so I reported it. In early January of 2022. Until I get clarification, Meerkat DSA will mark every entry as a contributing member if there is a match, but no identified contributing members.
Protected Passwords
Annex E of ITU Recommendation X.511 (2019) defines a proposed algorithm for producing protected passwords as used by simple authentication. This is a non-normative section of the specification, and as such, it is not technically a deviation from the X.500 specifications.
The Annex E solution incorporates the raw, unhashed password into the data
structure that will be used to produce the hash that is evaluated. This means
that the password would have to be stored unhashed to make this possible,
which goes against all modern expectations for the secure storage of passwords
in software architecture. For this reason, Meerkat DSA uses a procedure similar
to the Annex E procedure, but by redefining the first hash input to replace the
raw user password with a UserPwd (and, unrelated, to make the random1
BIT STRING optional):
-- This is the data structure described in ITU Rec. X.511, Annex E.
X511-AnnexE-Hashable1 ::= SEQUENCE {
name DistinguishedName,
time1 GeneralizedTime,
random1 BIT STRING,
password OCTET STRING }
-- This is what Meerkat DSA actually hashes to produce f1.
Meerkats-Actual-Hashable1 ::= SEQUENCE {
name DistinguishedName,
time1 GeneralizedTime,
random1 BIT STRING OPTIONAL,
password encrypted < UserPwd -- This is an ASN.1 "selection type." -- }
The password field MUST use the encrypted alternative of UserPwd. The
encrypted value MUST use the exact algorithm that can be found the pwdEncAlg
operational attribute. The reason for this is that the password is stored
encrypted / hashed in the database and cannot be "re-encrypted" using some other
algorithm. Clients must first read the algorithm used for this password before
attempting to produce a protected password, because they need the exact
algorithm and parameters to produce an identical encrypted / hashed value.
Unrelated to the problems above, Meerkat DSA also makes random1 and random2
optional for protected passwords. You will notice that the above
Meerkats-Actual-Hashable1 that random1 is OPTIONAL. In addition to this,
the second hashable defined in Annex E is also modified as such:
-- This is the data structure described in ITU Rec. X.511, Annex E.
X511-AnnexE-Hashable2 ::= SEQUENCE {
f1 OCTET STRING, -- hashed octet string from above
time2 GeneralizedTime,
random2 BIT STRING }
-- This is what Meerkat DSA actually hashes to locally produce the hash value.
Meerkats-Actual-Hashable2 ::= SEQUENCE {
f1 OCTET STRING, -- hashed octet string from above
time2 GeneralizedTime,
random2 BIT STRING OPTIONAL }
It is also worth noting that Meerkat DSA does not support every hash algorithm under the sun. These are the ones that are supported at the time of writing:
sha1sha224sha256sha384sha512sha3-244sha3-256sha3-384sha3-512shake128shake256
If a user produces a protected password with an algorithm not listed above, the password authentication will simply fail as though the password had been wrong.
Finally, if a user provides simple credentials using the protected
alternative, but does not supply a time1 value, the protected password will
be used to construct a UserPwd value (using the encrypted alternative), and
that will be asserted. To reiterate, this does not violate the specifications,
since this behavior is explicitly undefined therein.
Hidden Service Admin Areas
The X.500 specifications mandate that searches are not to recurse into other
service administrative areas, but this means that service admin points will not
be discoverable at all via search operations. Since LDAP has no list
operation, it also means that LDAP users will never be able to find any entry
that lies in a different service administrative area (except by "guessing" that
it exists).
For example, if C=US,ST=FL is a service admin point, and a user performs a
one-level search at C=US, the ST=FL subordinate will be hidden from the
results entirely. The user will have no way of even finding ST=FL except for
performing a list operation and noticing that this subordinate differs from
the results obtained by a one-level search (since list is not governed by
service administration).
Meerkat DSA deviates from the specification by recursing one entry into other
service administrative areas so that the DIT is traversible to users. Continuing
on the previous example, this means that, if a user performs a one-level search
at C=US, the ST=FL subordinate will be returned. If a subtree search at
C=US is performed, ST=FL will be returned as well, but none of its
subordinates (the latter of which is technically correct behavior).
This behavior can be turned off by setting
MEERKAT_PRINCIPLED_SERVICE_ADMIN
to 1.
The above issue will be reported to the ITU working group that authors the X.500 specifications, so it may be resolved in a future version.
Other Deviations
There are other deviations that haven't been mentioned here. Most deviations are the frequency of automated updates to operational bindings. Meerkat DSA updates operational bindings more frequently than is required by the specifications. This should not be of significance more the vast majority of users, and is probably desirable.