clboss/Boss/Signer.cpp

239 lines
5.8 KiB
C++

#include"Boss/Signer.hpp"
#include"Ev/Io.hpp"
#include"Net/Fd.hpp"
#include"Secp256k1/PrivKey.hpp"
#include"Secp256k1/PubKey.hpp"
#include"Secp256k1/Signature.hpp"
#include"Secp256k1/SignerIF.hpp"
#include"Sha256/Hash.hpp"
#include"Sha256/Hasher.hpp"
#include"Sqlite3.hpp"
#include"Util/Rw.hpp"
#include"Util/make_unique.hpp"
#include"Util/stringify.hpp"
#include<basicsecure.h>
#include<errno.h>
#include<fcntl.h>
#include<stdexcept>
#include<string.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<unistd.h>
namespace {
/* The actual signer object returned. */
class ActualSigner : public Secp256k1::SignerIF {
private:
Secp256k1::PrivKey const sk;
public:
ActualSigner() =delete;
ActualSigner(ActualSigner&&) =delete;
ActualSigner(ActualSigner const&) =delete;
explicit
ActualSigner(Secp256k1::PrivKey const& sk_) : sk(sk_) { }
Secp256k1::PubKey
get_pubkey_tweak(Secp256k1::PrivKey const& tweak) override {
return tweak * Secp256k1::PubKey(sk);
}
Secp256k1::Signature
get_signature_tweak( Secp256k1::PrivKey const& tweak
, Sha256::Hash const& m
) override {
return Secp256k1::Signature::create(tweak * sk, m);
}
Sha256::Hash
get_privkey_salted_hash(std::uint8_t salt[32]) override {
auto hasher = Sha256::Hasher();
hasher.feed(salt, 32);
std::uint8_t buf[32];
sk.to_buffer(buf);
hasher.feed(buf, 32);
basicsecure_clear(buf, sizeof(buf));
return std::move(hasher).finalize();
}
};
void unlink_noerr(std::string const& filename) {
auto my_errno = errno;
unlink(filename.c_str());
errno = my_errno;
}
/* Attempt to create the privkey file. */
void try_create_privkey_file( std::string const& privkey_filename
, Secp256k1::Random& random
) {
auto fd = Net::Fd(open( privkey_filename.c_str()
, O_CREAT|O_EXCL|O_WRONLY
/* Readable only by owner,
* not writeable/executable at all
*/
, 0400
));
if (!fd && errno == EEXIST)
/* Benign failure, file already exists. */
return;
if (!fd)
throw Util::BacktraceException<std::runtime_error>(
std::string("Boss::Signer: creat(\"") +
privkey_filename +
"\"): " + strerror(errno)
);
/* Create the privkey. */
auto sk = Secp256k1::PrivKey(random);
/* Try writing it. */
auto res = Util::Rw::write_all(fd.get(), &sk, sizeof(sk));
if (!res) {
unlink_noerr(privkey_filename);
throw Util::BacktraceException<std::runtime_error>(
std::string("Boss::Signer: write(\"") +
privkey_filename +
"\"): " + strerror(errno)
);
}
}
/* Read private key from given file. */
Secp256k1::PrivKey
read_privkey_file(std::string const& privkey_filename) {
auto fd = Net::Fd(open(privkey_filename.c_str(), O_RDONLY));
if (!fd)
throw Util::BacktraceException<std::runtime_error>(
std::string("Boss::Signer: open(\"") +
privkey_filename +
"\"): " + strerror(errno)
);
auto sk = Secp256k1::PrivKey();
auto size = sizeof(sk);
auto res = Util::Rw::read_all(fd.get(), &sk, size);
if (!res)
throw Util::BacktraceException<std::runtime_error>(
std::string("Boss::Signer: read(\"") +
privkey_filename +
"\"): " + strerror(errno)
);
if (size != sizeof(sk))
throw Util::BacktraceException<std::runtime_error>(
std::string("Boss::Signer: read(\"") +
privkey_filename +
"\"): unexpected end-of-file"
);
return sk;
}
}
namespace Boss {
class Signer::Impl {
private:
std::string privkey_filename;
Secp256k1::Random& random;
Sqlite3::Db db;
Secp256k1::PrivKey sk;
public:
Impl( std::string const& privkey_filename_
, Secp256k1::Random& random_
, Sqlite3::Db db_
) : privkey_filename(privkey_filename_)
, random(random_)
, db(std::move(db_))
{ }
static
Ev::Io<std::unique_ptr<Secp256k1::SignerIF>>
construct(std::shared_ptr<Impl> self) {
return Ev::lift().then([self]() {
try_create_privkey_file( self->privkey_filename
, self->random
);
self->sk = read_privkey_file(self->privkey_filename);
return self->db.transact();
}).then([self](Sqlite3::Tx tx) {
/* Create table if needed. */
tx.query_execute(R"QRY(
CREATE TABLE IF NOT EXISTS "Boss::Signer"
( id INTEGER PRIMARY KEY
, pubkey TEXT NOT NULL
);
)QRY");
/* Check if we already have pubkey+tweak. */
auto fetch = tx.query(R"QRY(
SELECT pubkey FROM "Boss::Signer"
WHERE id = 0
;
)QRY").execute();
auto found = false;
auto pubkey = Secp256k1::PubKey();
for (auto& r : fetch) {
found = true;
pubkey = Secp256k1::PubKey(
r.get<std::string>(0)
);
}
if (!found) {
/* No entry yet, insert it. */
pubkey = Secp256k1::PubKey(self->sk);
tx.query(R"QRY(
INSERT INTO "Boss::Signer"
VALUES( 0
, :pubkey
)
)QRY")
.bind( ":pubkey"
, Util::stringify(pubkey)
)
.execute()
;
} else {
/* Check it matches. */
auto actual_pk = Secp256k1::PubKey(self->sk);
if (actual_pk != pubkey)
throw Util::BacktraceException<std::runtime_error>(
std::string("Boss::Signer: ") +
self->privkey_filename +
": not matched to database!"
);
}
tx.commit();
auto signer = Util::make_unique<ActualSigner>(
self->sk
);
auto rv = std::unique_ptr<Secp256k1::SignerIF>(
std::move(signer)
);
return Ev::lift(std::move(rv));
});
}
};
Signer::Signer(Signer&&) =default;
Signer& Signer::operator=(Signer&&) =default;
Signer::~Signer() =default;
Signer::Signer( std::string const& privkey_filename
, Secp256k1::Random& random
, Sqlite3::Db db
) : pimpl(Util::make_unique<Impl>( privkey_filename
, random
, std::move(db)
))
{ }
Ev::Io<std::unique_ptr<Secp256k1::SignerIF>>
Signer::construct()&& {
auto my_pimpl = std::shared_ptr<Impl>(std::move(pimpl));
return Impl::construct(my_pimpl);
}
}