Skip to content

Protocol Discussion WIP

Steven Allen edited this page May 28, 2014 · 22 revisions

DRACL

Purpose

The purpose of DRACL is to provide a distributed access control list mechanism. This protocol allows users to access content without identifying themselves and without (overly) trusting or relying on third parties.

Terminology

Actors

There are five parties in this system:

Authentication Agent
The agent responsible for managing a producer's "friends list" and revoking access.
Distribution System
The subsystem responsible for distributing keys/other encrypted resources. This might be the same service as the authentication agent.
Producer
The producer is the user uploading and assigning access to a resource.
Consumer
The consumer is the user accessing a resource.
Service
The service is the service using DRACL to control access to its content.

Components

Entity
Someone or a group of someones to which access can be given.
Group Entity
A specific type of entity that contains other entities.
Named Group Entity
A (named) group of other entities that share access to a common entity key.
Anonymous Group Entity
A (unnamed) group of other entities that share access to a common entity key.
User Entity
An entity that contains exactly one consumer.
Entity Keypair
An entity's asymmetric keypair. Only members of the associated entity (and the entity's producer) should know this keypair. This keypair is used to access resources.
ACL
An object that can be used to check access to a resource.
Resource
Content restricted by an ACL.
Encrypted Resource
Content encrypted to a set of entities.
User Keypair
This is the keypair that identifies each producer/consumer. On the producer side, this is used to both sign ACLs and accept "friend requests" (decrypt the user's entity key, keep reading).

Other

User Agent
The browser acting on behalf of a user.
User
The user (the person). Either the producer, consumer, or both.

Overview

This section gives a very broad overview of the protocol sans details.

Friends

To add a friend (consumer) to one's contacts, the producer creates a user entity by (a) generating an asymmetric entity key pair for the new user entity and (b) giving this key pair to the friend (encrypted with the user's private key).

Groups

To make a group, the producer (a) generates an asymmetric entity key pair for the new group entity and (b) encrypts this asymmetric key to all of the member entities. The producer then distributes the encrypted key using the distribution system.

ACLs

To grant access to a piece of content, the producer generates a resource key, (anonymously) encrypts it with the key of the entity to be given access, and then gives both a copy of the encrypted and unencrypted resource key to the service.

Authentication

To check access, the service sends the encrypted resource key to the consumer requesting access. The consumer must then decrypt the encrypted resource key and then return it to the service (we discuss security measures such as preventing MITM attacks below).

Key Distribution

Encrypted entity keys will be distributed to consumers using some distribution system TBD. I discuss possible solutions below.

Preferred Knowledge Constraints

Authentication Provider

The authentication provider doesn't inherently need to know anything.

The relaxable restrictions are as follows:

  • No knowledge of the guarded resource.
  • No knowledge of the members ACLs
  • No knowledge of the members of groups.
  • No knowledge of the members friends.
  • No knowledge of the members services used.

Service

The service must only be able to determine if a consumer's user agent should be granted access to some resource guarded by some ACL.

The relaxable restrictions are as follows:

  • No knowledge of the guarded resource.
  • No knowledge of the identity of publishers.
  • No knowledge of the identity of consumers requesting access to resources.
  • No knowledge of the members of its ACLs (who should be able to access resources).
  • Given the knowledge that a consumer has access to one resource, the Service should not be able to determine whether or not the consumer has access to any other resource.
  • If a consumer accesses a resource multiple times, the relying party should not be able to link those access requests to the same consumer.

Producer

The relaxable restrictions are as follows:

The producer must be able to restrict the access of his or her resources to a subset of his or her friends.

  • No knowledge of friend identities (only know producer specific pseudonyms).
  • No knowledge of whether or not a friend has accessed a resource.

Consumer

The relaxable restrictions are as follows:

The consumer must be able to access content to which he or she has been granted access.

  • No knowledge of a resource's producer.
  • No knowledge of which other consumers may access a resource.
  • No knowledge of its "relationship" with any producer.
  • No knowledge of any other consumers that have a "relationship" with this producer.
  • No knowledge of resources to which the consumer does not have access.

Reduced Knowledge Constraints

Service

  • No knowledge of the identity of publishers.
    • Broken
  • No knowledge of the guarded resource.
  • Optional: Unless encrypted

Producer

  • No knowledge of whether or not a friend has accessed a resource.
    • Weakened to: Identities of consumers that MAY have accessed a resource.
  • No knowledge of friend identities (only know producer specific pseudonyms).
    • Optional: A user entity key may be assigned without knowing the consumer's identity.

