Mic92 / ssh-to-age

Convert SSH Ed25519 keys to age keys. This is useful for usage in sops-nix and sops
MIT License
97 stars 5 forks source link

Add support for sk- (hardware-backed) ed25519 keys #35

Open anoadragon453 opened 1 year ago

anoadragon453 commented 1 year ago

OpenSSH 8.2 or higher supports hardware-backed variants of ed25519 and ecdsa SSH key types, otherwise known as "secure key" (sk) variants.

I have a Yubikey and use its FIDO2 support to store a sk-ed25519 SSH key on the device. It is not (supposedly) possible to get access to the private keys of this variant, but you can access the public key of course.

I use this key to authenticate to my servers, and I'd like to use it with sops-nix as well. However, it appears that ssh-to-age does not support the -sk variant of ed25519 keys, so I get an error:

$ ssh-to-age -i ~/.ssh/id_ed25519_sk_rk.pub
ssh-to-age: failed to convert 'sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIGVdcgCRUwCd83w5L+k5yhDHrLDF88GgDWdhvMqYAUiAAAAABHNzaDo= ssh:': failed to parse ssh public key: illegal base64 data at input byte 3

which I believe is due to the check for the "ssh-" prefix here:

https://github.com/Mic92/ssh-to-age/blob/3d775f6681c7b9d001200516ff20c5e5a3e94b58/convert.go#L99-L103

If we expand it to:

    if strings.HasPrefix(string(sshKey), "ssh-") || strings.HasPrefix(string(sshKey), "sk-ssh-") {
        pk, _, _, _, err = ssh.ParseAuthorizedKey(sshKey)
    } else {
        _, _, pk, _, _, err = ssh.ParseKnownHosts(sshKey)
    }

then we predictably get:

$ ssh-to-age -i ~/.ssh/id_ed25519_sk_rk.pub  
skipped key: got sk-ssh-ed25519@openssh.com key type, but only ed25519 keys are supported

due to:

https://github.com/Mic92/ssh-to-age/blob/3d775f6681c7b9d001200516ff20c5e5a3e94b58/convert.go#L107-L110

if we then expand that code to:

    // We only care about ed25519 (and the secure key variant)
    if pk.Type() != ssh.KeyAlgoED25519 && pk.Type() != ssh.KeyAlgoSKED25519 {
        return nil, fmt.Errorf("got %s key type, but %w", pk.Type(), UnsupportedKeyType)
    }

we then get:

$ ssh-to-age -i ~/.ssh/id_ed25519_sk_rk.pub
/nix/store/lngv3dl3yal64xripysv284781qrpxj9-ssh-to-age-1.1.1/bin/ssh-to-age: failed to convert 'sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIGVdcgCRUwCd83w5L+k5yhDHrLDF88GgDWdhvMqYAUiAAAAABHNzaDo= ssh:': BUG! public key does not implement ssh.CryptoPublicKey

which makes sense. ssh.skEd25519PublicKey does not implement ssh.CryptoPublicKey, while ssh.ed25519PublicKey does.

I'm wondering if it's still possible to get the elliptic curve points out of pk and convert it to an age key regardless? Or am I barking up the wrong tree and should go pester to go devs to implement ssh.CryptoPublicKey on ssh.skEd25519PublicKey? :)

Mic92 commented 1 year ago

The bigger issue here is that sops also needs to support the yubikey for encryption, which is currently not the case: https://github.com/getsops/sops/issues/1103 So yes we could add the conversion here but we would still need support in sops's age implementation to make actually use of it.

anoadragon453 commented 1 year ago

I see, and apologies, I've now found https://github.com/Mic92/ssh-to-age/issues/25, which I missed in an initial search of the repo, asking for the same functionality.

The bigger issue here is that sops also needs to support the yubikey for encryption

I'm confused though - the public key is entirely stored in a file on my computer (~/.ssh/id_ed25519_sk_rk.pub). The yubikey should not be required to be plugged in in order to encrypt content with that public key, is that right?

Or is the issue that the public key types don't match up? Why does the user in https://github.com/getsops/sops/issues/1103 have a age1yubikey type key, rather than just an age type?

Is it an additional problem that sops doesn't engage with the yubikey (or any agent?) in order to decrypt the content?

Or alternatively to all that, do you agree with the way forward laid out in https://github.com/Mic92/ssh-to-age/issues/25#issuecomment-1475282895, and we should just use a plugin based approach with sops instead? :)

Mic92 commented 1 year ago

Sops cannot decrypt because it would have to reach out for the yubikey, which is not implemented. I am not sure what you mean by a plugin based approach.

anoadragon453 commented 1 year ago

Right, that part makes sense to me. Thanks for responding.

I'll wait on that front first then. Feel free to leave this issue open in the meantime or close it if you like.

Mic92 commented 1 year ago

I'll keep it open because otherwise someone else will just come again with the same issue.