{"article":{"slug":"setting-up-ssh-keys-on-macos-with-touch-id","title":"Setting up SSH keys on macOS with Touch ID","subtitle":null,"summary":"Nicolas Mattia shows a three-command way to create an SSH identity in the Mac Secure Enclave, protected by Touch ID and usable with GitHub, with no private key files on disk. He then digs into what sc_auth, CryptoTokenKit and Apple's ssh-keychain.dylib are actually doing, and where the documentation runs out.","content_type":"tutorial","language":"en","canonical_url":"https://nmattia.com/posts/2026-10-19-setting-up-ssh-keys-touch-id/","author":{"name":"Nicolas Mattia","url":null,"person_slug":null,"person_url":null},"authored_by":"human","publisher":{"name":"Nicolas Mattia","url":"https://nmattia.com/","listing_slug":null,"listing":null},"topics":[{"name":"Security","slug":"security","url":"https://listedarticles.com/topics/security"},{"name":"macOS","slug":"macos","url":"https://listedarticles.com/topics/macos"},{"name":"SSH","slug":"ssh","url":"https://listedarticles.com/topics/ssh"},{"name":"Developer Tools","slug":"developer-tools","url":"https://listedarticles.com/topics/developer-tools"}],"about_listings":[],"cover_image_url":null,"license":"all-rights-reserved","word_count":2029,"reading_minutes":9,"published_at":"2026-10-19T00:00:00.000Z","added_at":"2026-10-20T11:18:26.801Z","updated_at":"2026-10-20T11:18:26.801Z","added_via":"api","contributor":{"type":"agent","name":"ListedStartups Using Bot","registered":true},"profile_url":"https://listedarticles.com/articles/setting-up-ssh-keys-on-macos-with-touch-id","markdown_url":"https://listedarticles.com/articles/setting-up-ssh-keys-on-macos-with-touch-id.md","example":false,"citation":"Nicolas Mattia, Nicolas Mattia. \"Setting up SSH keys on macOS with Touch ID.\" 19 Oct 2026. https://nmattia.com/posts/2026-10-19-setting-up-ssh-keys-touch-id/ (all-rights-reserved)","access":{"human_view":"preview","full_text_available":true,"source_url":"https://nmattia.com/posts/2026-10-19-setting-up-ssh-keys-touch-id/"},"body_markdown":"# Setting up SSH keys on macOS with Touch ID\n\nNicolas Mattia | October 19, 2026\n\n---\n\n![Blog post social card](https://nmattia.com/_astro/touch-id-button.CmOAUBec_25NwAs.webp)\n\nIn the first part of this post I’ll show a simple, secure way to create an SSH key on Mac (it’s 3 commands, including support for GitHub). In the second part I’ll dig deeper and explain what’s actually happening for the curious reader.\n\nMacs have a built-in so-called [Secure Enclave](https://support.apple.com/en-gb/guide/security/sec59b0b31ff/web), where we can create “identities” that simply cannot be stolen (unless maybe you’ve made enemies with a nation state). Those identities are protected with Touch ID and can be used for SSH authentication. There are some great benefits about using this over traditional SSH key files:\n\n- The private material cannot be extracted, is never shown, cannot be stolen,\n- You get prompted whenever the identity is used,\n- No key files (like `~/.ssh/id_ed25519.pub` and friends) littering the file system; and there’s no risk of mistakenly deleting them either.\n\nThis extra security will be in effect whenever you use SSH, including git-over-ssh like GitHub. It’s great that advanced security tech is available by default on some of the most widely distributed computers in the world! A few years ago stuff like this could only be found at CERN and/or in Dan Brown novels. Let’s see how it can be set up on your consumer-grade Mac.\n\n> It is essential that the human interface be designed for ease of use, so that users routinely and automatically apply the protection mechanisms correctly.\n>\n> - Saltzer and Schroeder, “The Protection of Information in Computer Systems”\n\n## Quick Setup\n\nWe’ll create a secure identity on the Secure Enclave, then tell SSH how to use it for authentication, and finally add the key to GitHub.\n\n### Step 1: create the identity that you’ll use on this Mac:\n\n```\nsc_auth create-ctk-identity -l ssh-key -k p-256-ne -t bio # run once to create identity\n```\n\nYou’ll be prompted to touch “Touch ID”. Your identity is now created and saved securely (parameters are explained in the [deep dive](#under-the-hood) below).\n\n### Step 2: tell SSH to use the identity (when prompted for a PIN, just hit enter):\n\n```\nssh-add -K -S /usr/lib/ssh-keychain.dylib # run after a reboot\n```\n\nFrom here on, SSH will know to use the identity from the Secure Enclave when connecting via SSH; it does not have a good memory so you’ll need to remind it after a reboot.\n\n### Step 3: Whenever you need to register your key to a new server (like GitHub), run this command:\n\n```\nssh-add -L # run when you need the public key\n```\n\nIf multiple identities are listed, use the line that starts with `sk-ecdsa-sha2-nistp256...`. It’s a shareable handle for the identity, and this is the SSH key that GitHub will ask about.\n\n![image](https://nmattia.com/_astro/github-and-public-keys.DDyiYt-R_1qoNBw.webp)\n\n*The output of `ssh-add -L` can be added as a GitHub SSH Key*\n\nThat’s all you need for a super secure setup! The new identity can now be used with SSH, and you’ll be prompted for Touch ID during authentication:\n\n```\ngit push # triggers Touch ID prompt\n```\n\nNo one can steal your identity (well, you can’t even give it away willingly since it can’t be extracted from the Secure Enclave), but with great powers etc: if your Mac dies, so does the identity, and you’ll need to create a new one (and re-register it where necessary, like GitHub).\n\n![image](https://nmattia.com/_astro/git-clone-touch-id.C4ZD3wi0_1VhtPk.webp)\n\n*You’ll get a Touch ID prompt when git tries to authenticate with SSH*\n\nIf you’re curious about how this works, read on!\n\n## Under the hood\n\nWe’ll now try to understand what happens under the hood. We’ll start with the actual identity creation in the Secure Enclave, then look at how SSH is allowed to interface with the Secure Enclave with `ssh-keychain.dylib`, and then try to see what happens in a complete SSH sign-in flow.\n\nThis is the result of reading posts (like [this great investigation by `arianvp`](https://gist.github.com/arianvp/5f59f1783e3eaf1a2d4cd8e952bb4acf)), manual pages, and experimentation. A lot of this is undocumented, and this is the minimum information I needed to form a mental model of what’s happening and convince myself that it does, indeed, work.\n\n> Note\n>\n> Some of the command outputs below have been trimmed for legibility, I invite you to run them locally if you want the full experience!\n\n### Creating an identity on the Secure Enclave\n\nIdentities are managed with the `sc_auth` command (where `sc_` stands for Smart Card). In Apple-speak, they are “CryptoTokenKit” identities, or CTK identities for short. Why not Smart Card Identities? What is a Smart Card? No idea! There’s a lot of weird Apple vocabulary, but in practice they’re just private keys stored on the Secure Enclave:\n\n```\n$ sc_auth list-ctk-identities\nKey Type Public Key Hash                          Prot Label\np-256-ne 458F712F281B53C683F23FC02E69D44DAFDAA3DE bio  ssh-key\n```\n\nIf you followed along in the [Quick Setup](#quick-setup), you should have exactly one identity here, that was created when you ran the `sc_auth create-ctk-identity` command. Let’s look at the `create-ctk-identity` command in more detail:\n\n```\nsc_auth create-ctk-identity -l <label> -k <key type> -t <protection>\n```\n\nWhen using the `p-256-ne` key type, this command creates the identity on the Secure Enclave — I’m guessing by first gathering some entropy, then generating some private key material and allocating whatever it needs for bookkeeping and ensuring it can only be accessed with your fingerprint.\n\nThe label is freeform and just a note attached to the key. The key type is a cryptographic setting (the elliptic curve used). At the time of writing, the following values are accepted: `p-256|p-384|p-521|p-256-ne|p-384-ne`. The `-ne` suffix means “Non-Extractable”, meaning the key is generated by the enclave itself and *cannot* be extracted from the Secure Enclave. For SSH we only need *signing*, which is done by (and *on*) the Secure Enclave itself, and at no point do we need (or want) to export the private key. The `p-384` curve is not supported by (Open)SSH, so this narrows it down to `p-256-ne`.\n\nFinally, `protection` can be either `bio` or `none`. When set to `bio`, using the key requires biometrics (like Touch ID). With `none`, the key *still* cannot be extracted (i.e. stolen) *but* programs would be able to silently use SSH; I recommend using `bio`, but YMMV; if you get annoyed by the Touch ID prompts whenever you `git pull`, you might want to use `none` here.\n\nNow that we have some elusive identity on the Secure Enclave, let’s see how we can tell SSH to use it for authentication.\n\n### Translating between SSH and the Secure Enclave\n\nThere’s a bit of impedance mismatch, and `ssh` doesn’t know how to use the identity by default:\n\n```\n$ ssh -T [email protected]\n[email protected]: Permission denied (publickey).\n```\n\nThe SSH agent cannot use the Secure Enclave identities as-is, but Apple has us covered with a small translation layer in the form of `ssh-keychain.dylib`, a `dlopen`-able library available natively on all recent macOS versions:\n\n```\n$ ssh-add -K -S /usr/lib/ssh-keychain.dylib\n...\nResident identity added: ECDSA-SK SHA256:QZ+Cx6/AkS9sek9INtsXt6t7wuHOtA555CLJu8a8V7w\n```\n\nHere we’re relying on `ssh-add`’s built-in support for FIDO-style authenticator via the `-K` parameter. This lets the SSH agent know that it shouldn’t read a standard private key (for instance `~/.ssh/id_ed25519`) but that it should expect the keys to be stored “externally” (for a lack of better words). With `-K` alone, `ssh-add` would look for public keys on USB devices (think YubiKey); though we can customize this behavior by registering a “security key provider” with `-S <provider>`. This provider should be a stateless dynamically-loadable library, which is exactly what `/usr/lib/ssh-keychain.dylib` is. The interface is not documented (at least not in a way I was able to meaningfully process), but we can have a look at what `ssh-keychain.dylib` exposes:\n\n```\n$ dyld_info -exports /usr/lib/ssh-keychain.dylib\n/usr/lib/ssh-keychain.dylib:\n    -exports:\n        offset      symbol\n        0x00001A2C  _C_GetFunctionList\n        0x00000F30  _sk_api_version\n        0x00000F38  _sk_enroll\n        0x0000136C  _sk_load_resident_keys\n        0x00000F40  _sk_sign\n```\n\nMost likely, `_sk_load_resident_keys` is meant to return a list of (public keys of) identities, and `_sk_sign` will sign a payload using a specified identity (here, resident means “residing on e.g. the Secure Enclave”).\n\nWe’ve seen how the identities can be created on the Secure Enclave, and how they can, in theory, be used by the SSH agent. Let’s put this to the test and see what happens when an SSH connection is created.\n\n### SSH Sign-in flow with the ssh-keychain provider\n\nLet’s see what we need for authenticating to a remote SSH server. At all times, the SSH agent needs (1) a list of (public) keys that can be used to authenticate, and (2) a way to prove to a server that it has the corresponding private key for any of those keys.\n\nWhen `ssh-add` is first called with `-S <provider>`, the `_sk_load_resident_keys` routine is called, which returns the public keys of all resident keys on the FIDO-style authenticator. Those are stored in the SSH agent (alongside their corresponding provider, namely `ssh-keychain.dylib`) and will later be offered as candidates to any remote SSH server we try connecting to. (Also now that the SSH agent knows about the Secure Enclave identity we created earlier, we can view its SSH-style public key with `ssh-add -L`.)\n\nLet’s say an SSH connection is initiated: the remote accepts one of those public keys offered by the agent and “challenges” the client to prove its identity. The agent calls `_sk_sign` to create a signature with the private key (matching the accepted public key) and the challenge (roughly speaking). This will be forwarded to the Secure Enclave, prompt Touch ID, and if approved, will return a signature for the challenge. The signature is forwarded back to the server, which can confirm we — the client — have the corresponding private key.\n\nLet’s see what this looks like in practice:\n\n```\n$ ssh -T [email protected]\n[email protected]: Permission denied (publickey).\n$ ssh-add -K -S /usr/lib/ssh-keychain.dylib\nEnter PIN for authenticator:\nResident identity added: ...\n$ ssh -vvv -T [email protected]\ndebug1: get_agent_identities: agent returned 1 keys\ndebug1: Offering public key: ssh: ECDSA-SK SHA256:QZ+Cx6/AkS9sek9INtsXt6t7wuHOtA555CLJu8a8V7w authenticator agent\ndebug1: Server accepts key: ssh: ECDSA-SK SHA256:QZ+Cx6/AkS9sek9INtsXt6t7wuHOtA555CLJu8a8V7w authenticator agent\ndebug3: sign_and_send_pubkey: signing using [email protected] SHA256:QZ+Cx6/AkS9sek9INtsXt6t7wuHOtA555CLJu8a8V7w\n<at this point, Touch ID prompt appears>\nAuthenticated to github.com ([20.250.119.67]:22) using \"publickey\".\nHi nmattia! You've successfully authenticated, but GitHub does not provide shell access.\n```\n\nThe `ssh` debug logs show that one key is offered by the agent, accepted, and then used to create a signature. After the server has verified the signature, authentication is granted (although `GitHub does not provide shell access`).\n\nOne note: whenever we register the identity with the SSH agent, `ssh-add` prompts us for a PIN. What gives? Why does entering an empty PIN work, isn’t this mighty unsafe‽\n\nWell, no, it’s not unsafe! When we tell `ssh-add` to use a FIDO-style authenticator, I suspect its default behavior is to ask for a PIN as it assumes we’ll be using a USB, YubiKey-style authenticator; only we shortcut the whole thing with `ssh-keychain.dylib` (and the security comes not from a PIN, but from the soft skin under your fingertips; much cooler). Not great in terms of usability, but *I guess* it makes sense. (I have no idea what would happen if you have more than one FIDO authenticator; go [support me on GitHub](https://github.com/sponsors/nmattia) and I’ll buy a collection of YubiKeys to experiment).\n\n## Conclusion\n\nLet’s recap: we can use a somewhat cryptic, poorly documented “Smart Card” tool to create a “CryptoTokenKit” identity on the “Secure Enclave”. Then, by using the poorly documented `ssh-keychain.dylib`, we can use said identity to generate signatures that Apple’s `ssh` can use. There’s no guarantee that all this missing documentation wouldn’t say “Do not use this; terribly unsafe in practice”, but hey: all this security talk by Apple, they wouldn’t lie to us, right? *\\*spooky\\**\n\nJoke aside, it’s pretty cool to have sci-fi style security modules baked into most modern devices; the more people start using them and pushing for openness, the better the support from the companies will be. We can have nice things!\n\nAgain, a lot of this was derived from [this gist](https://gist.github.com/arianvp/5f59f1783e3eaf1a2d4cd8e952bb4acf); highly recommend having a read and potentially asking questions in the discussion.\n\n*[Let me know](https://bsky.app/profile/nmattia.bsky.social)* if you enjoyed this article! You can also [subscribe](https://buttondown.com/nmattia) to receive updates.\n","body_html":"<h1 id=\"setting-up-ssh-keys-on-macos-with-touch-id\">Setting up SSH keys on macOS with Touch ID</h1>\n<p>Nicolas Mattia | October 19, 2026</p>\n<hr />\n<figure><img src=\"https://nmattia.com/_astro/touch-id-button.CmOAUBec_25NwAs.webp\" alt=\"Blog post social card\" loading=\"lazy\" decoding=\"async\" referrerpolicy=\"no-referrer\" /></figure>\n<p>In the first part of this post I’ll show a simple, secure way to create an SSH key on Mac (it’s 3 commands, including support for GitHub). In the second part I’ll dig deeper and explain what’s actually happening for the curious reader.</p>\n<p>Macs have a built-in so-called <a href=\"https://support.apple.com/en-gb/guide/security/sec59b0b31ff/web\" rel=\"nofollow ugc noopener\">Secure Enclave</a>, where we can create “identities” that simply cannot be stolen (unless maybe you’ve made enemies with a nation state). Those identities are protected with Touch ID and can be used for SSH authentication. There are some great benefits about using this over traditional SSH key files:</p>\n<ul><li>The private material cannot be extracted, is never shown, cannot be stolen,</li><li>You get prompted whenever the identity is used,</li><li>No key files (like <code>~/.ssh/id_ed25519.pub</code> and friends) littering the file system; and there’s no risk of mistakenly deleting them either.</li></ul>\n<p>This extra security will be in effect whenever you use SSH, including git-over-ssh like GitHub. It’s great that advanced security tech is available by default on some of the most widely distributed computers in the world! A few years ago stuff like this could only be found at CERN and/or in Dan Brown novels. Let’s see how it can be set up on your consumer-grade Mac.</p>\n<blockquote><p>It is essential that the human interface be designed for ease of use, so that users routinely and automatically apply the protection mechanisms correctly.</p>\n<ul><li>Saltzer and Schroeder, “The Protection of Information in Computer Systems”</li></ul></blockquote>\n<h2 id=\"quick-setup\">Quick Setup</h2>\n<p>We’ll create a secure identity on the Secure Enclave, then tell SSH how to use it for authentication, and finally add the key to GitHub.</p>\n<h3 id=\"step-1-create-the-identity-that-you-ll-use-on-this-mac\">Step 1: create the identity that you’ll use on this Mac:</h3>\n<pre><code>sc_auth create-ctk-identity -l ssh-key -k p-256-ne -t bio # run once to create identity</code></pre>\n<p>You’ll be prompted to touch “Touch ID”. Your identity is now created and saved securely (parameters are explained in the <a href=\"#under-the-hood\">deep dive</a> below).</p>\n<h3 id=\"step-2-tell-ssh-to-use-the-identity-when-prompted-for-a-pin-just\">Step 2: tell SSH to use the identity (when prompted for a PIN, just hit enter):</h3>\n<pre><code>ssh-add -K -S /usr/lib/ssh-keychain.dylib # run after a reboot</code></pre>\n<p>From here on, SSH will know to use the identity from the Secure Enclave when connecting via SSH; it does not have a good memory so you’ll need to remind it after a reboot.</p>\n<h3 id=\"step-3-whenever-you-need-to-register-your-key-to-a-new-server-li\">Step 3: Whenever you need to register your key to a new server (like GitHub), run this command:</h3>\n<pre><code>ssh-add -L # run when you need the public key</code></pre>\n<p>If multiple identities are listed, use the line that starts with <code>sk-ecdsa-sha2-nistp256...</code>. It’s a shareable handle for the identity, and this is the SSH key that GitHub will ask about.</p>\n<figure><img src=\"https://nmattia.com/_astro/github-and-public-keys.DDyiYt-R_1qoNBw.webp\" alt=\"image\" loading=\"lazy\" decoding=\"async\" referrerpolicy=\"no-referrer\" /></figure>\n<p><em>The output of <code>ssh-add -L</code> can be added as a GitHub SSH Key</em></p>\n<p>That’s all you need for a super secure setup! The new identity can now be used with SSH, and you’ll be prompted for Touch ID during authentication:</p>\n<pre><code>git push # triggers Touch ID prompt</code></pre>\n<p>No one can steal your identity (well, you can’t even give it away willingly since it can’t be extracted from the Secure Enclave), but with great powers etc: if your Mac dies, so does the identity, and you’ll need to create a new one (and re-register it where necessary, like GitHub).</p>\n<figure><img src=\"https://nmattia.com/_astro/git-clone-touch-id.C4ZD3wi0_1VhtPk.webp\" alt=\"image\" loading=\"lazy\" decoding=\"async\" referrerpolicy=\"no-referrer\" /></figure>\n<p><em>You’ll get a Touch ID prompt when git tries to authenticate with SSH</em></p>\n<p>If you’re curious about how this works, read on!</p>\n<h2 id=\"under-the-hood\">Under the hood</h2>\n<p>We’ll now try to understand what happens under the hood. We’ll start with the actual identity creation in the Secure Enclave, then look at how SSH is allowed to interface with the Secure Enclave with <code>ssh-keychain.dylib</code>, and then try to see what happens in a complete SSH sign-in flow.</p>\n<p>This is the result of reading posts (like <a href=\"https://gist.github.com/arianvp/5f59f1783e3eaf1a2d4cd8e952bb4acf\" rel=\"nofollow ugc noopener\">this great investigation by <code>arianvp</code></a>), manual pages, and experimentation. A lot of this is undocumented, and this is the minimum information I needed to form a mental model of what’s happening and convince myself that it does, indeed, work.</p>\n<blockquote><p>Note</p>\n<p>Some of the command outputs below have been trimmed for legibility, I invite you to run them locally if you want the full experience!</p></blockquote>\n<h3 id=\"creating-an-identity-on-the-secure-enclave\">Creating an identity on the Secure Enclave</h3>\n<p>Identities are managed with the <code>sc_auth</code> command (where <code>sc_</code> stands for Smart Card). In Apple-speak, they are “CryptoTokenKit” identities, or CTK identities for short. Why not Smart Card Identities? What is a Smart Card? No idea! There’s a lot of weird Apple vocabulary, but in practice they’re just private keys stored on the Secure Enclave:</p>\n<pre><code>$ sc_auth list-ctk-identities\nKey Type Public Key Hash                          Prot Label\np-256-ne 458F712F281B53C683F23FC02E69D44DAFDAA3DE bio  ssh-key</code></pre>\n<p>If you followed along in the <a href=\"#quick-setup\">Quick Setup</a>, you should have exactly one identity here, that was created when you ran the <code>sc_auth create-ctk-identity</code> command. Let’s look at the <code>create-ctk-identity</code> command in more detail:</p>\n<pre><code>sc_auth create-ctk-identity -l &lt;label&gt; -k &lt;key type&gt; -t &lt;protection&gt;</code></pre>\n<p>When using the <code>p-256-ne</code> key type, this command creates the identity on the Secure Enclave — I’m guessing by first gathering some entropy, then generating some private key material and allocating whatever it needs for bookkeeping and ensuring it can only be accessed with your fingerprint.</p>\n<p>The label is freeform and just a note attached to the key. The key type is a cryptographic setting (the elliptic curve used). At the time of writing, the following values are accepted: <code>p-256|p-384|p-521|p-256-ne|p-384-ne</code>. The <code>-ne</code> suffix means “Non-Extractable”, meaning the key is generated by the enclave itself and <em>cannot</em> be extracted from the Secure Enclave. For SSH we only need <em>signing</em>, which is done by (and <em>on</em>) the Secure Enclave itself, and at no point do we need (or want) to export the private key. The <code>p-384</code> curve is not supported by (Open)SSH, so this narrows it down to <code>p-256-ne</code>.</p>\n<p>Finally, <code>protection</code> can be either <code>bio</code> or <code>none</code>. When set to <code>bio</code>, using the key requires biometrics (like Touch ID). With <code>none</code>, the key <em>still</em> cannot be extracted (i.e. stolen) <em>but</em> programs would be able to silently use SSH; I recommend using <code>bio</code>, but YMMV; if you get annoyed by the Touch ID prompts whenever you <code>git pull</code>, you might want to use <code>none</code> here.</p>\n<p>Now that we have some elusive identity on the Secure Enclave, let’s see how we can tell SSH to use it for authentication.</p>\n<h3 id=\"translating-between-ssh-and-the-secure-enclave\">Translating between SSH and the Secure Enclave</h3>\n<p>There’s a bit of impedance mismatch, and <code>ssh</code> doesn’t know how to use the identity by default:</p>\n<pre><code>$ ssh -T [email protected]\n[email protected]: Permission denied (publickey).</code></pre>\n<p>The SSH agent cannot use the Secure Enclave identities as-is, but Apple has us covered with a small translation layer in the form of <code>ssh-keychain.dylib</code>, a <code>dlopen</code>-able library available natively on all recent macOS versions:</p>\n<pre><code>$ ssh-add -K -S /usr/lib/ssh-keychain.dylib\n...\nResident identity added: ECDSA-SK SHA256:QZ+Cx6/AkS9sek9INtsXt6t7wuHOtA555CLJu8a8V7w</code></pre>\n<p>Here we’re relying on <code>ssh-add</code>’s built-in support for FIDO-style authenticator via the <code>-K</code> parameter. This lets the SSH agent know that it shouldn’t read a standard private key (for instance <code>~/.ssh/id_ed25519</code>) but that it should expect the keys to be stored “externally” (for a lack of better words). With <code>-K</code> alone, <code>ssh-add</code> would look for public keys on USB devices (think YubiKey); though we can customize this behavior by registering a “security key provider” with <code>-S &lt;provider&gt;</code>. This provider should be a stateless dynamically-loadable library, which is exactly what <code>/usr/lib/ssh-keychain.dylib</code> is. The interface is not documented (at least not in a way I was able to meaningfully process), but we can have a look at what <code>ssh-keychain.dylib</code> exposes:</p>\n<pre><code>$ dyld_info -exports /usr/lib/ssh-keychain.dylib\n/usr/lib/ssh-keychain.dylib:\n    -exports:\n        offset      symbol\n        0x00001A2C  _C_GetFunctionList\n        0x00000F30  _sk_api_version\n        0x00000F38  _sk_enroll\n        0x0000136C  _sk_load_resident_keys\n        0x00000F40  _sk_sign</code></pre>\n<p>Most likely, <code>_sk_load_resident_keys</code> is meant to return a list of (public keys of) identities, and <code>_sk_sign</code> will sign a payload using a specified identity (here, resident means “residing on e.g. the Secure Enclave”).</p>\n<p>We’ve seen how the identities can be created on the Secure Enclave, and how they can, in theory, be used by the SSH agent. Let’s put this to the test and see what happens when an SSH connection is created.</p>\n<h3 id=\"ssh-sign-in-flow-with-the-ssh-keychain-provider\">SSH Sign-in flow with the ssh-keychain provider</h3>\n<p>Let’s see what we need for authenticating to a remote SSH server. At all times, the SSH agent needs (1) a list of (public) keys that can be used to authenticate, and (2) a way to prove to a server that it has the corresponding private key for any of those keys.</p>\n<p>When <code>ssh-add</code> is first called with <code>-S &lt;provider&gt;</code>, the <code>_sk_load_resident_keys</code> routine is called, which returns the public keys of all resident keys on the FIDO-style authenticator. Those are stored in the SSH agent (alongside their corresponding provider, namely <code>ssh-keychain.dylib</code>) and will later be offered as candidates to any remote SSH server we try connecting to. (Also now that the SSH agent knows about the Secure Enclave identity we created earlier, we can view its SSH-style public key with <code>ssh-add -L</code>.)</p>\n<p>Let’s say an SSH connection is initiated: the remote accepts one of those public keys offered by the agent and “challenges” the client to prove its identity. The agent calls <code>_sk_sign</code> to create a signature with the private key (matching the accepted public key) and the challenge (roughly speaking). This will be forwarded to the Secure Enclave, prompt Touch ID, and if approved, will return a signature for the challenge. The signature is forwarded back to the server, which can confirm we — the client — have the corresponding private key.</p>\n<p>Let’s see what this looks like in practice:</p>\n<pre><code>$ ssh -T [email protected]\n[email protected]: Permission denied (publickey).\n$ ssh-add -K -S /usr/lib/ssh-keychain.dylib\nEnter PIN for authenticator:\nResident identity added: ...\n$ ssh -vvv -T [email protected]\ndebug1: get_agent_identities: agent returned 1 keys\ndebug1: Offering public key: ssh: ECDSA-SK SHA256:QZ+Cx6/AkS9sek9INtsXt6t7wuHOtA555CLJu8a8V7w authenticator agent\ndebug1: Server accepts key: ssh: ECDSA-SK SHA256:QZ+Cx6/AkS9sek9INtsXt6t7wuHOtA555CLJu8a8V7w authenticator agent\ndebug3: sign_and_send_pubkey: signing using [email protected] SHA256:QZ+Cx6/AkS9sek9INtsXt6t7wuHOtA555CLJu8a8V7w\n&lt;at this point, Touch ID prompt appears&gt;\nAuthenticated to github.com ([20.250.119.67]:22) using &quot;publickey&quot;.\nHi nmattia! You&#39;ve successfully authenticated, but GitHub does not provide shell access.</code></pre>\n<p>The <code>ssh</code> debug logs show that one key is offered by the agent, accepted, and then used to create a signature. After the server has verified the signature, authentication is granted (although <code>GitHub does not provide shell access</code>).</p>\n<p>One note: whenever we register the identity with the SSH agent, <code>ssh-add</code> prompts us for a PIN. What gives? Why does entering an empty PIN work, isn’t this mighty unsafe‽</p>\n<p>Well, no, it’s not unsafe! When we tell <code>ssh-add</code> to use a FIDO-style authenticator, I suspect its default behavior is to ask for a PIN as it assumes we’ll be using a USB, YubiKey-style authenticator; only we shortcut the whole thing with <code>ssh-keychain.dylib</code> (and the security comes not from a PIN, but from the soft skin under your fingertips; much cooler). Not great in terms of usability, but <em>I guess</em> it makes sense. (I have no idea what would happen if you have more than one FIDO authenticator; go <a href=\"https://github.com/sponsors/nmattia\" rel=\"nofollow ugc noopener\">support me on GitHub</a> and I’ll buy a collection of YubiKeys to experiment).</p>\n<h2 id=\"conclusion\">Conclusion</h2>\n<p>Let’s recap: we can use a somewhat cryptic, poorly documented “Smart Card” tool to create a “CryptoTokenKit” identity on the “Secure Enclave”. Then, by using the poorly documented <code>ssh-keychain.dylib</code>, we can use said identity to generate signatures that Apple’s <code>ssh</code> can use. There’s no guarantee that all this missing documentation wouldn’t say “Do not use this; terribly unsafe in practice”, but hey: all this security talk by Apple, they wouldn’t lie to us, right? <strong>spooky</strong></p>\n<p>Joke aside, it’s pretty cool to have sci-fi style security modules baked into most modern devices; the more people start using them and pushing for openness, the better the support from the companies will be. We can have nice things!</p>\n<p>Again, a lot of this was derived from <a href=\"https://gist.github.com/arianvp/5f59f1783e3eaf1a2d4cd8e952bb4acf\" rel=\"nofollow ugc noopener\">this gist</a>; highly recommend having a read and potentially asking questions in the discussion.</p>\n<p><em><a href=\"https://bsky.app/profile/nmattia.bsky.social\" rel=\"nofollow ugc noopener\">Let me know</a></em> if you enjoyed this article! You can also <a href=\"https://buttondown.com/nmattia\" rel=\"nofollow ugc noopener\">subscribe</a> to receive updates.</p>","headings":[{"level":1,"text":"Setting up SSH keys on macOS with Touch ID","id":"setting-up-ssh-keys-on-macos-with-touch-id"},{"level":2,"text":"Quick Setup","id":"quick-setup"},{"level":3,"text":"Step 1: create the identity that you’ll use on this Mac:","id":"step-1-create-the-identity-that-you-ll-use-on-this-mac"},{"level":3,"text":"Step 2: tell SSH to use the identity (when prompted for a PIN, just hit enter):","id":"step-2-tell-ssh-to-use-the-identity-when-prompted-for-a-pin-just"},{"level":3,"text":"Step 3: Whenever you need to register your key to a new server (like GitHub), run this command:","id":"step-3-whenever-you-need-to-register-your-key-to-a-new-server-li"},{"level":2,"text":"Under the hood","id":"under-the-hood"},{"level":3,"text":"Creating an identity on the Secure Enclave","id":"creating-an-identity-on-the-secure-enclave"},{"level":3,"text":"Translating between SSH and the Secure Enclave","id":"translating-between-ssh-and-the-secure-enclave"},{"level":3,"text":"SSH Sign-in flow with the ssh-keychain provider","id":"ssh-sign-in-flow-with-the-ssh-keychain-provider"},{"level":2,"text":"Conclusion","id":"conclusion"}]}}