MightyNetwork :: Doc :: HTTP-signatures (source, CPAN)

CONTENTS

NAME

MightyNetwork::Doc::HTTP-signatures

DESCRIPTION

Documentation about HTTP-signatures.

The ActivityPub specification asked every application implementing the protocol to ensure users’ safety:

However, the specification does not suggest a solution for that. The de facto solution is HTTP requests signature.

DOCUMENTATION

Here’s the keys to understand and use HTTP signatures.

Asymetric cryptography

Every actor will have a public and a private key, provided by its instance.

The keys are created with the RSA algorithm, with a length of 4096 bits.

Digest header

You need to create the Digest header before signing your HTTP response.

In order to do that, you need to transform the content of your response to base64, then calculate its SHA256 hashsum.

use Mojo::Util qw(b64_encode);
use Digest::SHA qw(sha256_base64);

my $content = 'foobarbaz';
my $base64  = b64_encode $content;
my $digest  = 'SHA-256=' . sha256_base64($base64);

Signature creation

You will sign the concatenation of different headers: request-target (like POST /foo), Host, Date, Digest.

See https://tools.ietf.org/id/draft-cavage-http-signatures-10.html#canonicalization for details.

use Mojo::Date;
use Crypt::OpenSSL::RSA;
use Mojo::Util qw(b64_encode);

my $date                 = Mojo::Date->new(time)->to_string; # HTTP date format
my $host                 = 'foo.org';
my $concatenated_headers = <<EOF;
(request-target): post /
host: $host
date: $date
digest: $digest
EOF

my $rsa     = Crypt::OpenSSL::RSA->new_private_key($actor->private_key)
                                 ->use_sha256_hash();
my $signing = b64_encode $rsa->sign($concatenated_headers);

my $header  = sprintf('Signature keyId="%s",algorithm="rsa-sha256",headers="request-target host date digest",signature="%s"',
                      $actor->url.'#main-key', $signing);

Then use $date, $digest and $signing in your request headers (respectively: Date, Digest and Authorization headers).

Perl easier signature creation

Luckily, there is module to sign requests more easily: Authen::HTTP::Signature.

use Authen::HTTP::Signature;
use HTTP::Request::Common;
use Mojo::Date;
my $date   = Mojo::Date->new(time)->to_string; # HTTP date format
my $url    = 'https://foo.org';
my $signer = Authen::HTTP::Signature->new(
    key    => $actor->private_key;
    key_id => $actor->url.'#main-key'
);
my $req = POST($url,
    Date    => $date,
    Digest  => $digest,
    Content => $body
);

my $signed_req = $signer->sign($req);

Then use $date, $digest and $signed_req in your request headers (respectively: Date, Digest and Authorization headers).

Digest verification

Before verifying the signature of the request, recreate its digest and verify it’s the same as the one sent in the Digest header.

Signature verification

That’s quite easy: fetch the public key of the actor if you don’t already have it (it’s in the actor object that you got with WebFinger).

Recreate the concatenation of the headers listed in the Authorization header (the headers part) and verify the signature with the public key.

use Crypt::OpenSSL::RSA;
use Mojo::Util qw(b64_decode);

my $host   = $request->headers->host;
my $date   = $request->headers->date;
my $digest = $request->headers->digest;
my $body   = $request->body;
my $concatenated_headers = <<EOF;
POST / HTTP/1.1
Host: $host
Date: $date
Digest: $digest

$body
EOF

my $signature = $request->headers->authorization;
$signature    =~ s/.*signature="(.*?)".*/$1/;

my $rsa = Crypt::OpenSSL::RSA->new_public_key($actor->public_key);
if ($rsa->verify($concatenated_headers, b64_decode($signature))) {
    say "Request is valid!"
} else {
    say "Request isn’t valid";
}

Perl easier signature verification

Once again, Authen::HTTP::Signature make it easier.

use Authen::HTTP::Signature::Parser;
use HTTP::Request::Common;

my $req = POST($request->url,
    Date          => $request->headers->date,
    Digest        => $request->headers->digest,
    Authorization => $request->headers->authorization,
    Content       => $request->body
);

my $p;
try {
    $p = Authen::HTTP::Signature::Parser->new($req)->parse();
} catch {
    die "Parse failed: $_\n";
};

$p->key($actor->public_key);

if ($p->verify()) {
    say "Request is valid!"
} else {
    say "Request isn’t valid";
};

SEE ALSO

MightyNetwork::Doc, Authen::HTTP::Signature, HTTP::Request::Common