Consumer

  • No knowledge of which other consumers may access a resource
    • Weakened: They know which group they used to access a resource.
  • No knowledge of a resource's producer.
    • Broken
  • No knowledge of its "relationship" with any producer.
    • Weakened: If two consumers collude, they may find the intersection of the group entities to which they belong. This could be used to determine if the to consumer's have similar "relationships".

Authentication Agent

  • No knowledge of the guarded resource.
    • Weakened: Knows the number of guarded resources.
    • Weakened: Learns information (but not the resource itself) if the resources ACL expires.
  • No knowledge of the members ACLs
    • Broken
  • No knowledge of the members of groups.
    • Broken
  • No knowledge of the members friends.
    • Broken
  • No knowledge of the members services used.
    • Weakened: Learns about the service if the service attempts to refresh an ACL.

Explicit Negative Constraints

  • An authentication agent SHOULD NOT be able to act on behalf of any party using its service. This constraint MAY be relaxed to make account recovery possible.
  • A service MUST NOT be able to determine the members of a group entity.
  • A service MUST NOT be able to access a producer's resources on other services.

Conventions

In code blocks:

  1. Encrypt(a, b) encrypts b with key a (randomized/authenticated).
  2. Signed(a, b) returns a b signed with a.
  3. Decrypt(a, b) decrypts b with key a.
  4. UnauthEncrypt(a, b) is the same as Encrypt(a, b) but the result is anonymous and unauthenticated.
  5. UnauthDecrypt(a, b) is the same as Decrypt(a, b) but it reverses UnauthEncrypt(a, b)

Additionally, all encryption is assumed to be randomized and authenticated.

Protocol

Initialization (both producers and consumers)

On initial sign-up, user agents generate "user keypairs" (or use existing GPG keys). When this document refers to consumerKey or producerKey, it is referring to a "user keypair".

