Sending OCAP-LD invocation in ActivityPub


#1

Hi people! While I do think I have a reasonable idea how to do this, I’d like to share my thoughts with you and hear your feedback, and maybe there’s a much better way, that I haven’t considered.

UPDATE: I think I now have this thing figured out :slight_smile: but if anyone has new ideas, I always wish to hear :slight_smile:

User [email protected] wishes to edit a resource managed by user [email protected]. Earlier, Bob sent Alice an object capability delegation document. Now, when Alice sends Bob an Update activity to edit that resource, she wants to send the delegation document along with it, such that Bob can verify he can trust Alice’s edit and apply it on the resource.

How exactly should server A include the OCAP-LD delegation in the activity that it sends to server B? Based on ActivityPub spec and OCAP-LD draft, it seems a JSON(-LD) object can an ActivityPub activity and an OCAP-LD invocation at the same time, for example:

{
"@context": [ ... ],
"id": ...
"type": "Update",
"object":
    {
    "id": "https://serverB.net/user/bob/notes/113"
    "type": "Note",
    "content": "Hi! I'm Bob and my email is [email protected]"
    },
"actor": "https://serverA.net/user/alice",
"summary": "Mention Bob's email address",
"proof":
    {
    "type": "Ed25519Signature2019",
    "proofPurpose": "capabilityInvocation",
    "capability": "https://serverB.net/user/bob/delegations/kf903kf",
    "created": "2019-02-08T17:13:48Z",
    "creator": "https://serverA.net/user/alice/capabilities/nf49nd9",
    "signatureValue": "..."
    }
}

This really looks nice, but there’s a problem: The actual capability delegation isn’t included here. OCAP-LD is based on the idea of authorization based on possessing something, so, Alice needs to prove to Bob that she possesses the delegation document. I suppose there are various ways to do this, but I’d like to skip the discussion about that for a moment, and ask you to follow me on the following idea: Instead of mentioning the ID of the delegation, Alice sends Bob the actual delegation document. In fact, Bob’s server doesn’t even need to host it at all! Alice takes care of that, and her server sends Bob’s server the delegation. Bob’s server simply verifies its own signature on that document.

So, in the example above, let’s replace the capability field’s URI with the whole actual delegation object:

