Commit bd5889c8 authored by ale's avatar ale

Validate ticket fields before signing

It's probably wise to verify that the ticket that we're about to sign is
syntactically correct. Required fields must be present, and values
should not contain separator characters.
parent 7dc1803f
......@@ -251,6 +251,33 @@ int sso_generate_keys(unsigned char *publicp, unsigned char *secretp) {
return crypto_sign_keypair(publicp, secretp);
}
static int sso_ticket_validate_fields(sso_ticket_t t) {
char **gp;
// Ensure that required fields are set.
if ((t->user == NULL) || (t->service == NULL) || (t->domain == NULL)) {
return SSO_ERR_MISSING_REQUIRED_FIELD;
}
// Check the syntax of the fields. The only requirement is that they
// do not contain the separator character, besides that anything
// will do. The (better) alternative would be to escape the values.
if ((strchr(t->user, FIELD_SEP_CH) != NULL) ||
(strchr(t->service, FIELD_SEP_CH) != NULL) ||
(strchr(t->domain, FIELD_SEP_CH) != NULL)) {
return SSO_ERR_INVALID_FIELD;
}
if (t->groups != NULL) {
for (gp = t->groups; *gp; gp++) {
if ((strchr(*gp, FIELD_SEP_CH) != NULL) ||
strchr(*gp, GROUP_SEP_CH) != NULL) {
return SSO_ERR_INVALID_FIELD;
}
}
}
return SSO_OK;
}
int sso_ticket_sign(sso_ticket_t t, const unsigned char *secret_key, char *out,
size_t out_size) {
char *serialized;
......@@ -259,6 +286,11 @@ int sso_ticket_sign(sso_ticket_t t, const unsigned char *secret_key, char *out,
unsigned long long signed_size;
int r;
r = sso_ticket_validate_fields(t);
if (r != SSO_OK) {
return r;
}
serialized = sso_ticket_serialize(t);
if (serialized == NULL) {
return SSO_ERR_SERIALIZATION;
......@@ -360,6 +392,10 @@ const char *sso_strerror(int err) {
return "invalid ticket (bad domain)";
case SSO_ERR_NO_MATCHING_GROUPS:
return "no matching groups";
case SSO_ERR_MISSING_REQUIRED_FIELD:
return "missing a required field";
case SSO_ERR_INVALID_FIELD:
return "a field has invalid syntax";
default:
return "unknown error";
}
......
......@@ -21,6 +21,8 @@ extern "C" {
#define SSO_ERR_BAD_DOMAIN -9
#define SSO_ERR_NO_MATCHING_GROUPS -10
#define SSO_ERR_DECODE64 -11
#define SSO_ERR_MISSING_REQUIRED_FIELD -13
#define SSO_ERR_INVALID_FIELD -14
#define SSO_PUBLIC_KEY_SIZE 32
#define SSO_SECRET_KEY_SIZE 64
......
......@@ -12,7 +12,8 @@ TEST(Base64, EncodeOk) {
const char *in = "tests";
char out[128] = {0};
size_t sz = sizeof(out);
EXPECT_EQ(0, sso_base64_encode((unsigned char *)out, &sz, (unsigned char *)in, (size_t)strlen(in)));
EXPECT_EQ(0, sso_base64_encode((unsigned char *)out, &sz, (unsigned char *)in,
(size_t)strlen(in)));
EXPECT_EQ(8, int(sso_base64_encode_size((size_t)strlen(in))));
EXPECT_EQ(8, int(sz));
EXPECT_EQ("dGVzdHM=", std::string(out));
......@@ -22,23 +23,20 @@ TEST(Base64, DecodeOk) {
char in[] = "dGVzdHM=";
char out[128] = {0};
size_t sz = sizeof(out);
EXPECT_EQ(0, sso_base64_decode((unsigned char*)out, &sz, (unsigned char *)in, (size_t)strlen(in)));
EXPECT_EQ(0, sso_base64_decode((unsigned char *)out, &sz, (unsigned char *)in,
(size_t)strlen(in)));
EXPECT_EQ(5, int(sz));
EXPECT_EQ("tests", std::string(out));
}
class SSO : public testing::Test {
protected:
virtual void SetUp() {
sso_generate_keys(public_key, secret_key);
}
virtual void SetUp() { sso_generate_keys(public_key, secret_key); }
// Return a signed ticket, for test data generation.
char *sign_ticket(sso_ticket_t t) {
char buf[1024];
EXPECT_EQ(0, sso_ticket_sign(t, secret_key,
buf, sizeof(buf)));
EXPECT_EQ(0, sso_ticket_sign(t, secret_key, buf, sizeof(buf)));
// No further use for the original ticket.
sso_ticket_free(t);
return strdup(buf);
......@@ -46,12 +44,10 @@ protected:
// Sign a ticket with a random secret key.
char *sign_ticket_with_random_key(sso_ticket_t t) {
unsigned char pk[SSO_PUBLIC_KEY_SIZE],
sk[SSO_SECRET_KEY_SIZE];
unsigned char pk[SSO_PUBLIC_KEY_SIZE], sk[SSO_SECRET_KEY_SIZE];
sso_generate_keys(pk, sk);
char buf[1024];
EXPECT_EQ(0, sso_ticket_sign(t, sk,
buf, sizeof(buf)));
EXPECT_EQ(0, sso_ticket_sign(t, sk, buf, sizeof(buf)));
// No further use for the original ticket.
sso_ticket_free(t);
return strdup(buf);
......@@ -64,12 +60,10 @@ protected:
size_t sz = sizeof(buf);
unsigned long long encsz = sizeof(encbuf);
crypto_sign(encbuf, &encsz,
(const unsigned char *)contents,
crypto_sign(encbuf, &encsz, (const unsigned char *)contents,
strlen(contents), secret_key);
sso_base64_encode((unsigned char *)buf, &sz,
encbuf, encsz);
sso_base64_encode((unsigned char *)buf, &sz, encbuf, encsz);
return strdup((const char *)buf);
}
......@@ -77,11 +71,41 @@ protected:
unsigned char secret_key[SSO_SECRET_KEY_SIZE];
};
struct sign_testdata {
sso_ticket_t tkt;
int expected_result;
};
TEST_F(SSO, Sign) {
sso_ticket_t t = sso_ticket_new("user", "service/", "domain", NULL, 7200);
struct sign_testdata td[] = {
{sso_ticket_new("user", "service/", "domain", NULL, 7200), 0},
{sso_ticket_new(NULL, NULL, NULL, NULL, 7200),
SSO_ERR_MISSING_REQUIRED_FIELD},
{sso_ticket_new(NULL, "service", "domain", NULL, 7200),
SSO_ERR_MISSING_REQUIRED_FIELD},
{sso_ticket_new("user", NULL, "domain", NULL, 7200),
SSO_ERR_MISSING_REQUIRED_FIELD},
{sso_ticket_new("user", "service", NULL, NULL, 7200),
SSO_ERR_MISSING_REQUIRED_FIELD},
{sso_ticket_new("u|ser", "service/", "domain", NULL, 7200),
SSO_ERR_INVALID_FIELD},
{sso_ticket_new("user", "s|ervice/", "domain", NULL, 7200),
SSO_ERR_INVALID_FIELD},
{sso_ticket_new("user", "service/", "d|omain", NULL, 7200),
SSO_ERR_INVALID_FIELD},
{NULL, 0},
};
char buf[1024];
EXPECT_EQ(0, sso_ticket_sign(t, secret_key, buf, sizeof(buf)));
sso_ticket_free(t);
for (struct sign_testdata *tdp = td; tdp->tkt; tdp++) {
EXPECT_EQ(tdp->expected_result,
sso_ticket_sign(tdp->tkt, secret_key, buf, sizeof(buf)))
<< "Failed test: #" << (tdp - td);
sso_ticket_free(tdp->tkt);
}
}
struct open_testdata {
......@@ -90,33 +114,36 @@ struct open_testdata {
};
TEST_F(SSO, Open) {
const char *groups[] = {
"users", "wheel", "daemon", NULL
};
const char *groups[] = {"users", "wheel", "daemon", NULL};
struct open_testdata td[] = {
{sign_ticket(sso_ticket_new("user", "service/", "domain", NULL, 7200)), 0},
{sign_ticket(sso_ticket_new("user", "service/", "domain", groups, 7200)), 0},
{sign_ticket(sso_ticket_new("user", "", "", NULL, 7200)), 0},
{sign_string("4|user|service/|domain|1414402999|"), SSO_ERR_UNSUPPORTED_VERSION},
{sign_string("3|definitely not a ticket"), SSO_ERR_DESERIALIZATION},
{sign_string("3|||||"), 0},
{sign_ticket_with_random_key(sso_ticket_new("user", "service/", "domain", NULL, 7200)), SSO_ERR_BAD_SIGNATURE},
{strdup("not a base64-encoded string"), SSO_ERR_DECODE64},
{strdup("\377\377\377"), SSO_ERR_DECODE64},
{strdup("dGVzdHM="), SSO_ERR_BAD_SIGNATURE},
{strdup(""), SSO_ERR_BAD_SIGNATURE},
{NULL, 0},
{sign_ticket(sso_ticket_new("user", "service/", "domain", NULL, 7200)),
0},
{sign_ticket(sso_ticket_new("user", "service/", "domain", groups, 7200)),
0},
{sign_ticket(sso_ticket_new("user", "", "", NULL, 7200)), 0},
{sign_string("4|user|service/|domain|1414402999|"),
SSO_ERR_UNSUPPORTED_VERSION},
{sign_string("3|definitely not a ticket"), SSO_ERR_DESERIALIZATION},
{sign_string("3|||||"), 0},
{sign_ticket_with_random_key(
sso_ticket_new("user", "service/", "domain", NULL, 7200)),
SSO_ERR_BAD_SIGNATURE},
{strdup("not a base64-encoded string"), SSO_ERR_DECODE64},
{strdup("\377\377\377"), SSO_ERR_DECODE64},
{strdup("dGVzdHM="), SSO_ERR_BAD_SIGNATURE},
{strdup(""), SSO_ERR_BAD_SIGNATURE},
{NULL, 0},
};
for (struct open_testdata *tdp = td; tdp->ticket_str; tdp++) {
sso_ticket_t t2 = NULL;
EXPECT_EQ(tdp->expected_result,
sso_ticket_open(&t2, tdp->ticket_str, public_key))
<< "Failed decoding ticket: " << tdp->ticket_str;
<< "Failed decoding ticket: " << tdp->ticket_str;
if (tdp->expected_result == 0) {
EXPECT_TRUE(t2);
} else {
......@@ -144,7 +171,7 @@ TEST_F(SSO, InternalSerializationBufferFull) {
// Ticket with a very big field that should exceed the size of the
// internal serialization buffer.
char *big = big_string();
sso_ticket_t t = sso_ticket_new(big, NULL, NULL, NULL, 7200);
sso_ticket_t t = sso_ticket_new(big, "service", "domain", NULL, 7200);
free(big);
EXPECT_EQ(SSO_ERR_SERIALIZATION,
sso_ticket_sign(t, secret_key, buf, sizeof(buf)));
......@@ -152,7 +179,7 @@ TEST_F(SSO, InternalSerializationBufferFull) {
// Ticket with a very large list of groups.
int ng = 255;
const char *groups[ng+1];
const char *groups[ng + 1];
for (int i = 0; i < ng; i++)
groups[i] = "aaaaaaaaaaaaaaaa";
groups[ng] = NULL;
......@@ -169,8 +196,7 @@ TEST_F(SSO, SerializationOutputBufferFull) {
char buf[1024];
memset(buf, 42, sizeof(buf));
EXPECT_EQ(SSO_ERR_BUFFER_TOO_SMALL,
sso_ticket_sign(t, secret_key, buf, 16));
EXPECT_EQ(SSO_ERR_BUFFER_TOO_SMALL, sso_ticket_sign(t, secret_key, buf, 16));
for (int i = 16; i < 1024; i++) {
EXPECT_EQ(42, buf[i]) << "Detected overflow at pos " << i;
......@@ -198,36 +224,44 @@ TEST_F(SSO, Validation) {
const char *groups_fail[] = {"users", "others", NULL};
struct validate_testcontext with_groups = {
"service/", "domain", allowed_groups,
"service/", "domain", allowed_groups,
};
struct validate_testcontext without_groups = {
"service/", "domain", NULL,
"service/", "domain", NULL,
};
struct validate_testdata td[] = {
{"ticket_with_groups, validate_groups",
sso_ticket_new("user", "service/", "domain", groups_ok, 7200), &with_groups, 0},
{"ticket_with_groups, validate_no_groups",
sso_ticket_new("user", "service/", "domain", groups_ok, 7200), &without_groups, 0},
{"ticket_without_groups, validate_groups",
sso_ticket_new("user", "service/", "domain", NULL, 7200), &with_groups, SSO_ERR_NO_MATCHING_GROUPS},
{"ticket_without_groups, validate_no_groups",
sso_ticket_new("user", "service/", "domain", NULL, 7200), &without_groups, 0},
{"ticket_with_bad_groups",
sso_ticket_new("user", "service/", "domain", groups_fail, 7200), &with_groups, SSO_ERR_NO_MATCHING_GROUPS},
{"bad_domain",
sso_ticket_new("user", "service/", "other", groups_ok, 7200), &with_groups, SSO_ERR_BAD_DOMAIN},
{"bad_service",
sso_ticket_new("user", "other/", "domain", groups_ok, 7200), &with_groups, SSO_ERR_BAD_SERVICE},
{"expired",
sso_ticket_new("user", "service/", "domain", NULL, -1000), &without_groups, SSO_ERR_EXPIRED},
{NULL, NULL, NULL, 0},
{"ticket_with_groups, validate_groups",
sso_ticket_new("user", "service/", "domain", groups_ok, 7200),
&with_groups, 0},
{"ticket_with_groups, validate_no_groups",
sso_ticket_new("user", "service/", "domain", groups_ok, 7200),
&without_groups, 0},
{"ticket_without_groups, validate_groups",
sso_ticket_new("user", "service/", "domain", NULL, 7200), &with_groups,
SSO_ERR_NO_MATCHING_GROUPS},
{"ticket_without_groups, validate_no_groups",
sso_ticket_new("user", "service/", "domain", NULL, 7200),
&without_groups, 0},
{"ticket_with_bad_groups",
sso_ticket_new("user", "service/", "domain", groups_fail, 7200),
&with_groups, SSO_ERR_NO_MATCHING_GROUPS},
{"bad_domain",
sso_ticket_new("user", "service/", "other", groups_ok, 7200),
&with_groups, SSO_ERR_BAD_DOMAIN},
{"bad_service",
sso_ticket_new("user", "other/", "domain", groups_ok, 7200),
&with_groups, SSO_ERR_BAD_SERVICE},
{"expired", sso_ticket_new("user", "service/", "domain", NULL, -1000),
&without_groups, SSO_ERR_EXPIRED},
{NULL, NULL, NULL, 0},
};
for (struct validate_testdata *tdp = td; tdp->t; tdp++) {
EXPECT_EQ(tdp->expected_result,
sso_validate(tdp->t, tdp->c->service, tdp->c->domain, tdp->c->ok_groups))
<< "Validation failed: " << tdp->descr;
sso_validate(tdp->t, tdp->c->service, tdp->c->domain,
tdp->c->ok_groups))
<< "Validation failed: " << tdp->descr;
sso_ticket_free(tdp->t);
}
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment