New PasskeySecretKit Package for Passkey-Derived SSH Keys
Hello Max,
First of all, thank you for creating and maintaining Secretive — it's an incredibly well-designed and secure tool for managing SSH keys. I really appreciate your approach of leveraging the Secure Enclave for protecting private keys, and the app's clean, intuitive UI makes it a pleasure to use.
I'd like to propose a new capability for Secretive: support for Passkey-based SSH keypair creation and authentication. This approach leverages the most modern and secure passwordless authentication method — Passkeys, to eliminate the need for storing SSH private keys locally. I've developed a new package, PasskeySecretKit, designed to integrate cleanly into the existing Secretive architecture.
If you're open to exploring this further, I'd be more than happy to share more implementation details, walk through the design, and contribute this as a PR!
Looking forward to hearing your thoughts!
Best, Minghua
https://github.com/user-attachments/assets/8e8660f1-c15c-4294-916f-655fed65434b
Hm, this is an interesting idea – I'm not positive it's the right fit for the security model of this project, but I'll admit I'm definitely curious to see what your implementation looks like.
Main theoretical concerns:
- As much as I love passkeys (and I really, really do), the security benefits here are, in my mind, almost identical to a non-SEP backed keychain store – in both situations, the keys are tied to the bundle ID and not trivially extractable from disk, but are ultimately extractable.
- Using a passkey requires (as you mentioned) binding to a domain. This gets a little tricky – you could bind to some private local URL, but that's a little weird. Alternately, you could attempt to bind to a specific domain (ie, bind a GitHub signing key to GitHub.com) but I suspect the passkey API might have a problem with that both at an enforcement level, and in terms of offering to try and use that passkey in the browser to sign in.
I share @yminghua's concern about losing all of the private keys when I upgrade to new hardware. For some users, some of the time, it is likely worth the security tradeoff.
But instead of Passkeys, might it be easier to add a setting to Secretive in which the keys are marked kSecAttrSynchronizable instead of kSecAttrTokenIDSecureEnclave? What are Passkeys providing that the non-local iCloud keychain can't provide?
@aschw
I share @yminghua's concern about losing all of the private keys when I upgrade to new hardware. For some users, some of the time, it is likely worth the security tradeoff.
To be perfectly honest, I'm a little bit of a split mind on this. I think in terms of (for example) passkey design. this is.... probably the correct tradeoff, for most people, for most applications. I'm more skeptical in terms of something like Secretive though, since it's sort of intended explicitly for more high-security applications. SSH keys are also somewhat unique in that virtually all applications allow you to have multiple ones of them enrolled, and generally speaking, are enrolled in a manner that is resettable by other means (eg, you log into GitHub with your password/passkey, and can replace any SSH keys you need).
On the other hand, I will point out that the manner most people use passkeys in is also the way that most people store login credentials, ie, iCloud Keychain. Should some sort of catastrophic bug happen to your iCloud keychain, you're very likely locked out of your SSH access and your means to reset it – both direct login and possibly even email.
But instead of Passkeys, might it be easier to add a setting to Secretive in which the keys are marked kSecAttrSynchronizable instead of kSecAttrTokenIDSecureEnclave? What are Passkeys providing that the non-local iCloud keychain can't provide?
This part I actually have somewhat strong feelings about. One of Secretive's sort of key design principles can be roughly boiled down to:
"It's much harder to have a security bug that leaks a key if you don't ever have the ability to access raw key material in the first place."
The way all current keys are treated in Secretive, the app can't access key material even if it wants to. Even if the application somehow is highjacked, sort of a catastrophic compromise of the security model of the SEP itself, the fallout is limited to what the SEP will allow the app to do: perform operation with the keys, but not leak the key material itself.
Changing keys to be treated as synchronizable changes this – we'd still have some security guarantees provided by the Keychain – that keys will only be accessed by an app with rights to it, as defined by its code signature and entitlements, but those guarantees are much less strong.
Overall: I do think there's definitely theoretical benefits of using a Passkey over a synchronized key, for sure. I'm not positive that Passkeys make sense as displayed here in the context of Secretive. I do wonder if there's maybe some sort of middle-ground that's useful in the -sk family of SSH key types though... The idea is that a FIDO key (such as YubiKey, or more recently, Passkeys as implemented in iCloud) can act as an SSH key. I'm not aware of any current implementations of this using Apple Passkeys, but I might poke around there a little more and think about it.
I don't think the keys are actually extractable when they are marked kSecAttrTokenIDSecureEnclave but they otherwise behave the same, so I'm not sure what the point of downgrading the security would be :) All key transports are wrapped to a Secure Enclave of a target device and imported into it, it's not a soft token per se, so unless you can pair a new device that somehow "fakes" attestation of Secure Enclave and you follow the prompts on your other device and add that device (or supply the PIN code for the other device, I'm not sure what the workflow is), then the key remains just in Apple Cloud Keyvault (HSM fleet) wrapped to... something :)... and in your other devices that synchronize Passkeys. In theory, not even Apple can just extract the key without your cooperation because they'd have to modify and resign their HSM firmware, which is supposedly impossible.
As for the feature, this is exactly what I am looking for!!! I posted about something like this on OpenSSH mailing list, sadly without any reply... https://lists.mindrot.org/pipermail/openssh-unix-dev/2024-July/041451.html
Please, please, please, implement this ♥️
Btw if possible make it an option to present the key either as a normal pubkey or a -sk variant or maybe both. There are still many RHEL7 and other outdated (or commercial enterprise = $$$+outdated) servers that just don't support -sk keys :(
I don't think the keys are actually extractable when they are marked kSecAttrTokenIDSecureEnclave
That's correct but not applicable to passkeys (their public interface isn't through keychain)
All key transports are wrapped to a Secure Enclave of a target device and imported into it, it's not a soft token per se, so unless you can pair a new device that somehow "fakes" attestation of Secure Enclave and you follow the prompts on your other device and add that device (or supply the PIN code for the other device, I'm not sure what the workflow is), then the key remains just in Apple Cloud Keyvault (HSM fleet) wrapped to... something :)... and in your other devices that synchronize Passkeys. In theory, not even Apple can just extract the key without your cooperation because they'd have to modify and resign their HSM firmware, which is supposedly impossible.
To my knowledge (I could definitely be wrong!) some of this is incorrect.
Best I'm aware, the passkey is basically just (internally) stored as a keychain item, with the same e2ee as any other iCloud Keychain item (which is what you're describing with "supply the PIN code for the other device" - that workflow is described here: https://support.apple.com/guide/security/escrow-security-for-icloud-keychain-sec3e341e75d/web). It's a good system, but to the best of my knowledge it doesn't interact with the SEP and is ultimately an extractable item once it reaches your device. Like I said, definitely possible I'm wrong here, If you've got docs to the contrary there, link me!
https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.u2f#L222
Doing a little evening reading and I did stumble on this – it does somewhat look like this was kinda anticipated to be added at the vendor level.
What exatly do you mean by that? I used FIDO tokens for SSH for a while, but there are several reasons why it's not useful to implement only the -sk variant of the key.
- not all servers support that (though I don't think there's any new cryptography from the server's point of view).
- it's more useful for "real" FIDO keys, ones that don't exactly work like smartcards to sign arbitrary stuff. FIDO protocol is more complex as to be less phishable (origin constraints) and is meant to have different key for each relying party (with different presence or PIN requirements) not sure SSH protocol benefits from any of that.
Those docs are more of a technical background on how FIDO/WebauthN was "coerced" for SSH use. Instad of HTTP origin that varies with the remote server (which would require more than a single enrollment), the origin here is your SSH client, I don't think the cryptographical guarantees that exist in WebauthN are there in SSH even if defined constraints in via ssh-keygen. In web browser use, the browser must check the origin server and is responsible for checking the identity (e.g. via HTTPS certs), I'm not exactly sure if any keys are derived from this workflow, I don't think so, sadly. Server is the one that created this enrollment and constraints, it can even hold keys (by default). SSH client is in the same position, except the server still only checks a nonce signature by a pubkey.
~~So I don't think -sk is worth implementing unless it's somehow "simpler", but I can only imagine it being "simpler" if it were incorporated into OpenSSH or other SSH client directly. Secretive is not in a position to impersonate this WebauthN-like workflow unless it's done the same way OpenSSH did it, which in effect would mean faking it twice - once in SSH client to look like origin server to the FIDO API, then once more in Secretive to expose this FIDO API but backing it up by another FIDO API faking the origin the same way~~
Actually, Secretive could probably just "proxy" FIDO API to the platform FIDO API without touching anything and provide -sk key backing, but the non-sk key variant would mean implementing the origin behaviour like OpenSSH does.
Probably the biggest added benefit I can see in Secretive doing some real FIDO integration could be attestations when enrolling the key (for a long time there was no tooling for verification, recent OpenSSH versions implement something but I haven't tried it yet), maybe defining constraints in a useful way (not sure the protocol allows that, but I still haven't looked into what hostbound keys are etc...)
Anyway, just having a credential that can be synced accross devices would be a huge benefit, and some iOS clients (Termius, Blink?) support HW FIDO tokens but not Passkeys (yes, I know it's all confusingly called Passkey now... :)), so having a wrapper layer they could reuse would make it so much more useful...
What exatly do you mean by that? I used FIDO tokens for SSH for a while, but there are several reasons why it's not useful to implement only the -sk variant of the key.
Wasn't really related to that – I don't have any strong feelings in terms of presentation of -sk or not tbh.
Probably the biggest added benefit I can see in Secretive doing some real FIDO integration could be attestations when enrolling the key (for a long time there was no tooling for verification, recent OpenSSH versions implement something but I haven't tried it yet), maybe defining constraints in a useful way (not sure the protocol allows that, but I still haven't looked into what hostbound keys are etc...)
The reason I actually saw that doc is I'd been wondering if attestation was possible to provide in SSH keys actually.
Attestation is supported, you could generate it years ago, but you couldn't easily verify it. OpenSSH 10.0 ships with a new tool for that (haven't tried it). Shockingly, there was nothing to even buy (we tried and it was a bank), all commercial tooling revolved around SSH X509 certificates and vendors were cluless. Attestation documentation was pretty obscure unless you dealt with WebauthN low level stuff previously so we scrambled and just barely put a PoC out, though it was never used and instead we opted for short-lived certs and SSO. Later I encountered 2 projects trying to do the same, one simply asserted that "sk-*" keys are hardware backed (which is not true) and the other didn't use the "challenge=path" option nor did it verify that the asserted public key and the public key being presented are the same, so you could generate attestation but get a soft token signed (sigh). Vendors really are clueless. I wonder if the PKI ecosystem is any better... Btw Yubikeys can also attest PIV keys quite easily out of the box, most smartcards need pricey middleware and lots of "implementation" mandays to give you that.
For serious use, Secretive could provide attestation data to remote server hat would in turn provide a certificate (for all key types most likely), there are APIs for that, it's also possible to use ACME and get a x509 cert since maybe year ago? Could save lots of people a lot of money and get them out of vendor locks...
Btw I looked into hostbound hostkeys, and wow, did I miss a lot. That would be a pretty cool feature to have in Secretive and immediatelly useful (to me :)). One could have a key with confirmation that gets forwarded by default and is usable everywhere, and another key with just notification (usable for ansible mass automation stuff) with constraints on forwarding without dealing with different key enrollments for different machines.
Sorry for turning this into a brainstorming session.
@zviratko I had been thinking about attestation in the context from a request from a few years back, and I looked and it was you ;) – let's move this convo back to #383
Btw I looked into hostbound hostkeys, and wow, did I miss a lot yep this is on my radar, there's some of the foundations for this work in 3.0 (some more coverage of the overall SSH agent protocol)
FWIW iCloud Keychain passkeys don't provide any attestation info. They only provide them when your device is enrolled in MDM and even then the passkey attestation doesn't tell anything about hardware-backedness it just proves that the passkey is stored in an icloud keychain of a managed apple ID.