{
"@context": [ ... ],
"id": ...
"type": "Update",
"object":
    {
    "id": "https://serverB.net/user/bob/notes/113"
    "type": "Note",
    "content": "Hi! I'm Bob and my email is [email protected]"
    },
"actor": "https://serverA.net/user/alice",
"summary": "Mention Bob's email address",
"proof":
    {
    "type": "Ed25519Signature2019",
    "proofPurpose": "capabilityInvocation",
    "capability":
        {
        "id": "https://serverB.net/user/bob/delegations/kf903kf",
        "parentCapability": "https://serverB.net/resource/bobIntro",
        "caveat":
            { "type": "Role"
            , "role": "Editor"
            },
        "invoker": "https://serverA.net/user/alice#key1",
        "proof": {
            "type": "Ed25519Signature2019",
            "proofPurpose": "capabilityDelegation",
            "created": "2019-02-08T16:02:20Z",
            "creator": "https://serverB.net/user/bob#key1",
            "signatureValue": "..."
        },
    "created": "2019-02-08T17:13:48Z",
    "creator": "https://serverA.net/user/alice#key1",
    "signatureValue": "..."
    }
}

What do you think? This looks reasonable to me, but under an important condition: If server B stores this Update activity, or delivers it to anyone, it must strip the proof object, because it contains the capability delegation. Since authorization is based on possession, we don’t want anyone to have this delegation document, other than the person to whom we originally sent it (i.e. Alice). I suppose the usage of digital signatures still prevents other people from invoking the capability, even if they have the delegation, as long as they don’t have access to Alice’s private key. But still, sounds safe and correct to me, to strip off that part because it’s private, much like stripping off a “bcc” field.

Question: If we strip the proof object, how would other servers being notified about this Update activity verify its authenticity?

Answer: I haven’t looked into using LDS for authenticated boosts/announcements, but I suppose we could keep the actual signature there, keep the proof object, and simply remove the delegation object and any other field that isn’t required for LDS verification.

What do you think? Sounds reasonable? All feedback very welcome, appreciated and needed :slight_smile:


#2

Have you read Kaniini’s What would ActivityPub look like with capability-based security, anyway? What are your thoughts on that proposal?


#3

@_cj, thanks for the link! But yes I’ve read it :slight_smile:

I find it interesting, but the problem it solves is different: what that proposal does is to define a mechanisn for approving activities other servers send that are related to my content. In my use case though, permission is given individually and in advance. The capability URIs are publicly available there, while what I need is private per-user capability grants. In addition, a list of capabilities per activity blocks future use of new kinds of actions, unless they’re added with an Update. And with OCAP-LD, the issuer can define any caveat it wants and apply any logic it wants based on it.

I’m going to start by using OCAP-LD, and then I’ll see if my usage has a useful mapping/compatibility to that proposal. And if yes, I’ll be open to both of them. :slight_smile:


#4

@_cj, what are your thoughts about it?


#5

@fr33domlover I probably misunderstood what you were going after, thanks for clarifying.

OCAP-LD is based on the idea of authorization based on possessing something, so, Alice needs to prove to Bob that she possesses the delegation document.

OCAP-LD’s concept of proof by possession is tied to possession of private keys, not of the capability document itself. So simply doing the linked-data dance of deciding whether to embed a literal versus embed its IRI doesn’t affect the security model. If others get their hands on the capabilities the most they can do is:

  • Verify the chain is correct; or
  • Generate a forged invocation with an incorrect private key. This will be invalid in an obvious way, since the whole world can do the first bullet point anyway and note that the signature doesn’t pass validation.

what I need is private per-user capability grants

Emphasis is mine.

If I am reading between the lines, it sounds like what you’re really after is privately giving a delegation, where no one else is allowed to know that a delegation occurred. If this guess is correct then I honestly don’t know what the boundary/scope of this privacy problem is, as I think this quickly leaves the realm of OCAP-LD. A “guaranteed” privacy scheme will need to consider varying degrees of what privacy means:

  • hiding the fact that there is traffic between two end points? (attacker sees communication between instances and guesses from meta data analysis it is a private delegation message).
  • End-to-end encryption at application level? (attacker intercepts the message, has TLS session keys (or equivalent effect) and can decrypt the message at the transportation layer to plaintext).
  • Make a lot of custom rules about stripping specific fields under certain conditions to have an AP implementation never leak the fact that it received an object/activity with a capability delegation?

The above brainstormed points are with the following point in mind: ActivityPub already has direct source-to-destination, known individual recipients, and end-to-end encryption when using HTTPS delivery mechanisms to individual actors and not collections of actors. (EDIT: Due to inbox forwarding, this doesn’t necessarily hold when addressing collections of actors).

To revisit the above bullet points in order:

  • The only solution I am aware of is Tor.
  • Will need a new paradigm that doesn’t break the linked data model. OCAP-LD is not the solution, IMO.
  • I don’t like this last option either: It sets privacy expectations high in an ideal designer’s world, and in practice implementors will have a lot of buggy code whose side effects are violations of these expectations.

To sum up my thoughts:

  • For publicly delegating 1-actor-to-1-actor or 1-actor-to-closed-set-of-actors OCAP-LD makes sense.
  • For publicly delegating 1-actor-to-open-set-of-actors then Kaniini’s outline makes sense (but it needs a mechanism to update the capabilities for sure, IDK if the existing Update side effects are sufficient).
  • I think privately delegating is too specific because what “private” really means in ActivityPub in general hasn’t really been addressed by anyone. Though it has been mentioned off and on before (ex at FOSDEM by @cwebber)

#6

@_cj, thank you for the detailed reply! My general use case doesn’t require the existence of the delegation to be private, imagine a public project developed by a public team, and the list of team members is visible to everyone. Some details, such as the exact role/access each team member has, may still be private, but I don’t think that’s a problem. So yeah, OCAP-LD works fine for my need.

kaniini’s proposal has a separate dedicated URI for each activity, which is in a way an improvement over all-activities-go-to-same-URI and such a thing can be done regardless of whether OCAP-LD is used for personal grants, these 2 mechanisms can perhaps be used together :slight_smile:


#7

@fr33domlover

I think you misunderstand my proposal.

It is definitely an acceptable replacement for OCAP-LD, in fact it is basically OCAP-LD but without cryptography. As I have written in the specification, cryptography is frequently used inappropriately in this space in ways that can be harmful (such as reduction of deniability).

There are two components to my proposal:

  • Capability URIs: these provide implicit proof of authorization as an opaque token which can either be directly invoked or fetched to retrieve a proof object.

  • Proof objects: these are essentially the same as proof objects in OCAP-LD, but addressed by opaque IRI. A Capability URI can return a proof object if it is not directly invokable.

Including proof objects by reference, e.g.

{
  "@context": ["https://www.w3.org/ns/activitystreams", ...],
  "id": ...,
  "type": "Update",
  "object": {
    "id": "https://serverB.net/user/bob/notes/113",
    "type": "Note",
    "content": "Hi! I'm Bob and my email is [email protected]"
  }
  "actor": "https://serverA.net/user/alice",
  "summary": "Mention Bob's email address",
  "proof": "https://serverB.net/user/bob/authorizations/226"
}

would work for your usecase, wouldn’t they?

(edit: to be absolutely clear; in no case is OCAP-LD itself better or worse than my proposal. i think OCAP-LD is problematic because the authors push more LDSigs at us, when in reality we should be eliminating LDSigs from AP.)


#8

Hey @kaniini! Thanks for the feedback, it’s highly valuable!

I dislike LDSigs too and I consider their dependency on JSON-LD expansion, nquads generation etc. to be really heavy and painful. If I’ll need them for my ForgeFed demo, I’m going to use a custom scheme that simply signs whitespace-removed JSON with sorted keys or something like that.

The way I’m going to use delegations avoids the need for servers signing them, like it is in your proposal :slight_smile:

I want to avoid using ACLs, authorize activities by posession of a capability grant, not by looking up the user’s ID or public key in some list. Here’s how my use case works, perhaps you can suggest how to do it using your proposal?

There is a role based access control system: You can create a project, define roles, define which actions each role does, define inheritance between roles. And then you can add team members to your project and each member is assigned a role (contributor, developer, lead, reviewer, translator, etc. whatever you like).

A capability grant is therefore based on roles: It says “I’m granting actor A a role R in project P”. And your server gives that team member a capability URI, which is a private token they keep to themselves, and pass it along with activities whenever they need to use exercise their role and access. The URI is opaque, but when that actor A GETs that URI, they can see whether or not they still have access, maybe also see the role ID and project ID.

The way I’ve been doing this, is that nothing is changed in activity JSONs, except you add the proof line etc. to prove you possess the capability.

If you add me as a team member, your server sends my server an Add activity etc. holding my personal capability URI, and when I want to edit stuff on your project, my server attaches my personal capability URI before sending the activity to you. Right now I hardcode the list of activities for which a capability is required, but I suppose a project can publish a list of actions that require a role.

@kaniini I’d love to hear your feedback on this :slight_smile: I’ll try too, to play with the JSON and see how the approaches probably converge anyway, as soon as I have some calm free time to sit and do this :slight_smile:


#9

Actually, looking at this now, I see:

  1. The server doesn’t need to sign grants because it can keep them in its local DB, which it trusts
  2. Remote actors don’t need to LD-sign their activities because there are already HTTP signatures on the activities when they’re POSTed to the server

The proof field can just be a URI as you suggested :slight_smile:

Currently in my scheme, that proof URI isn’t returned from a public capability URI on demand; it’s issued exactly once, when someone gives you a given role in a given context, and your instance keeps it for you and attaches it to relevant activities that you send. An added security measure is that the server remembers your actor ID so even if someone has your capability URI, they can’t use it because their actor ID is different. No LDSigs required :slight_smile:

(Question: Should you POST activities to the project’s inbox, attaching the capability URI to the activity JSON or in any other way (such as HTTP header), or should you POST to the capability URI itself? The former is more like the way ActivityPub works, with actor inboxes, but technically the latter is possible too, I’m open to either if any of them proves to be better than the other)


#10

regarding POSTing to a capability URI or just using the proof field always, I’m open to doing it whichever way, honestly. if we were to follow the OCAP model used in Second Life though, we would want to prefer POSTing to capability URIs as proof of authority.


#11

Hmmmm I have another pro/con regarding that choice: if you post to a capability URI, then the URI is exposed even in HTTPS, while if it’s part of the payload, then in HTTPS it’s encrypted, so its privacy is protected better. Idk if it matters, just a thought. Another issue is how this choice affects ActivityPub servers that aren’t aware of capabilities and post everything to the inbox.

I think I’ll try to stick with the actor inbox and see along the way how it goes.


#12

I do not recommend “no one knows about this endpoint” as a security model. I also don’t recommend putting PII in a URL.


#13

@_cj, agreed. I mean, I wouldn’t want to rely on a URL as the whole access mechanism, at least not for this kind of usage, where trust is critical. But there is a problem with making a personal capability URI public: I need a secure way to identify project team members.

Suppose you’re [email protected] and you add [email protected] as a developer for your go-fed repository. If the capability URI is public (suppose instead of a URI it can even just be some token) the only thing protecting you is recognizing Alice by her actor key ID. But those keys can be rotated, and instance A can shut down and a new one start with the same domain name. So our options are:

  1. Take that risk, assuming the chance for abuse by instance “hijacking” is too small to matter
  2. In addition to rotating keys, give each actor a permanent signing key (that’s equivalent to stuff like signing git commits)
  3. Keep the capability token private, which is then a truly capability based access control system in which identifying people isn’t needed, it’s only based on possessing an access token. And verifying actors can be done with usual HTTP signatures.

I feel most comfortable with 3. I agree it’s best in that case, that the capability token is kept private. And if accidentally it leaks, you could revoke it and make a new one. Perhaps it’s safer to put the token in an HTTP header, so that capability- unaware servers which dont strip the token dont end up exposing it.

Instead of a capability URI, we can have a single public endpoint per server, for checking capability token validity. And maybe even that isn’t really necessary, which makes things easier.


#14

Taking a step back, I still don’t understand why it is important for you to keep the token private: going back to my previous post, the only things other people can do with a public capability token are:

  • Verify the chain is correct; or
  • Generate a forged invocation with an incorrect private key. This will be invalid in an obvious way, since the whole world can do the first bullet point anyway and note that the signature doesn’t pass validation.

So whenever you keep on mentioning “private”, what is going through my mind is “skipping validation of the invokee’s signature on the invocation of the capability”. Which is why I am strongly against this sort of solution.

Going back to your problem:

Suppose you’re [email protected] and you add [email protected] as a developer for your go-fed repository. If the capability URI is public (suppose instead of a URI it can even just be some token) the only thing protecting you is recognizing Alice by her actor key ID.

Yes, I agree with you: verifying Alice’s signature on the invocation on the OCAP-LD object is exactly what is needed.

But those keys can be rotated…

My answer would be: Keys being rotated on the issuee side should know to forward the delegation with the same scope on their end with the existing OCAP-LD solution anyway, after creating the new key and before destroying the old private key. The public key can hang around for verification purposes. If the rotator fails to do so, simply revoke the previous delegation and issue a new capability from the issuing side.

…and instance A can shut down and a new one start with the same domain name.

EDIT: The new server that DNS points to shouldn’t have the existing private keys for a user on a totally different server that DNS used to point to. I don’t see a problem here. (Again see above’s “the only things other people can do with a public capability token are …”)

I think also you’re trying to solve a general Fediverse-wide problem that isn’t specific to your use case: How to handle nomadic identities? (And the user authentication/authorization associated with it). For example, when instances go down, a user migrates, etc. I don’t think OCAP-LD is the appropriate place to solve that. Furthermore, I think it is more likely for users to hold onto their private keys and migrate with them than to hold onto “private” capability tokens. So trying to shift the burden away from well-known private key management onto “private” capability tokens is asking for trouble, in my opinion.

My recommendation: Don’t try to solve all the problems at once. Break down the problems into smaller, more manageable chunks.


#15

Ex: if a smaller problem is: “federated key migration”, then I recommend building an automated solution (tools at your disposal: OAuth, AP extension w/ side effects, symmetric key encryption). I don’t recommend modifying the actual mechanism giving you the security guarantees (OCAP-LD) to eliminate the problem (you’re probably eliminating the security guarantees somehow).


#16

@_cj, thank you for taking the time to write a thorough reply!

First of all, I want to say I’m not doubting the security properties of OCAP-LD. However, I’m noticing the following (and I guess perhaps @kaniini does too, when comparing their proposal with OCAP-LD):

  1. OCAP-LD requires JSON-LD processing. That’s a heavy thing to implement and depend on, and in practice on the fediverse we don’t seem to need it much, and ActivityPub works fine on the fediverse without using JSON-LD processing.
  2. OCAP-LD requires actor keys to be specified in delegations as a URI. That’s also how it works in HTTP signatures, and if we rely on the regular HTTP signature, we get rid of the need t LDS-sign invocations. It does mean invocation signatures can’t be verified in the future, but that’s not a problem at all in our case as far as I can see.
  3. OCAP-LD supports delegation chains. Theoretically these can be useful, when people with access appoint people for lower-permission access. Even if we need them, we could sign specific data and not need LDS, but in the ForgeFed use case right now they aren’t needed, so at least for the current draft I want to suggest that we don’t need signatures for delegations either. That’s because delegations never need to be published, so there’s nothing to long-term sign.

When a server does send a delegation, however, what should it send, and how should it verify it? Since servers need to remember delegations anyway, even if just for UI purposes, it’s enough that the server sends a capability token to the issuee. I agree we don’t want to remove the security and safety of this token compared to OCAP-LD, so I suggest the server either signs the token with a private key, or encrypts it, or just uses HMAC on it because the server is the only entity verifying the token.

That way, we retain the security properties of OCAP-LD, but we have a much simpler system. When I publish an actual description of the exact protocol, you can verify the security aspect, and if I missed something, we’ll fix it :slight_smile:

One question remains, as you said, the instance reinstall/migration/admin change stuff. And that’s indeed not specific to my use case (but it does matter in particular, because if someone maliciously makes a git push --force to your repo, or replaces your whole list of 1000 repo tickets with random garbage, that’s a big problem).

My proposal is to solve it in the same way: Use object capabilities. When instance A first makes contact with instance B that involves user activities that require permission, instance A gives instance B a special delegation/token that says: “I give you access to send invocations to me”. That way if a domain is maliciously reused, no harm can be done because the new server doesn’t possess the token the old server had. And if you do legit server migration, simply migrate the token along with your DB, configs etc. and you’re covered.

If this proposal gets good feedback and successful implementation etc., we can even do this on the whole fediverse, not just ForgeFed :slight_smile:


#17

Thanks for your reply!

Here are the problems I see that you’ve identified for me:

  • JSON-LD processing (due to Linked Data Signatures)
  • Public keys no longer accessible
  • Private keys rotating
  • Nomadic identities

I agree that all of these are problems. For example, I don’t like OCAP-LD because of the first bullet point and it’s unlikely go-fed will support LDS. On the other hand, I have no problem with the concept of “object capabilities” in general. I like the principle.

I do think trying to solve all the above problems with a single solution is extremely ambitious (at best) or very misguided (at worst) when building on top of OCAP-LD which has these specific problems as a consequence of it’s security properties.

The problems above are very broad and general, but the solution you start out with is very specific and narrow (OCAP-LD). This mismatch is making it very hard to take a step back and evaluate other valid approaches not based on this specific solution.

I don’t want to discourage you from a solution that has the “object capability” principle, but I will continue discouraging you from artificially narrowing your perspective by starting out with a specific solution (OCAP-LD) that has the very problems you want to solve. Don’t be afraid to take a step back and think bigger.

Once that’s done, then we can begin discussing breaking the problem down into, in my opinion, more manageable chunks:

  • A (new?) way to sign a Linked Data object with a token that doesn’t require JSON-LD canonicalization. Maybe as simple as LDS + the “none” canonicalization algorithm?
  • A solution for ensuring high-availability of public keys. Something like @cwebber and the Golem demo?
  • A solution for ensuring private key rotation doesn’t impact the security model. Client-to-server delegation is needed then? (OCAP-LD doesn’t have this problem, it seems to only be a problem in your modified version of it)
  • A solution for nomadic identities. This alone is probably going to generate a lot of disagreement.

And now, your breadth of solutions available can help solve your more general problems, when smartly applied together. Hopefully this helps.


#18

@_cj I’ll read your whole reply in a bit but I have a moment now and I just want to clarify, my solution doesn’t try to deal with keys disappearing or with nomadic identities! I only meant “migrate” as in copy your server to another computer but still having the same domain. And public key rotation I treat as if it it’s already the fediverse reality (because I support it and Pleroma leading it and bringing it to Mastodon).

And my solution as I described it is obviously incompatible with OCAP-LD LDS stuff, but that’s fine, we aren’t locked to LD/W3C stuff :slight_smile:

I’ll read more thoroughly in a bit and reply more :slight_smile:


#19

I now have time to reply more thoroughly, although it seems I already wrote most of my thoughts in my previous message ^ _ ^

I don’t care about whether we use OCAP-LD or some other thing. I just took OCAP-LD as a possible flexible way to do OCAP, and then discovered challenges and decided to do something else, that isn’t necessarily compatible with OCAP-LD, but is still based on the OCAP concept. Therefore,

  • No need for LDS at all. If, however, we do want LDS later for any reason or need, yes we can use an alternative canonicalization method. Note that “none” won’t work if you want to support general linked data, but it can definitely be enough for us here to use some method based on plain JSON, not needing any JSON-LD processing. If we discover that long-term signatures are required, we can do things that way (at the cost of being incompatible with general OCAP-LD usage) or use JSON-LD (painful but compatible with the spec and potentially other OCAP-LD uses on the web out there).
  • Since there won’t be a need for one party verifying a signature made by the other, no need for key availability. Actually, I’m worried that using Golem would complicate things for us: If keys become content-addressable, we can’t rotate them anymore. But anyway, that’s a separate issue to think about in Spritely, I’m assuming the current fediverse based on HTTPS.
  • Key rotation is a problem for us only in the case where we need to verify things at a later time. This is probably required in some OCAP-LD uses, but in my use case so far it isn’t. And even if it did become required, it could probably be a just a single permanent key per server, which maybe is okay (I’ll explore that if verification-at-a-later-time becomes required). Actually, we have the opposite problem here: When processing an invocation, we can allow the key to have been rotated since the last time. So rotation is fine, but we face the (unlikely but still makes me uncomfortable) problem of malicious domain name reuse. Like I said above, it’s a general fediverse challenge and I have a separate proposal for this (that happens to be based on the object capabilities concept). The reason I’m putting thought into this, like I said above, is that malicious domain reuse can cause lots of damage and I feel more comfortable when I have an idea how to mitigate it.
  • Nomadic identities are interesting but I haven’t put any thought about them at all so far. I’d love to, but it’s a general fediverse challenge, not specific to ForgeFed, and I’m not touching it yet, and curious if @cwebber has some ideas about it. Either way, so far out of scope for me.

Thank you so so much for all the replies and thoughts! I’m excited to write a specification draft.

(By the way, if it’s written without formalities like the W3C and IETF documents, does it still count as a specification? ^ _ ^)


#20

For completeness, @fr33domlover and I spoke in the w3c socialcg IRC room where they were better able to explain the missing pieces to me. For example, I didn’t realize blind key rotation was a key requirement. So like the rest of us I will be awaiting to see the first draft design and/or implementation!