Signing HTTP Signatures in C++ using Botan (for ActivityPub)

DISCLAIMER: I am not a cryptographer. For your own sake, consult a cryptographer if you want to use the following code in production.

I’m attempting to implement a basic ActivityPub server following Mastodon’s tutorial. It’s easy to follow and and explains everything clearly. However I got stock at the last step signing the HTTP signature.

The code looks like this in their official example:

First generate a RSA keypair

openssl genrsa -out private.pem 2048
openssl rsa -in private.pem -outform PEM -pubout -out public.pem

Then sign the HTTP signature using OpenSSL (RSA with SHA256)

require 'openssl'
require 'base64'

keypair       = OpenSSL::PKey::RSA.new(File.read('private.pem'))
signed_string = "(request-target): post /inbox\nhost: mastodon.social\ndate: the_date"
signature     = Base64.strict_encode64(keypair.sign(OpenSSL::Digest::SHA256.new, signed_string))

puts signature

How exactly am I supposed to do this in C++? First of all. I’m not fond of the idea using OpenSSL’s C API in my project. It’s too cumbersome. Nor I want to use some third party C++ wrapper that could be dropped by it’s maintainer randomly. So I felled back to using Botan, a C++ cryptography library that I used in other projects.

First of all, key formats. What is created above is stored in the PEM (Private Encrypted Email) format. But the keys have to be converted from PEM into PKCS8. Somehow Botan doesn’t seem to want to load the specific PEM files even though it supports it.

openssl pkcs8 -topk8 -inform PEM -outform DER -in private.pem -out private.pkcs8 -nocrypt

And now to sign the message we ask Botan to load the just created key stored in PKCS8. Then create a singer using the same SHA-256 (and EMSA3 seems to be what OpenSSL on Ruby uses by default).

std::string data = "(request-target): post /inbox\nhost: mastodon.social\ndate: the_date";

Botan::System_RNG rng;
Botan::Private_Key* private_key = Botan::PKCS8::load_key("private.pkcs8", rng);
Botan::PK_Signer signer(*private_key, rng, "EMSA3(SHA-256)");
signer.update(data);

auto signicture = Botan::base64_encode(signer.signature(rng));
std::cout << signicture << "\n";

That’s it. Both programs generates the same output!

❯ ruby test.rb
jpCO8Oe37MjXMHsuzb7olftsfSEeUtjT1xxUSBgJCqm7XoWccoIOuR2DUrEOWujqfOQdNnVfiQhx5Co3hljXjeBT8V+dOtKcqknX/t0d+XDwgp2EtkNipIRqrghji4YLI95Xx+zDyrfk5gEm7jGwO9qBg48s6pQkVmfEPCVol56PlycyaYjGW+kq0LjVZCw538Gk8xjo8p8tLdbBG7/mX2wY19Pa0BHh9TNEV32Iw9OHC0EanCTgBqnv1Zj34wX2SXgVHfrsHst58UgFRAXSh5I4Bk6glLltiqijJMSo3rJvT8RJZqX3eJ4LbgiN4P57ExmO4hEZozX0v/eHwQ5k1A==
❯ c++ test.cpp -o test -I/usr/include/botan-2/ -lbotan-2 && ./test
jpCO8Oe37MjXMHsuzb7olftsfSEeUtjT1xxUSBgJCqm7XoWccoIOuR2DUrEOWujqfOQdNnVfiQhx5Co3hljXjeBT8V+dOtKcqknX/t0d+XDwgp2EtkNipIRqrghji4YLI95Xx+zDyrfk5gEm7jGwO9qBg48s6pQkVmfEPCVol56PlycyaYjGW+kq0LjVZCw538Gk8xjo8p8tLdbBG7/mX2wY19Pa0BHh9TNEV32Iw9OHC0EanCTgBqnv1Zj34wX2SXgVHfrsHst58UgFRAXSh5I4Bk6glLltiqijJMSo3rJvT8RJZqX3eJ4LbgiN4P57ExmO4hEZozX0v/eHwQ5k1A==

Enough blogging for today. Going back to getting my ActivityPub server working.

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Website Powered by WordPress.com.

Up ↑