7684972a |
/* Copyright (C) CJ Affiliate
*
* You may use, distribute and modify this code under the
* terms of the GNU General Public License version 2 or
* later.
*
* You should have received a copy of the license with this
* file. If not, you will find a copy in the "LICENSE" file
* at https://github.com/cjdev/dual-control.
*/
|
bef74c28 |
#include <memory>
|
8ab94c71 |
#include <cstring>
|
69be5e4e |
#include <pwd.h>
|
8ab94c71 |
#include <cstdio>
|
69be5e4e |
#include <sys/stat.h>
|
4bf199d2 |
#include <fstream>
|
215ea751 |
#include <sstream>
|
69be5e4e |
|
de34378f |
#include "token.h"
|
1c7f8bf0 |
#include "test_util.h"
|
bef74c28 |
#include "user.h"
|
215ea751 |
#include "sys_fstream.h"
|
194e6869 |
#include "generator.h"
|
b6eb15ba |
#include "base32.h"
|
c1e18905 |
#include "typealiases.h"
|
8ddb4fe9 |
|
0e218820 |
class fake_user : public user_ifc
{
private:
std::string home_directory_;
public:
|
8ddb4fe9 |
fake_user() {}
|
bb783d31 |
fake_user (const std::string &home_directory) :
home_directory_ (home_directory)
|
0e218820 |
{
}
|
af3a4cd4 |
std::string home_directory() const override
|
0e218820 |
{
return home_directory_;
}
|
bef74c28 |
};
|
d10906ee |
class mock_write_fstreams: public fstreams_ifc
{
private:
mutable std::string captured_filename_;
mutable std::shared_ptr<std::ostringstream> capture_stream_;
public:
|
b6eb15ba |
pstream open_fstream (const std::string &file_path) const override
{
|
a120158c |
pstream result = std::make_shared<std::istringstream> ("");
|
b6eb15ba |
std::string badinator (1, '\0');
result->read (&badinator[0], badinator.size());
return result;
}
|
d10906ee |
postream open_ofstream (const std::string &file_path,
|
fefe31f5 |
std::ios_base::openmode mode) const override
|
d10906ee |
{
captured_filename_ = file_path;
capture_stream_ = std::make_shared<std::ostringstream>();
return capture_stream_;
}
std::string captured_filename()
{
return captured_filename_;
}
std::string captured_written()
{
return capture_stream_->str();
}
|
af3a4cd4 |
};
|
bc6c3d35 |
class fake_fstreams : public fstreams_ifc
{
private:
std::string expected_file_path_;
std::string file_contents_;
public:
fake_fstreams (const std::string &expected_file_path,
const std::string &file_contents)
: expected_file_path_ (expected_file_path),
file_contents_ (file_contents) {}
|
af3a4cd4 |
pstream open_fstream (const std::string &file_path) const override
|
bc6c3d35 |
{
if (file_path == expected_file_path_) {
return fstreams::pstream (new std::istringstream (file_contents_));
} else {
return fstreams_ifc::open_fstream (file_path);
|
215ea751 |
}
|
bc6c3d35 |
}
|
215ea751 |
};
|
b6eb15ba |
class fake_rand : public random_source_ifc
{
public:
fake_rand ()
{}
std::vector<uint8_t> get_random_bytes (int length) const override
{
return {};
}
};
class fake_rand_with_specified_result : public random_source_ifc
{
private:
octet_vector expected_bytes_;
public:
fake_rand_with_specified_result (octet_vector expected_bytes)
: expected_bytes_ (expected_bytes)
{}
std::vector<uint8_t> get_random_bytes (int length) const override
{
return expected_bytes_;
}
};
|
194e6869 |
class fake_totp_generator : public token_generator_ifc
{
private:
std::string expected_token = "<unspecified>";
|
e57144d3 |
std::string key = "<unset>";
|
194e6869 |
public:
|
43889d2c |
fake_totp_generator (std::string expected_token = "<unspecified>") :
expected_token (expected_token)
{}
|
40fd6c82 |
std::string generate_token (const octet_vector &key) const override
|
8c62e61c |
{
|
194e6869 |
return expected_token;
}
};
|
0e218820 |
int reads_from_the_right_file ()
{
|
cb403154 |
//given
|
bb783d31 |
std::string home_directory = "/somedir";
|
215ea751 |
// hardcoded file name is .dual_control in the user's home directory
std::string token_file = home_directory + "/.dual_control";
|
c895ed8a |
std::string token ("AAAAAAAAAAAAAAAA");
|
43889d2c |
|
8c62e61c |
fstreams test_streams (fstreams::delegate (new fake_fstreams (token_file,
token)));
totp_generator generator (totp_generator::delegate (new
fake_totp_generator (token)));
|
a120158c |
random_source fake_rand (random_source::delegate (new class fake_rand));
|
215ea751 |
//file_reader test_file_reader (file_reader::delegate (new fake_file_reader));
|
bb783d31 |
user test_user (user::delegate (new fake_user (home_directory)));
|
b6eb15ba |
tokens supplier (tokens::create (test_streams, generator, fake_rand));
|
cb403154 |
//when
|
0e218820 |
std::string actual = supplier.token (test_user);
|
69be5e4e |
//then
|
215ea751 |
check (actual == token, "token does not match");
|
69be5e4e |
succeed();
}
|
43794015 |
int returns_empty_string_if_file_open_fail()
{
|
8ddb4fe9 |
//given
|
215ea751 |
std::string home_directory = "/somedir";
// hardcoded file name is .dual_control in the user's home directory
std::string token_file = home_directory + "/.not_dual_control";
|
bc6c3d35 |
fstreams test_streams (fstreams::delegate (new fake_fstreams (token_file,
"654321")));
|
8c62e61c |
totp_generator generator (totp_generator::delegate (new
fake_totp_generator ()));
|
a120158c |
random_source fake_rand (random_source::delegate (new class fake_rand));
|
194e6869 |
|
215ea751 |
user test_user (user::delegate (new fake_user (home_directory)));
|
b6eb15ba |
tokens supplier (tokens::create (test_streams, generator, fake_rand));
|
215ea751 |
|
8ddb4fe9 |
//when
std::string actual = supplier.token (test_user);
//then
|
43794015 |
check (actual == "", "should have returned empty string");
|
8ddb4fe9 |
succeed();
}
|
c05e07a6 |
int returns_empty_string_if_file_too_short()
{
//given
std::string home_directory = "/somedir";
// hardcoded file name is .dual_control in the user's home directory
std::string token_file = home_directory + "/.dual_control";
// we want a 40-byte key, so we need a 64-byte base32-encoded file.
|
c895ed8a |
std::string token ("AAAAAAAAAAAAAAA");
|
c05e07a6 |
|
8c62e61c |
fstreams test_streams (fstreams::delegate (new fake_fstreams (token_file,
token)));
totp_generator generator (totp_generator::delegate (new
fake_totp_generator (token)));
|
a120158c |
random_source fake_rand (random_source::delegate (new class fake_rand));
|
c05e07a6 |
//file_reader test_file_reader (file_reader::delegate (new fake_file_reader));
user test_user (user::delegate (new fake_user (home_directory)));
|
b6eb15ba |
tokens supplier (tokens::create (test_streams, generator, fake_rand));
|
c05e07a6 |
//when
std::string actual = supplier.token (test_user);
//then
check (actual == "", "should have returned empty string");
succeed();
}
|
b6eb15ba |
int writes_the_key ()
|
d10906ee |
{
|
af3a4cd4 |
// given
|
d10906ee |
std::string home_directory ("/somedir");
user test_user (user::delegate (new fake_user (home_directory)));
mock_write_fstreams *mockfs (new mock_write_fstreams);
fstreams test_streams{fstreams::delegate (mockfs)};
|
8c62e61c |
totp_generator generator (totp_generator::delegate (new
fake_totp_generator ()));
|
d10906ee |
std::string token ("token");
|
a120158c |
random_source fake_rand (random_source::delegate (new class fake_rand));
|
b6eb15ba |
tokens tokens (tokens::create (test_streams, generator, fake_rand));
|
af3a4cd4 |
//when
|
decf9568 |
tokens.save (test_user, token);
|
af3a4cd4 |
|
d10906ee |
// then
|
af3a4cd4 |
std::ostringstream temp;
temp << token << std::endl;
std::string expected_written = temp.str();
|
d10906ee |
std::string expected_filename (home_directory + "/.dual_control");
|
8c62e61c |
check (mockfs->captured_filename() == expected_filename,
"filename does not match");
check (mockfs->captured_written() == expected_written,
"token does not match");
|
d10906ee |
succeed();
|
af3a4cd4 |
}
|
b6eb15ba |
int ensure_key_creates_key_file_if_not_exists ()
{
// given
std::string home_directory ("/somedir");
user test_user (user::delegate (new fake_user (home_directory)));
mock_write_fstreams *mockfs (new mock_write_fstreams);
fstreams test_streams{fstreams::delegate (mockfs)};
|
a120158c |
totp_generator generator (totp_generator::delegate (new
fake_totp_generator ()));
|
b6eb15ba |
std::vector<uint8_t> random_bytes {4,2,4, 2,4, 2,4,2, 4,2};
|
a120158c |
random_source fake_rand (random_source::delegate (new
fake_rand_with_specified_result (random_bytes)));
|
b6eb15ba |
tokens tokens (tokens::create (test_streams, generator, fake_rand));
//when
std::string actual = tokens.ensure_key (test_user);
base32 codec;
|
a120158c |
octet_vector actual_decoded = codec.decode (actual);
|
b6eb15ba |
// then
std::string expected_filename (home_directory + "/.dual_control");
check (mockfs->captured_filename() == expected_filename,
"filename does not match");
check (random_bytes == actual_decoded,
"key does not match");
succeed();
}
int ensure_key_reads_key_file_if_exists ()
{
//given
std::string home_directory ("/somedir");
std::string key_file = home_directory + "/.dual_control";
std::string key ("AAAAAAAAAAAAAAAA");
user test_user (user::delegate (new fake_user (home_directory)));
fstreams test_streams (fstreams::delegate (new fake_fstreams (key_file,
key)));
|
a120158c |
totp_generator generator (totp_generator::delegate (new
fake_totp_generator ()));
random_source fake_rand (random_source::delegate (new class fake_rand));
|
b6eb15ba |
tokens tokens (tokens::create (test_streams, generator, fake_rand));
//when
std::string actual = tokens.ensure_key (test_user);
//then
|
a120158c |
check (actual == key, "read key does not match given key");
|
b6eb15ba |
succeed();
}
|
d4fb1155 |
int generate_key_uses_random_source ()
{
// given
user test_user (user::delegate (new fake_user ("/nowhere")));
fstreams test_streams{fstreams::delegate (new fake_fstreams ("<>", "<>"))};
|
a120158c |
totp_generator generator (totp_generator::delegate (new
fake_totp_generator ()));
|
d4fb1155 |
std::vector<uint8_t> random_bytes1 {4,2,4, 2,4, 2,4,2, 4,2};
|
a120158c |
random_source fake_rand1 (random_source::delegate (new
fake_rand_with_specified_result (random_bytes1)));
|
d4fb1155 |
tokens tokens1 (tokens::create (test_streams, generator, fake_rand1));
std::vector<uint8_t> random_bytes2 {1,2,1, 2,1, 1,2,1, 2,1};
|
a120158c |
random_source fake_rand2 (random_source::delegate (new
fake_rand_with_specified_result (random_bytes2)));
|
d4fb1155 |
tokens tokens2 (tokens::create (test_streams, generator, fake_rand2));
//when
std::string first_key = tokens1.generate_key();
std::string second_key = tokens2.generate_key();
// then
|
a120158c |
check (first_key != second_key,
"keys generated from differing random data should not match");
|
d4fb1155 |
succeed();
}
|
0e218820 |
int run_tests()
{
|
43889d2c |
test (reads_from_the_right_file);
test (returns_empty_string_if_file_open_fail);
|
c05e07a6 |
test (returns_empty_string_if_file_too_short);
|
b6eb15ba |
test (ensure_key_creates_key_file_if_not_exists);
test (ensure_key_reads_key_file_if_exists);
|
d4fb1155 |
test (generate_key_uses_random_source);
|
de34378f |
succeed();
}
|
0e218820 |
int main (int argc, char *argv[])
{
|
bef74c28 |
return !run_tests();
|
de34378f |
}
|
0d8b9a17 |
|