♻️ refactor: switch to Bytes typedef
parent
199aacadd3
commit
02346c3285
|
@ -36,7 +36,7 @@ int file (const string & file_path) {
|
||||||
const auto len = f.tellg ();
|
const auto len = f.tellg ();
|
||||||
f.seekg (0, ifstream::beg);
|
f.seekg (0, ifstream::beg);
|
||||||
|
|
||||||
vector <uint8_t> bytes;
|
Bytes bytes;
|
||||||
bytes.resize (len);
|
bytes.resize (len);
|
||||||
|
|
||||||
f.read ((char *)bytes.data (), bytes.size ());
|
f.read ((char *)bytes.data (), bytes.size ());
|
||||||
|
@ -63,7 +63,7 @@ int file (const string & file_path) {
|
||||||
<< " (" << now.x - key.time_created.x << " seconds ago)"
|
<< " (" << now.x - key.time_created.x << " seconds ago)"
|
||||||
<< endl;
|
<< endl;
|
||||||
cout << "Generated on machine ID " << key.machine_id << endl;
|
cout << "Generated on machine ID " << key.machine_id << endl;
|
||||||
cout << "Claims to have Base64 pubkey " << base64_encode (key.pubkey) << endl;
|
cout << "Claims to have Base64 pubkey `" << base64_encode (key.pubkey) << "`" << endl;
|
||||||
|
|
||||||
// Print warnings
|
// Print warnings
|
||||||
if (now.x < key.time_created.x) {
|
if (now.x < key.time_created.x) {
|
||||||
|
@ -90,7 +90,7 @@ int file (const string & file_path) {
|
||||||
<< " (" << now.x - key.time_created.x << " seconds ago)"
|
<< " (" << now.x - key.time_created.x << " seconds ago)"
|
||||||
<< endl;
|
<< endl;
|
||||||
cout << "Generated on machine ID " << key.machine_id << endl;
|
cout << "Generated on machine ID " << key.machine_id << endl;
|
||||||
cout << "Claims to have Base64 pubkey " << base64_encode (key.pubkey ()) << endl;
|
cout << "Claims to have Base64 pubkey `" << base64_encode (key.pubkey ()) << "`" << endl;
|
||||||
|
|
||||||
// Print warnings
|
// Print warnings
|
||||||
if (now.x < key.time_created.x) {
|
if (now.x < key.time_created.x) {
|
||||||
|
@ -166,6 +166,8 @@ int main (int argc, char ** argv) {
|
||||||
options.add_options ()
|
options.add_options ()
|
||||||
("generate-human-key", "Generate a passphrase-protected key for human use", cxxopts::value <string> ())
|
("generate-human-key", "Generate a passphrase-protected key for human use", cxxopts::value <string> ())
|
||||||
("generate-machine-key", "Generate a key for machine use, with no passphrase", cxxopts::value <string> ())
|
("generate-machine-key", "Generate a key for machine use, with no passphrase", cxxopts::value <string> ())
|
||||||
|
("generate-key-cert", "Certify a key for 1 month and save the cert here", cxxopts::value <string> ())
|
||||||
|
("using-key", "Key to load for other operations", cxxopts::value <string> ())
|
||||||
("file", "Print info about any file generated by BMC", cxxopts::value <string> ())
|
("file", "Print info about any file generated by BMC", cxxopts::value <string> ())
|
||||||
("test", "Run self-test")
|
("test", "Run self-test")
|
||||||
("help", "Print usage")
|
("help", "Print usage")
|
||||||
|
|
|
@ -9,10 +9,12 @@
|
||||||
namespace BareMinimumCrypto {
|
namespace BareMinimumCrypto {
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
|
typedef vector <uint8_t> Bytes;
|
||||||
|
|
||||||
struct ExpiringSignature {
|
struct ExpiringSignature {
|
||||||
// Payload is contained in here
|
// Payload is contained in here
|
||||||
vector <uint8_t> cert;
|
Bytes cert;
|
||||||
vector <uint8_t> sig;
|
Bytes sig;
|
||||||
|
|
||||||
bool operator == (const ExpiringSignature & o) const;
|
bool operator == (const ExpiringSignature & o) const;
|
||||||
bool operator != (const ExpiringSignature & o) const;
|
bool operator != (const ExpiringSignature & o) const;
|
||||||
|
|
|
@ -12,12 +12,12 @@
|
||||||
namespace BareMinimumCrypto::Receiver {
|
namespace BareMinimumCrypto::Receiver {
|
||||||
using nlohmann::json;
|
using nlohmann::json;
|
||||||
|
|
||||||
bool is_pubkey_length (const vector <uint8_t> & v) {
|
bool is_pubkey_length (const Bytes & v) {
|
||||||
return v.size () == crypto_sign_PUBLICKEYBYTES;
|
return v.size () == crypto_sign_PUBLICKEYBYTES;
|
||||||
}
|
}
|
||||||
|
|
||||||
optional <vector <uint8_t>> try_verify_signed_data (
|
optional <Bytes> try_verify_signed_data (
|
||||||
const vector <uint8_t> & pubkey,
|
const Bytes & pubkey,
|
||||||
const ExpiringSignature & sig,
|
const ExpiringSignature & sig,
|
||||||
Instant now
|
Instant now
|
||||||
) {
|
) {
|
||||||
|
@ -52,8 +52,8 @@ namespace BareMinimumCrypto::Receiver {
|
||||||
return payload;
|
return payload;
|
||||||
}
|
}
|
||||||
|
|
||||||
optional <vector <uint8_t>> verify_signed_data (
|
optional <Bytes> verify_signed_data (
|
||||||
const vector <uint8_t> & pubkey,
|
const Bytes & pubkey,
|
||||||
const ExpiringSignature & sig,
|
const ExpiringSignature & sig,
|
||||||
Instant now
|
Instant now
|
||||||
) {
|
) {
|
||||||
|
@ -65,9 +65,9 @@ namespace BareMinimumCrypto::Receiver {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
optional <vector <uint8_t>> try_verify_cert_and_data (
|
optional <Bytes> try_verify_cert_and_data (
|
||||||
const vector <uint8_t> & root_pubkey,
|
const Bytes & root_pubkey,
|
||||||
const vector <uint8_t> & msgpack,
|
const Bytes & msgpack,
|
||||||
Instant now
|
Instant now
|
||||||
) {
|
) {
|
||||||
const auto j = json::from_msgpack (msgpack);
|
const auto j = json::from_msgpack (msgpack);
|
||||||
|
@ -90,9 +90,9 @@ namespace BareMinimumCrypto::Receiver {
|
||||||
return verify_signed_data (subkey, data, now);
|
return verify_signed_data (subkey, data, now);
|
||||||
}
|
}
|
||||||
|
|
||||||
optional <vector <uint8_t>> verify_cert_and_data (
|
optional <Bytes> verify_cert_and_data (
|
||||||
const vector <uint8_t> & root_pubkey,
|
const Bytes & root_pubkey,
|
||||||
const vector <uint8_t> & msgpack
|
const Bytes & msgpack
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
return try_verify_cert_and_data (root_pubkey, msgpack, Instant::now ());
|
return try_verify_cert_and_data (root_pubkey, msgpack, Instant::now ());
|
||||||
|
|
|
@ -24,14 +24,16 @@ data are disposable and not exposed to callers.
|
||||||
namespace BareMinimumCrypto::Receiver {
|
namespace BareMinimumCrypto::Receiver {
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
optional <vector <uint8_t>> verify_cert_and_data (
|
typedef vector <uint8_t> Bytes;
|
||||||
const vector <uint8_t> & root_pubkey,
|
|
||||||
|
optional <Bytes> verify_cert_and_data (
|
||||||
|
const Bytes & root_pubkey,
|
||||||
const ExpiringSignature & signed_cert,
|
const ExpiringSignature & signed_cert,
|
||||||
const ExpiringSignature & signed_data
|
const ExpiringSignature & signed_data
|
||||||
);
|
);
|
||||||
|
|
||||||
optional <vector <uint8_t>> verify_cert_and_data (
|
optional <Bytes> verify_cert_and_data (
|
||||||
const vector <uint8_t> & root_pubkey,
|
const Bytes & root_pubkey,
|
||||||
const vector <uint8_t> & msgpack
|
const Bytes & msgpack
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ namespace BareMinimumCrypto {
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
vector <uint8_t> Sender::try_sign (const vector <uint8_t> & data, Instant now) const {
|
Bytes Sender::try_sign (const Bytes & data, Instant now) const {
|
||||||
const auto signed_data = std::move (*sender_key.sign_data (data, now));
|
const auto signed_data = std::move (*sender_key.sign_data (data, now));
|
||||||
|
|
||||||
const json j {
|
const json j {
|
||||||
|
@ -35,7 +35,7 @@ namespace BareMinimumCrypto {
|
||||||
return json::to_msgpack (j);
|
return json::to_msgpack (j);
|
||||||
}
|
}
|
||||||
|
|
||||||
optional <vector <uint8_t>> Sender::sign (const vector <uint8_t> & data, Instant now) const {
|
optional <Bytes> Sender::sign (const Bytes & data, Instant now) const {
|
||||||
try {
|
try {
|
||||||
return try_sign (data, now);
|
return try_sign (data, now);
|
||||||
}
|
}
|
||||||
|
@ -44,7 +44,7 @@ namespace BareMinimumCrypto {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
optional <vector <uint8_t>> Sender::sign (const vector <uint8_t> & data) const {
|
optional <Bytes> Sender::sign (const Bytes & data) const {
|
||||||
return sign (data, Instant::now ());
|
return sign (data, Instant::now ());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,13 +15,13 @@ namespace BareMinimumCrypto {
|
||||||
static optional <Sender> create (SigningKey k, ExpiringSignature c);
|
static optional <Sender> create (SigningKey k, ExpiringSignature c);
|
||||||
|
|
||||||
// Signs data and attaches our cert from the CA
|
// Signs data and attaches our cert from the CA
|
||||||
optional <vector <uint8_t>> sign (const vector <uint8_t> & data) const;
|
optional <Bytes> sign (const Bytes & data) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
SigningKey sender_key;
|
SigningKey sender_key;
|
||||||
ExpiringSignature cert;
|
ExpiringSignature cert;
|
||||||
|
|
||||||
vector <uint8_t> try_sign (const vector <uint8_t> & data, Instant now) const;
|
Bytes try_sign (const Bytes & data, Instant now) const;
|
||||||
optional <vector <uint8_t>> sign (const vector <uint8_t> & data, Instant now) const;
|
optional <Bytes> sign (const Bytes & data, Instant now) const;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,7 @@ namespace BareMinimumCrypto {
|
||||||
return machine_id;
|
return machine_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
vector <uint8_t> HumanKeyFile::to_msgpack () const {
|
Bytes HumanKeyFile::to_msgpack () const {
|
||||||
const auto j = json {
|
const auto j = json {
|
||||||
// Breaking changes should generate a new Base32 schema.
|
// Breaking changes should generate a new Base32 schema.
|
||||||
{"schema", "3T6XF5DZ"},
|
{"schema", "3T6XF5DZ"},
|
||||||
|
@ -49,7 +49,7 @@ namespace BareMinimumCrypto {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
vector <uint8_t> MachineKeyFile::to_msgpack () const {
|
Bytes MachineKeyFile::to_msgpack () const {
|
||||||
const auto j = json {
|
const auto j = json {
|
||||||
// Breaking changes should generate a new Base32 schema.
|
// Breaking changes should generate a new Base32 schema.
|
||||||
{"schema", "2PVHIKMA"},
|
{"schema", "2PVHIKMA"},
|
||||||
|
@ -73,14 +73,14 @@ namespace BareMinimumCrypto {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
vector <uint8_t> MachineKeyFile::pubkey () const {
|
Bytes MachineKeyFile::pubkey () const {
|
||||||
vector <uint8_t> pk;
|
Bytes pk;
|
||||||
pk.resize (crypto_sign_PUBLICKEYBYTES);
|
pk.resize (crypto_sign_PUBLICKEYBYTES);
|
||||||
crypto_sign_ed25519_sk_to_pk (pk.data (), secretkey.data ());
|
crypto_sign_ed25519_sk_to_pk (pk.data (), secretkey.data ());
|
||||||
return pk;
|
return pk;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool save_key_file (const string & file_path, const vector <uint8_t> msg)
|
bool save_key_file (const string & file_path, const Bytes msg)
|
||||||
{
|
{
|
||||||
ofstream f;
|
ofstream f;
|
||||||
f.open (file_path, ofstream::binary);
|
f.open (file_path, ofstream::binary);
|
||||||
|
@ -114,10 +114,10 @@ namespace BareMinimumCrypto {
|
||||||
return nullopt;
|
return nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
vector <uint8_t> seed;
|
Bytes seed;
|
||||||
seed.resize (crypto_sign_SEEDBYTES);
|
seed.resize (crypto_sign_SEEDBYTES);
|
||||||
|
|
||||||
vector <uint8_t> salt;
|
Bytes salt;
|
||||||
salt.resize (crypto_pwhash_SALTBYTES);
|
salt.resize (crypto_pwhash_SALTBYTES);
|
||||||
randombytes_buf (salt.data (), salt.size ());
|
randombytes_buf (salt.data (), salt.size ());
|
||||||
|
|
||||||
|
@ -136,7 +136,7 @@ namespace BareMinimumCrypto {
|
||||||
//key.pk.resize (crypto_sign_PUBLICKEYBYTES);
|
//key.pk.resize (crypto_sign_PUBLICKEYBYTES);
|
||||||
key.sk.resize (crypto_sign_SECRETKEYBYTES);
|
key.sk.resize (crypto_sign_SECRETKEYBYTES);
|
||||||
|
|
||||||
vector <uint8_t> pk;
|
Bytes pk;
|
||||||
pk.resize (crypto_sign_PUBLICKEYBYTES);
|
pk.resize (crypto_sign_PUBLICKEYBYTES);
|
||||||
if (crypto_sign_seed_keypair (pk.data (), key.sk.data (), seed.data ()) != 0) {
|
if (crypto_sign_seed_keypair (pk.data (), key.sk.data (), seed.data ()) != 0) {
|
||||||
return nullopt;
|
return nullopt;
|
||||||
|
@ -182,21 +182,21 @@ namespace BareMinimumCrypto {
|
||||||
SigningKey::SigningKey () {
|
SigningKey::SigningKey () {
|
||||||
try_sodium_init ();
|
try_sodium_init ();
|
||||||
|
|
||||||
vector <uint8_t> pk;
|
Bytes pk;
|
||||||
pk.resize (crypto_sign_PUBLICKEYBYTES);
|
pk.resize (crypto_sign_PUBLICKEYBYTES);
|
||||||
sk.resize (crypto_sign_SECRETKEYBYTES);
|
sk.resize (crypto_sign_SECRETKEYBYTES);
|
||||||
|
|
||||||
crypto_sign_keypair (pk.data (), sk.data ());
|
crypto_sign_keypair (pk.data (), sk.data ());
|
||||||
}
|
}
|
||||||
|
|
||||||
vector <uint8_t> SigningKey::pubkey () const {
|
Bytes SigningKey::pubkey () const {
|
||||||
vector <uint8_t> pk;
|
Bytes pk;
|
||||||
pk.resize (crypto_sign_PUBLICKEYBYTES);
|
pk.resize (crypto_sign_PUBLICKEYBYTES);
|
||||||
crypto_sign_ed25519_sk_to_pk (pk.data (), sk.data ());
|
crypto_sign_ed25519_sk_to_pk (pk.data (), sk.data ());
|
||||||
return pk;
|
return pk;
|
||||||
}
|
}
|
||||||
|
|
||||||
vector <uint8_t> SigningKey::pub_to_msgpack () const {
|
Bytes SigningKey::pub_to_msgpack () const {
|
||||||
const json j = {
|
const json j = {
|
||||||
{"key", json::binary (pubkey ())},
|
{"key", json::binary (pubkey ())},
|
||||||
};
|
};
|
||||||
|
@ -204,7 +204,7 @@ namespace BareMinimumCrypto {
|
||||||
}
|
}
|
||||||
|
|
||||||
optional <ExpiringSignature> SigningKey::sign (
|
optional <ExpiringSignature> SigningKey::sign (
|
||||||
const vector <uint8_t> & payload,
|
const Bytes & payload,
|
||||||
TimeRange tr
|
TimeRange tr
|
||||||
) const {
|
) const {
|
||||||
try_sodium_init ();
|
try_sodium_init ();
|
||||||
|
@ -221,7 +221,7 @@ namespace BareMinimumCrypto {
|
||||||
|
|
||||||
const auto cert = json::to_msgpack (j);
|
const auto cert = json::to_msgpack (j);
|
||||||
|
|
||||||
vector <uint8_t> sig;
|
Bytes sig;
|
||||||
sig.resize (crypto_sign_BYTES);
|
sig.resize (crypto_sign_BYTES);
|
||||||
|
|
||||||
crypto_sign_detached (sig.data (), nullptr, cert.data (), cert.size (), sk.data ());
|
crypto_sign_detached (sig.data (), nullptr, cert.data (), cert.size (), sk.data ());
|
||||||
|
@ -237,7 +237,7 @@ namespace BareMinimumCrypto {
|
||||||
return sign (k.pub_to_msgpack (), TimeRange::from_start_and_dur (now, about_3_months));
|
return sign (k.pub_to_msgpack (), TimeRange::from_start_and_dur (now, about_3_months));
|
||||||
}
|
}
|
||||||
|
|
||||||
optional <ExpiringSignature> SigningKey::sign_data (const vector <uint8_t> & v, Instant now) const
|
optional <ExpiringSignature> SigningKey::sign_data (const Bytes & v, Instant now) const
|
||||||
{
|
{
|
||||||
return sign (v, TimeRange::from_start_and_dur (now, about_1_week));
|
return sign (v, TimeRange::from_start_and_dur (now, about_1_week));
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,28 +17,28 @@ namespace BareMinimumCrypto {
|
||||||
string get_machine_id ();
|
string get_machine_id ();
|
||||||
|
|
||||||
struct SigningKey {
|
struct SigningKey {
|
||||||
vector <uint8_t> sk;
|
Bytes sk;
|
||||||
|
|
||||||
SigningKey ();
|
SigningKey ();
|
||||||
|
|
||||||
static optional <SigningKey> load_human_key_file (const string & file_path, const string & passphrase);
|
static optional <SigningKey> load_human_key_file (const string & file_path, const string & passphrase);
|
||||||
|
|
||||||
vector <uint8_t> pubkey () const;
|
Bytes pubkey () const;
|
||||||
vector <uint8_t> pub_to_msgpack () const;
|
Bytes pub_to_msgpack () const;
|
||||||
|
|
||||||
optional <ExpiringSignature> sign (
|
optional <ExpiringSignature> sign (
|
||||||
const vector <uint8_t> & payload,
|
const Bytes & payload,
|
||||||
TimeRange tr
|
TimeRange tr
|
||||||
) const;
|
) const;
|
||||||
|
|
||||||
optional <ExpiringSignature> sign_key (const SigningKey & k, Instant now) const;
|
optional <ExpiringSignature> sign_key (const SigningKey & k, Instant now) const;
|
||||||
optional <ExpiringSignature> sign_data (const vector <uint8_t> & v, Instant now) const;
|
optional <ExpiringSignature> sign_data (const Bytes & v, Instant now) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct HumanKeyFile {
|
struct HumanKeyFile {
|
||||||
vector <uint8_t> salt;
|
Bytes salt;
|
||||||
Instant time_created;
|
Instant time_created;
|
||||||
vector <uint8_t> pubkey;
|
Bytes pubkey;
|
||||||
string machine_id;
|
string machine_id;
|
||||||
|
|
||||||
// This doesn't fsync, so it's possible to lose the key due to a power outage
|
// This doesn't fsync, so it's possible to lose the key due to a power outage
|
||||||
|
@ -47,20 +47,20 @@ namespace BareMinimumCrypto {
|
||||||
|
|
||||||
static optional <SigningKey> generate (const string & file_path, const string & passphrase);
|
static optional <SigningKey> generate (const string & file_path, const string & passphrase);
|
||||||
|
|
||||||
vector <uint8_t> to_msgpack () const;
|
Bytes to_msgpack () const;
|
||||||
static optional <HumanKeyFile> try_from_msgpack (const json & msg);
|
static optional <HumanKeyFile> try_from_msgpack (const json & msg);
|
||||||
};
|
};
|
||||||
|
|
||||||
struct MachineKeyFile {
|
struct MachineKeyFile {
|
||||||
vector <uint8_t> secretkey;
|
Bytes secretkey;
|
||||||
Instant time_created;
|
Instant time_created;
|
||||||
string machine_id;
|
string machine_id;
|
||||||
|
|
||||||
static optional <SigningKey> generate (const string & file_path);
|
static optional <SigningKey> generate (const string & file_path);
|
||||||
|
|
||||||
vector <uint8_t> pubkey () const;
|
Bytes pubkey () const;
|
||||||
|
|
||||||
vector <uint8_t> to_msgpack () const;
|
Bytes to_msgpack () const;
|
||||||
static optional <MachineKeyFile> try_from_msgpack (const json & msg);
|
static optional <MachineKeyFile> try_from_msgpack (const json & msg);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,15 +7,15 @@
|
||||||
namespace BareMinimumCrypto {
|
namespace BareMinimumCrypto {
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
vector <uint8_t> copy_to_bytes (const string & s) {
|
Bytes copy_to_bytes (const string & s) {
|
||||||
return vector <uint8_t> ((const uint8_t *)&s [0], (const uint8_t *)&s [s.size ()]);
|
return Bytes ((const uint8_t *)&s [0], (const uint8_t *)&s [s.size ()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
string base64_encode (const vector <uint8_t> & v) {
|
string base64_encode (const Bytes & v) {
|
||||||
return ::base64_encode (string_view ((const char *)v.data (), v.size ()));
|
return ::base64_encode (string_view ((const char *)v.data (), v.size ()));
|
||||||
}
|
}
|
||||||
|
|
||||||
optional <vector <uint8_t>> base64_decode (const string & s) {
|
optional <Bytes> base64_decode (const string & s) {
|
||||||
try {
|
try {
|
||||||
const auto decoded = ::base64_decode (s);
|
const auto decoded = ::base64_decode (s);
|
||||||
return copy_to_bytes (decoded);
|
return copy_to_bytes (decoded);
|
||||||
|
@ -33,7 +33,7 @@ namespace BareMinimumCrypto {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
vector <uint8_t> v {1, 2, 3, 4, 5, 6};
|
Bytes v {1, 2, 3, 4, 5, 6};
|
||||||
const auto s = base64_encode (v);
|
const auto s = base64_encode (v);
|
||||||
|
|
||||||
if (s != "AQIDBAUG") {
|
if (s != "AQIDBAUG") {
|
||||||
|
|
|
@ -8,11 +8,13 @@
|
||||||
namespace BareMinimumCrypto {
|
namespace BareMinimumCrypto {
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
vector <uint8_t> copy_to_bytes (const string & s);
|
typedef vector <uint8_t> Bytes;
|
||||||
|
|
||||||
|
Bytes copy_to_bytes (const string & s);
|
||||||
|
|
||||||
// Not sure why the Base64 lib fails to provide this API
|
// Not sure why the Base64 lib fails to provide this API
|
||||||
string base64_encode (const vector <uint8_t> & v);
|
string base64_encode (const Bytes & v);
|
||||||
optional <vector <uint8_t>> base64_decode (const string & s);
|
optional <Bytes> base64_decode (const string & s);
|
||||||
|
|
||||||
int test_base64 ();
|
int test_base64 ();
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue