Peios Generic System Standards
The cross-platform standards a system must implement to be Peios — contracts between two roles, either of which a third party may replace.
Single-page view · as markdown
1.1 Scope
Peios / Advanced Peios / PGSS / Introduction
This document defines the Peios Generic System Standards (PGSS): the cross-platform protocols and standards a system MUST implement in order to be Peios.
A standard belongs in this document when three things are true of it.
It is a conformance requirement. A system that does not offer the protocol, at the specified path, with the specified semantics, is not Peios. Each standard here is a bar to clear, not a recommendation to weigh.
It belongs to no implementation. A PGSS standard describes a contract between two roles, not the behaviour of a particular program. Mainline ships an implementation of each role; nothing in this document depends on that implementation, or describes it.
Either role may be replaced. A third party MAY ship its own implementation of one role — or of both — and interoperate with the other unchanged. A standard that cannot survive that substitution is a description of one system rather than a contract between two, and does not belong here.
For each standard, this document covers:
- the channel it is offered on, and the access control governing it
- message framing, encoding, and the rules under which the format may be extended
- the messages exchanged, their fields, and the order in which they are exchanged
- the obligations binding on each role, including what a role MUST establish for itself rather than believe from a message
- the conformance requirements for each role
This document does not cover:
- How an implementation reaches the answers it gives — that is precisely what different systems exist to do differently
- The binary structures these protocols carry — defined in PCDS
- Protocols spoken across the kernel boundary — defined in PSPK
- Protocols between foundational userspace components — defined in PSPU
- Interfaces particular to one Mainline component — defined in the specification of the component that offers them
1.1.1 Distinguishing a standard from a protocol #
The anthology's three protocol documents are told apart by what happens when you disagree with one.
Disagreeing with a standard in this document means shipping something that is not Peios.
Disagreeing with PSPK or PSPU means shipping a system built from different parts — a different kernel subsystem, or a different set of userspace components. That is a design choice, not a conformance failure.
1.2 Conventions
Peios / Advanced Peios / PGSS / Introduction
The key words MUST, MUST NOT, SHOULD, SHOULD NOT, and MAY in this document are to be interpreted as described in RFC 2119. Text set off as a note is informative, not normative.
Everything else — roles, byte order, sizes, layout tables, notation, strings, timestamps, citation, and the external standards this anthology depends on — is defined in the Conventions book and is not restated here. PGSS departs from none of it.
Where a chapter needs a convention of its own, that chapter states it.
2.1 Scope and Roles
Peios / Advanced Peios / PGSS / Logon
This chapter specifies PGSS Logon: the protocol by which a caller obtains a KACS token and a logon session from an authentication authority, and the protocol by which any program on the system resolves an identity it already holds into a name, a SID, or the attributes a POSIX program expects of one.
Two roles participate.
The authority is the process that listens on the logon socket. It decides whether a logon succeeds, mints the resulting token, creates the logon session, and answers identity lookups. There MUST be at most one authority on a running system.
The client connects and speaks on a principal's behalf. It proposes what kind of logon it wants, renders prompts and returns answers without interpreting them, and receives the token and installs it itself. A client is not trusted: everything it sends is a claim (§2.4).
The principal is the identity a logon is for — the person or service being authenticated. The principal is not a party to the conversation.
Both roles are publicly implementable. A third party MAY ship a different authority, a different logon originator, or both, and interoperate with the other half unchanged.
This chapter covers:
- the two channels an authority offers, and the access control governing each (§2.5, §2.14)
- message framing, header layout, and the rules under which the format may be extended (§2.6)
- the messages of the logon conversation, their fields, and their encodings (§2.7 to §2.10)
- the shape of a conversation: who speaks, in what order, and how it terminates (§2.3)
- the transfer of the minted token to the caller, and the session-profile values a logon originator needs in order to start a session (§2.9)
- credential handling obligations binding on both roles (§2.11, §2.12)
- what an authority MUST establish for itself rather than believe from a message (§2.4)
- resolving a principal to a name, a SID, a POSIX identifier, or the attributes a POSIX program expects of one (§2.13 to §2.18)
- how a bare name is resolved when more than one source could answer it (§2.15)
- the obligations binding on each role (§2.19)
This chapter does not cover:
- Tokens, SIDs, privileges, integrity levels, logon sessions, logon types, and access checks — described in the Peios Kernel TRM. SIDs and security descriptors are specified in PCDS.
- How an authority decides whether a credential is valid, where it keeps identity, which principals exist, or what they are called. Those belong to the authority's own design. Mainline's authority federates them over PSI, the Principal Source Interface, specified in PSPU §2.
- Password change, credential enrolment, and account administration, which are not specified.
- Service startup and ordering, described in the peinit TRM.
The distinction in the second point is the load-bearing one. PGSS Logon specifies how a caller asks and how an authority answers. It says nothing about how the authority reaches its answer, because that is exactly what different systems will do differently, and constraining it would make the standard a description of one implementation rather than a contract between two.
2.1.1 What the authority does not do #
An authority MUST NOT be a process factory. It does not fork the principal's shell, does not learn about controlling terminals, environments, or session leadership, and does not decide what the caller does with the token it receives.
The caller installs the token and proceeds. This keeps the most privileged process on the system out of the business of launching arbitrary programs, and it means a client can obtain a token for a purpose the authority need never have anticipated.
2.1.2 Authentication and derivation #
Two acts, deliberately separated.
Authentication establishes that the principal is who they claim. Its output is an identity and nothing more.
Derivation constructs the token: which SIDs it carries, which privileges, at what integrity level, with what projected identifiers. Its inputs are the authenticated identity and local policy.
The separation matters because the second is where a machine's own rules apply. An identity established elsewhere — by a directory, by a remote authority — does not carry entitlements onto this machine with it. What that identity means here is decided here, every time, by the authority applying local policy.
An authority MUST perform derivation itself. It MUST NOT accept a token, a privilege set, or an integrity level supplied by any other party, whatever its trust level.
2.1.3 The token this chapter does not describe #
This chapter governs the conversation and the delivery of its result. It does not govern the contents of the token that results. What SIDs, privileges, and integrity level a principal receives is derivation, and derivation is the authority's judgement applied to local policy.
A caller that receives a token from this protocol has been told who it may act as. It has not been told, and MUST NOT infer, anything further about how that conclusion was reached.
2.2 Terminology
Peios / Advanced Peios / PGSS / Logon
Terms defined in the Peios Kernel TRM (token, logon session, privilege, integrity level, logon type) and in PCDS (SID, security descriptor, DACL) are used here with the same meaning and are not redefined.
Authority. The process that listens on the logon socket, decides whether a logon succeeds, mints the resulting token, creates the logon session, and answers identity lookups. There is at most one authority on a running system.
Client. A process that connects to the authority. Also called the originator when the emphasis is on whose identity the authority verifies. There are two client roles — originating a logon and looking an identity up — and they are independent (§2.19).
Principal. The identity a logon is for — the person or service being authenticated. The principal is not a party to the conversation; the client speaks on their behalf.
Conversation. One logon connection's exchange, from the client's opening message to a terminal message from the authority. One connection carries exactly one conversation.
Round. One CredentialRequest from the authority and the
CredentialResponse that answers it. A conversation MAY take several
rounds.
Terminal message. AccessGranted or AccessDenied. Exactly one is
sent, and nothing follows it.
Credential material. Any byte sequence a principal supplies as proof of identity — a password, a one-time code, a response from a token. Distinguished throughout from a verifier, which is what an authority stores and which MUST NOT be usable as credential material.
Prompt. A request from the authority for one piece of credential material, carrying enough description for a client to render it without understanding what it is for.
Derivation. The authority's construction of a token's contents — its SIDs, privileges and integrity level — from the authenticated identity and local policy. Distinct from authentication, which establishes only that the principal is who they claim.
Source. A party an authority consults in order to answer a question about identity. Whether an authority has sources at all, and what they are, is its own design; the term appears here only where the protocol's behaviour depends on there being more than one possible answerer (§2.15, §2.18).
2.2.1 A note on "logon type" #
LogonType (§2.7) describes the kind of session being established —
interactive, network, batch, service. It is a property of the situation,
not of the principal, and the same principal may hold several sessions
of different types at once. Its values and meanings are defined by KACS
and described in the Peios Kernel TRM; this chapter carries it but does
not define it. The values are listed for reference in §2.B.
2.3 The Conversation
Peios / Advanced Peios / PGSS / Logon
A logon is a conversation, not a call. The client opens it, the authority asks for whatever the principal's policy requires, and the authority ends it.
client authority
| |
|------------- LogonStart ---------------->|
| |
|<--------- CredentialRequest -------------| round 1
|---------- CredentialResponse ----------->|
| |
|<--------- CredentialRequest -------------| round 2 (if required)
|---------- CredentialResponse ----------->|
| |
|<-- AccessGranted (+ token fd) -----------| terminal
| or AccessDenied |
2.3.1 Why a conversation #
The value of this shape is that the client stays generic. It does not know what a password is. It renders the prompts it is given, collects the answers, and returns them.
That is what makes multi-factor authentication, password-expiry-forces-change, smartcards, or a policy that asks for a second factor only from an unfamiliar host, changes to authorities rather than to every client on the system. A protocol that named its credential kinds would have to be revised, and every client rebuilt, for each one.
Most logons are one round: the authority asks for everything the principal's policy requires, in one array, and decides. The conversational shape exists for what one round cannot express — choosing between authentication paths, or a shared account where one credential unlocks a requirement for another.
2.3.2 Sequence rules #
A conversation MUST proceed as follows.
- The client sends exactly one
LogonStart. It MUST be the first message. An authority MUST reject any conversation that opens with something else. - The authority sends zero or more
CredentialRequestmessages. Each MUST be answered by exactly oneCredentialResponsebefore the authority sends anything further. - The authority sends exactly one terminal message,
AccessGrantedorAccessDenied. - Both parties close the connection.
The authority MAY send a terminal message at any point after
LogonStart, including before any CredentialRequest. Zero rounds is a
conforming conversation: an authority that can decide from LogonStart
alone — a pre-authenticated caller, or a refusal on logon type — is not
required to ask for anything.
A client MUST NOT send a CredentialResponse that was not solicited by
a CredentialRequest. An authority MUST reject one that was not.
2.3.3 Bounding the conversation #
An authority MUST bound the number of rounds it will conduct and MUST
bound the time it will wait for a CredentialResponse. Neither bound is
fixed by this chapter, since both are policy. An authority that exhausts
either MUST terminate with AccessDenied carrying ConversationLimit
rather than closing silently, so that the client can distinguish a
policy limit from a crash.
2.3.4 Termination #
Exactly one terminal message is sent. After it, the authority MUST NOT send anything further on that connection, and MUST close it.
A connection that closes without a terminal message is an abnormal termination. A client MUST treat it as a failed logon and MUST NOT retry automatically, since the reason is unknown and may be a policy refusal the authority could not express.
2.4 What an Authority Must Not Trust
Peios / Advanced Peios / PGSS / Logon
Nothing a client sends is trusted. This section states the rule once, because every field in §2.7 is subject to it.
2.4.1 Peer identity #
An authority MUST establish the connected peer's identity from the connected socket, by reading the peer's token from the kernel. It MUST NOT take the peer's identity from any message body, because there is no field in which a client could put it that the client could not also lie in.
An authority MUST NOT use SO_PEERCRED for this purpose. It answers a
similar-looking question and is the wrong answer: it returns the
projected UID, which cannot distinguish an authenticated principal
from an unauthenticated process running under the same projection, and
carries none of the token's SIDs, groups, integrity level or privileges.
2.4.2 Logon type #
LogonStart.logon_type is a proposal, not an instruction.
Only the caller knows whether an inbound connection is an interactive shell or a batch command, so the caller has to be the one to say. But an authority MUST constrain the proposal against what that verified peer is permitted to request. Otherwise anything that can reach the socket can mint itself an interactive session, and the logon type — which access control decisions depend on — becomes a value chosen by the least trusted party in the exchange.
2.4.3 Identifier #
The identifier names the principal a logon is for. It is a claim about who is being authenticated, and it is what the subsequent credential exchange exists to test. An authority MUST NOT treat an identifier as established until authentication has succeeded.
2.4.4 Everything else #
tty and remote_host are unverified context. An authority MAY record
them, MAY use them in policy, and MUST NOT treat them as established
facts. A client that lies about its remote host is not prevented from
doing so by this protocol.
2.4.5 Access to the socket #
Access to the logon socket MUST be controlled by a security descriptor.
It MUST NOT be controlled by process integrity level. PIP can gate a socket, but using it here would require every future caller — a graphical greeter, a web console, a remote access daemon — to be signed at high trust merely to collect a password. Collecting a credential is not a privileged act; deciding whether it is correct is, and that decision happens on the other side of the socket.
Rate limiting is defence in depth. It is not the access control, and an authority MUST NOT rely on it as such.
2.5 The Logon Channel
Peios / Advanced Peios / PGSS / Logon
2.5.1 Socket #
An authority MUST listen on a SOCK_STREAM Unix domain socket at:
/run/logon.sock
The path is normative. It is the standard's path, not an implementation's, and a client MUST NOT require configuration to find it.
2.5.2 Access control #
The socket MUST carry a security descriptor granting connect access to the principals permitted to originate logons. See §2.4 for why this, and not process integrity, is the control.
2.5.3 One conversation per connection #
A connection carries exactly one conversation. The connection is the conversation's identity.
There is therefore no correlation identifier in the header, and none is needed: a message belongs to the conversation it arrived on. This removes a class of error and attack — a forged or confused identifier cannot attach a credential response to somebody else's logon, because there is no identifier to forge.
It also bounds the credential's lifetime by the connection's, which makes that the kernel's job to enforce rather than the authority's to remember.
An authority MUST close the connection after sending its terminal message. A client MUST close after receiving one.
2.5.4 Concurrency #
An authority MUST serve conversations concurrently. A logon that stalls — a principal who walks away mid-prompt — MUST NOT prevent other logons from proceeding.
An authority MUST bound the number of conversations it will serve at once, and MUST bound the time a conversation may remain open. Both are policy; neither is fixed here.
2.5.5 Descriptor passing #
The channel MUST support ancillary data (SCM_RIGHTS). The token is
transferred as a file descriptor alongside AccessGranted — see §2.9.
2.6 Message Framing
Peios / Advanced Peios / PGSS / Logon
Both of an authority's channels use the framing specified here without modification. The identity socket's departures are stated in §2.14; they concern which message types are served where, not the encoding.
2.6.1 Header #
Every message begins with a 12-byte header:
| Offset | Size | Field | Value |
|---|---|---|---|
| 0 | 4 | magic | PGSL (50 47 53 4c) |
| 4 | 2 | version | 1 |
| 6 | 2 | msg_type | See §2.A |
| 8 | 4 | total_len | Header plus body, in bytes |
total_len counts the header. A message is therefore self-delimiting
from its first 12 bytes, and a reader can size its buffer before reading
the body.
2.6.2 Magic #
The magic MUST be checked on every message and MUST cause an immediate, whole-connection failure when wrong.
This is not decoration. Peios protocols share this codec and this header layout, and some of them share message bodies. A socket plugged into the wrong daemon would otherwise partially work, which is far worse than failing outright — a subtle misbehaviour several fields in, rather than a hard error on byte zero. A protocol reusing this codec MUST take a distinct magic.
2.6.3 Version #
A peer receiving a version it does not implement MUST refuse the
message. An authority MUST refuse with AccessDenied carrying
UnsupportedVersion where it can still encode one.
2.6.4 Size limit #
A message MUST NOT exceed 65536 bytes in total. A decoder MUST reject a header declaring more, without reading the body, and MUST reject one declaring fewer than the header's own length.
2.6.5 Message type #
The high bit of msg_type marks a message sent by the authority.
Client messages have it clear. This is a readability property rather
than a security one — a peer MUST validate the message type it received
against what it expected, not merely against the direction bit.
2.6.6 Encoding #
All multi-byte integers are little-endian.
Strings are UTF-8 and are not NUL-terminated. A string is
encoded as a u32 byte count followed by exactly that many bytes. A
decoder MUST reject a string whose bytes are not valid UTF-8.
An empty string and an absent optional string are encoded identically, as a zero byte count. A field documented as optional is therefore absent when empty, and an implementation MUST NOT distinguish the two.
Byte strings are encoded the same way and carry no encoding requirement.
Arrays are encoded as a u32 element count followed by that many
length-framed structures. Every array in this chapter has a stated
maximum; an encoder MUST refuse to produce a longer one and a decoder
MUST reject one it receives. The same holds for every stated byte limit.
Two distinct length mechanisms therefore appear, and confusing them is the most likely implementation error:
- Byte strings and strings are prefixed with a
u32byte count, and the bytes follow immediately. - Structures and array elements are prefixed with a
u32byte count of the whole structure, and a decoder MUST skip to the structure's declared end after reading the fields it knows.
The second is what makes the format extensible.
2.6.7 Body extensibility #
The body is a single length-framed structure, and every structure and every array element within it is length-framed too.
A decoder reads the fields it knows and then skips to the structure's declared end. A field appended by a newer peer is therefore stepped over rather than mistaken for the next field — which is what makes the format extensible inside arrays, where ignoring trailing bytes at the message level would not help.
A structure that ends before a field an older encoder never wrote is not an error. A decoder that reaches the end of a structure's body where an optional trailing field would have been MUST substitute that field's documented default (§2.7, §2.9) rather than failing.
The rules this keeps working under are binding on anyone revising this chapter:
- Fields are appended only. Never reordered, never removed, never changed in meaning.
- A new field MUST be optional with a safe default, because older peers will not send it and will not read it.
- Adding a value to an enumeration is a breaking change and requires a version bump, because every peer is required to understand every value it is sent.
The last rule is the one that surprises people. An unknown enumeration value cannot be safely ignored: a client that skipped an unrecognised credential type would silently fail to collect something the authority required, and an authority that skipped an unrecognised logon type would grant the wrong kind of session.
There are exactly two exceptions, and both are stated where they apply:
LogonStart.supported_credential_types, which is a statement of
capability rather than an instruction (§2.7), and the field mask of
Lookup, whose reply says which bits it answered (§2.16).
2.7 LogonStart
Peios / Advanced Peios / PGSS / Logon
msg_type = 0x0001. Client to authority. Opens the conversation; MUST
be the first message.
2.7.1 Layout #
| Field | Encoding | Limit |
|---|---|---|
logon_type | u8 | §2.B |
identifier_type | u8 | §2.B |
identifier | length-framed bytes | 1024 |
tty | string | 128 |
remote_host | string | 256 |
supported_credential_types | length-framed bytes, one u8 per type | 32 |
Everything here is asserted by the client, and §2.4 governs all of it.
2.7.2 logon_type #
The kind of session the client is asking for. A proposal, which the authority MUST constrain against the verified peer — see §2.4.
Values are defined by KACS and listed for reference in §2.B.
2.7.3 identifier_type and identifier #
Together these name the principal. identifier_type says how to read
identifier; identifier is opaque bytes, not a string.
Bytes rather than a string because an identifier is not always text. A certificate thumbprint, a smartcard serial, or a binary principal name are all reasonable identifiers, and a protocol that insisted on UTF-8 would exclude them. An authority that expects text MUST validate the encoding itself.
An identifier MAY be empty. An empty identifier means the principal is not named here — the authority is expected to determine it from the credential, as with a smartcard that carries its own identity.
identifier_type is distinct from credential_type (§2.8): this is the
claim of identity, that is the proof. A passkey names a principal and
proves them in one artefact; a username names them and proves nothing.
2.7.4 tty and remote_host #
Unverified context, both optional, both empty when absent.
tty names the terminal the logon is happening on, where there is one.
remote_host names where a network logon came from.
An authority MAY use either in policy and MAY record either in its audit trail. It MUST NOT treat either as established. A client that lies about them is not prevented from doing so.
2.7.5 supported_credential_types #
Every credential type this client can render, one byte each.
This is the client declaring its capabilities, and it is binding on the authority: an authority MUST NOT send a prompt for a credential type absent from this list (§2.8).
A client that supports nothing sends an empty list. That is meaningful rather than degenerate — it says "I can complete a logon that requires no interaction, and nothing else" — and an authority MUST either complete the logon without prompting or deny it.
2.7.5.1 The two exceptions this field carries #
supported_credential_types is an optional trailing field, and it is
the only enumeration in this chapter whose unrecognised values are
dropped rather than refused. Both departures from §2.6 are deliberate,
and both are what make adding a credential type workable at all.
An absent field means Password, not an empty list. A decoder that
reaches the end of the body where this field would have been MUST read
it as a client supporting exactly the credential types that existed
before the field did — which is Password, and only Password. A
client predating the field could render nothing else. An absent field
and an explicitly empty one are therefore different statements, and an
implementation MUST NOT collapse them: the empty list is how a client
asks for a logon that requires no interaction, and reading it as
Password would turn that request into a prompt.
This is the one place where an empty length-framed field is not equivalent to an absent one (§2.6).
An unrecognised value is dropped, not refused. A decoder MUST discard a credential type it does not recognise and proceed with the rest, leaving the intersection of what the client claims and what the decoder understands. This is safe here and nowhere else, because the field is a statement of capability rather than an instruction: an authority that ignores a type it has never heard of merely declines to use it, which is the correct outcome.
Without this, a client that learned a new credential type could not speak to an older authority at all — the authority would be obliged by §2.6 to refuse the whole message. The capability list exists precisely so that an authority using a new type cannot reach an older client; it would be self-defeating if a client using a new type could not reach an older authority either.
2.8 Credential Exchange
Peios / Advanced Peios / PGSS / Logon
2.8.1 CredentialRequest #
msg_type = 0x8001. Authority to client.
| Field | Encoding | Limit |
|---|---|---|
messages | array of Message | 8 |
prompts | array of Prompt | 16 |
Message:
| Field | Encoding | Limit |
|---|---|---|
severity | u8 | §2.B |
text | string | 512 |
Prompt:
| Field | Encoding | Limit |
|---|---|---|
credential_ref | u32 | — |
credential_type | u8 | §2.B |
credential_name | string | 128 |
Both arrays MAY be empty.
2.8.1.1 Messages #
Text for the principal to read, before any prompt is presented. "Your password expires in three days"; "Authenticating against the local source".
A client MUST display messages it receives, in order, before the prompts in the same request. It MUST NOT interpret them, and MUST NOT vary its behaviour on their content.
2.8.1.2 Prompts #
Each prompt asks for one piece of credential material.
credential_ref identifies the prompt within the conversation. The
client echoes it back unchanged in its answer. It MUST be unique among
the prompts of a single request. An authority MAY reuse a value in a
later round.
credential_name is what to show the principal — "Password",
"Verification code". It is a display string. A client MUST NOT branch on
it; credential_type is what says how to collect the answer.
2.8.1.3 The client capability rule #
An authority MUST NOT send a prompt whose credential_type is
absent from the client's supported_credential_types (§2.7).
This is a hard requirement rather than a courtesy. A client that receives a prompt it cannot render has no good option: failing the logon punishes the principal for a mismatch neither party chose, and guessing — collecting a line of text for a credential type that is not a password — may echo a secret to the screen. An authority that cannot proceed within the client's declared capabilities MUST deny the logon instead.
A client that nevertheless receives an unrenderable prompt MUST fail the conversation rather than guess.
2.8.1.4 Empty requests #
A CredentialRequest with no prompts is valid. It carries messages
only, and the client MUST answer it with a CredentialResponse carrying
no answers. This is how an authority conveys information mid-conversation
without asking for anything.
2.8.2 CredentialResponse #
msg_type = 0x0002. Client to authority.
| Field | Encoding | Limit |
|---|---|---|
answers | array of Answer | 16 |
Answer:
| Field | Encoding | Limit |
|---|---|---|
credential_ref | u32 | — |
data | length-framed bytes | 32768 |
credential_ref MUST match a prompt from the request being answered.
data is the material the principal supplied — opaque bytes, no
encoding implied.
A client SHOULD answer every prompt it was given. An authority MUST NOT assume it has: a missing answer is a failed logon, not a protocol violation, and MUST be treated as such rather than as grounds to tear down the connection.
An authority MUST match answers to prompts by credential_ref and MUST
NOT rely on ordering.
Both parties encode and decode this message under the obligations of
§2.12. The encoded message holds the credential just as much as the
data field inside it does.
2.9 AccessGranted
Peios / Advanced Peios / PGSS / Logon
msg_type = 0x8002. Authority to client. One of the two terminal
messages; nothing follows it.
| Field | Encoding |
|---|---|
session_id | u64 |
profile | length-framed structure, optional |
Ancillary data: the token, as one file descriptor via SCM_RIGHTS.
2.9.1 The descriptor #
The token MUST be transferred as a file descriptor attached to this message. It MUST NOT be named, and there MUST be no path, handle, or identifier by which another process could reach it.
Possession of the conversation is what confers the token. There is no window in which a minted token exists under a name something else could open, and no lookup that could be raced or guessed.
A client MUST read the descriptor from the ancillary data of this
message. An AccessGranted arriving without one is a protocol violation
and MUST be treated as a failed logon — a client MUST NOT proceed as
though a logon succeeded when it holds no token.
2.9.2 session_id #
The logon session the token belongs to. The client MAY record it, MAY report it, and needs it to relate this logon to the kernel's session records.
It is informational to the protocol. The token is the thing that confers authority; the session identifier merely names the session the token already belongs to.
2.9.3 profile #
Where the session starts, and what to call the principal. A
length-framed structure, so it can grow without displacing anything
appended to AccessGranted after it.
| Field | Encoding | Limit |
|---|---|---|
home | string | 4096 |
shell | string | 4096 |
display_name | string | 256 |
profile is an optional trailing field. Every field may be empty,
and an empty field means the authority did not say. An authority that
knows nothing about home directories is conforming, and so is one that
omits the whole structure — a client MUST treat an absent profile
exactly as it treats one whose fields are all empty.
A client MUST have a fallback for each field and MUST NOT treat an empty value as an error.
home and shell, when non-empty, MUST be absolute paths. A client
MUST NOT execute a relative shell or resolve a relative home against
its own working directory. An authority with no value for a field MUST
leave it empty rather than invent one.
2.9.3.1 Why this is on the terminal message #
Nothing here is an access-control input. No ACL names a home directory, and the token carries none of these fields — so this is not identity, and an authority that got it wrong would produce an inconvenient session rather than an unsafe one.
It is here because the authority has just read these values in order to
decide the logon, and a caller that needs them in order to start a
session would otherwise have to ask a second time. A logon originator
cannot chdir or exec without them.
2.9.3.2 What this is not #
It is not a directory lookup, and it MUST NOT be treated as one. It answers for exactly one principal — the one who just authenticated — at exactly one moment. It cannot answer for anybody else, it cannot answer for a principal who never logged on, and nothing in this protocol says what it means an instant later.
Resolving arbitrary principals to home directories or shells is §2.16's, and a client that caches these values as though they had come from there has misread them.
2.9.3.3 display_name #
A human's name for a human to read.
It is deliberately not a GECOS field. GECOS is a comma-separated
/etc/passwd artefact carrying a name, an office, and two telephone
numbers in one string; a protocol that carried one would oblige every
client to parse it apart, and would tie this chapter to a file format it
has nothing to do with.
An implementation that must produce a GECOS field renders it from this, not the other way round.
2.9.4 What the client does next #
Installs the token, if that is what it wanted. The authority is not involved and MUST NOT be told (§2.1).
A client that decides not to install the token MUST close the descriptor. A logon session whose token is never installed still exists as far as the kernel is concerned.
A client SHOULD NOT fail a logon because a home it was given does not
exist. The principal has authenticated and holds a token; refusing to
start their session over a missing directory turns a cosmetic problem
into being locked out. Starting elsewhere and saying so is the better
failure.
2.10 AccessDenied
Peios / Advanced Peios / PGSS / Logon
msg_type = 0x8003. Authority to client. The other terminal message;
nothing follows it.
| Field | Encoding | Limit |
|---|---|---|
denial | u32 | §2.B |
reason | string | 512 |
2.10.1 denial #
A code from a deliberately small vocabulary (§2.B), for a client that needs to act differently — retry, offer a different account, report a system fault.
PermissionDenied and LogonTypeNotPermitted are distinct on purpose:
the first says the peer may not use this socket for this at all, the
second that it may originate logons but not of this kind. A client can
act differently on each.
2.10.2 reason #
Text for the principal to read. A client SHOULD display it and MUST NOT interpret it.
The division is the same one CredentialRequest makes between
credential_type and credential_name: the machine-readable field is
small and stable, the human-readable one is free.
reason MUST NOT narrow an AuthenticationFailed into a specific
cause.
2.10.3 What a denial must not reveal #
The denial vocabulary deliberately does not distinguish "no such
principal" from "wrong credential". Both are AuthenticationFailed.
A protocol that distinguished them would make account enumeration a
supported feature: anyone able to reach the socket could test names and
learn which exist. An authority MUST NOT provide that distinction
through the denial code, through reason, or through observable timing.
The timing obligation is the one most often missed, and it binds even though this chapter cannot check it. An authority whose unknown-principal path returns faster than its wrong-credential path has published the distinction it just declined to state — see §2.12.
2.11 Credential Types
Peios / Advanced Peios / PGSS / Logon
A credential type tells a client how to collect an answer. It does not tell it what the answer means.
| Value | Name | Collection |
|---|---|---|
| 1 | Password | A line of text, not echoed |
The registry is deliberately short. A type is added when a client would need to collect something differently, not when an authority acquires a new way of checking something.
2.11.1 Adding a type #
Adding a value to this enumeration is a breaking change and requires a version bump (§2.6).
An authority MUST NOT send a prompt for a type absent from the client's
supported_credential_types, so an authority using a new type simply
cannot reach an older client — which is the correct outcome, and is why
the capability list exists. The converse — a newer client reaching an
older authority — is what the dropping rule in §2.7 exists for.
2.11.2 Why credentials cross in the clear #
Credential material travels this socket as plaintext. That is deliberate, and the alternative is worse.
Challenge-response requires the verifier to store something it can recompute the response from: either the plaintext, or a value that is password-equivalent. NTLM works exactly this way — the stored hash is as good as the password, forever, which is why pass-the-hash has been the most valuable credential on a Windows network for twenty years.
A modern verifier — argon2id and its relatives — is deliberately not password-equivalent and cannot answer a challenge. That is the property that makes stealing the store meaningfully weaker than knowing the passwords.
So challenge-response would trade a permanent weakness at rest for
protection of a channel that is not the weak point. Anyone able to read
this socket can already ptrace the process holding the password.
2.11.2.1 What would change the answer #
A PAKE — OPAQUE, SRP — gives both properties at once: nothing password-equivalent at rest, and no plaintext on the wire. It is worth revisiting if an authority ever authenticates across a network without TLS.
It is not worth its complexity for a local, kernel-mediated socket, where the threat it defends against is already able to do worse.
2.11.2.2 What follows #
Because plaintext crosses the wire, bounding how long it survives becomes an obligation of both roles rather than a nicety. See §2.12.
2.12 Credential Handling
Peios / Advanced Peios / PGSS / Logon
These bind both roles. They are stated normatively because plaintext on the wire (§2.11) is only defensible if its lifetime is short.
2.12.1 Bounding lifetime #
An implementation MUST hold credential material in memory that is erased before it is released, and the erasure MUST NOT be removable by an optimising compiler.
The obligation extends to the encoded message, not only to the
credential field. A CredentialResponse holds the password just as much
as the answer inside it does, and a buffer wiped at one level while
another copy is dropped unerased achieves nothing.
An implementation MUST erase:
- the buffer credential material was read into;
- any buffer it was copied into during encoding or decoding, including an intermediate allocation abandoned by a buffer that grew;
- any structure holding it once the conversation reaches a terminal state.
The middle clause is the one an implementation is most likely to miss. A growable buffer that reallocates while a message is being encoded leaves a complete copy of everything written so far in the abandoned allocation, and erasing the buffer that survives does not touch it. Reserving the encoded size before writing avoids the problem entirely.
2.12.2 What this does not promise #
It guarantees that these buffers are erased before their memory is reused. It cannot guarantee anything about copies made elsewhere — a string the caller parsed a credential out of, a register or stack slot the optimiser chose, a terminal's own input buffer. Those belong to whoever made them.
Nor does it defend against a hostile kernel, a core dump, or swap. Those are addressed elsewhere: process integrity protection, and disabling core dumps for the authority.
2.12.3 Logging and diagnostics #
An implementation MUST NOT write credential material to a log, an audit record, an error message, or a debugging aid.
A type carrying credential material SHOULD render, under whatever debug formatting its language provides, as a redaction rather than as its contents. A stray diagnostic print is the most common way material escapes a process that was otherwise careful, and the defence is to make the careless thing produce nothing useful.
2.12.4 Collection #
A client MUST collect a Password without echoing it. Where it cannot
establish that echo is suppressed, it MUST NOT collect the credential
anyway.
A client MUST NOT silently truncate an answer. data admits 32768 bytes
(§2.8); a client whose collection method admits fewer MUST fail the
conversation rather than send a prefix of what the principal supplied,
which would present as a wrong credential and leave the remainder in
whatever buffer it was read from.
2.12.5 Timing #
An authority MUST NOT allow the time it takes to reach a denial to distinguish an unknown principal from a bad credential (§2.10).
This is more demanding than it looks with a memory-hard verifier, because the natural implementation returns immediately when a principal does not exist and spends tens of milliseconds when one does. An authority MUST perform equivalent work in both cases — verifying against a decoy verifier that no credential matches is the usual construction.
2.13 Identity Lookup
Peios / Advanced Peios / PGSS / Logon
Sections 2.3 to 2.12 specify how a caller obtains a token. The remainder of this chapter specifies how any program on the system turns an identity it already holds into something it can display or compare: a name into a SID, a SID into a name, a POSIX identifier into either.
It exists because a Linux program calls getpwuid and has never heard
of a token. Without this surface, every principal an authority mints
appears throughout the system as a bare number.
2.13.1 Why the authority answers this #
An authority may federate identity to separate sources, and a source counts POSIX identifiers relative to a range the authority assigns it. The authority adds the base. No message in either direction carries an absolute identifier to or from a source: a source asserts relative numbers, and is asked by relative number or by SID.
The arithmetic that makes a number absolute therefore exists in exactly one place, and only that place can invert it. No source can be asked "who is uid 1001000", because no source is ever asked an absolute number at all. The authority is not merely a convenient place to put this — it is the only party the protocol permits to answer.
The same holds for names. A bare name may exist in more than one source, and which one wins is a property of the system rather than of any source in it (§2.15).
2.13.2 A second socket, not a second standard #
Identity lookup is served on /run/ident.sock (§2.14), separately from
/run/logon.sock, by the same authority speaking the same framing
(§2.6).
The separation is not for isolation. One authority answers both, so a defect or a hang in either reaches the other regardless; a second socket buys nothing there and this chapter does not pretend otherwise.
It is for admission. A listening socket has one accept queue. A
single directory listing is thousands of lookups and a filesystem walk
is millions, where logons are a handful per boot — so a shared socket
would let an ordinary find fill the queue that an administrator needs
in order to sign in and stop it. Two sockets means the two populations
of caller cannot starve each other, whatever load either is under.
The second reason is access control. The set of programs that may originate a logon is small and enumerable; the set that may look up a name is every program on the system. Those want different security descriptors, and a descriptor is a property of a socket.
2.13.3 Not a conversation #
A logon is stateful: the connection is the conversation, and needs no correlation identifier (§2.5).
A lookup is not. Requests are independent, a connection carries as many
as a client cares to send, and replies MAY return in an order other than
the one requests arrived in. Each request therefore carries a tag that
its reply echoes (§2.14).
2.13.4 Not authentication #
No credential ever crosses this socket. There is no message with which a client could offer one and none with which an authority could ask.
Everything here returns the kind of data a POSIX system has historically kept in a world-readable file. An authority MAY nonetheless restrict individual fields (§2.16), and the request names the fields it wants precisely so that it can.
2.13.5 Not in this version #
Privileges, integrity levels, owner and default DACL are not returned by any message here. They are not identity: nothing stores them, and an authority computes them from local policy at the moment it derives a token (§2.1).
They are, however, deliberately reserved rather than excluded. An authority that cannot be asked what a policy would produce can only be verified by signing someone in and observing the result. A future revision is expected to add a distinct request of the shape:
Evaluate { key, logon_type } -> { privileges, integrity, owner, default_dacl }
— a token that is derived and then discarded. It is parameterised by logon type because the answer genuinely depends on it: an authority adds SIDs reflecting how a principal signed in (§2.16), so "what privileges does this principal have" has no single answer.
A revision MUST NOT instead add privileges or integrity as fields of
Lookup (§2.16). A field of a record describes something a source
holds; this does not.
2.14 The Identity Channel
Peios / Advanced Peios / PGSS / Logon
2.14.1 Socket #
An authority MUST listen on a SOCK_STREAM Unix domain socket at:
/run/ident.sock
The path is normative, for the same reason /run/logon.sock is: a name
resolver linked into every process on the system cannot be asked to find
it by configuration.
An authority MUST listen on both sockets. Offering one without the other is not conformance — a system whose principals cannot be named is as broken as one whose principals cannot sign in.
2.14.2 Access control #
The socket MUST carry a security descriptor granting connect access to every principal that runs ordinary programs.
This is the opposite posture to /run/logon.sock, and deliberately so.
A descriptor that withheld connect access would not protect anything: it
would make ls -l print numbers for the principals it excluded, while a
principal who can connect learns the same names either way.
Restriction, where an authority wants it, belongs on individual fields
(§2.16) — not on reaching the socket at all.
2.14.3 Framing #
Messages use the header, magic, version, size limit and body extensibility rules of §2.6 without modification. The message types served here are disjoint from those of the logon socket (§2.A).
A message of one socket's range arriving on the other MUST be refused.
An authority MUST NOT serve a Lookup received on /run/logon.sock,
and MUST NOT serve a LogonStart received on /run/ident.sock.
2.14.4 Multiplexing #
A connection carries any number of requests. A client MAY have more than one outstanding at a time, and an authority MAY answer them in any order.
Every request carries a tag, a u32 chosen by the client, and its
reply carries the same value. The tag is in the message body, not
the header, so that the header of §2.6 is unchanged.
A client MUST NOT reuse a tag while a request bearing it is outstanding. An authority MUST echo the tag it received and MUST NOT interpret it otherwise.
A client MUST NOT assume replies arrive in request order. An authority that answers strictly in order is conformant; a client that depends on it is not.
2.14.5 Concurrency and bounds #
An authority MUST serve connections concurrently, and MUST NOT let a slow answer on one connection delay answers on another.
An authority MUST bound the number of connections it will accept and the number of requests it will hold outstanding per connection, and SHOULD refuse further requests on a connection that exceeds the second rather than closing it.
An authority MUST NOT wait indefinitely for a source. A request that
cannot be answered within the authority's own bound MUST be answered
Unavailable (§2.18). The bound is on the request, not on each source
consulted: an authority that walks several sources in turn MUST NOT let
the total exceed what it would have allowed one.
2.14.6 No descriptor passing #
Nothing is transferred over this channel by descriptor. An authority MUST ignore ancillary data received here.
2.14.7 Peer identity #
An authority that restricts any field (§2.16) MUST establish peer identity from the connected socket's peer token, as §2.4 requires, and MUST NOT take it from a message body.
An authority that restricts no field MAY skip establishing peer identity, since it would make no decision with it.
2.15 Names and Resolution
Peios / Advanced Peios / PGSS / Logon
2.15.1 The name is opaque to the client #
A name is carried as a single string. A client MUST forward what it was given, unchanged, and MUST NOT parse, split, qualify, case-fold, or otherwise interpret it.
All interpretation is the authority's.
This is the load-bearing rule of the section. If a client resolved part
of a name itself — chose which source to consult, or which of two
candidates wins — then that policy would live in every client
separately, and a native tool could resolve jack to a different
principal than a POSIX name resolver on the same machine. Two components
disagreeing about who a name refers to is not a display inconsistency;
it is a program acting on one principal's behalf while checking
another's access.
One authority means one answer, and it means a name resolves identically here and in a logon (§2.7), because the same resolution serves both.
2.15.2 Comparison #
Name comparison is ASCII case-insensitive.
This is normative rather than each source's own choice, because a system
may have more than one source and they must not disagree about whether
JACK is jack. An authority MUST establish the comparison for itself
rather than delegate it to whatever a source happens to do.
2.15.3 Reserved characters #
A principal or group name MUST NOT contain any of:
| Character | Reserved for |
|---|---|
@ | Qualified names (below) |
\ | Qualified names, in the form some callers will type |
/ | Path separation — a name reaches a filesystem as a home directory |
: | Field separation in POSIX passwd and group records |
, | Member and subfield separation in POSIX group and GECOS records |
A name MUST NOT contain a byte outside the printable ASCII range
0x20–0x7e, and MUST NOT begin or end with a space.
An authority MUST refuse to create a name violating these rules, MUST
refuse one it is asked to resolve, and MUST refuse one asserted by a
source. The third is the one that matters: a name reaching a caller from
a source has crossed a boundary the other two never did, and it is the
one that ends up in a passwd-format record, an audit line, or a log.
The excluded control characters are the record separators. A name
carrying a newline could forge a whole line in a passwd-format file,
an audit record, or a log — and the damage is done by the reader, so it
cannot be prevented at the point the name is displayed.
The rules apply to every name an authority emits, including the names carried alongside SIDs in references (§2.16), not only to the name a request was keyed on.
2.15.4 Qualified names #
A name MAY be qualified with a realm, written name@realm.
No realm syntax is defined in this version. An authority MUST refuse
a name containing @, and the character is reserved so that a principal
literally named jack@local cannot come into existence before the
syntax does.
Nothing here changes when realms arrive: a qualified name is a different string in the same field, parsed by the same authority. That is why the field is one opaque string rather than a structured pair — a structure would have put the parsing in the client, which the first rule of this section forbids.
2.15.5 Search order #
A bare name is resolved by consulting sources in an order that is local configuration of the authority.
An authority MUST resolve in a configured order, MUST NOT derive that order from the order in which sources registered, and MUST stop at the first source that answers.
2.15.6 Answers an authority holds itself #
An authority MAY answer from its own knowledge, without consulting any source. Well-known principals and groups — those whose SIDs are fixed by the system rather than issued by anybody — are the ordinary case: their names, and the fact that nothing records their membership (§2.16), are properties of the system.
Such an answer is subject to every rule in this section. In particular an authority MUST apply the reserved-character and comparison rules to it, and MUST NOT return an answer for a name it would have refused.
An answer of this kind is not an outage and does not make a reply
Unavailable, whatever the state of the configured sources. It is also
not a source: an authority MUST NOT report it in the incomplete list
of an enumeration (§2.17), and MUST NOT count it as a source having
answered for the purpose of §2.18.
2.15.7 Bare in, qualified out #
Every successful reply carries the resolved SID and the canonical qualified name, whatever form the request used (§2.16).
A caller that looked up a bare name can therefore always tell which principal it got, compare two answers for identity, and record an unambiguous name in a log. Ambiguity is permitted in what a caller may ask; it is not permitted in what an authority answers.
Where no realm syntax exists, a canonical qualified name is indistinguishable from a bare one, and the SID alongside it is what carries the unambiguity. A client MUST NOT rely on the two being distinguishable, in either direction: it MUST NOT assume a returned name is unqualified, and MUST NOT treat one that is as a failure to canonicalise.
2.15.8 Shadowing #
Adding a source ahead of another in the search order changes which principal a bare name resolves to. The new principal has a different SID, so every security descriptor naming the old one stops applying to the person now signing in under that name.
This is inherent to a flat namespace and this chapter does not forbid it. An authority SHOULD detect it at logon — where the cost is one additional query per sign-in rather than one per lookup — and SHOULD record that a bare name resolved while another source could also have answered.
An authority MUST NOT fail a logon because it could not complete that check. A source being unreachable is not a reason to refuse a principal whose own source answered.
2.16 Lookup
Peios / Advanced Peios / PGSS / Logon
One request answers every question a name resolver asks.
2.16.1 Lookup #
msg_type = 0x0010. Client to authority.
| Field | Encoding | Limit |
|---|---|---|
tag | u32 | §2.14 |
key_type | u8 | §2.B |
name | string | 256 bytes |
sid | length-framed bytes (SID) | 68 bytes |
unix_id | u32 | |
kind | u8 | §2.B |
fields | u32 | §2.B |
Exactly one of name, sid and unix_id is meaningful, selected by
key_type. An encoder MUST leave the others empty or zero, and a
decoder MUST ignore them.
2.16.1.1 key_type #
| Value | Name | Answers |
|---|---|---|
| 1 | Name | getpwnam, getgrnam |
| 2 | Sid | Rendering a security descriptor |
| 3 | UnixId | getpwuid, getgrgid |
UnixId is a key even though no source is ever asked one. An
authority converts the number to a source and a relative identifier by
the range arithmetic it assigned, and asks that source by relative
identifier or by SID.
A source therefore never receives an absolute number here, exactly as it never receives one during a logon. The property that made the authority the only possible answerer (§2.13) is the same property that keeps this side of it confined.
2.16.1.2 kind #
| Value | Name |
|---|---|
| 0 | Any |
| 1 | Principal |
| 2 | Group |
A request for Principal MUST NOT be answered with a group, and a
request for Group MUST NOT be answered with a principal. Where the key
matches only an object of the other kind, the outcome is NotFound
(§2.18).
An authority MUST establish this for itself. Where the object came from a source, the authority MUST check the kind it received against the kind that was asked for, rather than relaying the source's answer and letting the client discover the mismatch.
2.16.1.3 fields #
A bitmask of the attributes the reply should carry. Identity is not among them: every successful reply carries the SID, the canonical qualified name, and the kind actually found, and a client cannot decline them.
| Bit | Name | Value encoding |
|---|---|---|
| 0 | UNIX_ID | u32 |
| 1 | PRIMARY_GROUP | reference |
| 2 | HOME | string, 4096 bytes |
| 3 | SHELL | string, 4096 bytes |
| 4 | DISPLAY_NAME | string, 256 bytes |
| 5 | GROUPS | array of references, 128 |
| 6 | MEMBERS | array of references, 256 |
| 7 | CLAIMS | array of claim entries, 64 |
| 8 | ENABLED | u8 |
An authority MUST ignore a bit it does not implement, and MUST NOT report it (below). Claim entries use the claim attribute format PCDS §5.9 specifies.
Requesting only what is wanted is not primarily a bandwidth measure — a
getpwuid wants nearly every field anyway. It matters for the caller
that resolves a dozen SIDs to render a security descriptor and wants
only names, and it is the seam at which an authority can restrict
individual fields without restricting the socket (§2.14).
2.16.2 LookupReply #
msg_type = 0x8010. Authority to client.
| Field | Encoding | Limit |
|---|---|---|
tag | u32 | §2.14 |
outcome | u8 | §2.B |
sid | length-framed bytes (SID) | 68 bytes |
qualified_name | string | 512 bytes |
kind_found | u8 | §2.B |
present | u32 | §2.B |
withheld | array of withheld entries | 32 |
values | array of length-framed values | 32 |
Where outcome is not Found, everything after it MUST be empty or
zero, and a client MUST NOT read it.
kind_found MUST be Principal or Group, never Any.
2.16.2.1 present, withheld and values #
values holds one length-framed value for each bit set in present, in
ascending bit order. Each is length-framed so that a client can step
over a value whose field it does not recognise.
Every bit set in the request is in exactly one of three states:
- set in
present— its value is invalues; - listed in
withheld— with a reason, below; - in neither — this authority does not implement the field.
The third state is a statement about the authority, not about the
object. An authority that implements a field MUST place every request
for it in one of the first two states, whatever happened underneath: a
field an authority supports but could not obtain is Absent,
Declined, Restricted or TooLarge, never silence. Reporting it as
neither would tell the client the authority cannot answer that question
at all, and a client is entitled to stop asking.
A withheld entry is a length-framed structure:
| Field | Encoding | Limit |
|---|---|---|
field | u32 | one bit, §2.B |
reason | u8 | §2.B |
| Reason | Meaning |
|---|---|
Absent | The field has no value. |
Restricted | The caller may not have this field. |
Declined | The source will not produce it. |
TooLarge | It exists and exceeds one reply. Use Enumerate (§2.17). |
Distinguishing these is the point of the structure. An empty member list and a source that refuses to enumerate members are the same bytes to a POSIX caller, and an administrator diagnosing a system needs to know which one happened.
2.16.2.2 References #
PRIMARY_GROUP, GROUPS and MEMBERS carry references rather than
bare SIDs:
| Field | Encoding | Limit |
|---|---|---|
sid | length-framed bytes (SID) | 68 bytes |
name | string | 512 bytes |
unix_id | u32 |
An empty name means the authority has no name for that SID; a
unix_id of zero means it has no number for it. Both are ordinary
answers for a SID belonging to no source on this machine.
Zero is the only encoding of "no number". An authority MUST NOT
substitute a POSIX identifier that a caller could mistake for a real
one — a projection onto nobody is a rendering decision belonging to
whatever produces a passwd record, and putting it on the wire
destroys the distinction the client needs in order to make it.
Carrying the name is what keeps the round-trip discipline below intact.
A reply of bare SIDs would make a single getgrnam into one request
plus one per member.
2.16.3 Memberships #
GROUPS on a principal is the membership question, and it is the
direction sources actually hold — the same one a logon uses. An
authority MUST answer it whenever it can answer anything about the
principal at all.
MEMBERS on a group is the reverse index, and it is not owed the same
guarantee.
An authority MUST return MEMBERS as withheld, rather than as an error
or an empty list, when it will not or cannot produce it:
Declinedwhere the source will not enumerate the group's members.TooLargewhere the membership exceeds what one reply can carry.Absentwhere the group is not one that has recorded members at all.
That last case is not a limitation. A group's membership may be
recorded — held by a source, as a local group's is — or it may be a
rule an authority applies when it derives a token. Nothing records
who belongs to Everyone; an authority adds it to every token it mints.
Groups reflecting how a principal signed in are further still from a
recorded membership: they are properties of a logon rather than of a
principal, and the same principal is in one at a console and not over a
network.
An authority MUST report Absent for such a group rather than
manufacturing a list, and MUST NOT report Declined, which would
suggest an answer exists somewhere.
2.16.4 One round trip #
An authority MUST be able to answer each of the following in a single request:
| Caller wants | Request |
|---|---|
A passwd record | Lookup{key_type: UnixId or Name, kind: Principal, fields: UNIX_ID | PRIMARY_GROUP | HOME | SHELL | DISPLAY_NAME} |
A group record | Lookup{key_type: UnixId or Name, kind: Group, fields: UNIX_ID | MEMBERS} |
| A principal's groups | Lookup{key_type: Name, kind: Principal, fields: GROUPS} |
| A name for a SID | Lookup{key_type: Sid, kind: Any, fields: 0} |
This is a design constraint on future revisions as much as a statement about this one. A name resolver is called from every process on the system, synchronously, and a field that cannot be fetched alongside the record it belongs to turns one lookup into several.
2.17 Enumeration
Peios / Advanced Peios / PGSS / Logon
Lookup answers about one object that a caller can already name.
Enumeration answers when it cannot: walking every principal on the
system, or every member of a group too large for one reply.
2.17.1 Enumerate #
msg_type = 0x0011. Client to authority.
| Field | Encoding | Limit |
|---|---|---|
tag | u32 | §2.14 |
kind | u8 | §2.B |
fields | u32 | §2.B |
of_key_type | u8 | §2.B |
of_name | string | 256 bytes |
of_sid | length-framed bytes (SID) | 68 bytes |
of_unix_id | u32 | |
cursor | length-framed bytes | 256 bytes |
kind MUST NOT be Any. A caller enumerating is filling a passwd or
a group table, and the two are separate.
2.17.1.1 of #
Where of_key_type is zero, the request enumerates every object of
kind that the authority knows.
Where it is non-zero, the named object MUST be a group, and the request
enumerates that group's members. This is the continuation path for a
MEMBERS field withheld as TooLarge (§2.16): the same answer, paged.
An authority that withholds MEMBERS as TooLarge MUST serve this
mode, since there is otherwise no way to obtain what it said existed.
2.17.1.2 cursor #
Empty on the first request. On a continuation it MUST be the next
returned by the immediately preceding reply.
A cursor is opaque. A client MUST NOT construct, parse or modify one, and MUST NOT present one to a different authority or after reconnecting.
An authority MUST reject a cursor it did not issue, or one it can no
longer honour, with Malformed (§2.18). It MUST NOT silently restart
the enumeration, and MUST NOT answer with an empty page and an empty
next: the first hands the caller a second copy of the beginning under
the impression it is continuing, and the second reports a truncated walk
as a complete one.
2.17.2 EnumerateReply #
msg_type = 0x8011. Authority to client.
| Field | Encoding | Limit |
|---|---|---|
tag | u32 | §2.14 |
outcome | u8 | §2.B |
entries | array of entries | 256 |
next | length-framed bytes | 256 bytes |
incomplete | array of strings | 32 |
An entry is a length-framed structure carrying the same fields as a
successful LookupReply (§2.16), from sid through values.
An empty next means the enumeration is complete. A non-empty next
means there is more, even if entries was empty — an authority may
return a short or empty page while working through a source.
A client MUST continue until next is empty. A client MUST NOT infer
completion from an empty page.
A client MUST NOT report an enumeration as complete when it stopped for
any other reason. A transport failure, a timeout, or an outcome other
than Found mid-walk is a truncated enumeration, and a client that
presents it to its caller as the end of the list has manufactured an
empty system out of an outage — the same error §2.18 forbids on a single
lookup, at a scale where nothing records that it happened.
2.17.3 incomplete #
Each string names a source that did not contribute: because it declined to enumerate, or because it could not be reached.
An authority MUST list every such source. A client displaying an enumeration SHOULD say that it is partial, and MUST NOT discard the list without doing so.
2.17.4 No completeness guarantee #
An authority MUST NOT be required to enumerate.
A source may hold more principals than a reply, a page, or an
administrator's patience can carry, and a source backed by a remote
directory may be able to answer any single question while being quite
unable to answer all of them. incomplete is the honest outcome, not a
degraded one.
A caller MUST NOT treat enumeration as a way to test whether a principal
exists. Lookup answers that, exactly, at any scale.
2.18 Outcomes
Peios / Advanced Peios / PGSS / Logon
Every reply on the identity channel carries an outcome.
| Value | Name | Meaning |
|---|---|---|
| 1 | Found | The request was answered. |
| 2 | NotFound | No such object, and every source that could have said so was asked. |
| 3 | Unavailable | A source that could have answered did not. |
| 4 | Refused | The caller may not make this request. |
| 5 | Malformed | The request could not be understood. |
Unlike the denial codes of §2.10, these are not a security boundary.
NotFound reveals that a name is unused, which is what a name lookup is
for.
An EnumerateReply carries an outcome on the same terms. An authority
MUST NOT report Found on a page it could not produce.
2.18.1 NotFound and Unavailable #
If any source that could have answered was unavailable, the outcome is
Unavailable — even if every source that did answer said no.
This is the most important rule in this part of the chapter, and it is stated normatively rather than left to implementations because it is invisible when wrong.
An authority is expected to cache. A cache that is told NotFound will
store an absence, and if that absence was really a source being
unreachable, it has memoised an outage as a fact. The account comes back
when the source does; the cached answer does not. A principal is then
unable to sign in, or a file is shown as owned by a number, for as long
as the entry lives — with nothing in the system still recording that
anything failed.
Unavailable is not cacheable, and that is the whole of the difference.
An authority MUST NOT report NotFound unless every source in the
search order (§2.15) that could have answered was consulted and
answered. A source that does not serve lookups at all is not such a
source and does not need to be asked; a source that does, and could not
be reached, is.
2.18.2 Unavailable and the search order #
An authority MUST consult sources in the configured order and MUST stop
at the first that answers, so an unreachable source later in the
order does not make an answer Unavailable. It was never going to be
asked.
An unreachable source earlier in the order does, even if a later one holds a matching name — because the earlier source is the one whose answer would have won.
2.18.3 Refused #
Reserved. No field in this version is restricted by default, and an authority that restricts none will never send it.
It exists because the field mask (§2.16) is where restriction belongs,
and a request refused in its entirety needs an answer that is not
NotFound. An authority restricting a field MUST use the Restricted
withheld reason instead, and MUST still answer the rest of the request.
Because it is reserved, Refused MUST NOT be used for anything else. A
source that will not answer, a mode an authority has not implemented, or
a question it cannot serve are not the caller lacking permission, and
reporting them as Refused tells a client to stop asking on behalf of
this caller when the answer would be the same for every caller. Those
outcomes are Unavailable, Absent, or Malformed as the case
requires.
An authority MUST NOT use NotFound in place of Refused or of a
restricted field. Concealing a principal's existence from a caller that
may not read their shell protects nothing — the SID is already in the
file listing that prompted the lookup — and would make an authorization
decision indistinguishable from an empty system.
2.18.4 Timing #
The prohibition in §2.10 on distinguishing an unknown principal from a
bad credential does not apply here. There is no credential, and
NotFound is an ordinary answer that this chapter states plainly.
An authority MUST NOT allow the presence of an answer in a cache to be observable to a caller that would be refused the answer itself. Where no field is restricted, nothing is refused, and the requirement is vacuous.
2.19 Conformance
Peios / Advanced Peios / PGSS / Logon
A conforming implementation MUST satisfy every requirement in this chapter. This section collects them by role.
2.19.1 Authority obligations #
An implementation claiming to be a PGSS Logon authority MUST satisfy all of the following.
2.19.1.1 Channel #
- Listen on
/run/logon.sockas aSOCK_STREAMUnix domain socket (§2.5). - Control access to it with a security descriptor, not with process integrity and not with POSIX permission bits (§2.4).
- Serve conversations concurrently, so that one stalled logon does not block others (§2.5).
- Support
SCM_RIGHTSon the channel (§2.9).
2.19.1.2 Framing #
- Reject any message whose magic is not
PGSL, as a whole-connection failure (§2.6). - Reject any message whose version it does not implement, answering
UnsupportedVersionwhere it can still encode a denial (§2.6). - Reject any message declaring more than 65536 bytes, or fewer than a header, without reading the body (§2.6).
- Skip to a structure's declared end after reading known fields, rather than assuming its length, and substitute a documented default for an optional trailing field a peer did not write (§2.6).
2.19.1.3 Conversation #
- Require
LogonStartas the first message, and reject a conversation opening otherwise (§2.3). - Send exactly one terminal message, nothing after it, and close the connection (§2.3).
- Bound the number of rounds, the time spent awaiting an answer, and
the time a conversation may remain open, and terminate with
ConversationLimiton exhausting any of them (§2.3, §2.5). A bound reached MUST produce a terminal message; closing the connection silently is what the code exists to prevent. - Reject a
CredentialResponseit did not solicit (§2.3). - Match answers to prompts by
credential_ref, never by position, and treat a missing answer as a failed logon rather than as grounds to tear down the connection (§2.8).
2.19.1.4 Trust #
- Establish peer identity from the connected socket's peer token,
never from a message body, and never via
SO_PEERCRED(§2.4). - Treat
logon_typeas a proposal and constrain it against the verified peer (§2.4). - Treat
identifier,ttyandremote_hostas unverified claims (§2.4).
2.19.1.5 Derivation #
- Perform derivation itself, and never accept a token, privilege set or integrity level from another party (§2.1).
- Never fork or exec on the client's behalf; never learn about terminals, environments or session leadership (§2.1).
2.19.1.6 Prompting #
- Never send a prompt for a credential type absent from the client's
supported_credential_types, and deny the logon instead if it cannot proceed within them (§2.8). - Ensure
credential_refis unique among the prompts of one request (§2.8).
2.19.1.7 Result #
- Transfer the token as a file descriptor in the ancillary data of
AccessGranted, never by name (§2.9). - Never distinguish an unknown principal from a bad credential — by denial code, by reason text, or by timing (§2.10, §2.12).
- Send
homeandshellas absolute paths, or empty, whatever their provenance; an authority with no value for a profile field MUST leave it empty rather than invent one (§2.9).
2.19.1.8 Credential handling #
- Erase credential material, and every buffer it was encoded and decoded through — including an allocation abandoned by a buffer that grew — before that memory is released (§2.12).
- Never write credential material to a log, audit record, error message or diagnostic (§2.12).
2.19.1.9 Identity lookup #
- Listen on
/run/ident.sockas well as/run/logon.sock, as aSOCK_STREAMUnix domain socket (§2.14). - Grant connect access to that socket, by security descriptor, to every principal that runs ordinary programs (§2.14).
- Refuse a message of one socket's range received on the other (§2.14).
- Echo each request's
tag, and never interpret it otherwise (§2.14). - Serve requests concurrently, and never let a slow answer on one connection delay another (§2.14).
- Bound the time spent answering a request, and answer
Unavailablerather than waiting indefinitely (§2.14). - Ignore ancillary data received on the identity socket (§2.14).
2.19.1.10 Resolution #
- Interpret names itself, and never require a client to parse, qualify or case-fold one (§2.15).
- Compare names ASCII case-insensitively, establishing the comparison itself rather than delegating it to a source (§2.15).
- Refuse a name containing a reserved character, a byte outside
0x20–0x7e, or a leading or trailing space — whether created locally, received in a request, asserted by a source, or carried alongside a SID in a reference (§2.15). - Resolve bare names in a configured order, never in registration order, stopping at the first source that answers (§2.15).
- Carry the resolved SID and the canonical qualified name on every successful reply, whatever form the request used (§2.15).
2.19.1.11 Answers #
- Never answer a
Principalrequest with a group, or aGrouprequest with a principal, and establish the kind itself rather than relaying a source's (§2.16). - Ignore a field bit it does not implement, and never report it as present (§2.16).
- Report each requested field it implements as present or as withheld with a reason, never as neither (§2.16).
- Withhold
MEMBERSwith a reason rather than returning a partial or empty list, where it will not or cannot produce it (§2.16). - Report
Absentrather thanDeclinedfor a group whose membership is a rule rather than a record (§2.16). - Encode "no POSIX identifier" as zero, and never substitute a number a caller could mistake for a real one (§2.16).
- Answer a
passwdrecord, agrouprecord, a principal's memberships, or a name for a SID, each in one request (§2.16). - Never report
NotFoundunless every source in the search order that could have answered was consulted and answered (§2.18). - Report
Unavailablewhere a source earlier in the search order could not be reached (§2.18). - Never report
NotFoundin place ofRefusedor of aRestrictedfield, and never reportRefusedfor anything other than a caller that may not make the request (§2.18). - List every source that did not contribute to an enumeration, and
never report
Foundon a page it could not produce (§2.17, §2.18). - Reject a cursor it did not issue, or can no longer honour, with
Malformed— never by restarting the walk and never by reporting it complete (§2.17). - Serve member enumeration where it withholds
MEMBERSasTooLarge(§2.17).
2.19.2 Client obligations #
There are two client roles, and they are independent. A program may be either, both, or neither: a logon originator never looks a principal up; a name resolver does the reverse.
An implementation originating logons MUST satisfy obligations 1 to 20. An implementation performing identity lookup MUST satisfy 21 to 27.
2.19.2.1 Conversation #
- Send exactly one
LogonStart, as the first message (§2.3). - Answer each
CredentialRequestwith exactly oneCredentialResponse(§2.3). - Never send a
CredentialResponsethat was not solicited (§2.3). - Treat a connection that closes without a terminal message as a failed logon, and not retry automatically (§2.3).
2.19.2.2 Framing #
- Reject any message whose magic is not
PGSL(§2.6). - Reject any message whose version it does not implement (§2.6).
- Skip to a structure's declared end after reading known fields (§2.6).
2.19.2.3 Capabilities #
- Never declare in
supported_credential_typesa credential type it cannot render (§2.7). Declaring fewer than it can render is permitted, and an empty list is how a client asks for a logon requiring no interaction. - Fail the conversation, rather than guess, if it receives a prompt it cannot render (§2.8).
2.19.2.4 Rendering #
- Display received messages, in order, before the prompts of the same request (§2.8).
- Not interpret or branch on message text,
credential_name, orreason(§2.8, §2.10). - Echo
credential_refback unchanged (§2.8).
2.19.2.5 Result #
- Read the token descriptor from the ancillary data of
AccessGranted, and treat anAccessGrantedwithout one as a failed logon (§2.9). - Close the descriptor if it does not install the token (§2.9).
- Have a fallback for every
profilefield, and treat an empty field, and an absentprofile, identically and not as an error (§2.9). - Never execute a relative
shell, nor resolve a relativehomeagainst its own working directory (§2.9). Ashellcontaining no separator is relative, and MUST NOT be resolved against a search path. - Never treat
profileas a directory lookup, nor cache it as an answer about any principal other than the one who just authenticated (§2.9).
2.19.2.6 Credential handling #
- Collect a credential only where it can establish that its collection
method meets the type's requirements, and never collect a
Passwordwith echo enabled (§2.12). - Never silently truncate an answer to fit its own buffer (§2.12).
- Erase credential material, and every buffer it was collected and encoded through, before that memory is released, and never write it to a log, error message or diagnostic (§2.12).
2.19.2.7 Identity lookup #
- Forward a name unchanged, and never parse, split, qualify or case-fold one (§2.15).
- Never reuse a
tagwhile a request bearing it is outstanding, and never assume replies arrive in request order (§2.14). - Treat a cursor as opaque: never construct, parse or modify one, nor present one to a different authority or across a reconnection (§2.17).
- Continue an enumeration until
nextis empty, never infer completion from an empty page, and never present a walk it abandoned for any other reason as a complete one (§2.17). - Surface an enumeration's
incompletelist rather than discarding it (§2.17). - Distinguish
NotFoundfromUnavailable, and never cache the second as though it were the first (§2.18). - Never use enumeration to test whether a principal exists (§2.17).
Obligations 24 and 26 bind a client that caches for the lifetime of a single process just as they bind an authority. A resolver that remembers "no such user" through an outage will keep reporting it after the outage ends, and one that reports a failed walk as an empty system does the same thing to every principal at once.
2.19.3 What a client is not required to do #
A client is not required to understand what any credential type means, what any message says, or why a logon was denied. It renders what it is given and returns what it collects.
That is the property the whole conversational shape exists to produce, and a client that starts reasoning about the content of prompts has given it up — it will need changing the next time an authority's policy does.
Appendix 2.A Message Reference
Peios / Advanced Peios / PGSS / Logon
2.A.1 Messages #
2.A.1.1 On /run/logon.sock #
msg_type | Message | Direction | Defined in |
|---|---|---|---|
0x0001 | LogonStart | client → authority | §2.7 |
0x0002 | CredentialResponse | client → authority | §2.8 |
0x8001 | CredentialRequest | authority → client | §2.8 |
0x8002 | AccessGranted | authority → client | §2.9 |
0x8003 | AccessDenied | authority → client | §2.10 |
2.A.1.2 On /run/ident.sock #
msg_type | Message | Direction | Defined in |
|---|---|---|---|
0x0010 | Lookup | client → authority | §2.16 |
0x0011 | Enumerate | client → authority | §2.17 |
0x8010 | LookupReply | authority → client | §2.16 |
0x8011 | EnumerateReply | authority → client | §2.17 |
The high bit marks a message sent by the authority (§2.6). The two ranges are disjoint, and a message of one range MUST be refused on the other socket (§2.14).
2.A.2 Protocol constants #
| Constant | Value | Defined in |
|---|---|---|
| Logon socket path | /run/logon.sock | §2.5 |
| Identity socket path | /run/ident.sock | §2.14 |
| Magic | PGSL (50 47 53 4c) | §2.6 |
| Version | 1 | §2.6 |
| Header size | 12 bytes | §2.6 |
| Maximum message size | 65536 bytes | §2.6 |
2.A.3 Field limits #
| Field | Maximum | Defined in |
|---|---|---|
identifier | 1024 bytes | §2.7 |
tty | 128 bytes | §2.7 |
remote_host | 256 bytes | §2.7 |
supported_credential_types | 32 entries | §2.7 |
messages | 8 entries | §2.8 |
prompts | 16 entries | §2.8 |
answers | 16 entries | §2.8 |
text | 512 bytes | §2.8 |
credential_name | 128 bytes | §2.8 |
data | 32768 bytes | §2.8 |
home | 4096 bytes | §2.9 |
shell | 4096 bytes | §2.9 |
display_name | 256 bytes | §2.9 |
reason | 512 bytes | §2.10 |
name (lookup key) | 256 bytes | §2.16 |
sid | 68 bytes | §2.16 |
qualified_name | 512 bytes | §2.16 |
withheld | 32 entries | §2.16 |
values | 32 entries | §2.16 |
reference name | 512 bytes | §2.16 |
HOME, SHELL | 4096 bytes | §2.16 |
DISPLAY_NAME | 256 bytes | §2.16 |
GROUPS | 128 entries | §2.16 |
MEMBERS | 256 entries | §2.16 |
CLAIMS | 64 entries | §2.16 |
of_name | 256 bytes | §2.17 |
cursor, next | 256 bytes | §2.17 |
entries | 256 entries | §2.17 |
incomplete | 32 entries | §2.17 |
incomplete source name | 32 bytes | §2.17 |
The 68-byte SID maximum is a property of the SID encoding rather than of this chapter: an eight-byte prelude plus the fifteen sub-authorities the one-byte count admits. It is specified in PCDS.
An encoder MUST refuse to produce a field exceeding its maximum; a decoder MUST reject one it receives (§2.6).
Appendix 2.B Enumerations
Peios / Advanced Peios / PGSS / Logon
Adding a value to any enumeration here is a breaking change requiring a version bump — see §2.6. The single exception is the field mask, noted below.
2.B.1 Logon types #
Carried in LogonStart.logon_type (§2.7) as a u8. Semantics are
defined by KACS and described in the Peios Kernel TRM; this table is for
reference.
| Value | Name |
|---|---|
| 2 | Interactive |
| 3 | Network |
| 4 | Batch |
| 5 | Service |
| 8 | NetworkCleartext |
| 9 | NewCredentials |
The gaps are deliberate: the numbering follows KACS, and values it does not define are not available here.
2.B.2 Identifier types #
Carried in LogonStart.identifier_type (§2.7) as a u8.
| Value | Name | identifier holds |
|---|---|---|
| 1 | Username | A principal name |
2.B.3 Credential types #
Carried in Prompt.credential_type (§2.8) and in
LogonStart.supported_credential_types (§2.7) as a u8. See §2.11 for
when a new one is warranted.
| Value | Name | Collection |
|---|---|---|
| 1 | Password | A line of text, not echoed |
2.B.4 Message severities #
Carried in Message.severity (§2.8) as a u8.
| Value | Name |
|---|---|
| 0 | Info |
| 1 | Error |
2.B.5 Denial codes #
Carried in AccessDenied.denial (§2.10) as a u32.
| Value | Name | Meaning |
|---|---|---|
| 1 | MalformedRequest | The message could not be understood. |
| 2 | UnsupportedVersion | The protocol version is not implemented. |
| 3 | PermissionDenied | The peer may not originate this logon at all. |
| 4 | AuthenticationFailed | The principal is unknown, or the credential is wrong. Deliberately one code — see §2.10. |
| 5 | LogonTypeNotPermitted | The peer may not request this kind of session. |
| 6 | AccountRestricted | The principal exists and authenticated, but policy refuses this logon. |
| 7 | AuthorityUnavailable | The authority cannot reach what it needs to decide. |
| 8 | ConversationLimit | Too many rounds, or too long without an answer. |
| 9 | Internal | The authority failed for a reason it will not describe. |
2.B.6 Key types #
Carried in Lookup.key_type (§2.16) and Enumerate.of_key_type
(§2.17) as a u8. Zero in of_key_type means the field is unused.
| Value | Name | Key is in |
|---|---|---|
| 1 | Name | name |
| 2 | Sid | sid |
| 3 | UnixId | unix_id |
2.B.7 Object kinds #
Carried in Lookup.kind, LookupReply.kind_found and Enumerate.kind
(§2.16, §2.17) as a u8.
| Value | Name |
|---|---|
| 0 | Any |
| 1 | Principal |
| 2 | Group |
Any is not valid in kind_found or in Enumerate.kind.
2.B.8 Fields #
Carried in Lookup.fields, Enumerate.fields and LookupReply.present
(§2.16) as a u32 bitmask, and in a withheld entry's field as a
single bit.
| Bit | Name | Value encoding |
|---|---|---|
| 0 | UNIX_ID | u32 |
| 1 | PRIMARY_GROUP | reference |
| 2 | HOME | string |
| 3 | SHELL | string |
| 4 | DISPLAY_NAME | string |
| 5 | GROUPS | array of references |
| 6 | MEMBERS | array of references |
| 7 | CLAIMS | array of claim entries |
| 8 | ENABLED | u8 |
This is the sole exception to the rule above. A bit MAY be added without a version bump, because a reply states which fields it answered and an authority MUST ignore a bit it does not implement (§2.16).
2.B.9 Lookup outcomes #
Carried in LookupReply.outcome and EnumerateReply.outcome (§2.18) as
a u8.
| Value | Name |
|---|---|
| 1 | Found |
| 2 | NotFound |
| 3 | Unavailable |
| 4 | Refused |
| 5 | Malformed |
2.B.10 Withheld reasons #
Carried in a withheld entry's reason (§2.16) as a u8.
| Value | Name | Meaning |
|---|---|---|
| 1 | Absent | The field has no value. |
| 2 | Restricted | The caller may not have this field. |
| 3 | Declined | The source will not produce it. |
| 4 | TooLarge | It exists and exceeds one reply; use Enumerate (§2.17). |
Appendix 2.C Prior Art
Peios / Advanced Peios / PGSS / Logon
2.C.1 Windows LSA #
The closest predecessor is the Windows Local Security Authority and its
LsaLogonUser interface. What is taken and what is deliberately left is
worth stating, because the resemblance is close enough that the
differences matter.
Taken. The separation of authentication from derivation — establishing who someone is, and then constructing a token for them, are different acts by different rules. The idea that a logon produces both a token and a session, and that the session records which authority vouched for it. The vocabulary of logon types.
Diverged. LSA's authentication packages are DLLs loaded into the LSA process, so a defect in any package is a defect in the most privileged process on the system. Nothing in PGSS Logon admits an in-process extension point; an authority that federates does so across a process boundary of its own choosing.
Rejected. The challenge-response family (NTLM and successors). Challenge-response requires the verifier to store something a response can be recomputed from, which is password-equivalent material — the pass-the-hash failure mode, where stealing the store is as good as knowing the passwords. This chapter requires the opposite property: what is stored MUST NOT be usable to authenticate. See §2.11.
2.C.2 PAM #
Pluggable Authentication Modules supplies the conversation shape: an authority that asks for what it needs, a client that renders prompts without understanding them, and several rounds where policy requires them. That shape is why adding a credential type is a change to authorities and not to every client, and it is adopted wholesale.
What is not adopted is PAM's stacking, in which a credential is offered to each module in turn until one accepts. Trying each source with the password hands every source the credentials of every other source's users, including on typos. Where an authority federates, resolution MUST select the answering party before any credential is collected.
PAM is also an in-process module system, and the objection under Diverged applies to it equally.
2.C.3 POSIX name resolution #
The identity-lookup half of this chapter answers the questions
getpwnam, getpwuid, getgrnam, getgrgid and getgrouplist ask,
and its field mask, reference encoding and one-round-trip requirement
are shaped by what those calls need in one go.
What is not adopted is the passwd record itself. GECOS is not carried
(§2.9), the flat name:uid:gid tuple is replaced by a SID plus a set of
requested fields, and the reserved-character rules (§2.15) exist
precisely because a name that reaches those callers ends up in a
colon-and-comma-separated line that nothing else validates.
Nor is the assumption that an absence is authoritative. A world-readable file cannot be unreachable, so POSIX has no vocabulary for "the source that would have known did not answer"; §2.18 exists because a federated authority does, and reporting that as "no such user" memoises an outage as a fact.
2.C.4 Design influences #
Descriptor-passing over ambient authority. The token is transferred as a file descriptor rather than named, so that possession of the conversation is what confers it. There is no window in which a minted token exists under a name another process could reach for.
One conversation per connection. The connection is the conversation's identity, so no correlation identifier exists to be forged or confused. This is deliberately unlike the identity channel of §2.14, and unlike PSI (PSPU §2.7), both of which multiplex because their connections are long-lived.
Capability declaration over negotiation. A client states what it can render and the authority works within it, rather than the two agreeing a version or a profile. That is what lets a credential type be added to authorities without a flag day, and it is why the capability list is the one enumeration whose unknown values are dropped rather than refused (§2.7).