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:
- 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_keyson remote servers. - 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:
| Field | Description |
|---|---|
algorithm | Wire-protocol algorithm string, e.g. ecdsa-sha2-nistp256, ssh-ed25519 |
publicKeyBlob | The raw OpenSSH public-key blob (no base64, no comment) |
comment | Human-readable label, set on the phone |
providerKind | Where the private key lives: secureEnclave, hardwareToken, or unknown |
displayName | Optional 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 -lstill 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
| Stage | What happens | Where |
|---|---|---|
| Issue | Mac generates a fresh P-256 keypair, sends the public half + requested validity to the iPhone | Mac and iPhone |
| Approve | iPhone CA prompts you with Face ID, signs the cert, returns certificateBlob + CA public key + validAfter / validBefore | iPhone |
| Use | vmuxAgent advertises the cert blob alongside phone-held keys; SSH signatures using the cert are made locally with the ephemeral key | Mac |
| Auto-renew | Once fewer than 60 minutes remain, vmuxAgent re-issues automatically while the phone is reachable | Mac |
| Expiry | Past validBefore, the cert is unusable. Sign requests fall back to the phone | Mac |
| Renew on demand | Request Certificate in the menu forces immediate re-issue | Mac |
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
gitworkflow, a build script that opens many SSH connections, ormoshreconnects 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
sshsessions 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.pubAppend 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 line —
Certificate valid until <time>,Certificate expired — connect phone to renew, orNo 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 -Lssh-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:
| Field | Purpose |
|---|---|
requestID | UUID for matching response |
keyAlgorithm | Extracted from the key blob (e.g. ecdsa-sha2-nistp256) |
publicKeyBlob | Identifies which key the iPhone should use |
sourceDeviceID / sourceDeviceName | Hostname and friendly name of the requesting Mac |
origin | Always macOSGenericAgent for vmuxAgent |
dataToSign | The challenge bytes; the iPhone signs this and returns the signature |
nonce | Anti-replay token |
issuedAtMilliseconds / expiresAtMilliseconds | Request window (default 60 seconds) |
context.contextQuality | unknown 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.
Related
- Menu bar reference — where to read each piece of state.
- Security — what the cert protects and what it does not.
- RemoteSignerPhone — phone-side identity creation, multi-key management, CA configuration.