Re: RFC: PROTOCOL.authrec, a structured authentication record from sshd

Damien Miller <[email protected]>
Newsgroups gmane.network.openssh.devel
Message-ID <[email protected]>
On Tue, 14 Jul 2026, Avinash Duduskar wrote:

> Hi,
> 
> By the end of userauth, sshd holds a set of structured facts:
> session identity, transport parameters, the authentication method
> sequence, the keys and certificates that authenticated.
> ExposeAuthInfo=yes (shipped in 7.6) exports a line-based text file
> carrying a fraction of them. Consumers that need the rest (audit
> pipelines, session policy hooks, identity-provider liveness checks)
> reconstruct it from log scraping or by parsing key blobs out of the
> text file.

Anyway, I really like this and would especially like to see this bit:

[...]
> Deliberately out of version 1, each a possible follow-on:
> - a post-auth command gate (AuthorizedKeysCommand-shaped: record
>   on stdin, exit code decides the session)
[...]

As well as a per-auth attempt gate that runs at the start of
auth2.c:userauth_finish() that can reject an otherwise-successful
authentication.

IMO these could be nicer to implement complex authz logic than
PAM modules.

> 1. Wire format
> 
>    #define MAGIC_PREAMBLE  "SSHAUTHR"
>    #define FORMAT_VERSION  0x00000001
> 
> 	byte[8]   MAGIC_PREAMBLE
> 	uint32    FORMAT_VERSION
> 	uint32    record_length       /* total bytes following this
> 	                                 field, see section 2 */

I think you could state this more succinctly and idiomatically as:

 	byte[8]   MAGIC_PREAMBLE
 	uint32    FORMAT_VERSION
 	string    contents

Where 'contents' contains everyting that followed:

> 	string    session_block
> 	string    transport_block
> 	string    credential_block
> 	string    cert_block
> 	string    reserved             /* empty in version 1; section 2 */

> 1.1 Session block
> 
> 	string    session_id          /* canonical SSH session_id from
> 	                                 the first key exchange */
> 	string    user                /* target username */
> 	uint32    uid                 /* numeric user ID */
> 	uint32    gid                 /* numeric primary group ID */
> 	string    home                /* user's home directory */
> 	uint64    auth_time           /* seconds since Unix epoch at
> 	                                 userauth completion */
> 	string    methods             /* the authentication method
> 	                                 sequence; sub-sshbuf, below */
> 	string    reserved            /* empty in version 1; section 2 */
> 
>    methods is an sshbuf carrying one entry per authentication step,
>    in the order the steps were satisfied:

Only for the authentication that succeeded, right?

> 	uint32    count
> 	repeat count times:

Generally I avoid using counts for structured data and instead prefer
using a self-delimiting format, e.g.

      string methods

where `methods` contains:

      string[] method_data

and `method_data` contains the actual data:

> 	    string  method            /* "publickey", "password",
> 	                                 "hostbased",
> 	                                 "keyboard-interactive",
> 	                                 "gssapi-with-mic" */
> 	    string  submethod         /* method-specific detail or empty */
> 	    string  method_info       /* free-form per-method info or
> 	                                 empty */
> 	    string  key_blob          /* the step's public key in the
> 	                                 standard SSH serialisation, or
> 	                                 the full certificate for a
> 	                                 certificate key, for key-bearing
> 	                                 methods (publickey, hostbased);
> 	                                 empty otherwise. The same key the
> 	                                 legacy file appends as text */

This avoids any risk of desynchronisation if the count doesn't match
the actual data.

[...]
>    The Credential block (section 1.3) is a top-level convenience view
>    of the key for a single key-bearing step, not the sole carrier of
>    key identity: per-step key identity for a chain lives in the
>    methods block above. For single-method auth the Credential block
>    is that step's key; for multi-step auth it is the last key-bearing
>    step's, so it is not empty whenever some step presented a key.

IMO the credential and certificate blocks are redundant to the key_blob
here and should be removed for simplicity. It's quite easy to get at
the contents and fingerprint of a certificate using open-source
libraries in every major programming language, or even ssh-keygen if
one is writing hooks in shell.

> 1.2 Transport block
> 
> 	string    cipher_c2s          /* client-to-server cipher name */
> 	string    cipher_s2c          /* server-to-client cipher name */
> 	string    mac_c2s             /* MAC name; empty for AEAD ciphers,
> 	                                 which authenticate internally
> 	                                 (see cipher_c2s) */
> 	string    mac_s2c             /* MAC name; empty for AEAD ciphers,
> 	                                 which authenticate internally
> 	                                 (see cipher_s2c) */
> 	string    comp_c2s            /* compression method, "none" if disabled */
> 	string    comp_s2c            /* compression method, "none" if disabled */
> 	string    kex_algorithm       /* negotiated KEX algorithm */
> 	byte      strict_kex          /* 1 if strict KEX
> 	                                 (kex-strict-*[email protected], the
> 	                                 Terrapin mitigation) was
> 	                                 negotiated, else 0 */

I think this should be a `uint32 kex_options` bitfield to make this
trivially extensible.

Anyway, it would be good to see a prototype of this.

Thanks,
Damien
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.