New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Linked Data Signatures + public key URI #203

Closed
jaywink opened this Issue Apr 23, 2017 · 15 comments

Comments

Projects
None yet
4 participants
@jaywink
Copy link

jaywink commented Apr 23, 2017

9.2 says to include a "link to the public key" in the actors profile object, but nowhere is it mentioned what name this should have in the profile object, AFAICT.

When Linked Data Signatures is used in combination with ActivityPub, the server should assign an actor a public and private key pair, and link the public key to the actor's profile object, which may later be used with the Linked Data Signatures verification algorithm to validate the authenticity of messages passed along the network.

The paragraph links to the profile section which has provideClientKey and signClientKey attributes related to Linked Data Signatures + HTTP signatures. Neither of them however sounds like it should contain an URI to the public key.

Also, why a "link to the public key"? Why not just place the public key in the profile object itself? What should be behind the link - the public key itself as text or some document containing the public key?

Suggestion: Add to "4.1 Actor objects" something like the following:

publicKey

If Linked Data Signatures and HTTP Signatures are being used for authentication and authorization, this property should contain the public key of the actor.

I'm trying to wrap my head around how to do the signing part compared to for example Diaspora, so expect a few issues maybe ;)

@cwebber

This comment has been minimized.

Copy link
Collaborator

cwebber commented Apr 26, 2017

I think that's because in Linked Data Signatures, the key would actually be attached to the object itself?

Unfortunately it seems under-documented on the LDS spec itself, but we can hop straight to a Real Implementation (TM) to see what's expected. Reading from the reference library's README, it looks like it would look like:

{"@context": ["https://www.w3.org/ns/activitystreams",
              "https://w3id.org/security/v1"],
 "id": "https://schemers.example/u/alyssa",
 "type": "Person",
 "name": "Alyssa P. Hacker",
 "publicKey": [{
    "id": "https://schemers.example/u/alyssa#main-key",
    "owner": "https://schemers.example/u/alyssa",
    "publicKeyPem": "-----BEGIN PUBLIC KEY-----\r\n..."}]}

(You could also have the publicKey link to an object rather than embedding it.)
Does that make sense?

@cwebber

This comment has been minimized.

Copy link
Collaborator

cwebber commented Apr 26, 2017

Note that we might want to have the AS2 context reference the LDS context itself, or its terms, so you don't have to include both in the @context.

@msporny

This comment has been minimized.

Copy link
Member

msporny commented Apr 27, 2017

@cwebber @jaywink apologies for the poor documentation. We have a to do in the spec to define it, even though every library that exists today implements key retrieval and expression in the same way:

https://w3c-dvcg.github.io/ld-signatures/#issue-7

@cwebber figured it out. Here's a live example of how we're using LDS in the Verifiable Claims WG:

https://demo.authorization.io/dids/did:6aa2ef7b-bd61-41ea-913b-c87fcd96fec5

It looks very similar to what @cwebber expressed in his comment above. These are the important bits:

  1. You typically start out with a signed document and you get to the Entity Profile document (aka "Person") by checking the creator field in the signature, which is typically a URL that you can dereference.
  2. You dereference the creator URL and get an Entity Profile document (aka "Person"). That document MUST contain at least the public key ID in the list of public keys owned by the Entity Profile.
  3. The Entity Profile document (aka "Person") MUST be provided over a secure communications channel (such as TLS)... or be digitally signed in some way. If it's not signed, you're depending on TLS and the Certificate Authority system to ensure a valid document. If it's signed, you're depending on whatever key management system you're using to ensure a valid document. Doing both is recommended, but not required.
  4. The Entity Profile document (aka "Person") MUST reference the public key identifier and SHOULD include the public key PEM.
  5. The Key must point back to its owner.

It's vital that this bidirectional link is expressed in the data (person points to key id and key points to person id). Otherwise, you can spoof the key information. It's recommended that systems cache data (but not use unreasonably long cache times) and use key pinning for highly sensitive transactions.

