Representing SSH Keys

Much like actors have HTTP Signature keys, in my application people also have SSH keys. I’d like to represent those as AS2 objects. One option is to do that in the same way the HTTP Signature keys are represented. But it would be a little bit weird, because those keys are used by SSH implementations, not by OpenSSL and such, so there’s very little benefit and also maybe some annoyance in representing those keys in PEM format.

githu8 has API for viewing people’s SSH keys, and they’re simply represented as the key type and the base64-encoded key material. Similar to the content of the ~/.ssh/id_rsa.pub file (just excluding the key name/comment which should probably be private).

So I’m wondering, if I don’t use the publicKeyPem property, which property to use? I can use content but then, which mediaType to specify? Just application/octet-stream? Or maybe use some custom sshPublicKeyMaterial property?

For now I’m going for content and application/octet-stream`, and I’ll add a full example very soon. But wondering what people think and what would be the best way to represent those keys. The Security Vocabulary seems concerned with encryption and signing, so it’s not obvious how to use it for SSH. But also, I don’t mind adding some specific types and properties for this (I’m extending AP anyway).

I’m not sure.
How about using application/jwk+json
and representing them as a JSON Web Key –
Online Demo
RFC
?

@Sebastian, interesting idea! What are the benefits of taking that route? Basically I’m seeing 2 directions:

  1. Use the format most comfortable for the specific software that uses the data
  2. Use the most general-purpose standard format

HTTP Signature keys on the Fediverse are sort of taking both at the same time? That PEM fornat is standard, but it’s also the format OpenSSL uses, so implementations can pass those keys straight to OpenSSL and verify the input data.

Some options for SSH keys:

Option 1, general formats:

  • Use the PEM format, with the Security Vocabulary (like HTTP Signature keys)
  • Use the standard SSH key format, which looks like PEM but defined in a separate RFC
  • Use JWK mentioned above

Option 2, use what the software expects:

  • Use the OpenSSH public key format (this is also what the githu8 api seems to use)

Feedback and thoughts from everyone are welcome :slight_smile: I’ll be examining the what-the-software-expects side

I would actually prefer to see all keys represented as JWKs. I believe publicKeyPem to be a mistake in the design of the Security Vocabulary.

1 Like

@kaniini, can you explain why you prefer JWKs and consider publicKeyPem a mistake? I’m fine with JWKs, just wanting to understand the arguments for/against each suggestion made here :slight_smile:

Btw technical question: How to put the JWK in the AS2 document? Do JWKs have some sort of JSON-LD version/context? Or do we just place them as embedded JSON, i.e. as a string value in the JSON-LD document?

Much like actors have HTTP Signature keys, in my application people also have SSH keys.

I’d like to question the underlying assumption.

Actors having HTTP Signature keys is essential for (Fediverse) AP federation, because in the Fediverse you must sign your requests.

SSH keys, from my understanding, are not in this area. You don’t sign requests with them. Standard AP software won’t need to care about them.

Thinking about ForgeFed, I think even there the SSH keys play an minor role: They are mainly used by the user’s instance for authenticating for SSH push and are not essential to federation.

So, because AP does not need to work with the keys, I would go with option 2, declaring them as ForgeFed data, putting them into an userSshPublic property using (OpenSSH?) default ssh-rsa AAAA... format, specified by the ForgeFed spec.

@criztovyl, I didn’t say they’re for AP, I said “in my application”. And indeed I’m asking for ForgeFed :slight_smile:

SSH keys actually will need to be federated! It’s required for remote collaboration. If you and I are users on different servers, and you add me as a collaborator with push access to your repo, your server will need to have my SSH keys to authenticate my pushes. So, we do need a standard representation for them.

Does that change your answer? ^ _ ^

Hmm, for me remote collaboration does not fall into the (AP-)federation part of ForgeFed, that’s my reason for stating them not being essential there; details below.

Regarding the standard representation, my answer stays the same.

Due to not being essential for AP federation (i.e. the passing around of messages via in- and outboxes), (ForgeFed-)AP can treat the keys as opaque data.
Remote collaboration will happen (most likely) via programs that use (Open-)SSH, so the format best know to those programs (OpenSSH ssh-rsa AAAA...) looks best to me. :slight_smile:

PEM (and other ASN.1 encodings) makes sense in a traditional environment where there is no standard transport format for objects. in the web, we have JSON. JWK is basically a standard for mapping the properties of cryptographic keys onto JSON: the components of the key are human readable. I think using PEM inside JSON-LD documents is a bad idea because it isn’t transparent. You’re stuffing a container inside another container, basically.

Any JSON object is also a valid JSON-LD object, it just maps to the “default” namespace. There may be value in creating a JSON-LD vocabulary for JWK keys, however, which would be compatible with non-LD JWKs. At any rate they should not be represented as an opaque blob of any kind, you lose the transparency benefit as you do with publicKeyPem.

1 Like

While I agree to @kaniini and personally just use JWK I’d love to know what @cwebber thinks about it because just so that this unrelated issue does not “go nowhere” :wink:

@kaniini, thank you for explaining in detail! The JWK RFC refers to a parameter registry, and looks like such a registry exists indeed and we could define and publish a @context that assigns URIs to all those parameters. Hm anyone thinks we should talk to whoever is speccing JWK/JSON-LD about this? Or just host that context document here somewhere?

criztovyl, hmmm idk about that. I mean, yes, the OpenSSH key format is what most servers already use and take as user input. What I’m unsure about is the statement that just because SSH keys are used in the SSH protocol and not in AP, they’re opaque data. My thoughts about this:

  1. ForgeFed is about forge federation. ActivityPub is a tool being used for that. But forges typically involve other things: Interaction via email, VCS push and pull via HTTP(S). via SSH and via custom protocols such as the git:// protocol… so forge federation isn’t defined by what flows through AP inboxes and outboxes: Everything that participates in federating forge functionality is relevant. And SSH keys do. Even if the integration behavior between AP and VCSs won’t be a part of the core specs, and will be in a separate extra spec etc., the SSH keys are still part of the vocabulary.
  2. I’m not aware of anything else on the Fediverse that uses or plans to use SSH keys, but that could change. Imagine SSH keys started being used for shell access, or by SSH servers that doesn’t use the OpenSSH format. So we may have more people and servers wanting to represent them in AP. Perhaps it’s best to choose now a good longish-term general representation. That may still be the OpenSSH format; I’m just suggesting that those SSH keys matter as a core part of forges, even if they’re used outside of AP message passing.

Summary so far: Nobody is arguing for the PEM format or for the standard SSH2 key format (which looks very much like PEM). The options suggested are:

  • OpenSSH format
  • JWK

Personally, I’m leaning towards JWKs, because they’re general-purpose and readable as-is in the AP JSON document, and not tied to OpenSSH. githu8 may be using OpenSSH (idk if they do), GitLab/Gitea/etc. may be using OpenSSH (IIRC they both do), but this could change (OpenSSH is an SSH implementation, not the only implementation), and the OpenSSH format may become much less useful. Also, in a federated situation we have have many ForgeFed server implementations, using different SSH implementations. For example, Vervis (which I’m developing) currently uses an SSH implementation written in Haskell. I could probably just pass the keys as structured data extracted from JWKs and skip the whole parse-OpenSSH-key-format step.

Side note: The format OpenSSH is using is the same as the actual serialization in the SSH protocol, so it’s defined in the SSH specs, but the SSH specs don’t require any storage format AFAIK, so the choice is arbitrary.

Does anyone have more arguments for using the OpenSSH format? criztovyl, thoughts?

Another question, which property name to use? Some ideas:

  • sshKey (con: doesn’t state that it’s public, pro: private keys aren’t published anyway, although yes they could be shared internally by server components for whatever reason)
  • publicSshKey (con: longer and stating the obvious, pro: explicitly says the key is public)

@criztovyl, Discourse won’t let me mention more than 1 user, so mentioning you in a separate comment :stuck_out_tongue:

Format

I think I am focusing too much on the “SSH” from the “Representing SSH keys”, that’s why I was in favor of the OpenSSH format.

But, more generally, JWK seems way better.
I think my initial opposition came due to me having misread JWK as JWT and thought that we’re talking about ssh-rsa AAAA vs. abcabc.defdefdef.ghighighi.
That was pretty stupid from my side. (I mean, keys != tokens, that should have rang some bells…)

Anyway, now that’s ssh-rsa AAA vs {"kty": "RSA", "n": "...", "e": "..."}, I am in favor of JWK :smiley:

Property name

Regarding whether to include public or not: JWK has additional properties for the private part of the key, so that way the content decides whether the key is public or private and we don’t need to include it in the name.

But I am unsure about sshKey. Less due to the name and rather due to violation of 0-1-∞ “rule”.
Currently there is publicKey for the user’s http signature key.
Then we add sshKey. And maybe later gpgKey. And fooKey. And barKey?
Hmm, not that confident about that.

I’d rather add an keys property that contains all relevant keys, qualified by JWT use (and key_ops?).
The http signature key could get sig. For SSH one could add auth. GPG fit’s into sig, too.

Ok, going for JWKs :slight_smile:

@kaniini, @Sebastian , looking for your feedback here too :slight_smile:

I’d like to choose property names. Here are the open questions:

  1. Do we use an sshKey property, or just a generic key property and rely on JWK use and key_ops to determine which keys are the ones meant for SSH? Since we’re using linked data here, it’s easy to host the keys in separate documents, and just list their IDs in the actor document. I’m in favor of using an sshKey property in that way, and it’s totally possible to also, additionally, provide JWK key_ops and a generic keys property. The standard JWK values aren’t specific enough anyway. Is anyone in favor of using only a generic key property (and potentially eventually deprecating the commonly used publicKey) and relying only on JWK key_ops to determine which keys are for SSH, which are for HTTPSigs, etc.?
  2. sshKey vs publicSshKey: indeed the JWK fields can tell you whether it’s public or private, but it’s more involved than just looking at the property name, not having to understand those key-algorithm-specific parameters. However, obviously, it’s possible for publicSshKey to map to a JWK that actually contains a private key. I’m going to go for sshKey, anyone in favor of publicSshKey?
  3. Should the JWK be the top-level object, i.e. the @id and owner properties etc. are inside the JWK object itself, or should it be like with publicKey, i.e. there’s an AS2 object with some sshKeyJWK property that maps to the JWK itself? The JWK spec allows custom parameters and requires that parameters that aren’t understood are ignored, so technically we could put RDF/AP/JSON-LD properties in there. I’m in favor of keeping the JWK clean, avoiding potential trouble with JWK and AP property name confusions, and just having the JWK as its own object. Anyone in favor of putting the JWK parameters and the AP properties together in the same JSON object?
  4. If the JWK gets to be a separate object, which property name to use for it? Similar to publicKeyPEM, one option is to call it sshKeyJWK. Or keyJWK. Or jwk. Also, material, keyMaterial, keyMaterialJwk. Or something like that. Another option: JWKs have a media type, application/jwk+json. So we could hypothetically simply use the content property for this, and set mediaType to "application/jwk+json". However, the AS2 vocabulary spec says that content's range is a string, which means it can’t be an object. Hmmm ideas?

I think using the key_ops + keys properties is the way to go, but I think we need to flesh it out a bit.

As for the key itself, what we should have is perhaps something like publicKey.materialJWK or similar, but I think it makes sense to involve the security vocabulary folks at W3C before we just jump into that. I could host an extension the litepub registry in the worst case scenario that they don’t want to cooperate – that’s the entire point of the litepub registry after all.

Also, while I am here, I would like to point out that both publicKeyPEM and SSH key payloads are in fact PEM. PEM is just an encoding method for ASN.1 structures, called Portable Encoding Method. The only thing different between publicKeyPEM and the SSH key PEM files are that the ASN.1 structures encoded are slightly different. So really, publicKeyPEM should be subjectPublicKeyInfoPEM or similar, to reference the specific ASN.1 structure being encoded.

If we go with determining which key is for what using JWK use, I think all keys should be included in the main document so the client does not need to fetch key by key until it finds the right one.
While separating the whole keys object should be less of a problem, I think profiles might be fetched mainly for http signature verification, so we would save clients an additional request.
I also think that actor should be consumeable without knowing a thing about JSON-LD, which also opposes separating the keys.

Are you refering to use only having encrypt and sign?
I think trying to register a auth here is a better way than just for this reason going down the road of adding (possibly) many additional fooKey attributes.
(Until it’s official we can use an temporay vnd.forgefed.auth, allowed by the JWK spec.)

Implementations MUST (per spec) ignore other properties. If a implementation get’s confused by additional keys, it’s broken. And I don’t think building for broken things is any good.

The JWK key set already having a keys property also makes we rather wanting to combine actor document and key set.
Mainly because I cannot make out a good name for the outer property without making it look ugly. Naming the outer property keys would lead to redundant property names (and looks ugly). Naming it jwk is a bad name because, well, you would not put forgefed properties into an forgefed sub-object either, looks like bad design to me.

{
  "@context": { /* ... */ }
  "id": "https://example.com/foo"
  "type": "Person"
  // ...
  "keys": {
    "main": {
      "kty": "RSA",
      "use": "sign",
      "n": "...",
      "e": "...."
    },
    "ssh1": {
      "kty": "RSA",
      "use": "auth",
      "n": "...",
      "e": "...."
    },
    "ssh2": {
      "kty": "RSA",
      "use": "auth",
      "n": "...",
      "e": "...."
    },
    "gpg": {
      "kty": "RSA",
      "use": "sign",
      "n": "...",
      "e": "...."
    }
  }
  // ...
}

While the keys have speaking names in my example, I don’t think they should be relied on.

For http signatures, IIRC the key that signed the request is given, looking at the JoinMaston’s How to implement a basic ActivityPub server this is like Signature: keyId="https://my-example.com/actor#main-key",headers=....
For our example this could be Signature: keyId="https://example.com/foo#main",headers=... (Although this will need communication that keys are now in keys and not in publicKey.)
And if you want, this would also allow C2S using your GPG key. ^^

For SSH the situation is a little bit different.
Speaking in the ForgeFed context, I think basically the remote forge should just allow access using any key that can be used for authentication.
In our example, all keys match, in the end it’s all just asymmetric crypto.
While there is "use": "auth", to me this just disqualifies the key to be used for signature and encryption. (Because you might not protect your ssh keys as good as your gpg key, although you should.)

So far. ^^

i don’t think having a keys object in that style makes sense.

what i had in mind was an array of key objects, with a name property. this fits into the JSON-LD design principle.

What about using JSON-LD index maps? 8.6 Index Maps / 6.16 Data Indexing
If we go with the JSON-LD 1.1 Draft(?), one could also use ID Maps.

An ID map the would look like below.

{
  "@context": { /* ... */ }
  "id": "https://example.com/foo"
  "type": "Person"
  // ...
  "keys": {
    "https://example.com/foo#main": {
      "kty": "RSA",
      "use": "sign",
      "n": "...",
      "e": "...."
    },
    "https://example.com/foo#ssh1": { // Edit: This means SSH key #1, not SSHv1 key.
      "kty": "RSA",
      "use": "auth",
      "n": "...",
      "e": "...."
    },
    "https://example.com/foo#ssh2": { // Edit: This means SSH key #2, not SSHv2 key.
      "kty": "RSA",
      "use": "auth",
      "n": "...",
      "e": "...."
    },
    "https://example.com/foo#gpg": {
      "kty": "RSA",
      "use": "sign",
      "n": "...",
      "e": "...."
    }
  }
  // ...
}

If we cannot use 1.1, then we could try slightly change the current publicKey approach.
Like, the keyId as show in the Mastodon example is not a valid JSON-LD id (it is not correctly dereferenceable). It seems like the key id is the one in the URL hash, which then is “extracted” from the publicKey attribute.
One could instead search for the key using it’s key id (i.e. just the part after in the has) in the keys map.
But it is possible that I misunderstood how the keyId for the HTTP Signatures in the Fediverse work.

I think we should keep it simple and just keep it as an array. What if somebody has multiple SSH2 keys?