Active Cryptography: OpenPGP over Activity Streams 2.0
Table of Contents
1 Introduction
Version: | 0.0.1-draft-003 |
Author: | Ben McGinnes <ben@gnupg.org> |
Author GPG Key: | DB4724E6FA4286C92B4E55C4321E4E2373590E5D |
Language: | Australian English, British English |
Language code: | en-AU, en-GB, en |
This document provides a specification for using OpenPGP cryptography with the Activity Streams 2.0 transport method. It was devised with particular attention towards providing end-user encryption and verification on federated ActivityPub based instances (e.g. Mastodon and Pleroma).
This proposal is not an official part of the W3C's protocols, but is offered as an optional means of addressing some of the security issues identified as lacking or missing in those protocols. As such it is offered under the same terms as any IETF or W3 Consortium standards or proposals as free for any use. Example code, however, may be released under the same terms as the GnuPG Project or some other license as relevant. Example code will be provided separately from this document.
1.1 Motivation
The current Presidential Administration in the USA has diverged considerably from the policies of his predecessors with certain legislative and regulatory changes which are set to enable a far greater implementation of authoritarian policies and agendas, as well as enabling those policies to be enforced beyond the territory of the United States of America. This sets a dangerous precedent with regards to the freedom of all people around the globe to communicate freely and privately, particularly when they may become subject to matters which are entirely legal where they live, but which the United States legislates against.
The legislative and regulatory changes in the United States of most concern to the rest of the world at the present time are: the removal of Net Neutrality provisions by the FCC, the CLOUD Act and the SESTA-FOSTA Act. The latter being the attempt to enforce American laws regarding adult content, primarily of a sexual nature, globally. They also remove the “safe harbour” provisions which previously permitted hosting providers to ignore what their customers were doing, in turn making those providers tools of the state who must police the actions of end users and actively censor them.
Since the first draft of this protocol extension was written, the types of wholesale legislative threats to privacy have been exacerbated by Australia's efforts to “double down” on the arbitrary powers overreach in passing the Telecommunications and Other Legislation Amendment (Assistance and Access) Bill 2018.
1.2 Contributors and Attribution
Obviously the creators of the ActivityStreams 2.0 and ActivityPub protocols have already contributed significantly with those protocols. Those people are: Christopher Lemmer Webber, Jessica Tallon, Evan Prodromou, James M. Snell, Amy Guy and Erin Shepherd.
Additionally the lead developers of the two most popular ActivityPub implementations, Eugen Rochko (Mastodon) and Lain Soykaf (Pleroma), have been very welcoming of a proposal seeking to supply a comprehensive solution to a number of the frequently requested features in their two projects.
Feedback on the first draft has been very valuable; especially from Wiktor Kwapisiewicz. Particularly with regards to the discussions around the WebFinger protocol and related matters.
Feedback on the second draft has also been valuable, particularly the input from Peter Gutmann and Stephen Farrell via the IETF OpenPGP Working Group and mailing list.
Input and encouragement from David Ross (Mozilla), Eliza (Assembly Four, Switter), Emelia (Unobvious Technology) and Vince Werner has been a boon.
1.2.1 Additional Thanks
The following people have provided non-technical support of various types during the course of designing and writing this protocol extension: Chamara “Mara” Caldera, Trindy Oakley and Michael “Elgy” Eldritch.
Thanks are also due to Werner Koch for not even batting an eyelid at what could arguably have been a bit of a tangent from my work on GnuPG and GPGME in early 2018 in order to devise precisely how to apply OpenPGP to a whole new transport protocol.
1.3 Approach
Over the course of the last decade or a little more, a great deal of communication online has shifted towards using social media networks. Email is still good for many things, but it is not good for everything and various types of social networks fill that need.
Modifying the underlying protocols or specifications of proprietary networks, such as Twitter and Facebook, is generally not possible. It is also clear that these networks will act against some or even all of their own user base in order to achieve the goals of those running the companies in question.
In the case of Facebook that is in the form of surveillance of large populations and subsequent manipulation of them. In the case of Twitter it is in the form of banning those who vehemently oppose Nazism or who discuss or promote adult entertainment of various types, primarily pornography and sex work, regardless of the jurisdiction to which the end user is actually subject.
Open standards and protocols, however, can be leveraged freely and as necessary. This is what Phil Zimmermann did back in 1991 when he released the first version of Pretty Good Privacy for use with Email, USENET and, very likely, FidoNet (or FidoNet style) BBS networks. The same approach may be utilised now with social networks which themselves provide an open specification and where that specification provides the means for extending or advancing itself.
There is clearly grounds for social network users to have access to the tools to send and receive end-to-end encrypted private messages via their social network accounts and identities. Likewise there is a need for end users to be able to prove, should they wish to do so, that a message was not modified in transit; either by their own server or by another server within the federated networks in use.
This current proposal applies to the W3 Consortium's Activity Streams 2.0 and ActivityPub protocols; the latter being based upon the former.
1.4 Cryptographic Implementation Choice
The cryptographic choice with regards to the GnuPG Project was limited to the two engines which GnuPG currently supports: OpenPGP and S/MIME. Since the intended outcome of this proposal is to provide end users with a means of securing content or preventing content manipulation, the OpenPGP model was selected.
It would, however, be possible to switch the security focus to the server level in order to utilise some future advancement. This may necessitate or simply favour utilising a different cryptographic implementation or method. As a consequence this proposal is designed to more easily enable swapping one method for another.
Note that this is separate from and in addition to the use of a PEM key by ActivityPub servers for each of their users. In those cases the private key is generated by the ActivityPub server when the user account is created. As a consequence it is inherently flawed from a user security perspective. It does, however, move the complexity out of the user level and back to the server level. Whereas this proposal does not.
1.5 Definitions
This document uses the terms defined in RFC 4880 and in the same way.
The key words: "must", "must not", "required", "shall", "shall not", "should", "should not", "recommended", "may", and "optional" to be interpreted as defined in RFC 2119.
The document also draws on the same RFCs cited by both the Activity Streams core and vocabulary documents, as well as the ActivityPub protocol definition.
2 Cryptographic Activities
This section introduces the new objects, collections, activity types and properties necessary to implement OpenPGP functions with Activity Streams 2.0 and ActivityPub.
2.1 Cryptographic protocol
In order to handle any situations in which servers and/or clients may implement multiple cryptographic protocols, a property must be set for any cryptographic object or activity.
{"cryptographic-protocol": "openpgp"}
Where the relevant JSON data is already clearly part of a
cryptographic object or activity this proprty may be defined as
protocol
.
{"protocol": "openpgp"}
2.1.1 OpenPGP Protocol
When integrating OpenPGP with Activities or Objects, consideration must be given to both the versions in use throughout the network and setting sensible minimum requirements so as not to adversely affect the rest of the network.
For this reason the current standards defined in RFC 4880 must be implemented, while the recommendations of RFC 4880bis should be available. Though a number of older versions of the standard may be available with any given implementation, any older standard for which existing recommendations state not to use them due to security related issues then those older standards must not be used.
2.2 MIME and file types
The media or content types utilised are adapted from the PGP/MIME
types defined in RFC 2015 and RFC 3156. Specifically this covers the
pgp-keys
, application/pgp-encrypted
and application/pgp-signed
MIME types.
In addition to these an implementation may utilise
application/pgp-encrypted+activitystreams
and may utilise
application/pgp-signed+activitystreams
to indicate an Activity
Stream object (i.e. an application/activity+json
object) is either
entirely affected by the cryptographic function or the object is
OpenPGP data which contains an ActivityPub or Activity Streams object
or activity type which will need to be processed upon decryption or
signature validation.
2.3 Keys
Unlike the PEM key included with ActivityPub instances, OpenPGP keys are always intended to be generated by the end user(s) controlling a given actor's account and not controlled or accessed by the server, even when that server is controlled by a single user.
There are also valid reasons or use cases for assigning multiple keys to an actor or using the same key with multiple actors. This is particularly the case if proof of OpenPGP key control was adopted as an alternative means of providing authentication between a client and server, in addition to OAuth methods.
Though there is already a well established network of public keyservers, the SKS keyserver pool, and from GnuPG 2.1 there is the OpenPGP Web Key Directory (WKD); there are also valid reasons for not using these methods of providing access to a public key used with activities.
Likewise, there is a need for serving key information with actor information and referencing it with objects and activities where necessary. This would effectively turn an ActivityPub instance into a limited public keyserver for the keys assigned to actors under its purview, though it may not maintain or serve copies of those keys containing full web-of-trust signatures, particularly if there are size constraints or bandwidth limitations.1
2.3.1 Public keys and Actors
In order to enable access to cryptographic information controlled at
the user level we need to add an optional property to actors; one
where the absence of it equates to a value of null
.
Since it is theoretically possible for multiple cryptographic
protocols to be in use, in addition to the Linked Data and HTTP
Signatures referenced in the ActivityPub specification, this optional
property must contain an array of JSON data listing the protocol
or cryptographic-protocol
, the cryptoContext
for a URI of a
collection containing more relevant data, the publicKeys
for an
additional URI just for checking public key data and may contain a
primaryKeyID
referencing the preferred key ID used with the actor.
Here is an example using the same actor example in the ActivityPub specification. Note that the key ID or fingerprint used here does not exist on the keyservers and is really just a SHA1 sum of the actor's name.
{ "@context": ["https://www.w3.org/ns/activitystreams", {"@language": "ja"}], "type": "Person", "id": "https://kenzoishii.example.com/", "following": "https://kenzoishii.example.com/following.json", "followers": "https://kenzoishii.example.com/followers.json", "liked": "https://kenzoishii.example.com/liked.json", "inbox": "https://kenzoishii.example.com/inbox.json", "outbox": "https://kenzoishii.example.com/feed.json", "preferredUsername": "kenzoishii", "name": "石井健蔵", "summary": "この方はただの例です", "icon": [ "https://kenzoishii.example.com/image/165987aklre4" ], "cryptoProtocols": [ { "protocol": "openpgp", "cryptoContext": "https://kenzoishii.example.com/openpgp.json", "publicKeys": "https://kenzoishii.example.com/openpgpkeys.json", "primaryKeyID": "3A1222F4BE79DB2AF069FADCF507B8E7E6EF68BF" } ] }
A slight variation demonstrating how multiple cryptographic implementations could be utilised along with not specifying a primary key ID may appear more like this:
{ "@context": ["https://www.w3.org/ns/activitystreams", {"@language": "ja"}], "type": "Person", "id": "https://kenzoishii.example.com/", "following": "https://kenzoishii.example.com/following.json", "followers": "https://kenzoishii.example.com/followers.json", "liked": "https://kenzoishii.example.com/liked.json", "inbox": "https://kenzoishii.example.com/inbox.json", "outbox": "https://kenzoishii.example.com/feed.json", "preferredUsername": "kenzoishii", "name": "石井健蔵", "summary": "この方はただの例です", "icon": [ "https://kenzoishii.example.com/image/165987aklre4" ], "cryptoProtocols": [ { "protocol": "openpgp", "cryptoContext": "https://kenzoishii.example.com/openpgp.json", "publicKeys": "https://kenzoishii.example.com/openpgpkeys.json", "primaryKeyID": "3A1222F4BE79DB2AF069FADCF507B8E7E6EF68BF" }, { "protocol": "openquantum", "cryptoContext": "https://kenzoishii.example.com/openquantum.json", "publicKeys": "https://kenzoishii.example.com/openquantumkeys.json" } ] }
In this example of the near-ish future OpenPGP usage is complemented by advances in Quantum Cryptography and the development of the FOSS Quantum Privacy Guard (QPG) with the standard being developed right along side it.2
2.3.2 Cryptography Context
The cryptography contexts referenced from the actor define all the ways in which any key or keys are used in relation to actions and objects by or for that actor. First by identifying the keys and subkeys and then by defining which type of objects they're used in relation to. As well as whether the account is configured to always use them, as may be the case with signatures or not.
The Cryptography Context is a collection of nested collections and objects dealing with each key or subkey type and the ways they're used in regards to activities or other objects.
The following examples use a key created in the name of the same
fictional character used in the GPGME Python Bindings HOWTO in
conjunction with an imaginary ActivityPub instance on an example
domain with a thematically related subdomain, not.secret.example.com
.
The keys
item must contain a keyinfo
item for each public key
associated with the actor account.
The keyinfo
item must contain keyIDs
data for the primary key
and all enabled subkeys of the key.
The keyinfo
item must contain a type
property which indicates
both the key's cryptographic protocol and version number of that
protocol. Most current OpenPGP keys are version 4 keys.
The keyinfo
item may contain keyIDs
data for revoked or
disabled keys previously used with the actor or revoked subkeys of
an active key. Where this data is included the keyID
item must
contain an enabled
property with a boolean value of true or
false. Additionally a revoked
property may be included, also
with a boolean value of true or false.
Where the enabled
and revoked
properties are not included, the
default values are assumed to be that enabled
is true and
revoked
is false.
The keyinfo
item may contain userIDs
data for some or all of the
userIDs listed on the key itself.
The keyinfo
item may contain a keyfiles
property with direct
links to either or both of the GnuPG or PGP binary key formats or the
ASCII armored key file format.
The keyinfo
item must contain the publicKeys
property pointing
to a JSON encoded URL containing at least the minimised version of the
public key. Alternatively the publicKeys
property may point to an
array in which the first item is the JSON encoded URL containing key
material. The subsequent items in such an array may point to either
or both of URLs or URIs for accessing the keys via WebFinger or via
the Web Key Directory.
A keyID
item must contain an id
property of the full key ID
which is the hexadecimal key fingerprint without spaces. The id
property must not be either the short or long key ID formats.
A keyID
item must contain a type
property with a value
indicating whether the key is the primary (certification) key or a
subkey.
A keyID
item may contain a fingerprint
property with the full
key ID in a human readable format. This is the fingerprint format
which most OpenPGP users will be familiar with and normally presents
the fingerprint with spaces between hexadecimal groupings of four
characters each.
A keyID
item must contain an algorithm
property with a value
indicating which asymmetric cryptographic algorithm or whether the
key utilised elliptic curve cryptography (as ECC
).
If the algorithm
property has a value of ECC
then the keyID
item
must also include a curve
property with a value of the specific
elliptic curve in use. If the algorithm
property contains a value
specifying an asymmetric cryptographic algorithm then the curve
property may be omitted. If the curve
property is not omitted,
but the algorithm
property contains an asymmetric algorithm then the
curve
property must be null
.
A keyID
item must contain a size
property with an integer value
of the bit size of the key or subkey.
A keyID
item must contain properties for each of the four
capabilities a key or subkey may possess: certification
,
encryption
, signing
and authentication
. The values for each
property are boolean strings; true or false.
A keyID
item must contain a timestamp
property with an integer
value of the number of seconds since the epoch since the key or subkey
was last modified. This will usually be the timestamp of the key's
creation, but may indicate some other modification such as changing an
expiration date or revoking the key or subkey.
The remaining items address the three basic functions for which OpenPGP keys can be used with Activity Streams: signing, encryption and authentication. In addition to those three functions and policies, additional use case policies may be appended: refreshing a key from the keyservers, encrypting email notifications regarding activities to the relevant email address for the actor account.3
Each of these items must include a policy
property which
stipulates whether or not that function is available and the
consistency of that use. Possible policy values are must, may
and never. Recommended default values are may unless the
relevant key or subkey type is unavailable, in which case the correct
value is never.
If the policy value for an item is either must or may then the
authorizedKeyIDs
property must include an array with all full key
IDs of the primary key and relevant subkeys to perform that task. If
the policy value is never then the authorizedKeyIDs
may be
null
.
{ "@context": "https://www.w3.org/ns/activitystreams", "id": "https://not.secret.example.com/openpgp.json", "summary": "OpenPGP use and keys with this stream", "type": "openpgpCollection", "cryptographic-protocol": "openpgp", "totalItems": 6, "items": [ { "type": "openpgpKeys", "totalItems": 1, "items": [ { "id": "keyinfo", "type": "openpgpKeyV4", "timestamp": 1546043571, "lastUpdated": 1546039861, "keyIDs": [ { "id": "C2FA40FD7A2E6DDB7A4FDFCB1A7425A225C3EF1F", "type": "primary", "fingerprint": "C2FA 40FD 7A2E 6DDB 7A4F DFCB 1A74 25A2 25C3 EF1F", "algorithm": "ECC", "curve": "ed25519", "size": 256, "certification": true, "signing": true, "encryption": false, "authentication": false, "timestamp": 1546039687 }, { "id": "681CBF37BE8ED04CB20BD5D0483F423E32DD79A8", "type": "subkey", "fingerprint": "681C BF37 BE8E D04C B20B D5D0 483F 423E 32DD 79A8", "algorithm": "ECC", "curve": "cv25519", "size": 256, "certification": false, "signing": false, "encryption": true, "authentication": false, "timestamp": 1546039819 }, { "id": "65DF3A3814D3BADC5E7D68F48D87C4418347F2BB", "type": "subkey", "fingerprint": "65DF 3A38 14D3 BADC 5E7D 68F4 8D87 C441 8347 F2BB", "algorithm": "ECC", "curve": "ed25519", "size": 256, "certification": false, "signing": true, "encryption": false, "authentication": false, "timestamp": 1546039861 } ], "userIDs": [ { "name": "Danger Mouse", "comment": "Social: @dm@not.secret.example.com", "email": "dm@secret.example.net" } ], "keyfiles": [ { "url": "https://agents.secret.example.net/dm-key.asc", "Content-Type", "application/pgp-signature", "summary": "ASCII armored openpgp keyfile, full key" }, { "url": "https://agents.secret.example.net/dm-key.gpg", "Content-Type", "application/pgp-keys", "summary": "Binary openpgp keyfile, full key" }, { "url": "https://agents.secret.example.net/dm-key-clean.asc", "Content-Type", "application/pgp-signature", "summary": "ASCII armored openpgp keyfile, clean key" }, { "url": "https://agents.secret.example.net/dm-key-clean.gpg", "Content-Type", "application/pgp-keys", "summary": "Binary openpgp keyfile, clean key" }, { "url": "https://agents.secret.example.net/dm-key-min.asc", "Content-Type", "application/pgp-signature", "summary": "ASCII armored openpgp keyfile, minimised key" }, { "url": "https://agents.secret.example.net/dm-key-min.gpg", "Content-Type", "application/pgp-keys", "summary": "Binary openpgp keyfile, minimised key" } ], "publicKeys": "https://not.secret.example.com/openpgpkeys.json" } ] }, { "type": "content-signing", "policy": "May", "authorizedKeyIDs": [ "C2FA40FD7A2E6DDB7A4FDFCB1A7425A225C3EF1F", "65DF3A3814D3BADC5E7D68F48D87C4418347F2BB" ] }, { "type": "encryption", "policy": "May", "authorizedKeyIDs": [ "C2FA40FD7A2E6DDB7A4FDFCB1A7425A225C3EF1F", "681CBF37BE8ED04CB20BD5D0483F423E32DD79A8" ] }, { "type": "authentication", "policy": "Never", "authorizedKeyIDs": null }, { "type": "refresh" "policy": "May", "authorizedKeyIDs": [ "C2FA40FD7A2E6DDB7A4FDFCB1A7425A225C3EF1F" ] }, { "type": "email-encryption", "policy": "Must", "authorizedKeyIDs": [ "C2FA40FD7A2E6DDB7A4FDFCB1A7425A225C3EF1F", "681CBF37BE8ED04CB20BD5D0483F423E32DD79A8" ] } ] }
There are numerous ways in which OpenPGP may be leveraged by a server to provide authentication mechanisms for an actor utilising either signatures, encrypted tokens to be decrypted and used like OAuth or even using the authentication subkey type in a manner similar to TLS or SSH. For this example these possibilities are disregarded in order to demonstrate how a policy may be set to not use one possible function.
A server might also use the public keys in a more traditional manner for OpenPGP if end users receive email notifications of activites. In that circumstance the server could, if the public key had a subkey with the encryption capability and the relevant matching policy, encrypt those emailed notifications.
Also note that while default and recommended key generation stipulates that OpenPGP primary (certification) keys should not have the encryption capability, it is still advisable to include that primary key ID as authorized for any function granted to any of its subkeys. The reason being that not every OpenPGP implementation correctly interprets the relationship between the primary key and those subkeys (e.g. some of the JavaScript implementations). By explicitly including the primary as authorized, even for those tasks for which it does not have the capability we avoid unnecessary false error reports with certain OpenPGP implementations.
If an actor has multiple keys assigned to it, it should be permitted to extend the policy section to provide for different policies for each key.
For instance it may be preferred to have one main key which is always
refreshed from the keyservers, but a backup key which is only updated
manually by an end user. The following example demonstrates how a
single type can be expanded to cover multiple policies. Where there
is only one policy, as in the larger example above it is assumed that
the policies
property has a value of 1
and may be omitted.
{ "type": "email-encryption", "policies": 2, { "policy": "Must", "authorizedKeyIDs": [ "C2FA40FD7A2E6DDB7A4FDFCB1A7425A225C3EF1F", "681CBF37BE8ED04CB20BD5D0483F423E32DD79A8" ] }, { "policy": "May", "authorizedKeyIDs": [ "DB4724E6FA4286C92B4E55C4321E4E2373590E5D", "9CBEF6B7E0DF72CF91009AA5C98BAA1862E4484D" ] } }
Note that the second key listed here is that of the principal author of this proposal and thus secret key material for that key will never be provided; unlike the example key for “The Greatest Secret Agent in the World: Danger Mouse.”
While the secret key and paassphrase for these examples will be published in supplemental files. This document will also contain copies of the session keys used with encrypted examples; in case this document is distributed separately from the supplemental files.
2.3.3 Serving Public Keys Directly
The openpgpKeys.json
file contains a lot of matching data to the
main context file by necessity since both need to include the key ID
data and both will usually include some user ID data. Both of which
being data about the public key which is available from the public key
itself. The main differences, however, are that the context file
provides the information on the circumstances under which the public
key either can, should or must be used; but does not include a copy of
the public key itself. While the other file only has data about the
key itself and a copy of at least the minimised key (or keys if there
are multiple keys assigned to an actor or stream).
{ "@context": "https://www.w3.org/ns/activitystreams", "id": "https://not.secret.example.com/openpgpkeys.json", "stream": "https://not.secret.example.com/", "summary": "OpenPGP public keys for this stream.", "type": "openpgpKeys", "cryptographic-protocol": "openpgp", "totalItems": 1, "items": [ { "type": "openpgpKey", "keyVersion": 4, "totalItems": 2, "lastUpdated": 1524951377, "items": [ { "type": "openpgpKeyData", "timestamp": 1514332912, "keyIDs": [ { "id": "C2FA40FD7A2E6DDB7A4FDFCB1A7425A225C3EF1F", "type": "primary", "fingerprint": "DB47 24E6 FA42 86C9 2B4E 55C4 321E 4E23 7359 0E5D", "cipher": "RSA", "curve": null, "size": 4096, "certification": true, "signing": true, "encryption": false, "authentication": false, "timestamp": 1343480251 }, { "id": "B7F0FE759387430DD0C58BDB7FF2D37135C7553C", "type": "subkey", "fingerprint": "B7F0 FE75 9387 430D D0C5 8BDB 7FF2 D371 35C7 553C", "cipher": "RSA", "curve": null, "size": 3072, "certification": false, "signing": true, "encryption": false, "authentication": false, "timestamp": 1343480419 }, { "id": "681CBF37BE8ED04CB20BD5D0483F423E32DD79A8", "type": "subkey", "fingerprint": "681C BF37 BE8E D04C B20B D5D0 483F 423E 32DD 79A8", "cipher": "ECC", "curve": "cv25519", "size": 256, "certification": false, "signing": false, "encryption": true, "authentication": false, "timestamp": 1343480559 } ], "userIDs": [ { "name": "Danger Mouse", "comment": "Social: @dm@not.secret.example.com", "email": "dm@secret.example.net" } ], "keyfiles": [ { "url": "https://agents.secret.example.net/dm-key.asc", "Content-Type", "application/pgp-signature", "summary": "ASCII armored openpgp keyfile, full key" }, { "url": "https://agents.secret.example.net/dm-key.gpg", "Content-Type", "application/pgp-keys", "summary": "Binary openpgp keyfile, full key" }, { "url": "https://agents.secret.example.net/dm-key-clean.asc", "Content-Type", "application/pgp-signature", "summary": "ASCII armored openpgp keyfile, clean key" }, { "url": "https://agents.secret.example.net/dm-key-clean.gpg", "Content-Type", "application/pgp-keys", "summary": "Binary openpgp keyfile, clean key" }, { "url": "https://agents.secret.example.net/dm-key-min.asc", "Content-Type", "application/pgp-signature", "summary": "ASCII armored openpgp keyfile, minimised key" }, { "url": "https://agents.secret.example.net/dm-key-min.gpg", "Content-Type", "application/pgp-keys", "summary": "Binary openpgp keyfile, minimised key" } ] }, { "keyblockASCII": "-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nmDMEXCaxhxYJKwYBBAHaRw8BAQdAMSVyt57XCqdve8pvgC4BDkj+BYq6xKlsdMua\nIYiKl+y0SURhbmdlciBNb3VzZSAoU29jaWFsOiBAZG1Abm90LnNlY3JldC5leGFt\ncGxlLmNvbSkgPGRtQHNlY3JldC5leGFtcGxlLm5ldD6ImQQTFgoAQRYhBML6QP16\nLm3bek/fyxp0JaIlw+8fBQJcJrGHAhsDDAsKDQkMCAsHBAEDAgcVCgkICwMCBRYC\nAwEAAh4BAheAAAoJEBp0JaIlw+8fR0wBAOLXF7eYegcI4w21BsceE669hpwHBl6b\n5G5/dJQObkSkAP0Vdx7+CyIMJwAqDesQtnUKrxLp1TEsR3FWXmPO5fAvB7g4BFwm\nsgsSCisGAQQBl1UBBQEBB0AyMCTt0r9Gvth5whz4ED8znHo9KqR0AVjftlG86xe0\nLgMBCAeIeAQYFgoAIBYhBML6QP16Lm3bek/fyxp0JaIlw+8fBQJcJrILAhsMAAoJ\nEBp0JaIlw+8fRoQBANjV8BrkS0EzIcQpG8xgsfQESYGsj/B59h9QdL7eS6q5AP9V\nV/6JC2wBwzL38B4accNW/lNPDAMfS3LgqvQnAfgeDbgzBFwmsjUWCSsGAQQB2kcP\nAQEHQLiOaYOPCSEIc1SLk1dM/XbTr5+bIl1DPduKbl2aWsaTiO8EGBYKACAWIQTC\n+kD9ei5t23pP38sadCWiJcPvHwUCXCayNQIbAgCBCRAadCWiJcPvH3YgBBkWCgAd\nFiEEZd86OBTTutxefWj0jYfEQYNH8rsFAlwmsjUACgkQjYfEQYNH8rtiewEAp+wJ\nvMc3Qq8hv372nNVdzE7TySjvJpy05DmtPcbAZ4UBAMrpR6MadshSM7NZvlBAyhPl\n7YmogPQ2N28Ja3kX8l4GIMYBAKRzQtRVH+NlOA0tvPO2wcBYXJhSQF0k/S8EnzZu\nYMmwAP4h+e4ytRt5yUupjFRM+S4OY7rMRTAY0eeu8rJwBeLrAw==\n=0Eei\n-----END PGP PUBLIC KEY BLOCK-----\n" } ] } ] }
Note the main timestamp
is the date the key itself was last modified
and will usually match the timestamp of the last subkey to be added or
the timestamp of the most recent self-certification of a key. Whereas
the lastUpdated
property notes the last time the copy of the public
key was updated on the server serving that data. Such an update
should normally be the result of a client uploading the key to the
actor account, but may be the result of the server refreshing key
data from the SKS keyserver network or the Web Key Directory service.
2.3.4 Serving Public Keys Via WebFinger
An alternative approach to using the openpgpkeys.json
file defined
in the previous section is to instead direct key retrieval traffic to
the existing WebFinger service utilised by ActivityPub and defined in
RFC 7033.
In that case this part of the specification:
The <code>keyinfo</code> item <b>must</b> contain the <code>publicKeys</code> property pointing to a JSON encoded URL containing at least the minimised version of the public key. Alternatively the <code>publicKeys</code> property <b>may</b> point to an array in which the first item is the JSON encoded URL containing key material. The subsequent items in such an array <b>may</b> point to either or both of URLs or URIs for accessing the keys via WebFinger or via the Web Key Directory.
Would need to be modified slightly to something more akin to this:
The <code>keyinfo</code> item <b>must</b> contain the <code>publicKeys</code> property pointing to a JSON encoded WebFinger URL containing links to the relevant OpenPGP key or keys associated with the account. Alternatively the <code>publicKeys</code> property <b>may</b> point to an array in which the first item is the WebFinger URL. The subsequent items in such an array <b>may</b> point to either or both of URLs or URIs for accessing the keys directly or via the Web Key Directory.
As this transport protocol is ultimately driven by HTTP/S traffic, it
does not matter so much if the distribution of key data occurs in a
binary format (e.g. .pgp
, .gpg
and .sig
files). As a
consequence there is less need to serve Radix64 encoded versions of
those binary formats (e.g. .asc
files) for key distribution.
As the WebFinger specification already utilises existing rel values
and as there is already a pgpkey
value specifically serving OpenPGP
key data, leveraging this here ought to be fairly straight forward.
All the WebFinger service requires is a means of identifying each key and possibly subkeys, along with accessing those keys. Both of which are simple enough to deliver with a URL for a key file and the fingerprint(s) of the keys and subkeys.
Utilising the WebFinger specification here provides the additional advantage that other, otherwise unrelated, services or software may benefit from accessing these OpenPGP keys and subsequently enhancing privacy features.
2.3.5 Serving Public Keys Via Web Key Directory
An alternative to both serving key data directly and linking to keys via the WebFinger service is to incorporate the Web Key Directory service into the specification.
In that case this part of the specification:
The <code>keyinfo</code> item <b>must</b> contain the <code>publicKeys</code> property pointing to a JSON encoded URL containing at least the minimised version of the public key. Alternatively the <code>publicKeys</code> property <b>may</b> point to an array in which the first item is the JSON encoded URL containing key material. The subsequent items in such an array <b>may</b> point to either or both of URLs or URIs for accessing the keys via WebFinger or via the Web Key Directory.
Would need to be modified slightly to something more like this:
The <code>keyinfo</code> item <b>must</b> contain the <code>publicKeys</code> property pointing to a Well Known URI matching the Web Key Directory format and which links to a key or keys associated with the account. Alternatively the <code>publicKeys</code> property <b>may</b> point to an array in which the first item is the Web Key Directory URI. The subsequent items in such an array <b>may</b> point to either or both of URLs or URIs for accessing the keys directly, or the URL of a JSON encoded WebFinger file.
Though the Web Key Directory service may very well prove to be the ultimate replacement for the SKS keyserver network, it is not yet a finalised specification. As a consequence it is currently recommended as an optional supplementary key discovery method.
2.4 Signatures
Signing activities as a means of providing assurance that they genuinely originate with the client and have not been modified in transit will probably be one of the most common uses of these functions.
There are, however, issues with the possibility that a server may render the content differently to the author's system or sanitize the content in an unexpected manner. Also the author might use another content format (e.g. Markdown) which is intended to be rendered into HTML by the server.
The solution to this problem is a new object type, the Signed Note.
A Signed Note must contain a source
property containing the
original data transmitted, even if the mediaType is text/html
as the
server might still render it differently.
A Signed Note must contain a signatures
property which must
specify the protocol and must include a detached signature file for
the source data.
The scope
property specifies which source properties were signed,
usually this should only be the subject and content or just the
content.
The signatures
property may include a signature for the expected
rendered output. As with the source signature, the scope
property
specifies which rendered output properties were signed.
Since the order will matter with regards to the scope
a signedData
property must be included with with each signature.
This is followed by the detached signature
in ASCII armored
(radix64) format and some additional data pertaining to the key or
subkey used to sign the data as signingKeyID
, the algorithms used as
the pubkeyAlgorithm
and the digital hashAlgorithm
, and the
timestamp
of the signature.
It should be possible for anyone with the Signed Note object to take
the signedData and the detached signature, save them both to files and
then manually verify them with OpenPGP compliant software (e.g. gpg
or gpg.exe
).
{ "@context": ["https://www.w3.org/ns/activitystreams", { "@language": "en" } ], "type": "Signed Note", "id": "https://not.secret.example.com/agents/dm/posted/thing", "subject": "GnuPG rocks", "content": "<p>So, what <em>should</em> be signed, what was written or what was rendered?</p>", "source": { encryption "subject": "GnuPG rocks", "content": "So, what *should* be signed, what was written or what was rendered?", "mediaType": "text/markdown" }, "signatures": { "cryptographic-protocol": "openpgp", { "scope": { "source": ["subject", "content"] }, "signedData": "GnuPG rocksSo, what *should* be signed, what was written or what was rendered?", "signature": "-----BEGIN PGP SIGNATURE-----\n\niHUEABYIAB0WIQRl3zo4FNO63F59aPSNh8RBg0fyuwUCXCl4HgAKCRCNh8RBg0fy\nu6fZAQDqCKlaQRmIBdZgoHmMHDBU6KO/vw6iW5q/PYKChBM5dwEAv5UPYNY33mKh\n/CFvwLnZ0j+pVGgsuEidp5J1zk5JgA0=\n=xHh0\n-----END PGP SIGNATURE-----\n", "signingKeyID": "65DF3A3814D3BADC5E7D68F48D87C4418347F2BB", "pubkeyAlgorithm": "EDDSA", "hashAlgorithm": "SHA256", "timestamp": 1546221598 }, { "scope": { "expectedRender": ["subject", "content"] }, "signedData": "GnuPG rocks<p>So, what <em>should</em> be signed, what was written or what was rendered?</p>", "signature": "-----BEGIN PGP SIGNATURE-----\n\niHUEABYIAB0WIQRl3zo4FNO63F59aPSNh8RBg0fyuwUCXC6NDQAKCRCNh8RBg0fy\nuyH6AQDOD7QfcYfPx6xpHKRsv6SzDijNXOS3vq1qaIYRkY/a/AEAyvn6uwRSJ1L5\nKEEKIvWhFsJoFJf0RCIraxoNlyWnvQ0=\n=Qea9\n-----END PGP SIGNATURE-----\n", "signingKeyID": "65DF3A3814D3BADC5E7D68F48D87C4418347F2BB", "pubkeyAlgorithm": "EDDSA", "hashAlgorithm": "SHA256", "timestamp": 1546554637 } } }
2.5 Encryption
Encrypting activity content or content and subjects will meet the needs of many feature requests on numerous instances. There are, however, some variations of methods which may be worth examining, along with issues pertaining to availability of metadata and what options, if any, exist for providing any measure of forward secrecy.
There are multiple issues to be addressed when dealing with encrypted activities, objects or portions of either. Some of these issues relate to whether the ciphertext contains additional embedded JSON data to be interpreted or rendered by the recipient upon decryption, while others relate more to the addressing or total number of recipients or how to treat data when not all the intended recipients have a public ky available.
Still, one problem it readily solves is in providing end-to-end encrypted messages between two single actors.
2.5.1 Encrypted Private Messages
There are essentially two methods of sending an encrypted private message: one in which the encrypted content is just the message being sent, which may contain content or markup intended to be parsed or rendered at the recipient's end; and the other being when the encrypted content contains embedded JSON data matching the Activity Streams 2.0 specification and possibly the ActivityPub specification to be interpreted by software at the recipient's end.
Regardless of which it is, the sending of it requires another new ActivityPub object, the Encrypted Note.
The Encrypted Note must contain an encrypted
property.
The encrypted
property may contain a subject
property.
The encrypted
property must contain a content
property in which
the encrypted data is inserted in radix64 ASCII armored format.
The encrypted
property should contain a mediaType
property with
a value of application/pgp-encrypted
or
application/pgp-encrypted+activitystreams
.
The encrypted
property may contain a signingKeyID
property
containing the id
of the key used to sign the encrypted content, if
any. Alternatively the signingKeyID
property may be an array of
multiple keys or subkeys if more than one key was used to sign the
data.
The encrypted
property may contain a recipientKeyIDs
property
containing an array of the key IDs to which the encrypted data has
been encrypted. If the recipients have been hidden then the
recipientKeyIDs
property may be excluded or explicitly set to
either null
or hidden.
The encrypted
property must contain a cipher
property with a
value of the symmetric cipher used to encrypt the content
data.
The encrypted
property must contain an encryptedAlgorithm
property containing a value of the asymmetric encryption or elliptic
curve algorithms of the recipientKeyIDs
. If there multiple
algorithms then this data must be included in an array. This
requirement remains even if the recipientKeyIDs
property is null
or hidden.
The encrypted
property may contain a hash
property with a value
of the hash digest algorithm used to sign the content
data, if any.
The encrypted
property may contain a signingAlgorithm
property
with a value of the digital signature algorithm of the key used to
sign the content
data. If multiple keys were used to sign the data
and those keys used different signing algorithms then this may be an
array containing each algorithm.
The encrypted
property should contain a timestamp
, except where
enough of the data regarding the encrypted content
does not include
an actual timestamp.
The following example is about as simple as it gets. The content
is
encrypted and signed, in this case simply containing a small Markdown
text file.4
{ "@context": ["https://www.w3.org/ns/activitystreams", { "@language": "en-GB" } ], "type": "Encrypted Note", "id": "https://not.secret.example.com/agents/dm/posted/encrypted-thing", "to": "https://not.secret.example.com/agents/dm/inbox", "subject": "Secret Message", "cryptographic-protocol": "openpgp", "encrypted": { "subject": "Secret Message", "content": "-----BEGIN PGP MESSAGE-----\n\nhF4DSD9CPjLdeagSAQdAqdWMriKCydTELA/6Rn0V6v0iCx2tTz4qFzvl0iutjWMw\nl8OJnLw+5xy0aUEr17PujJCnrcI8hUVxarZHZSOILLjLLVtWjI5LB3YuSepP0Iav\n0sEsARd02MNCp32Eyj8X1vFEsf8pvWxPe0ojrZ9afwjWF6ZIYpOHoiYPZc/za3Gf\nJGeyDyZ+FJMDkP5TnJsME9K6vqF+fZnwP4m2K1HoPOMH1pCqH4jI54IMy06c4ZUx\nLh7zPrOmfcdFMSBQ4jVxw/hDaeLUaPw7J1bE21jd9dTuK8Nn6q1zteI0hmw9d6t7\nQYHw7CwNI3dsrU5y1YiHs7PoEZO2W1qqoykvOFeNzkx8RmkbNUPy1LULFiDED+Y+\nDrFYPH9Xpfaqp4SqV+kE/zL7T/edftL/ZCDmRNwzoCUcvUkg6MMTfmiTZglZ5O/k\nzFn74RTmrGjXDnQv7iikP+urs41bJvOzBKYRGfRFQ08GRgZR6HJS19NrdLiB8M9I\njHQZG2fpDpNKNByx3gfXwSCXEhpurYh7m4ssK80KFXdWKRpECTN0qXj5B9LFcok8\nD1GSdX0WvKIarvtyKDxaaruAS6gVD59QODELpDnK6sKHuP4mkX34D9zKpV/yJqMb\nMNiNlNnBvQF/9cp+wyVpA5BW5WlkqWKOgev+V7z0DuPkBHrsilAZOCFplaiVU//m\nmErPTT6FeHSP9U5iPXTKq6vkDnDnUkNEHLIR8LgUvVvQLvGHZXWqxQpMXOcMmiyd\n7rlVFL4CRXYaLlhfYH2c\n=OIPC\n-----END PGP MESSAGE-----\n", "mediaType": "application/pgp-encrypted", "signingKeyID": "65DF3A3814D3BADC5E7D68F48D87C4418347F2BB", "recipientKeyIDs": [ "681CBF37BE8ED04CB20BD5D0483F423E32DD79A8" ], "cipher": "TWOFISH", "encryptionAlgorithm": "EDDSA", "hash": "SHA512", "signingAlgorithm": "ECDH", "timestamp": 1546206334 } }
A more complete and possibly more effective method, however, is in the
following example. Like the preceding one, the Encrypted Note object
contains OpenPGP encrypted data in the content
property. A
summary
is optional and may indicate that the content is encrypted
if the Encrypted Note is being posted publicly (see next section).
The encrypted data, however is an entire ActivityPub object including source format which may be rendered by a recipient's software and which may include a Signed Note as described above.5
{ "@context": ["https://www.w3.org/ns/activitystreams", { "@language": "en-GB" } ], "type": "Encrypted Note", "id": "https://not.secret.example.com/agents/dm/posted/encrypted-thing", "to": "https://not.secret.example.com/agents/dm/inbox", "cryptographic-protocol": "openpgp", "encrypted": { "content": "-----BEGIN PGP MESSAGE-----\n\nhF4DSD9CPjLdeagSAQdAaR4vYSrOenqGK3sM0V3rGLJtRCcPb3NTpf1/yuNQLy0w\nHRirczb52+WarwgcbJXpnslVOyFNJnHnJ8fi6G++w98ZNycf7UrOPTbu/EoINPom\n0ukB1CC4aelHjhE90SosjP/wosrn7YzZxm3QUDu/kR2y6um1v/gIghpBlTHWovK9\nXJMd7c0JGSxtEqHoJAUlTXsRZn7CYMGHTJ1W+In4uc1rZb5aHNv+iHzKLBCylfsO\n0VsHJ6MET74FO40iWYjlfReoPP08n9x8Q2J/6RuuCDLbYKPX3W2VD2Gv4tASRCW8\nXJuv9knMCnbV4yUD0EAn2ZmJSH9LbBSiJUT5yBEmUqgme09EiuPxP8/uRmCf03+n\nab5S1yjR5xKUfGHhSs2MJZqKLP0xKmClgZIA3PYnPyHLtlzASn6EhZ9PZ0d2DFrA\n5uIKdTpuly0esfCWjCjMH+S7W85Zk3ne7Qk4ZOsuejj8Z+HHAjKMVBAlAZ0gFU4G\nJGvM9U0Hs84vFLcNShdY+KixTL1yxMT4nom9ch9vKZszT9KBFfTxFZP9JAeO2Xam\n1hbKCL7uo+xKLdGCD38X3FTOtQNAFohpffzb5aQqLRb5+GSO720Dkhn6/RwjCfpB\n4+PJiz8jnlVzdMPOb2QumfjF4BOAGK3L9L0wIdszelwP7WrIFrUHh0BwwHOM7F0e\nPHTFj5nxE+BZ7KO3EHcqR0oTokUjQY/oNm2W3rZr8ZtZCsWNMr5BDGN4yMBxOrQq\nnwe+kzys9bAR+u683DzPE6K7e8uyy4Fs+irZQO1AKC7Z1QA=\n=Kxtt\n-----END PGP MESSAGE-----\n", "mediaType": "application/pgp-encrypted+activitystreams", "signingKeyID": null, "recipientKeyIDs": [ "C2FA40FD7A2E6DDB7A4FDFCB1A7425A225C3EF1F", "681CBF37BE8ED04CB20BD5D0483F423E32DD79A8" ], "cipher": "TWOFISH", "encryptionAlgorithm": "ECDH", "timestamp": null } }
This second method of encrypting ActivityPub or Activity Streams data would enable providing signed information without revealing publicly which key actually signed that data except to the intended recipient(s).
2.5.2 Encrypted Public Messages
It would be possible to post an encrypted message publicly, but in
which the recipients' key IDs were hidden using any of the
hidden-recipient
(-R
), hidden-encrypt-to
or throw-keyids
options available when using GnuPG. For such messages the second of
the two options in the previous section is likely to be the most
useful, but it could be used with the first.
This would enable the use of a public stream of objects and activities as a “dead drop” as a means of providing anonymous or pseudonymous communication with any other party and without requiring a means by which that party might be directly identified by others.
2.6 Authentication
There are multiple methods by which OpenPGP keys could be employed to provide authentication services between a client and server, In particular as an alternative to using passwords or two-factor authentication when used in conjunction with OAuth tokens for sessions.
These methods have the additional advantage of providing a means by which a remote server could confirm the identity of a user of another server without requiring the transfer of any sensitive or secure data between the two servers. For the most part this advantage stems from confirming a status is signed by the same key as used on that remote server, but it could also be used to directly authenticate in order to access any private messages of a local user intended for that user and in the local user's ActivityPub outbox.
2.6.1 Authentication With Signing Keys
Utilising signing keys or subkeys would enable a means of authentication with a server without requiring an ongoing session between the client and the server. This could be used to facilitate a secure update or activity even across an insecure connection without compromising the security of the account itself as the server would be able to determine the authenticity of the activity and any relevant objects by verifying the signature alone.
2.6.2 Authentication With Encryption Keys
Utilising encryption subkeys would enable a means of establishing a secure session's token exchange which does not rely on the transmission of a password, two-factor authentication or other API key, as is most commonly utilised. Instead the server simply issues the token for that session in an encrypted format. Since only an authorised user or client with control of the OpenPGP key could decrypt the data and obtain the token.
2.6.3 Authentication With Authentication Keys
OpenPGP authentication keys or subkeys are intended for use with protocols like SSH or other remote access. In spite of the name they may be less useful in this use case. Nevertheless, it would be possible to configure a server to accept connections utilising an authentication key or subkey to establish an authorised connection from the client to the server.
3 Additional Technical Notes
3.1 Data size limitations
Since the conversion of encrypted binary data in the OpenPGP format to radix64 encoded ASCII text generally adds to the size of the output data, determined according to both the size of the original input data and the size of the keys to which that data is encrypted, the maximum message size should not be arbitrarily limited in the same way that many ActivityPub objects are limited. The common limitation of five hundred characters per status to be found with many Mastodon servers, for instance, would severely hamper the ability to usefully employ any of these options.
3.2 Metadata and Forward Secrecy
The nature of ActivityPub and Activity Streams 2.0 data is such that there is an inherent leakage of metadata with each object and activity posted to a stream. As a consequence there are certain limitations on what can or should be concealed. There are, however, methods of mitigating that leakage. A good example being the second message encryption method described above.
Forward secrecy is a little more difficult with a messaging format like this, even where it appears to be a stream to an end user. This is due to each object being separate packages in that stream rather than the data being transmitted as a single encrypted session originating with the author and ending with the recipient in real time. Even in those circumstances in which the overall communication (e.g. a conversation) does occur in real time or near real time.
Nevertheless, between using OpenPGP keys with pseudonymous identifiers
linked to the ActivityPub stream end points and minimising the amount
of data revealed by encrypted content, there are points which can
facilitate this process. In many respects this could be done in a
manner not too dissimilar to the use of anonymous remailers and posts
to the old alt.anonymous.messages
USENET news group.
4 References
TBA.
4.1 Normative References
4.2 Non-Normative References
4.3 Informative References
5 Copyright and Licensing
5.1 Copyright
Copyright © Benjamin D. McGinnes, 2018, 2019.
5.2 Licensing
This file is free software; as a special exception the author gives unlimited permission to copy and/or distribute it, with or without modifications, as long as this notice is preserved.
This file is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY, to the extent permitted by law; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
Footnotes:
As a point of comparison, the author's current public key in ASCII armored format with all the web-of-trust signatures included is approximately 100KB in size, whereas the same key exported in its most minimal and concise form is approximately 13KB. Most keys will be smaller than that (the author's key is a 4Kb RSA certification and signing primary key with a 3Kb RSA signing subkey, a 4Kb El-Gamal encryption subkey and a 3Kb DSA2 signing subkey).
Note that a future draft of this protocol extension may shift the key distribution method to utilise the proposed OpenPGP Web Key Directory protocol; which would meet all the requirements of this protocol along with very fine tuned user ID control with key distribution. At the current time, however, adoption of the Web Key Directory service is limited and its protocol design is not finalised.
As the example suggests, the example is heavily based on the current state of the GnuPG Project. As this is a fictional thing which may become real in the future, it's necessary to stress that such a project must be both free and permissive in its licensing.
To choose only free (e.g. GPL and/or Affero GPL only) means to sacrifice other people's security/lives for one's own political beliefs, while to choose only permissive (e.g. BSD and/or Apache and/or proprietary only) means to sacrifice other people's security/lives for profit.
If any reading this have ever wondered why the GnuPG Project hasn't moved away from its dual licensing under the GPLv2+ (free) and the LGPLv2.1+ (permissive), this is why.
Since an actor contact email address may be different from any
of the user IDs listed on the public key, servers should be configured
with their own means of matching key IDs to email addresses. In GnuPG
this is what the group
option is used for and various MUAs have
their own solutions (e.g. Enigmail's Per-Recipient Record and Mutt's
crypt-hook
). It is also recommended that servers automatically
encrypt such notifications with the trust model
set to always,
otherwise the server will need to be configured with its own key which
signs or locally signs all the keys uploaded by clients.
The session key for the encrypted message in this example is: 10:E877FC8B0CB0B69F15A3397E3A9CD00419A3F47795469A973A841BE0388F8BA0
The session key for the second encrypted message example is: 10:9A4CAAFD053E45B9895C9C882AC24D51C4810E1F6F7834DDFB29DE3EB5ABB083