/** * The SSH agent protocol, served rather than consumed. * * `ssh` talks to whatever `SSH_AUTH_SOCK` points at. Pointing it here means a * developer's key lives in the vault and is used without ever existing on their * disk — which removes the artefact that every leaked SSH key came from. * * ## Two operations, and the omissions are the design * * `REQUEST_IDENTITIES` and `SIGN_REQUEST` are implemented. `ADD_IDENTITY`, * `REMOVE_IDENTITY` and `REMOVE_ALL_IDENTITIES` are deliberately NOT: this * agent's keys come from the vault, and a client that could add one would let * any process on the machine inject a key that `ssh` would then offer to every * host the user connects to. Refusing them is not a missing feature, it is the * difference between an agent and a shared keyring. * * Lock and unlock are also absent. An agent that can be locked by a message can * be locked by anything that reaches the socket, and the socket is already the * boundary — filesystem permissions decide who may speak here. * * ## Framing is length-prefixed and bounded * * Every message is a 4-byte big-endian length then that many bytes. A length * field is attacker-controlled, so it is bounded before a buffer is allocated: * an unbounded read is a one-packet memory exhaustion against a process holding * a vault replica. */ declare const SSH_AGENT_FAILURE = 5; declare const SSH_AGENT_SUCCESS = 6; declare const SSH_AGENT_IDENTITIES_ANSWER = 12; declare const SSH_AGENT_SIGN_RESPONSE = 14; /** * The largest message worth reading. * * An SSH signing request is a session identifier and a little framing — a few * hundred bytes. 256 KiB is far above anything legitimate and far below a size * that matters, and the point is that the bound exists at all. */ export declare const MAX_MESSAGE_BYTES: number; export declare class SshAgentError extends Error { readonly code: 'TOO_LARGE' | 'TRUNCATED' | 'UNSUPPORTED'; constructor(code: 'TOO_LARGE' | 'TRUNCATED' | 'UNSUPPORTED', message: string); } /** An identity this agent will offer. The private half is never here. */ export interface AgentIdentity { /** 32 raw bytes. */ publicKey: Uint8Array; comment: string; } export interface AgentBackend { identities(): Promise; /** * Sign `data` with the key matching `publicKey`, or return null. * * Null rather than throwing for an unknown key: `ssh` offers every identity * in turn and expects a failure for the ones a server did not accept, so an * exception here would turn normal negotiation into a crash. */ sign(publicKey: Uint8Array, data: Uint8Array): Promise; } /** Wrap a payload in the outer length prefix the protocol frames with. */ export declare const frame: (payload: Uint8Array) => Uint8Array; /** * Pull one complete message out of a buffer. * * Returns null when more bytes are needed — a socket delivers whatever arrived, * not whatever was sent, and treating a partial read as a malformed message is * how an agent drops every request larger than one TCP segment. */ export declare function readMessage(buffer: Uint8Array): { message: Uint8Array; rest: Uint8Array; } | null; /** * Answer one message. * * Every unknown or unimplemented request answers FAILURE rather than closing * the connection. `ssh` probes an agent's capabilities, and a socket that hangs * up on an unexpected byte makes every client think the agent has died. */ export declare function handleMessage(message: Uint8Array, backend: AgentBackend): Promise; /** What `ssh-add -l` would print, for an operator checking what is offered. */ export declare const describeIdentities: (identities: readonly AgentIdentity[]) => string[]; export { SSH_AGENT_FAILURE, SSH_AGENT_SUCCESS, SSH_AGENT_IDENTITIES_ANSWER, SSH_AGENT_SIGN_RESPONSE };