%% %% ssh_agent %% ssh agent client %% %% Copyright 2020 The University of Queensland %% Author: Alex Wilson %% %% Redistribution and use in source and binary forms, with or without %% modification, are permitted provided that the following conditions %% are met: %% 1. Redistributions of source code must retain the above copyright %% notice, this list of conditions and the following disclaimer. %% 2. Redistributions in binary form must reproduce the above copyright %% notice, this list of conditions and the following disclaimer in the %% documentation and/or other materials provided with the distribution. %% %% THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR %% IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES %% OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. %% IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, %% INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT %% NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, %% DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY %% THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT %% (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF %% THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. %% -module(ssh_agent). -include_lib("public_key/include/public_key.hrl"). -export([open/1, open/0, close/1]). -export([list_identities/1, sign/3]). -export([list_extensions/1, ecdh/3]). -export_type([client/0, config/0]). -opaque client() :: pid(). -type config() :: #{path => string()}. %% Configuration for ssh_agent:open/1. If path is not given, %% defaults to using the contents of the SSH_AUTH_SOCK %% environment variable. -type identity() :: #{ pubkey => pubkey(), comment => binary(), fingerprints => [binary()] }. %% %% Information about an "identity" kept in the ssh-agent. %% %% This includes the public key itself, the "key comment" (if any), as well as %% a list of key "fingerprints". The "fingerprints" list includes the SHA256 %% and MD5 fingerprints in OpenSSH style (e.g. SHA256:base64 and %% 01:02:ab:cd:...) as binary strings. -type siginfo() :: #{hash => crypto:hash_algorithm(), signature => binary()}. %% Signature and associated hash algorithm, as signed by the agent. %% %% Signatures are converted to the same format produced by crypto:sign() and %% expected by crypto:verify() (i.e. X509 ASN.1 format). -type extension() :: binary(). %% Extension name, in the form specified in draft-miller-ssh-agent %% (e.g. extension@domain.com). -type pubkey() :: public_key:public_key(). %% A public key belonging to a key stored in the agent. %% @doc Opens a new agent connection. %% %% The connection is supervised internally within the ssh_agent %% application. If you want it to disconnect and stop associated processes, %% you will need to call ssh_agent:close(). -spec open() -> {ok, client()} | {error, term()}. open() -> open(#{}). %% @doc Opens a new agent connection with explicit configuration. -spec open(config()) -> {ok, client()} | {error, term()}. open(Config) -> ssh_agent_sup:start_child(Config). %% @doc Closes a connection to an ssh-agent created by ssh_agent:open(). -spec close(client()) -> ok | {error, term()}. close(Client) -> gen_server:call(Client, close). %% @doc Gets a list of all identities (key pairs) stored in the agent. -spec list_identities(client()) -> {ok, [identity()]} | {error, term()}. list_identities(Client) -> gen_server:call(Client, list_keys). %% @doc Signs binary data using a key stored in the agent. %% %% The ssh-agent itself always has the final say regarding which hash algorithm %% will be used for signing. The algorithm used will be returned along with %% the signature data. -spec sign(client(), pubkey(), binary()) -> {ok, siginfo()} | {error, term()}. sign(Client, #'RSAPublicKey'{} = PubKey, Data) -> % RSA is a special case, since we default to sending the RSA_SHA256 flag. If % this agent doesn't support RSA-SHA256, it will respond with a "failure" % message and we should retry without the flag. case gen_server:call(Client, {sign, PubKey, Data}) of {error, agent_failure} -> gen_server:call(Client, {sign, PubKey, Data, []}); Res -> Res end; sign(Client, PubKey, Data) -> gen_server:call(Client, {sign, PubKey, Data}). %% @doc Lists protocol extensions supported by the agent, if any. %% %% If extensions are not supported, this may return an error rather than %% an empty list. -spec list_extensions(client()) -> {ok, [extension()]} | {error, term()}. list_extensions(Client) -> gen_server:call(Client, list_extensions). %% @doc Computes a shared secret using ECDH with a private key in the agent. %% %% This is an extension command and may not be supported by the agent. -spec ecdh(client(), pubkey(), public_key:public_key()) -> {ok, binary()} | {error, term()}. ecdh(Client, PubKey, OtherKey) -> gen_server:call(Client, {ecdh, PubKey, OtherKey}).