@jaywink

This comment has been minimized.

Copy link

jaywink commented Apr 27, 2017

Awesome, thanks! Any possibility @cwebber we could mention how the publicKey is given in the Actor object in ActivityPub spec in a non-normative way? An alternative would be to write a little tutorial on how it can be done, to guide implementers down the same path.

@jaywink jaywink referenced this issue Apr 27, 2017

Closed

ActivityPub support #1557

10 of 15 tasks complete
@cwebber

This comment has been minimized.

Copy link
Collaborator

cwebber commented Apr 27, 2017

@jaywink Yes I think so... I'll work on some text!

@cwebber

This comment has been minimized.

Copy link
Collaborator

cwebber commented May 1, 2017

Since this is going to be informative text, I'm marking this as editorial.

@ghost

This comment has been minimized.

Copy link

ghost commented Jul 19, 2017

Just did a test integration of salmon magic envelopes w/json serialisation and activitypub. This is mostly informative and you are free to accept or reject. It's a workable alternative to the current lack of a solid specification when it comes to forgery and provenance.

Here's a person object and what is returned normally:

{
  "@context":[
    "https://www.w3.org/ns/activitystreams",
    {
      "me":"http://salmon-protocol.org/ns/magic-env"
    }
  ],
  "type":"Person",
  "id":"https://hz.macgirvin.com/channel/hubzilla",
  "name":"Hubzilla",
  "icon":[
    {
      "type":"Image",
      "mediaType":"image/jpeg",
      "url":"https://hz.macgirvin.com/photo/profile/l/2",
      "height":300,
      "width":300
    },
    {
      "type":"Image",
      "mediaType":"image/jpeg",
      "url":"https://hz.macgirvin.com/photo/profile/m/2",
      "height":80,
      "width":80
    },
    {
      "type":"Image",
      "mediaType":"image/jpeg",
      "url":"https://hz.macgirvin.com/photo/profile/l/2",
      "height":48,
      "width":48
    }
  ],
  "url":{
    "type":"Link",
    "mediaType":"text/html",
    "href":"https://hz.macgirvin.com/channel/hubzilla"
  },
  "inbox":"https://hz.macgirvin.com/inbox/hubzilla",
  "outbox":"https://hz.macgirvin.com/outbox/hubzilla",
  "me:magic_keys":[
    {
      "value":"RSA.ANfCQe-pcj-M8y_8uwQno3iZ32SFmTbTNzGbyNcxFTFppsDkvrv8h_KD8Mmg5OGP7Z4_BszD49ZQhlUXHfBCWjawjHedBzgwORLCbcabDs0XCC2IznE7SABtSuC7SqhC4vSAgGhadHzX2viX9HgnpLz7rMdvzkYcvqNt4RaEjIS_OqNoJmokZ5pwN1c-GTBvdoniRAzF7fSANRJCKkuMuQT2X1Tyw9ab_Ior4_nbTdV48KZYI1G7cZlNPxI_NPJx6abHESKsf1ydF2VPdKDRsP8GKCthMyZT20bAJMDaqBawGJa-Rz6Z-ASdnmxWEIFgVK2g-x8EuWrbEBGsRnzttZVD9PpQ4Pbdwl77nQ49FBtY4hCm1SBgPuGhtBjdg0guQMkavxkcasmsuVtr0GwGR8r8Txq2RTtuCOuYN6Y4aYxLC6meIpUy0KCzLNHawiHEZuUcTFKOLPyzINepU-HBJNakbZNl9Em9QDEENrkV4lbVs--0BzDjRGJH-jluEh28HL-bpr5b-vTWBBnwnG4KtAFe1OPwVs7VM_0lo-K9feAhuNyPSPHZmhMfQhQP1zHjyAF0FLgAX3tUDDkeiMS6akxxtaXmYB0Cnhs-Gkhq4UJGTTaDlTWmaCH3L6KcXBw02S4oLHhCGjz4WuFhWKCj4lD22eaPIEy3O-dLs6529o8H.AQAB",
      "key_id":"MjZjYmY3ZjI4ODhlZjIzYjRjNGQ4OTQ5MDViOWY5YzEzZjY5ZTBhZjhkMDJmODU2M2JkNDQ5OTBmYTM0MWQzMA"
    }
  ]
}