The user agent must also generate a symmetric dataKey. This key is is used to encrypt any sensitive information (private keys etc) stored on the authentication agent. This allows the authentication agent to store private keys on behalf of the user without allowing it to masquerade as the user. This dataKey should, in turn, be encrypted with a password chosen by the user and uploaded to the authentication agent for safe keeping. The intermediate data key is used instead of just turning the password into a key to allow the user to easily change his or her password. To turn the user's password into an encryption key, it'll need to be stretched using standard key stretching mechanisms (someone else's problem).

However, we may choose to have multiple dataKeys: one recoverable by the authentication agent and one not (the way Mozilla's new sync protocol does it). This way, users will be able to recover less private content at the cost of some security.

Sign-up

When signing up with an authentication agent, the user should generate a new asymmetric keypair called the agentKeypair and share it with the authentication agent. This keypair will be used in cases where the authentication agent needs to act on behalf of the user.

TODO: Maybe we shouldn't share this agentKeypair. Instead, we could require that the producer get an agent certificate (signed by this keypair) every day. This would allow the agent to revoke the producer's key when compromised. Unfortunately, this would also give the authentication agent the ability to lock the producer out...

Create ACL

At any time, a service may request an ACL from the producer's user agent. When making this request, the service provides a human readable description of the request. The producer's user agent may either return a NAK or an ACL.

To make the ACL itself, the producer's user agent contacts its authentication provider, chooses the members of the ACL, downloads the relevant entity keys, generates a resource key and then constructs the ACL locally (in the user agent).

Service         Publisher           Authentication Agent
   |-(ACL Request)->|                        |
   |                |<-(Choose ACL Members)->|
   |                |<-(Download Entity Keys)-|
   |          <Generate ACL>
   |<-(Upload ACL)--|

ACL Request

An ACL request actually requests multiple ACL's: one per permission. If no permissions are specified, the "default" permission is assumed.

def createACLRequest(description):
  return {
    "type": "ACL-Request",
    "description": description, # Human readable
    "permissions": [
      {
        "name": "my_perm",
        "description": "My Description"
      }
    ] # An optional set of permissions.
  }

Special Permissions

  • default: The default permission for a single ACL request.
  • owner: The owner of the resource. The authentication agent should fill in the producer by default.
  • account: An account resource. The authentication agent shouldn't show the standard ACL creation dialog but should show an account creation dialog.

ACL

Any ACL can include up to 8 entities (because I like the number 8). If fewer than 8 entities are included, fake/random data should be used to fill the remaining space. This way, the number of entities with access to a resource is indeterminable.

If more than 8 entities must be included, the authentication agent should ask the user agent to should create anonymous group entities (literally just unnamed group entities). The composition of these group entities is up to the authentication agent but should probably be based on previous ACL composition (make anonymous group entities from common pairs).

Note: We could just stick with 1 entity per ACL however, allowing multiple entities per ACL could significantly reduce the number of entities in the system.

def generateACL(producerKey, entity_ids):
  resourceKey = generateSymmetricKey()

  return {
    "type": "ACL",
    "subtype": "symmetric",
    "serviceData": resourceKey,
    # The ID of the key that is allowed to reissue this ACL.
    "agentKeypair": ID(agentKeypair),
    "members": Encrypt(agentKeypair.public, entity_ids),
    "consumerData": Signed(producerKey.private, {
      "type": "ACL Challenge",
      "agentKeypair": ID(agentKeypair),
      "producer": producerID, # This will probably be something of the form producer@domain
      "data": makeTickets([lookupEntityKey(entity_id) for entity_id in entity_ids], resourceKey),
    })
  }



def getEntitySecret(key):
  # Returns some shared secret for the entity.
  # This secret should not be derived from the key itself. Learning this
  # secret should not compromise the key!
  return key.secretId

def makeTickets(entityKeys, resourceKey):
  ticketSet = [UnauthEncrypt(entityKey, resourceKey) for k in entityKeys)]


  # The tags are an optimization. As the tickets will be encrypted
  # anonymously, finding the right ticket would usually take up to 8*g
  # (potentially asymmetric) decryption operations where g is the number of the
  # number of entities assigned by this producer to the consumer (try each
  # ticket with each entity key). The tags, however, allow the consumer to
  # perform g+2 sha256 sums calculations to find the right ticket and 1
  # decryption to decrypt it.

  tag = SHA256(ticketSet)
  tagSet = [SHA256(getEntitySecret(entityKey), tag) for entityKey in entityKeys]

  # Padding is added to ensure that the number of included entity keys cannot be
  # guessed. Basically, add fake "tags" and "tickets" filled with random data.
  # May not really be necessary.
  return {
    "tags": tagSet + padding,
    "tickets": ticketSet + padding
  }

One of these will be generated per permission specified.

Replace Resource Key

In case of a break-in, the service will have to ask the authentication agent to issue a new ACLS (this time signed by the agentKeypair instead of the producerKey). We should be able to implement some scheme where the service can pass the ACL to the authentication agent along with a re-randomization key and get back a re-randomized ACL signed by the agentKeypair.

Unfortunately, this simple scheme will break if the authentication agent colludes with the party that broke into the service. If we only had one entity per ACL, we could just use blind signatures (and blind the re-randomization key). However, because we need to re-randomize multiple encrypted resource keys and then sign them into a single ACL, standard blind signature techniques won't work (AFAIK). Instead, we can have the service blind the re-randomization key, let the auth agent sign the blinded ACL and then have the service unblind the tickets. This will break the authentication service's signature on the ACL however, the service can encrypt the blinding key with the resource key and give this to the consumer so that the consumer can check the ACL's signature AFTER decrypting the resource key (by re-blinding the tickets).

This is complicated. Hopefully we can come up with a better scheme.

Encrypted Resource

In addition to protecting resources with ACLs, resources can be directly encrypted with entity keys. Encrypted Resources are basically symmetricACLs except that the resourceKey is used to encrypt the actual resource and is not given to the service.

def encryptedResource(producerKey, entityKeys, resource):
  resourceKey = generateSymmetricKey()
  return Signed(producerKey.private, {
    "type": "Resource",
    "producer": producerID, # This will probably be something of the form producer@domain
    "tickets": makeTickets(entityKey, resourceKey),
    "resource": Encrypt(resourceKey, resource)
  })

Friend

When a producer adds a consumer to his or her "friend list", the producer's user agent creates a new "user" entity (with an asymmetric key). The user entity key will probably be encrypted with the consumer's public key and transfered to the consumer either via the distribution system (described later) or sent directly via a side channel (email etc.).

Assigning each user a user entity key helps to decouple the producer/consumer and lets them change out their keys independently. However, this may not be necessary.

For further details, see the User Entity section.

Entities

Entities are the basic unit of access control.

To create an entity, the producer's user agent generates an entity and uploads it to the authentication agent. More specifically, the authentication agent asks the user agent to make an entity, the user agent generates the entity key and relevant data structures, and then returns the entity to the authentication agent.

Producer     Authentication Agent
   |<--(Request Entity)-|
   |--(Entity or NAK)-->|

There are three types of entities: named group, anonymous group, and user.

Named Group Entities

Named group entities are analogous to Google Plus circles. They can contain other entities. Additionally, they are mutable.

Create

When creating a named group, the user agent MUST ask the user for confirmation.

This is a very quick and dirty sketch of a named group creation function.

# Authentication Agent
def createNamedGroupRequest(group_name, members=[]):
  return {
    "type": "Entity Create",
    "subtype": "Named Group",
    "name": group_name,
    "members": members
  }

# User Agent
def createNamedGroup(producerKey, group_name, members=[], supersedes=None):
  secretKey = generateAsymmetricKey()
  entity_def = {
    "type": "Named Group",
      "name": group_name,
      "id": generateEntityId(),
      "members": [],
      "supersedes": supersedes
  }

  entity = {
    "type": "Entity",
    "data": entity_def,
    "private": Encrypt(dataKey, {
      "hash": SHA256(entity_def), # Used to verify integrity of the group name.
      "secretKey": secretKey
    })
  }

  # Give access to superseded group
  if supersedes:
    distributeResource(encryptedResource(producerKey, [lookupEntityKey(supersedes)], secretKey))

  # Initial Members
  if members:
    addMembersToGroup(producerKey, entity, members)

Add Member

To add a member (an entity), to a named group entity, the authentication agent passes an "Add To Group" request to the producer's user agent. If the producer chooses to add the members to the group, the user agent will decrypt the group's secretKey and wrap it in an encrypted resource decryptable by the new members. It will then distribute this encrypted group entity key using the distribution system.

Producer     Authentication Agent    Distribution System
   |<------(Add Member)-|                   |
   |--(Updated Group)-->|                   |
   |                                        |
   |---(Encrypted Group Keys)-------------->|
# Authentication Agent
def createAddMembersToGroupRequest(group_id, member_ids):
  return {
    "type": "Group Add",
    "group_id": group_id,
    "members": member_ids,
  }

# User Agent
def addMembersToGroup(producerKey, group, members):

  # skipped: ... Add Members to group structure, regenerate the hash, and re-encrypt ...

  group_key = getEntityKey(group)
  member_keys = [getEntityKey(member) for member in members]

  while member_keys:
    distributeResource(encryptedResource(producerKey, member_keys[:8], group_key))
    member_keys = member_keys[8:]

  return group

Remove Member

To remove a member from a group entity, the group must be recreated without the member to be removed. While this will prevent the consumer from accessing newly created resources, he or she will retain access to old resources until their ACLs expire.

Producer  Authentication Agent    Distribution System
   |<-(Remove Member)-|                   |
   |---(New Groups)-->|                   |
   |                                      |
   |---(Encrypted Group Keys)------------>|
def removeMembersFromGroup(producerKey, group, members):
  new_group = createNamedGroup(group["data"]["name"], members=list(set(group["data"]["members"]) - set(member_ids)), supersedes=group_id)
  containing_entities = findEntitiesWithMember(group)
  # ... Recreate `containing_entities` recursively. (TODO)

Anonymous Group

Unlike named group entities, anonymous group entities contain a list of members instead of a name. Additionally, anonymous group entities are immutable. Basically, they are a way of transparently grouping other entities if more than 8 entities are given access to a resource.

Create

To create an anonymous group entity, the authentication agent asks the producer's user agent to create an anonymous group entity from a list of members. The user should not be prompted by the user agent on anonymous group entity creation (they can't be used to trick the user).

