vmux
AppsvmuxAgent

Keys and Certificates

How vmuxAgent advertises iPhone-held public keys, caches them locally, and issues short-lived session certificates.

Two layers of identity

vmuxAgent presents two kinds of identities to the SSH agent socket:

  1. Phone-held keys. The authoritative identities. Each one is generated and stored on the iPhone (typically in the Secure Enclave). Every signature requires Face ID or Touch ID. These are what you put in authorized_keys on remote servers.
  2. Session certificates. A short-lived certificate signed by a CA on the iPhone, asserting that the Mac (or rather, an ephemeral keypair vmuxAgent generated this session) is allowed to act on behalf of one of the phone-held identities for a limited time. Signatures using the cert are made locally on the Mac without round-tripping the phone.

Both kinds of identity are advertised through the same ssh-add -l listing. SSH clients pick whichever one matches a server's authorized_keys entry.

Phone-held keys

How the list arrives

When vmuxAgent first connects to the iPhone, it sends a keyListRequest over the encrypted Multipeer link. RemoteSignerPhone responds with one or more RemoteSignerPublicKey records:

FieldDescription
algorithmWire-protocol algorithm string, e.g. ecdsa-sha2-nistp256, ssh-ed25519
publicKeyBlobThe raw OpenSSH public-key blob (no base64, no comment)
commentHuman-readable label, set on the phone
providerKindWhere the private key lives: secureEnclave, hardwareToken, or unknown
displayNameOptional friendly name shown in UIs

vmuxAgent maps each entry into its own AgentKeyInfo record, computes a SHA-256 fingerprint of the blob, and adds it to the in-memory list.

Cache

The fingerprint, comment, algorithm, and public-key blob are persisted in the standard macOS user defaults under cachedKeys. This means:

  • After a relaunch, ssh-add -l still lists the previous keys even if the iPhone is out of range.
  • After an OS restart, the cache survives.
  • Removing a key on the phone does not remove it from the Mac's cache until vmuxAgent reconnects and refreshes — use Refresh Keys in the menu to force a refresh, or quit and relaunch.

The cache holds public material only. There are no private keys on the Mac to leak.

Fingerprints

Each key's fingerprint shown in the menu and via ssh-add -l is a SHA-256 over the OpenSSH public-key blob. The format is the standard SHA256: prefix followed by the base64-encoded hash:

SHA256:K9F+Z3WfNqHXwK2y+e0p4...

This matches what ssh-keygen -l -f public_key.pub prints, and what GitHub, GitLab, and Bitbucket display next to authorized SSH keys. Use the fingerprint to confirm you are looking at the same key in two places.

Algorithms

vmuxAgent does not invent or enforce algorithm choices; it forwards whatever the iPhone advertises. RemoteSignerPhone defaults to ecdsa-sha2-nistp256 for Secure Enclave keys (this is the only algorithm Secure Enclave supports). Non-Enclave identities can be any algorithm OpenSSH supports — ed25519, rsa-sha2-512, etc.

Session certificates

Why they exist

Forwarding every single signature to the phone is great for security but tiresome in practice. Pushing five commits in a row should not require five Face ID prompts. Session certificates solve this: vmuxAgent generates an ephemeral P-256 keypair on the Mac, sends the public half to the iPhone, and asks the phone's CA to sign a short-lived SSH certificate. After that, vmuxAgent can sign locally with the ephemeral key, and remote servers that trust the CA accept the signature.

Lifecycle

StageWhat happensWhere
IssueMac generates a fresh P-256 keypair, sends the public half + requested validity to the iPhoneMac and iPhone
ApproveiPhone CA prompts you with Face ID, signs the cert, returns certificateBlob + CA public key + validAfter / validBeforeiPhone
UsevmuxAgent advertises the cert blob alongside phone-held keys; SSH signatures using the cert are made locally with the ephemeral keyMac
Auto-renewOnce fewer than 60 minutes remain, vmuxAgent re-issues automatically while the phone is reachableMac
ExpiryPast validBefore, the cert is unusable. Sign requests fall back to the phoneMac
Renew on demandRequest Certificate in the menu forces immediate re-issueMac

The default validity window is 8 hours (28800 seconds). The phone can deny or shorten the request based on its own policy.

When you'd want one

  • You are running a long-lived git workflow, a build script that opens many SSH connections, or mosh reconnects in a flaky network. One Face ID prompt at the start of the day amortizes hundreds of signatures.
  • You want unattended SSH (cron, automated deploys) for a known time window. Issue a cert, leave it valid for the window, and let it expire on its own.

When you wouldn't

  • For interactive ssh sessions where you sign rarely. The per-prompt friction is low and the per-prompt visibility is high.
  • For very high-value targets where you want every signature to be a deliberate decision. Disable cert auto-issuance on the phone (see RemoteSignerPhone).
  • For keys that aren't trusted by the CA on the remote side. Servers must list the CA public key in TrustedUserCAKeys (or equivalent) for cert-based signatures to authenticate.

Configuring servers to trust the CA

If you want servers to accept the session-certificate path, add the CA public key (which the phone returns with each cert) to your server's SSH config. The mechanism is exactly OpenSSH's TrustedUserCAKeys — there is nothing vmux-specific here:

# /etc/ssh/sshd_config on the server
TrustedUserCAKeys /etc/ssh/vmux-ca.pub

Append the CA public key to that file, then systemctl reload sshd. From then on, sessions signed with a vmux-issued cert will authenticate without needing the ephemeral key in authorized_keys.

For most users this is overkill; just put the phone-held public key in authorized_keys and let signatures forward through to the phone.

Status visibility

The current state of both layers is visible in the menu bar dropdown:

  • Connection status line — whether the iPhone is reachable.
  • Certificate status lineCertificate valid until <time>, Certificate expired — connect phone to renew, or No session certificate.
  • Key list — every phone-held identity by comment and fingerprint.

In a terminal:

# What the agent is willing to authenticate as
ssh-add -l

# The OpenSSH public key blobs (paste these into authorized_keys)
ssh-add -L

ssh-add -L prints both the phone-held identities and the active session certificate (the cert appears under the comment vmux-session-cert). Servers that trust the certificate's CA will accept signatures from the cert; servers that only know the phone-held key will accept signatures from the underlying identity.

Sign request anatomy

When an SSH client asks vmuxAgent to sign, the agent constructs a RemoteSignRequest with these fields:

FieldPurpose
requestIDUUID for matching response
keyAlgorithmExtracted from the key blob (e.g. ecdsa-sha2-nistp256)
publicKeyBlobIdentifies which key the iPhone should use
sourceDeviceID / sourceDeviceNameHostname and friendly name of the requesting Mac
originAlways macOSGenericAgent for vmuxAgent
dataToSignThe challenge bytes; the iPhone signs this and returns the signature
nonceAnti-replay token
issuedAtMilliseconds / expiresAtMillisecondsRequest window (default 60 seconds)
context.contextQualityunknown for the generic agent — vmuxAgent does not see destination host or username

The phone may deny the request and return one of several error codes (userDenied, requestExpired, pairingInvalid, etc.) — see Troubleshooting for the full list and what each means.