And AP entities are accessed with this:

Accept: application/magic-envelope+json, application/ld+json; profile="https://www.w3.org/ns/activitystreams"

Which results in conforming magic-envelope servers returning this:

{
  "data":"eyJAY29udGV4dCI6WyJodHRwczpcL1wvd3d3LnczLm9yZ1wvbnNcL2FjdGl2aXR5c3RyZWFtcyIseyJtZSI6Imh0dHA6XC9cL3NhbG1vbi1wcm90b2NvbC5vcmdcL25zXC9tYWdpYy1lbnYifV0sInR5cGUiOiJQZXJzb24iLCJpZCI6Imh0dHBzOlwvXC9oei5tYWNnaXJ2aW4uY29tXC9jaGFubmVsXC9odWJ6aWxsYSIsIm5hbWUiOiJIdWJ6aWxsYSIsImljb24iOlt7InR5cGUiOiJJbWFnZSIsIm1lZGlhVHlwZSI6ImltYWdlXC9qcGVnIiwidXJsIjoiaHR0cHM6XC9cL2h6Lm1hY2dpcnZpbi5jb21cL3Bob3RvXC9wcm9maWxlXC9sXC8yIiwiaGVpZ2h0IjozMDAsIndpZHRoIjozMDB9LHsidHlwZSI6IkltYWdlIiwibWVkaWFUeXBlIjoiaW1hZ2VcL2pwZWciLCJ1cmwiOiJodHRwczpcL1wvaHoubWFjZ2lydmluLmNvbVwvcGhvdG9cL3Byb2ZpbGVcL21cLzIiLCJoZWlnaHQiOjgwLCJ3aWR0aCI6ODB9LHsidHlwZSI6IkltYWdlIiwibWVkaWFUeXBlIjoiaW1hZ2VcL2pwZWciLCJ1cmwiOiJodHRwczpcL1wvaHoubWFjZ2lydmluLmNvbVwvcGhvdG9cL3Byb2ZpbGVcL2xcLzIiLCJoZWlnaHQiOjQ4LCJ3aWR0aCI6NDh9XSwidXJsIjp7InR5cGUiOiJMaW5rIiwibWVkaWFUeXBlIjoidGV4dFwvaHRtbCIsImhyZWYiOiJodHRwczpcL1wvaHoubWFjZ2lydmluLmNvbVwvY2hhbm5lbFwvaHViemlsbGEifSwiaW5ib3giOiJodHRwczpcL1wvaHoubWFjZ2lydmluLmNvbVwvaW5ib3hcL2h1YnppbGxhIiwib3V0Ym94IjoiaHR0cHM6XC9cL2h6Lm1hY2dpcnZpbi5jb21cL291dGJveFwvaHViemlsbGEiLCJtZTptYWdpY19rZXlzIjpbeyJ2YWx1ZSI6IlJTQS5BTmZDUWUtcGNqLU04eV84dXdRbm8zaVozMlNGbVRiVE56R2J5TmN4RlRGcHBzRGt2cnY4aF9LRDhNbWc1T0dQN1o0X0JzekQ0OVpRaGxVWEhmQkNXamF3akhlZEJ6Z3dPUkxDYmNhYkRzMFhDQzJJem5FN1NBQnRTdUM3U3FoQzR2U0FnR2hhZEh6WDJ2aVg5SGducEx6N3JNZHZ6a1ljdnFOdDRSYUVqSVNfT3FOb0ptb2taNXB3TjFjLUdUQnZkb25pUkF6RjdmU0FOUkpDS2t1TXVRVDJYMVR5dzlhYl9Jb3I0X25iVGRWNDhLWllJMUc3Y1psTlB4SV9OUEp4NmFiSEVTS3NmMXlkRjJWUGRLRFJzUDhHS0N0aE15WlQyMGJBSk1EYXFCYXdHSmEtUno2Wi1BU2RubXhXRUlGZ1ZLMmcteDhFdVdyYkVCR3NSbnp0dFpWRDlQcFE0UGJkd2w3N25RNDlGQnRZNGhDbTFTQmdQdUdodEJqZGcwZ3VRTWthdnhrY2FzbXN1VnRyMEd3R1I4cjhUeHEyUlR0dUNPdVlONlk0YVl4TEM2bWVJcFV5MEtDekxOSGF3aUhFWnVVY1RGS09MUHl6SU5lcFUtSEJKTmFrYlpObDlFbTlRREVFTnJrVjRsYlZzLS0wQnpEalJHSkgtamx1RWgyOEhMLWJwcjViLXZUV0JCbnduRzRLdEFGZTFPUHdWczdWTV8wbG8tSzlmZUFodU55UFNQSFptaE1mUWhRUDF6SGp5QUYwRkxnQVgzdFVERGtlaU1TNmFreHh0YVhtWUIwQ25ocy1Ha2hxNFVKR1RUYURsVFdtYUNIM0w2S2NYQncwMlM0b0xIaENHano0V3VGaFdLQ2o0bEQyMmVhUElFeTNPLWRMczY1MjlvOEguQVFBQiIsImtleV9pZCI6Ik1qWmpZbVkzWmpJNE9EaGxaakl6WWpSak5HUTRPVFE1TURWaU9XWTVZekV6WmpZNVpUQmhaamhrTURKbU9EVTJNMkprTkRRNU9UQm1ZVE0wTVdRek1BIn1dfQ==",
  "data_type":"application/ld+json; profile="https://www.w3.org/ns/activitystreams"",
  "encoding":"base64url",
  "alg":"RSA-SHA256",
  "sigs":{
    "value":"y3UVUFzIxaIfmtUJTGa99s49BO0W348272bRSQ6GiysO-y_n79YWX3ge_BxtRoGO7fkaHYlGM15Xpsq6ptpJCyFYYEDjpxNPq-DsD5VCguRL1SwSouC1LW-ZEfWmrzep9sB0Qcd1OxKgfZ1wTs5dBR7uUW20n4kvtEC1-OL7JLRnxkCF_zsN8Mkgjh9kH8TNoajqiLDHlIOf2NRjhTbUEZuAlyZmJ_dkyi2XHkBt-EUs9X1QQOs_0ltIkLNQ1zVAN9ciIhukjrul_9N-nmD7A4EjjQ9zsjlCO59KbY_m-hf7gEsjPfWUqCvuTwtbT9Mb6cab_unLRZDmrc4iqNVigGLp0Cs0JWxC3OFnbbwIcCuf6b8BUQcIGYgSwQ_H5uvPc4byw0fBX_jkHZ5xHQHkpq4cwpTgPKpfOzwNY0mWNlksTzghREjWv_vDBLBZY9ou3fDGot8YxyTwD2PBZC4rsiFlv7rjUVfZKNdRTH00WL0wTFwIvV3_6tTVM5BD2lB0sFxh8GxYC3I5mcof62Z4fpL09mLbVgOZpv25AJ7_X4_Ke_QoFr7EsiydQMmnwVW3msWZDHGiprMc9PZIvFy1Eq0gn_aTqdzedZojeBNMp4AfZFkQd5wpHvteKVQUc7SLcEK2OeJzeja-sed0DYDFn4Oh8IX-7YVyhUycSmXhovE",
    "key_id":"MjZjYmY3ZjI4ODhlZjIzYjRjNGQ4OTQ5MDViOWY5YzEzZjY5ZTBhZjhkMDJmODU2M2JkNDQ5OTBmYTM0MWQzMA"
  }
}

