require'net/ssh/transport/server_version'# Only load pageant on WindowsifNet::SSH::Authentication::PLATFORM==:win32require'net/ssh/authentication/pageant'endmoduleNet;moduleSSH;moduleAuthentication# This class implements a simple client for the ssh-agent protocol. It# does not implement any specific protocol, but instead copies the# behavior of the ssh-agent functions in the OpenSSH library (3.8).## This means that although it behaves like a SSH1 client, it also has# some SSH2 functionality (like signing data).classAgentincludeLoggable# A simple module for extending keys, to allow comments to be specified# for them.moduleCommentattr_accessor:commentendSSH2_AGENT_REQUEST_VERSION=1SSH2_AGENT_REQUEST_IDENTITIES=11SSH2_AGENT_IDENTITIES_ANSWER=12SSH2_AGENT_SIGN_REQUEST=13SSH2_AGENT_SIGN_RESPONSE=14SSH2_AGENT_FAILURE=30SSH2_AGENT_VERSION_RESPONSE=103SSH_COM_AGENT2_FAILURE=102SSH_AGENT_REQUEST_RSA_IDENTITIES=1SSH_AGENT_RSA_IDENTITIES_ANSWER1=2SSH_AGENT_RSA_IDENTITIES_ANSWER2=5SSH_AGENT_FAILURE=5# The underlying socket being used to communicate with the SSH agent.attr_reader:socket# Instantiates a new agent object, connects to a running SSH agent,# negotiates the agent protocol version, and returns the agent object.defself.connect(logger=nil)agent=new(logger)agent.connect!agent.negotiate!agentend# Creates a new Agent object, using the optional logger instance to# report status.definitialize(logger=nil)self.logger=loggerend# Connect to the agent process using the socket factory and socket name# given by the attribute writers. If the agent on the other end of the# socket reports that it is an SSH2-compatible agent, this will fail# (it only supports the ssh-agent distributed by OpenSSH).defconnect!begindebug{"connecting to ssh-agent"}@socket=agent_socket_factory.open(ENV['SSH_AUTH_SOCK'])rescueerror{"could not connect to ssh-agent"}raiseAgentNotAvailable,$!.messageendend# Attempts to negotiate the SSH agent protocol version. Raises an error# if the version could not be negotiated successfully.defnegotiate!# determine what type of agent we're communicating withtype,body=send_and_wait(SSH2_AGENT_REQUEST_VERSION,:string,Transport::ServerVersion::PROTO_VERSION)iftype==SSH2_AGENT_VERSION_RESPONSEraiseAgentNotAvailable,"SSH2 agents are not yet supported"elsiftype==SSH2_AGENT_FAILUREdebug{"Unexpected response type==#{type}, this will be ignored"}elsiftype!=SSH_AGENT_RSA_IDENTITIES_ANSWER1&&type!=SSH_AGENT_RSA_IDENTITIES_ANSWER2raiseAgentNotAvailable,"unknown response from agent: #{type}, #{body.to_s.inspect}"endend# Return an array of all identities (public keys) known to the agent.# Each key returned is augmented with a +comment+ property which is set# to the comment returned by the agent for that key.defidentitiestype,body=send_and_wait(SSH2_AGENT_REQUEST_IDENTITIES)raiseAgentError,"could not get identity count"ifagent_failed(type)raiseAgentError,"bad authentication reply: #{type}"iftype!=SSH2_AGENT_IDENTITIES_ANSWERidentities=[]body.read_long.timesdokey_str=body.read_stringcomment_str=body.read_stringbeginkey=Buffer.new(key_str).read_keykey.extend(Comment)key.comment=comment_stridentities.pushkeyrescueNotImplementedError=>eerror{"ignoring unimplemented key:#{e.message}#{comment_str}"}endendreturnidentitiesend# Closes this socket. This agent reference is no longer able to# query the agent.defclose@socket.closeend# Using the agent and the given public key, sign the given data. The# signature is returned in SSH2 format.defsign(key,data)type,reply=send_and_wait(SSH2_AGENT_SIGN_REQUEST,:string,Buffer.from(:key,key),:string,data,:long,0)ifagent_failed(type)raiseAgentError,"agent could not sign data with requested identity"elsiftype!=SSH2_AGENT_SIGN_RESPONSEraiseAgentError,"bad authentication response #{type}"endreturnreply.read_stringendprivate# Returns the agent socket factory to use.defagent_socket_factoryifNet::SSH::Authentication::PLATFORM==:win32Pageant::SocketelseUNIXSocketendend# Send a new packet of the given type, with the associated data.defsend_packet(type,*args)buffer=Buffer.from(*args)data=[buffer.length+1,type.to_i,buffer.to_s].pack("NCA*")debug{"sending agent request #{type} len #{buffer.length}"}@socket.senddata,0end# Read the next packet from the agent. This will return a two-part# tuple consisting of the packet type, and the packet's body (which# is returned as a Net::SSH::Buffer).defread_packetbuffer=Net::SSH::Buffer.new(@socket.read(4))buffer.append(@socket.read(buffer.read_long))type=buffer.read_bytedebug{"received agent packet #{type} len #{buffer.length-4}"}returntype,bufferend# Send the given packet and return the subsequent reply from the agent.# (See #send_packet and #read_packet).defsend_and_wait(type,*args)send_packet(type,*args)read_packetend# Returns +true+ if the parameter indicates a "failure" response from# the agent, and +false+ otherwise.defagent_failed(type)type==SSH_AGENT_FAILURE||type==SSH2_AGENT_FAILURE||type==SSH_COM_AGENT2_FAILUREendendend;end;end