aputils/docs/usage/signer.rst

3.6 KiB

Signer

The :pyaputils.Signer object stores an RSA key (public or private) and is used as a representation of an actor key.

Using an existing key

signer = Signer(Path('/home/test/testkey'), 'https://social.example.com/actor#main-key')

key can be a string (usually the output of :pyCrypto.PublicKey.RSA.RsaKey.export_key), an :pyCrypto.PublicKey.RSA.RsaKey, or a :pypathlib.Path pointing to a file containing a text versino of a key.

keyid must be a url pointing to an ActivityPub actor containing a public key

Generating a new Signer

signer = Signer.new('https://social.example.com/actor#main-key', key_size=256)
signer = Signer.new('https://social.example.com/actor#main-key', key_size=4096)

If you don't already have a signing key, a new one can be generated for you. Just input the url to where the public key will be displayed and (optionally) the size of the key. The example above generates two equally-sized keys since values under 1024 are assumed to be bytes instead of bits. Be sure to use :pyaputils.Signer.export and store the resulting str somewhere.

Importing a key from an actor

ActivityPub Actor objects should always contain a public key in actor['publicKey']['publicKeyPem']. Just fetch the actor and pass the whole dict object to aputils.Signer.new_from_actor

signer = Signer.new_from_actor(actor)

Signing headers

Most ActivityPub resources can only be accessed with a valid Signature header. Fortunately ApUtils makes this extremely simple.

headers = {'Accept': 'application/activity+json'}
new_headers = signer.sign_headers('GET', 'https://social.example2.com/user/8_sergals_in_a_trenchcoat', headers=headers)

The above example creates Host, Date, and Signature and returns them with with original headers as new_headers. Just pass new_headers to whatever HTTP client you use. Example:

request = urllib.request.Request(
    url = 'https://social.example2.com/user/8_sergals_in_a_trenchcoat',
    method = 'GET',
    headers = new_headers
)

with urllib.request.urlopen(request) as response:
    print(json.loads(response.read(), indent=4))

Alternatively a whole request can be passed to aputils.Signer.sign_request to simplify signing even further:

request = signer.sign_request(urllib.request.Request(
    url = 'https://social.example2.com/user/8_sergals_in_a_trenchcoat',
    method = 'GET',
    headers = {'Accept': 'application/activity+json'}
))

with urllib.request.urlopen(request) as response:
    print(json.loads(response.read(), indent=4))

Validating headers

Making sure the signature matches on the other end is slightly more complicated, but still easy. First make sure you acquire the actor and pass it to aputils.Signer.new_from_actor. Then call aputils.Signer.validate_signature. If validation passes, True is returned. Otherwise a aputils.SignatureFailureError will be raised. If you're running aiohttp, you can use the aputils.Signer.validate_aiohttp_request method instead of validate_signature to simplify verification

signer = Signer.new_from_actor(actor)

try:
    signer.validate_signature(request.method, request.path, request.headers, request.body)

except SignatureFailureError as e:
    raise HttpError(body=str(e), status=401)