# Authentication Agent
def createAnonymousGroupRequest(entity_ids):
  return {
    "type": "Entity Create",
    "subtype": "Anonymous Group",
    "members": entity_ids
  }


# User Agent
def createAnonymousGroup(producerKey, members):
  group_id = generateEntityId()
  secretKey = generateAsymmetricKey()
  group_def = {
    "type": "Anonymous Group",
    "members": member_ids,
    "id": group_id
  }
  group = {
    "type": "Entity",
    "data": group_def,
    "private": Encrypt(dataKey, {
      "hash": SHA256(group_def), # Used to verify integrity of the group name.
      "secretKey": secretKey
    })
  }

  addMembersToGroup(producerKey, group, members)
  return group

User Entity

Every friend has a user entity. DRACL uses this user entity to assign a symmetric key to every consumer a producer "friends".

Create

Producer      Authentication Agent    Distribution System
   |<-(Create User Entity)-|               |
   |---(User Entity)------>|               |
   |                                       |
   |---(Encrypted User Entity)------------>|
def createUserEntity(name, consumerKey, consumerId):
  secretKey = generateAsymmetricKey()
  user_def = {
    "type": "User",
    "name": name,
    "id": consumerId
  }

  user = {
    "type": "Entity",
    "data": user_def,
    "private": Encrypt(dataKey, {
      "hash": SHA256(user_def),
      "secretKey": secretKey
    })
  }

  distributeResource(Encrypt(consumerKey.public, secretKey))

  return user

