ssh_file(3)

πŸ“¦
version 29.0.6 Β· 3 versions
, in ubuntu stonking

🌸 NAME 🌸

ssh_file β€” Default callback module for the client's and server's database operations in the ssh application. And yes, this whole page is the ssh_file(3) manual, an Erlang Module Definition. Bestie, we're in it now. 🫢

✨ DESCRIPTION ✨

Okay so this module is literally the default callback handler for the client's and the server's user and host "database" operations. All data, for instance key pairs, are stored in files in the normal file system. This page documents the files, where they are stored and configuration options for this callback module. No cap.

The intention is to be compatible with the OpenSSH storage in files. Therefore it mimics directories and filenames of OpenSSH. It's giving "we studied the popular girl's notes and copied her whole flow." πŸ’…

Ssh_file implements the ssh_server_key_api and the ssh_client_key_api. This enables the user to make an own interface using for example a database handler. Main character energy: swap in your own callback module and do your own thing.

Such another callback module could be used by setting the option key_cb when starting a client or a server (with for example ssh:connect, ssh:daemon or ssh:shell ). Yes, that space before the closing paren is in the original, keep it. πŸ™ˆ

CALLBACKS: The callback functions (host_key/2, is_auth_key/3, user_key/2, is_host_key/5, add_host_key/4) are called by the SSH application internally. They are documented here to show which files and options the default implementation uses. See ssh_server_key_api and ssh_client_key_api for the callback specifications. The public API functions are decode/2, encode/2, and extract_public_key/1. Slay.

πŸ¦‹ Files, directories and who uses them πŸ¦‹

Daemons

Daemons uses all files stored in the SYSDIR directory. πŸ’–

Optionally, in case of publickey authorization, one or more of the remote user's public keys in the USERDIR directory are used. See the files USERDIR/authorized_keys and USERDIR/authorized_keys2.

Clients

Clients uses all files stored in the USERDIR directory. Simple as that.

Directory contents

  • LOCALUSER β€” The user name of the OS process running the Erlang virtual machine (emulator). That's the emulator's whole identity, iykyk.
  • SYSDIR β€” This is the directory holding the server's files:
    • ssh_host_dsa_key - private dss host key (optional)
    • ssh_host_rsa_key β€” private rsa host key (optional)
    • ssh_host_ecdsa_key β€” private ecdsa host key (optional)
    • ssh_host_ed25519_key β€” private eddsa host key for curve 25519 (optional)
    • ssh_host_ed448_key β€” private eddsa host key for curve 448 (optional)

The key files could be generated with OpenSSH's ssh-keygen command. πŸ”‘

At least one host key must be defined. The default value of SYSDIR is /etc/ssh.

For security reasons, this directory is normally accessible only to the root user.

lowkey important, bestie. Keep this locked down like your private story.

To change the SYSDIR, see the system_dir option.

  • USERDIR β€” This is the directory holding the files:
    • authorized_keys and, as second alternative authorized_keys2 - the user's public keys are stored concatenated in one of those files.

It is composed of lines as for OpenSSH:

(options)? keytype base64-encoded-key comment

where

                      options :: option(,option)*
                      option :: % All options are skipped
                      keytype :: 'ssh-dsa'
                               | 'ssh-rsa'
                               | 'ssh-ecdsa-nistp256'
                            | 'ssh-ecdsa-nistp384'
                               | 'ssh-ecdsa-nistp521'
                               | 'ssh-ed25519'
                            | 'ssh-ed448'
                      base64-encoded-key :: % The user's public key
                      comment :: % Comments are skipped
  • known_hosts β€” host keys from hosts visited concatenated. The file is created and used by the client.

It is composed of lines as for OpenSSH:

(option)? pattern(,pattern)* keytype key (comment)?

where

                    option :: '@revoked'
                    pattern :: host | '[' host ']:' port
                    host :: ip-address | hostname | '*'
                    port :: portnumber | '*'
                    keytype :: 'ssh-dsa'
                             | 'ssh-rsa'
                             | 'ssh-ecdsa-nistp256'
                          | 'ssh-ecdsa-nistp384'
                             | 'ssh-ecdsa-nistp521'
                             | 'ssh-ed25519'
                          | 'ssh-ed448'
                    key :: % encoded key from eg ssh_host_*.pub
  • id_dsa - private dss user key (optional)
  • id_rsa β€” private rsa user key (optional)
  • id_ecdsa β€” private ecdsa user key (optional)
  • id_ed25519 β€” private eddsa user key for curve 25519 (optional)
  • id_ed448 β€” private eddsa user key for curve 448 (optional)

The key files could be generated with OpenSSH's ssh-keygen command.

The default value of USERDIR is /home/LOCALUSER/.ssh.

To change the USERDIR, see the user_dir option

πŸ”₯ DATA TYPES πŸ”₯

  • experimental_openssh_key_v1_decode() β€” the shape is:
[{key(), openssh_key_v1_attributes()}].
  • experimental_openssh_key_v1_encode() β€” the shape is:
