Bugs in ssh-ed25519 recipient line specification

203 views
Skip to first unread message

Jack Grigg

unread,
Dec 18, 2019, 10:57:03 AM12/18/19
to age-dev
Hi all,

I've been working on extending my interoperability tests between age and rage, which you can find here:


In doing so, I have uncovered two bugs in the specification for ssh-ed25519 recipient lines. Quoting the specification:

An ssh-ed25519 recipient line is

-> ssh-ed25519 encode(SHA-256(SSH key)[:4]) rest

where SSH key is the binary encoding of the SSH public key from draft-ietf-curdle-ssh-ed25519-ed448-08, and rest are the same arguments and payload as an X25519 recipient key.

The public key for a ssh-ed25519 recipient is X25519(tweak, converted key)
where tweak is HKDF[SSH key, "age-tool.com ssh-ed25519"]("")
and converted key is the Ed25519 public key converted to the Montgomery curve.

On the receiving side, the recipient needs to apply X25519 with both the Ed25519 private scalar SHA-512(private key)[:32] and with tweak.

Note that the definition of "rest" imports key derivation for encrypting the file key from the X25519 recipient line definition:

An X25519 recipient line is

-> X25519 encode(X25519(ephemeral secret, basepoint))

encode(encrypt[HKDF[salt, label](X25519(ephemeral secret, public key))](file key))

where ephemeral secret is random(32) and MUST be new for every new file key,
salt is X25519(ephemeral secret, basepoint) || public key,
and label is "age-tool.com X25519".

I interpret this to mean that it is imported exactly as specified. The two bugs this results in are:
  1. I used the label "age-tool.com X25519" in the HKDF label:
    https://github.com/str4d/rage/blob/58525fbf0a4e009c4d849557ccdc2cb226e8419f/src/format/ssh_ed25519.rs#L50-L54

    However, the age implementation instead reuses the label that is implicitly defined inside the ssh-ed25519 tweak:
    https://github.com/FiloSottile/age/blob/8c600131ec47c0ed7f6157bfa0bb6e8a0bc54020/internal/age/ssh.go#L236

  2. The ssh-ed25519 spec defines "public key" as the tweaked "converted key", and I used this as the "public key" in the HKDF salt:
    https://github.com/str4d/rage/blob/58525fbf0a4e009c4d849557ccdc2cb226e8419f/src/format/ssh_ed25519.rs#L48

    However, the age implementation instead uses the "converted key" directly as the "public key":
    https://github.com/FiloSottile/age/blob/8c600131ec47c0ed7f6157bfa0bb6e8a0bc54020/internal/age/ssh.go#L235
I believe is a mismatch between "what is written" vs "what is intended." As far as resolving the bugs:
  1. I think that the age implementation is correct here, and we should use the same label throughout ssh-ed25519 key wrapping. This is therefore a specification bug.
  2. I think that my reading of the spec is correct here, and we should use the tweaked "converted key" as the "public key" in the HKDF salt. This would not be a specification bug, but it would still be helpful to update the specification to clarify this.
Cheers,
str4d
Reply all
Reply to author
Forward
0 new messages