Unpacking and verifying this salmon results in the above Person object, signed and validated. As a bonus, most of our projects already have code to do this. Took me 20 minutes to implement. If you request just the ActivityPub object without adding the magic-envelope to the Accept header, you'll get the same potentially forged object you get today.

You can also potentially implement LDS on top of this or alongside this with no undesired side effects. JWS might be a bit more complicated if you wanted to support both - but it's possible.

@jaywink

This comment has been minimized.

Copy link

jaywink commented Jul 19, 2017

Nice 👍 Would certainly make AP more appealing to for example Diaspora I think :)

When talking about signatures, it would be nice to agree on where the spec requires signing to happen. The issues found in recent discussions (IRC logs approx here and hundreds lines down) I think largely concentrated on problems providing signatures for objects at a later stage, for example because a server might include extra extensions that would require re-signing the object, for example a remote like.

I think first we should agree on when signatures should be created and when they should be verified. There is one extremely clear case: "Server to server delivery". It's clear here that signing the object and then verifying it at the other end would be a good tried and tested way to provide receiving servers proof that the author actually did write what was delivered. This supports the IRC mentioned "author domain different from object domain" case too.

Another case is fetching objects for various reasons - for example populating notes from a remote user on first discovery, fetching note comments and likes to fill gaps, etc. I think this is the case in the conversations that was seen as problematic, as the object might look different depending on where you look at it. If a Like by user B to a note of user A is fetched from the Note of user A by user C - user A must use a saved signature created by user B - but it wouldn't verify since the object might be different.

