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