[{[{public_key:public_key(),
    public_key:private_key(),
    Comment :: binary()} |
   {public_key:private_key(), Comment :: binary()}],
  openssh_key_v1_attributes()}].
  • key() β€” definition:
key() :: public_key:public_key() | public_key:private_key().

The key representation. That's the vibe.

  • openssh_key_v1_attributes() β€” definition:
openssh_key_v1_attributes() :: [{atom(), term()}].

Types for the experimental implementation of the openssh_key_v1 format.

  • optimize_key_lookup() β€” definition:
optimize_key_lookup() :: {optimize, time | space}.

Make the handling of large files fast by setting time, but this will use more memory. The space variant shrinks the memory requirements, but with a higher time consumption.

To set it, set the option {key_cb, {ssh_file, [{optimize,TimeOrSpace}]} in the call of ssh:connect/3, ssh:daemon/2 or similar function call that initiates an ssh connection.

  • pubkey_passphrase_client_options() β€” definition:
pubkey_passphrase_client_options() ::
          {dsa_pass_phrase, string()} |
          {rsa_pass_phrase, string()} |
          {ecdsa_pass_phrase, string()}.

If the user's DSA, RSA or ECDSA key is protected by a passphrase, it can be supplied with those options.

Note that EdDSA passhrases (Curves 25519 and 448) are not implemented.

  • system_dir_daemon_option() β€” definition:
system_dir_daemon_option() :: {system_dir, string()}.

Sets the system directory.

  • user2dir() β€” definition:
user2dir() ::
          fun((RemoteUserName :: string()) -> UserDir :: string()).

Sets the user directory dynamically by evaluating the user2dir function.

  • user_dir_common_option() β€” definition:
user_dir_common_option() :: {user_dir, string()}.

Sets the user directory.

  • user_dir_fun_common_option() β€” definition:
user_dir_fun_common_option() :: {user_dir_fun, user2dir()}.

✨ FUNCTIONS ✨

okay bestie, this is the part where the actual work happens πŸ’… every single one of these is a little callback this module is out here serving, so let's go one by one, no skipping, no shortcuts 🫢


add_host_key β€” the "new friend, noted πŸ“" one

add_host_key(Host, Port, Key, Options) -> Result
                             when
                                 Host ::
                                     inet:ip_address() |
                                     inet:hostname() |
                                     [inet:ip_address() | inet:hostname()],
                                 Port :: inet:port_number(),
                                 Key :: public_key:public_key(),
                                 Options ::
                                     ssh_client_key_api:client_key_cb_options(none()),
                                 Result :: ok | {error, term()}.

This one is literally the implementation of ssh_client_key_api:add_host_key/4 β€” slay ✨

Option it cares about:

  • user_dir β€” yeah that one, the only one πŸ‘‘

File it touches:

  • USERDIR/known_hosts

decode β€” the glow-up from binary to readable πŸ’–

decode(SshBin, Type) -> Decoded | {error, term()}
                       when
                           SshBin :: binary(),
                           Type ::
                               ssh2_pubkey | public_key | openssh_key |
                               rfc4716_key | openssh_key_v1 | known_hosts |
                               auth_keys,
                           Decoded ::
                               Decoded_ssh2_pubkey | Decoded_public |
                               Decoded_openssh | Decoded_rfc4716 |
                               Decoded_openssh_key_v1 | Decoded_known_hosts |
                               Decoded_auth_keys,
                           Decoded_ssh2_pubkey :: public_key:public_key(),
                           Decoded_public ::
                               Decoded_rfc4716 | Decoded_openssh_key_v1 |
                               Decoded_openssh,
                           Decoded_openssh ::
                               [{public_key:public_key(),
                                 [{comment, string()}]}],
                           Decoded_rfc4716 :: [{key(), [{headers, Attrs}]}],
                           Decoded_openssh_key_v1 ::
                               experimental_openssh_key_v1_decode(),
                           Decoded_known_hosts ::
                               [{public_key:public_key(),
                                 [{comment, string()} | {hostnames,
       [string()]}]}],
                           Decoded_auth_keys ::
                               [{public_key:public_key(),
                                 [{comment, string()} | {options,
       [string()]}]}],
                           Attrs :: {Key :: string(), Value :: string()}.

Bestie, this one takes an SSH file-binary and decodes it. That's the whole vibe ✨

Okay but here's a lil detail that matters: if Type is public_key, the binary can lowkey be either an RFC4716 public key or an OpenSSH public key. Two formats, one type, the range 🀑

⚠️ NOTE: the implementation of the openssh_key_v1 format is still experimental. Do not come for me if it acts up, it's in its experimental era πŸ’€

encode β€” putting it back in the box πŸŽ€

encode(InData, Type) -> binary() | {error, term()}
                       when
                           Type ::
                               ssh2_pubkey | openssh_key | rfc4716_key |
                               openssh_key_v1 | known_hosts | auth_keys,
                           InData ::
                               InData_ssh2_pubkey | InData_openssh |
                               InData_rfc4716 | InData_openssh_key_v1 |
                               InData_known_hosts | InData_auth_keys,
                           InData_ssh2_pubkey :: public_key:public_key(),
                           InData_openssh ::
                               [{public_key:public_key(),
                                 [{comment, string()}]}],
                           InData_rfc4716 :: [{key(), [{headers, Attrs}]}],
                           InData_openssh_key_v1 ::
                               experimental_openssh_key_v1_encode(),
                           InData_known_hosts ::
                               [{public_key:public_key(),
                                 [{comment, string()} | {hostnames,
       [string()]}]}],
                           InData_auth_keys ::
                               [{public_key:public_key(),
                                 [{comment, string()} | {options,
       [string()]}]}],
                           Attrs :: {Key :: string(), Value :: string()}.

This one takes a whole list of SSH file entries β€” that's public keys and their attributes, both β€” and encodes them into a binary. It's giving ✨*serialization*✨

⚠️ NOTE: same energy as before, the openssh_key_v1 format implementation is still experimental, bestie. Still cooking 🍳

extract_public_key β€” finding the public half of your situationship πŸ”‘

extract_public_key(PrivKey) -> PubKey
                                   when
                                       PrivKey :: public_key:private_key(),
                                       PubKey :: public_key:public_key().

One job, one job only: it fetches the public key out of a private key. Fetches. That's it. Iconic minimalism honestly πŸ‘‘


host_key β€” the server's whole identity 🫢

host_key(Algorithm, Options) -> Result
                         when
                             Algorithm :: ssh:pubkey_alg(),
                             Result ::
                                 {ok, public_key:private_key()} |
                                 {error, term()},
                             Options ::
                                 ssh_server_key_api:daemon_key_cb_options(none()).

This implements ssh_server_key_api:host_key/2 β€” so, yeah, it's the server-side baddie handing out the host's private key πŸ’…

Options:

  • system_dir

Files it goes looking through (one per algorithm bestie, the full roster):

  • SYSDIR/ssh_host_rsa_key
  • SYSDIR/ssh_host_dsa_key
  • SYSDIR/ssh_host_ecdsa_key
  • SYSDIR/ssh_host_ed25519_key
  • SYSDIR/ssh_host_ed448_key

is_auth_key β€” vibe-checking a key against the list πŸ”

is_auth_key(Key, User, Options) -> boolean()
                            when
                                Key :: public_key:public_key(),
                                User :: string(),
                                Options ::
                                    ssh_server_key_api:daemon_key_cb_options(optimize_key_lookup()).

It implements ssh_server_key_api:is_auth_key/3 β€” yes, boolean(), a straight yes-or-no, no maybes, no {error, ...}, just truth. Periodt ✨

Options:

  • user_dir_fun
  • user_dir

Files it peeks at:

  • USERDIR/authorized_keys
  • USERDIR/authorized_keys2

And okay, important lil heads up: when this one reads through those files, it discards all the options sitting at the beginning of the lines. Like, thrown out. Bye πŸ‘‹ So don't be surprised when they're not there in the results πŸ™ˆ


is_host_key β€” vibe-checking this host's key πŸ¦‹

is_host_key(Key, Host, Port, Algorithm, Options) -> Result
                            when
                                Key :: public_key:public_key(),
                                Host ::
                                    inet:ip_address() |
                                    inet:hostname() |
                                    [inet:ip_address() | inet:hostname()],
                                Port :: inet:port_number(),
                                Algorithm :: ssh:pubkey_alg(),
                                Options ::
                                    ssh_client_key_api:client_key_cb_options(optimize_key_lookup()),
                                Result :: boolean() | {error, term()}.

Implements ssh_client_key_api:is_host_key/5. So it's the client-side twin of the last one, but it does get to say {error, term()} when it's feeling dramatic 😭

Option:

  • user_dir

File:

  • USERDIR/known_hosts

user_key β€” the keys you carry around everywhere πŸŽ€

user_key(Algorithm, Options) -> Result
                         when
                             Algorithm :: ssh:pubkey_alg(),
                             Result ::
                                 {ok, public_key:private_key()} |
                                 {error, string()},
                             Options ::
                                 ssh_client_key_api:client_key_cb_options(none()).

This one implements ssh_client_key_api:user_key/2 β€” your own keys, your own personal bag πŸ’–

Options:

  • user_dir
  • dsa_pass_phrase
  • rsa_pass_phrase
  • ecdsa_pass_phrase
⚠️ Heads up bestie: EdDSA passphrases (Curves 25519 and 448) are not implemented. Not ignoring you on purpose, it's just not there 😭

Files:

  • USERDIR/id_dsa
  • USERDIR/id_rsa
  • USERDIR/id_ecdsa
  • USERDIR/id_ed25519
  • USERDIR/id_ed448

aaaand that's the whole roster of functions bestie, every single one accounted for, nothing skipped, no crumbs left 🫢 this lil manual page came to you from Ericsson AB, riding along with ssh 6.0.5 β€” no date on the footer so I can't give you one, but everything else? got you πŸ’…

thanks for reading the whole thing, go touch some grass and then come back and read your known_hosts file πŸ’– ✨ πŸŽ€

πŸ“¦ where this page comes from