Authentication

Quick overview:

  1. The authentication agent sends the ACL's consumerData block to the consumer's user agent.
  2. The consumer's user agent checks the signature on the consumerData block (it must either be signed by the producer or the producer's authentication agent). Otherwise, fail.
  3. The consumer's user agent tries to decrypt the attached tickets to retrieve the resourceKey. If that fails, the consumer's user agent tries to fetch new entity keys and tries again. If it still fails, the consumer fails.
  4. The consumer's user agent proves to the service (using a MITM resistant zero knowledge proof) that it knows the resourceKey.

Note: All failures must be identical. The service (or someone impersonating the service) should never know why a consumer failed.

Also Note: If the consumer doesn't know a valid group key in the ACL, it should be unable to determine if it has correctly decrypted the resourceKey.

Service               Producer      Authentication Agent    Distribution System
   |<-(Request Resource)-|                     |                    |
   |---(Challenge)------>|                     |                    |
   |           <Try with cached keys>          |                    |
   |                     |-(Request New Keys)->|                    |
   |                     |<-(New Keys)---------|                    |
   |           <Try with new keys>             |                    |
   |                     |--------(Request New Keys)--------------->|
   |                     |<----------(New Keys)---------------------|
   |                     |--(Cache Keys)------>|                    |
   |           <Try with new keys>             |                    |
   |<--(Ticket)----------|                     |                    |
   |---(Resource)------->|                     |                    |

Decrypting Tickets

Recall, the tickets are in the form:

{
  "tags": [tagA, tagB, ...]
  "tickets": [encryptedTicketA, encryptedTicketB, ...]
}

To decrypt the attached tickets, the consumer iterates through his or her entity keys for a specific consumer and attempts to match them against each tickets "tag":

def decryptTickets(producerId, ticketData):
  tags = ticketData["tags"]
  tickets = ticketData["tickets"]
  baseTag = SHA256(tickets)
  tagMap = { tag: ticket for tag, ticket in zip(tags, tickets) }

  for entityKey in entityKeysForProducer(producerId):
    tag = SHA256(baseTag, getEntitySecret(entityKey))
    if tag in tagMap:
      return UnauthDecrypt(entityKey, tagMap[tag])

Revocation

To "rekey" an ACL (replace an ACL), the authentication agent uses proxy encryption to re-encrypt the resource keys with updated entity keys. Basically, we'll use the "Replace Resource Key" method noted above but, when re-randomizing the resource keys, we'll also swap out the entity keys using proxy encryption.

Currently, to revoke access to group G with key Kg, we create a new group G' with key Kg'. To allow authentication agents to update an ACL that use Kg to an ACL that uses Kg', the producer must generate a proxy encryption key that converts messages encrypted under Kg to a message encrypted under Kg'. This will let authentication agents replace superseded group keys.

Additionally, this proxy-encryption scheme will need to be able to mutate the encrypted resource keys (otherwise, consumers would just be able to use previously
).

Finally, unlike the original ACLs, authentication agents will have to be responsible for signing ACLs instead of producers. This is because producers won't be online when the authentication agent makes these new updated ACLs.

Effects Of A Compromise

This section discusses the effects of various possible compromises.

Service

If the service is compromised, its current ACLs will be rendered unusable because the attacker will know the resourceKeys. To get new ACLs, the service will have to ask expire all current ACLs and as the authentication agent to refresh them (re-randomize the resourceKey). It can do this as needed (on access) to avoid overloading the authentication agent.

Unfortunately, a service compromise will also give the attacker enough information to try to brute force the entity keys. This can be mitigated by periodically rotating the entity keys.

The effects of a service break-in could also be mitigated by never giving the service the resourceKey (theoretically, it can check whether or not the user has the resourceKey without ever knowing it but this could be really inefficient).

Authentication Agent

The attacker would learn the members of all groups managed under this authentication agent. Additionally, the attacker would get an encrypted copy of all group keys stored on this authentication agent.

