Skip to content

Get started

apsig is collection of signature implemention used in ActivityPub.

This library implements the creation/verification of signatures for HTTP Signatures (draft-cavage-http-signatures-12), Linked Data Signatures 1.0, and Object Integrity Proofs (FEP-8b32).

RFC9421 implementation is progress.

Installation

# pip
pip install apsig

# uv
uv add apsig

# pdm
pdm add apsig

Example

First, prepare the keys for signing and verification. apsig uses the cryptography library.

from cryptography.hazmat.primitives.asymmetric import rsa, ed25519
from cryptography.hazmat.primitives import serialization

# For HTTP Signatures (RSA)
private_key_rsa = rsa.generate_private_key(public_exponent=65537, key_size=3092)
public_key_rsa_pem = private_key_rsa.public_key().public_bytes(
    encoding=serialization.Encoding.PEM,
    format=serialization.PublicFormat.SubjectPublicKeyInfo,
)

# For Object Integrity Proofs (Ed25519)
private_key_ed = ed25519.Ed25519PrivateKey.generate()
public_key_ed = private_key_ed.public_key()

HTTP Signature (draft)

This is used for signing HTTP requests.

import email.utils
from apsig.draft import Signer, Verifier

# === Signing ===
method = "POST"
url = "https://example.com/api/resource"
headers = {
    "Content-Type": "application/json",
    "Date": email.utils.formatdate(usegmt=True),
}
body = '{"key": "value"}'
key_id = "https://example.com/users/johndoe#main-key"

signer = Signer(
    headers=headers,
    private_key=private_key_rsa,
    method=method,
    url=url,
    key_id=key_id,
    body=body.encode("utf-8"),
)
signed_headers = signer.sign()

print(signed_headers)

# === Verifying ===
verifier = Verifier(
    public_pem=public_key_rsa_pem.decode("utf-8"),
    method=method,
    url=url,
    headers=signed_headers,
    body=body.encode("utf-8"),
)
verified_key_id = verifier.verify(raise_on_fail=True)

print(f"Verified with key: {verified_key_id}")

Object Integrity Proofs (proof)

This is used for signing JSON objects (like ActivityStreams objects).

from apsig import ProofSigner, ProofVerifier

# === Signing ===
json_object = {
    "@context": [
        "https://www.w3.org/ns/activitystreams",
        "https://w3id.org/security/data-integrity/v1",
    ],
    "id": "https://server.example/objects/1",
    "type": "Note",
    "content": "Hello world",
}
proof_options = {
    "type": "DataIntegrityProof",
    "cryptosuite": "eddsa-jcs-2022",
    "verificationMethod": "https://example.com/keys/1",
    "created": "2024-01-01T09:00:00Z",
}

signer = ProofSigner(private_key_ed)
signed_object = signer.sign(json_object, proof_options)

print(signed_object)

# === Verifying ===
verifier = ProofVerifier(public_key_ed)
verified_key_id = verifier.verify(signed_object, raise_on_fail=True)

print(f"Verified with key: {verified_key_id}")

Linked Data Signature (LD-Signature)

This is another method for signing JSON-LD objects, often used in older ActivityPub implementations.

from apsig import LDSignature

# === Signing ===
ld_signer = LDSignature()
json_ld_object = {
    "@context": [
        "https://www.w3.org/ns/activitystreams",
        "https://w3id.org/security/v1",
    ],
    "type": "Note",
    "content": "Hello, Linked Data!",
}
creator = "https://example.com/users/johndoe#main-key"

signed_ld_object = ld_signer.sign(
    doc=json_ld_object,
    creator=creator,
    private_key=private_key_rsa
)

print(signed_ld_object)

# === Verifying ===
# The public key can be passed directly.
public_key_rsa = private_key_rsa.public_key()
verified_creator = ld_signer.verify(
    doc=signed_ld_object,
    public_key=public_key_rsa,
    raise_on_fail=True
)

print(f"Verified with creator: {verified_creator}")

License

MIT License