I think this would be easy to solve by always fetching objects not owned by the collection owner from the source instead of using the objects in the collection. So, if I want to sync a Note by user A with all the likes for it, I would fetch those Like objects separately and verifying them against a signature provided by the author of the like.

If this route was taken, the problems discussed in IRC on canonical JSON and changing objects would not exist. It also matches to how for example Diaspora protocol pretty much does things. And it works - whether it is LDS or Salmon Magic Envelope.

There seems to be some agreement at least on behalf of @cwebber and @Gargron on IRC that signatures would be the way to go. I really hope this discussion continues and we don't fall into the trap of going the easy fast route, providing a weak specification that developers will be stuck with for years.

@ghost

This comment has been minimized.

Copy link

ghost commented Jul 19, 2017

base64url the magic-envelope. Then you've got a self-contained signed object in a blob. Add it to any object with (for instance) a me:signed_object property. Sites would have to store this if they wanted to take advantage of it, but then you can forward the original object/activity anywhere as the object propagates to third parties and beyond and it can be verified by anybody. If you unpack the magic-envelope you get the original signed structure back. Unpack it's data field and you get the original object that was signed. You could also fetch it from source but that would rarely be necessary if it had a signed_object blob.

I personally don't think we should outlaw unsigned objects/activities; just indicate those that are signed correctly (or incorrectly) and let the buyer beware. That's kind of what zot does now - as many OStatus posts and posts from Wordpress and feeds and even zot wall-to-wall posts aren't or can't be signed. We allow all of these, but they don't get a 'verified' icon. We also don't block messages that don't verify. We give them a highly visible 'failed verification' icon and let the observer decide if this concerns them or not.

@cwebber

This comment has been minimized.

Copy link
Collaborator

cwebber commented Oct 30, 2017

So I think it's off topic for the ticket but I'll reply to the salmon magic envelope bit: you could also just embed the object you would have put in the envelope if you were going to do that and just store it document database style as a child object. Same effect, except you don't have to "unwrap" it. The reason things got more complicated in this thread is that both embedding and having a magic envelope is inefficient and harder to parse; you're duplicating data you had elsewhere, and you also can't eg query the fields if you stored it in jsonb.