If the break-in is discovered within a reasonable amount of time (where "reasonable" depends on the attacker's resources), entity keys can be re-issued and the system should remain secure.

Producer

TODO

Consumer

TODO

Combinations

TODO

Distribution

This system needs some way to distribute entity keys. Given a "smart" distribution server, each consumer could simply ask for his or her entity keys. However, to simplify things, a "dumb" file server is preferable.

Feeds

The simple solution is to give each entity a "feed" that lists all group entity keys encrypted to this entity (remember, group entities can be members of other group entities). Unfortunately, even if this feed is encrypted with the entity's key, an attacker could learn information about group membership through update times. That is, if userA's "feed" is updated at the same time as userB's there is a reasonable chance that they were just put into the same group.

One possible solution is to make the feed URLs private but this means trusting the distribution server (the feed URLs can't be private to the distribution server).

Predictable Locations

Instead of updating feeds in-place, one could use a cryptographic counter. That is, entity keys encrypted to entity E would be located at:

  1. /SHA256(E.entitySecret+1)
  2. /SHA256(E.entitySecret+2)
  3. /SHA256(E.entitySecret+3) ...

This system would actually require one fewer request per entity key fetch because a feed wouldn't have to be downloaded.

Granularity

Another question is granularity. That is, as groups can be recursive, consumers will need to check n feeds/predictable locations where n is the number of entities (for a particular consumer) to which they belong. This is obviously suboptimal.

A possible solution is to require that the producer generate the feed/predictable locations on a user entity by user entity basis. That is, each user entity will have either a feed or predictable location set which he or she will check for new entity keys. However, this could add quite a bit of overhead on the part of the producer.

Browser Integration

See JavaScript API (out of date).

User Interface

Currently, only: Mockups: Create ACL

What Ifs/Other Ideas

What if someone tries to crack an ACL?

Instead of using authenticated encryption (crackable offline) we can encrypt the resource key with the group key using with unauthenticated encryption and then in turn, encrypt the challenge with the resource key with unauthenticated encryption.

If we do this we'll have to attach a certificate (signed by the producer) that states:

  1. This ACL may be used by domain
  2. This ACL was issued by producer

Additionally, producers will have to verify that they are talking to domain using some other channel (such as SSL).

What if an ACL expires (or a contained group expires) and the authentication agent is gone?

We can't do anything. However, we can allow both expirable and non-expirable ACLs.

Expiration without revealing groups.

We might be able to do this with many-to-one public-private keys.

Simpler Authentication Schemes

Trust the service:

Give every consumer a certificate stating:

The bearer of this certificate is in group X until EXPIRATION.

The consumer would be able to access group X's resources by presenting the certificate to the service.

To prevent users from knowing which groups they are in, this certificate could be encrypted with a secret key known only to trusted services.

However, this tells the service quite a bit.

Preventing Resource->Group matching

With this naive system, users can match certificates to resources which means that they can group resources by group (even if they don't know the name of the group).

To prevent this, these certificates could be merged into a single (expiring) membership certificate:

The bearer of this certificate is in groups X, Y, Z... until EXPIRATION.

Unfortunately, this tells services about groups they don't need to know about. This can be fixed by hashing the group names:

The bearer of this certificate is in groups hash(A||salt), hash(B||salt), hash(C||salt)... with salt 'salt' until EXPIRATION.

This means that a service can only about a group if it knows it's name.

Additionally, this makes it easier for the service to uniquely identify users because the service knows why a user can access a resource.

Trust the authentication agent:

If we're willing to use (and trust) the authentication agent for every request, we can get (almost) everything we want.

When accessing a resource, the consumer would write the statement:

I am x trying to access y.

And sign this with his or her (the consumer's) secret key and encrypt it with the producer's public key.

The consumer would give this to the service which would then ask the authentication agent:

Should encrypted(signed(I am x trying to access y)) be able to access y?

This way, only the authentication agent knows about the groups, users, etc.

However, (a) the authentication agent will be able to tell if/when a service tests a user's to access a resource, (b) the authentication agent will have to be online, and (c) the authentication agent will have to be trusted.

Note: This does not mean that the producer will (necessarily) know about resource accesses. The authentication agent could hide this from the producer and the service could (maybe) obfuscate this information by testing if a consumer can access resources that the consumer doesn't explicitly request.

Brute forcing Protection

This system has some brute forcing prevention. If an ACL is not replicable and