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

Damien Miller djm at mindrot.org
Fri Aug 7 11:16:56 AEST 2026


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-*-v00 at openssh.com, 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


More information about the openssh-unix-dev mailing list