But knowing that, if you were going to encode it in a magic envelope the route @zotlabs laid out is probably a good approach.

@cwebber

This comment has been minimized.

Copy link
Collaborator

cwebber commented Oct 30, 2017

Also, I think this ticket technically has its answer, but I need to make the adjustments we agreed to in the SocialWG call to the auth&auth wiki page, so leaving this open until that's done.

@jaywink

This comment has been minimized.

Copy link

jaywink commented Oct 30, 2017

@cwebber isn't this a matter for SocialCG now that signatures were moved completely out of AP spec itself? IMHO what is important now is to document how current implementers are doing the signatures stuff and work on that. But this issue itself isn't really with AP but as continuation work in SocialCG.

@cwebber

This comment has been minimized.

Copy link
Collaborator

cwebber commented Oct 30, 2017

@jaywink Yup! You're completely right.

I still have to finish moving the auth stuff out of there, and yes it's technically SocialCG... I'm just leaving it open so I don't forget about editing the wiki today for the CR since I did say in the SocialWG I'd make some specific edits to it (which was finally making a singular but non-normative path for auth& using http signatures + LDS for server-to-server and oauth bearer tokens for client-to-server, but I haven't yet). Further edits after those initial edits will be handled in the SocialCG!

@cwebber

This comment has been minimized.

Copy link
Collaborator

cwebber commented Nov 21, 2017

Annoyingly I am having some issues where resetting my password in the w3c systems only resets it for wordpress and not for the wiki (???!) so I can't submit an update to the ActivityPub auth&auth page. If someone could update it for me while I'm trying to figure out what's going on with the w3c's systems, I'd appreciate it. Here's the updated text:

= ActivityPub: Authentication and Authorization =

ActivityPub uses authentication for two purposes; first, to authenticate clients to servers, and secondly in federated implementations to authenticate servers to each other.  While ActivityPub's core specification does not specify an official mechanism, the community has converged on the most common practices.

== Client to Server ==

ActivityPub clients authenticate against a server using [https://oauth.net/2/ OAuth 2.0] bearer tokens.

Bearer tokens may be acquired out of band. They may also be acquired using OAuth 2.0 authorization. To discover the correct endpoint for authorization, clients should use [[OAuth-Server-Metadata]] on the host part from the actor's ID URI.

Client IDs may be acquired out of band for some servers. They may also be acquired using [[RFC7591]].

== Server to Server ==

=== Signing requests using HTTP Signatures ===

Server to server federation is authenticated using [https://tools.ietf.org/html/draft-cavage-http-signatures-08 HTTP Signatures] in conjunction with the signing key from the actor's [publicKey https://web-payments.org/vocabs/security#publicKey] field.  The [keyId https://tools.ietf.org/html/draft-cavage-http-signatures-08#section-2.1.1] should link to the actor so that the publicKey field can be retrieved.  At minimum, the digest field should be included in the set of headers being signed.

=== Linked Data Signatures ===

For most forms of delivery, HTTP Signatures is enough.  However, some objects may be passed around the network, whether being shared, forwarded, or otherwise referenced at a future time.  In this case it is important to be able to verify that the actors specified in the actor and attributedTo fields really did author the objects they are being claimed to have authored.  In such a case we need to be able to verify the integrity of the object being passed around, and so we must sign the object itself.

For this we use [https://w3c-dvcg.github.io/ld-signatures/ Linked Data Signatures] to sign the object with the publicKey of the actor that is the actor or attributedTo of the object being passed around the network.
@cwebber

This comment has been minimized.

Copy link
Collaborator

cwebber commented Nov 21, 2017

Nm, I have a separate w3c account for SpecOps and I just used that one, even though that account isn't normally for AP things. I'm sure nobody minds since it's to get the damn wiki edited.

Done and closed.

@cwebber cwebber closed this Nov 21, 2017

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment