Commit 0e5a990d authored by ale's avatar ale
Browse files

Add functionality to encrypt tickets exchanged between endpoints

The communication between the SSO server and the /sso_login endpoint
of the target service (the 't' query argument) can be encrypted using
the NaCl cryptobox, with a new set of keypairs for the SSO service and
the target services.
parent 8f4c7d1e
......@@ -61,6 +61,12 @@ typedef struct {
// Note: public_key is a binary buffer (non zero-terminated).
const unsigned char *public_key;
// Encryption keys are fixed-size binary buffers too.
const unsigned char *encryption_public_key;
const unsigned char *local_encryption_public_key;
char *local_encryption_public_key_base64;
const unsigned char *local_encryption_secret_key;
// Same for the session_key (not fixed size though, so we store the
// size as well).
const unsigned char *session_key;
......@@ -70,6 +76,10 @@ typedef struct {
apr_array_header_t *groups;
} modsso_config;
static int modsso_config_ticket_encryption_enabled(modsso_config *s_cfg) {
return (s_cfg->encryption_public_key && s_cfg->local_encryption_public_key && s_cfg->local_encryption_secret_key);
}
typedef const char *(*CMD_HAND_TYPE)();
static char *groups_array_to_commasep_string(apr_pool_t *p,
......@@ -105,6 +115,10 @@ static void *create_modsso_config(apr_pool_t *p, char *s) {
newcfg->service = NULL;
newcfg->domain = NULL;
newcfg->public_key = NULL;
newcfg->encryption_public_key = NULL;
newcfg->local_encryption_public_key = NULL;
newcfg->local_encryption_public_key_base64 = NULL;
newcfg->local_encryption_secret_key = NULL;
newcfg->session_key = NULL;
newcfg->session_key_len = 0;
newcfg->groups = NULL;
......@@ -122,10 +136,16 @@ static void *merge_modsso_config(apr_pool_t *p, void *base, void *add) {
cadd->login_server ? cadd->login_server : cbase->login_server;
newcfg->service = cadd->service ? cadd->service : cbase->service;
newcfg->domain = cadd->domain ? cadd->domain : cbase->domain;
newcfg->public_key = cbase->public_key;
if (cadd->public_key) {
newcfg->public_key = cadd->public_key;
newcfg->public_key = cadd->public_key ? cadd->public_key : cbase->public_key;
newcfg->encryption_public_key = cadd->encryption_public_key ? cadd->encryption_public_key : cbase->encryption_public_key;
newcfg->local_encryption_secret_key = cadd->local_encryption_secret_key ? cadd->local_encryption_secret_key : cbase->local_encryption_secret_key;
newcfg->local_encryption_public_key = cbase->local_encryption_public_key;
newcfg->local_encryption_public_key_base64 = cbase->local_encryption_public_key_base64;
if (cadd->local_encryption_public_key) {
newcfg->local_encryption_public_key = cadd->local_encryption_public_key;
newcfg->local_encryption_public_key_base64 = cadd->local_encryption_public_key_base64;
}
newcfg->session_key = cbase->session_key;
newcfg->session_key_len = cbase->session_key_len;
if (cadd->session_key) {
......@@ -163,46 +183,59 @@ static const char *set_modsso_domain(cmd_parms *parms, void *mconfig,
static const char *set_modsso_public_key_file(cmd_parms *parms, void *mconfig,
const char *arg) {
modsso_config *s_cfg = (modsso_config *)mconfig;
char buf[128];
apr_size_t n = sizeof(buf);
apr_file_t *file;
int status;
if (apr_file_open(&file, arg, APR_FOPEN_READ, 0, parms->pool) !=
APR_SUCCESS) {
return "Could not open SSOPublicKeyFile";
if (modsso_read_fixed_size_file(parms->pool, arg, SSO_PUBLIC_KEY_SIZE, &s_cfg->public_key) < 0) {
return "Could not read SSOPublicKeyFile";
}
status = apr_file_read(file, (void *)buf, &n);
apr_file_close(file);
if (status != APR_SUCCESS) {
return "Could not read contents of SSOPublicKeyFile";
return NULL;
}
static const char *set_modsso_session_key_file(cmd_parms *parms, void *mconfig,
const char *arg) {
modsso_config *s_cfg = (modsso_config *)mconfig;
if (modsso_read_fixed_size_file(parms->pool, arg, MODSSO_SESSION_KEY_SIZE, &s_cfg->session_key) < 0) {
return "Could not read SSOSessionKeyFile";
}
s_cfg->session_key_len = MODSSO_SESSION_KEY_SIZE;
return NULL;
}
unsigned char *key = (unsigned char *)apr_palloc(parms->pool, n);
memcpy(key, buf, n);
s_cfg->public_key = key;
static const char *set_modsso_encryption_public_key_file(cmd_parms *parms, void *mconfig, const char *arg) {
modsso_config *s_cfg = (modsso_config *)mconfig;
if (modsso_read_fixed_size_file(parms->pool, arg, SSO_TICKET_ENCRYPTION_PUBLIC_KEY_SIZE, &s_cfg->encryption_public_key) < 0) {
return "Could not read SSOEncryptionPublicKeyFile";
}
return NULL;
}
static const char *set_modsso_session_key_file(cmd_parms *parms, void *mconfig,
const char *arg) {
static const char *set_modsso_local_encryption_public_key_file(cmd_parms *parms, void *mconfig, const char *arg) {
modsso_config *s_cfg = (modsso_config *)mconfig;
unsigned char *session_key = NULL;
size_t session_key_len = MODSSO_SESSION_KEY_SIZE;
size_t sz;
session_key = (unsigned char *)apr_palloc(parms->pool, session_key_len);
if (modsso_session_read_key_from_file(parms->pool, arg, session_key,
&session_key_len) < 0) {
return "Could not open SSOSessionKeyFile";
if (modsso_read_fixed_size_file(parms->pool, arg, SSO_TICKET_ENCRYPTION_PUBLIC_KEY_SIZE, &s_cfg->local_encryption_public_key) < 0) {
return "Could not read SSOLocalEncryptionPublicKeyFile";
}
s_cfg->session_key = session_key;
s_cfg->session_key_len = session_key_len;
sz = SSO_TICKET_ENCRYPTION_PUBLIC_KEY_SIZE * 2;
s_cfg->local_encryption_public_key_base64 = apr_palloc(parms->pool, sz);
if (sso_serialize_ticket_encryption_public_key(s_cfg->local_encryption_public_key, s_cfg->local_encryption_public_key_base64, sz) < 0) {
return "Failure to base64-encode the SSOLocalEncryptionPublicKeyFile";
}
return NULL;
}
static const char *set_modsso_local_encryption_secret_key_file(cmd_parms *parms, void *mconfig, const char *arg) {
modsso_config *s_cfg = (modsso_config *)mconfig;
if (modsso_read_fixed_size_file(parms->pool, arg, SSO_TICKET_ENCRYPTION_SECRET_KEY_SIZE, &s_cfg->local_encryption_secret_key) < 0) {
return "Could not read SSOEncryptionSecretKeyFile";
}
return NULL;
}
static const command_rec mod_sso_cmds[] = {
AP_INIT_TAKE1("SSOLoginServer", (CMD_HAND_TYPE)set_modsso_login_server,
NULL, OR_ALL,
......@@ -215,6 +248,18 @@ static const command_rec mod_sso_cmds[] = {
"SSOPublicKeyFile", (CMD_HAND_TYPE)set_modsso_public_key_file, NULL,
RSRC_CONF,
"SSOPublicKeyFile (string) Location of the login server public key"),
AP_INIT_TAKE1(
"SSOEncryptionPublicKeyFile", (CMD_HAND_TYPE)set_modsso_encryption_public_key_file, NULL,
RSRC_CONF,
"SSOEncryptionPublicKeyFile (string) Location of the login server SSO ticket encryption public key"),
AP_INIT_TAKE1(
"SSOLocalEncryptionPublicKeyFile", (CMD_HAND_TYPE)set_modsso_local_encryption_public_key_file, NULL,
RSRC_CONF,
"SSOLocalEncryptionPublicKeyFile (string) Location of the local SSO ticket encryption public key"),
AP_INIT_TAKE1(
"SSOLocalEncryptionSecretKeyFile", (CMD_HAND_TYPE)set_modsso_local_encryption_secret_key_file, NULL,
RSRC_CONF,
"SSOLocalEncryptionSecretKeyFile (string) Location of the local SSO ticket encryption secret key"),
AP_INIT_TAKE1(
"SSOSessionKeyFile", (CMD_HAND_TYPE)set_modsso_session_key_file, NULL,
RSRC_CONF,
......@@ -385,9 +430,49 @@ static int check_config(request_rec *r, modsso_config *s_cfg) {
"sso: SSOPublicKeyFile is not defined!");
return 0;
}
if ((s_cfg->encryption_public_key || s_cfg->local_encryption_public_key || s_cfg->local_encryption_secret_key)
&& !(s_cfg->encryption_public_key && s_cfg->local_encryption_public_key && s_cfg->local_encryption_secret_key)) {
ap_log_error(APLOG_MARK, APLOG_WARNING, 0, r->server,
"sso: all of SSOEncryptionPublicKey, SSOLocalEncryptionPublicKey and SSOLocalEncryptionSecretKey must be defined to enable ticket encryption!");
}
return 1;
}
static int modsso_get_params_from_request(modsso_config *s_cfg, request_rec *r, struct modsso_params *params) {
if (modsso_parse_query_string(r->pool, r->args, params) < 0) {
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
"sso: get_params_from_request: invalid query parameters");
return -1;
}
// If we have a local encryption key
if (modsso_config_ticket_encryption_enabled(s_cfg)) {
char ticket_buf[512];
if (!params->et || !params->et[0]) {
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
"sso: request is missing the 'et' query parameter");
return -1;
}
if (sso_ticket_decrypt(params->et, s_cfg->encryption_public_key, s_cfg->local_encryption_secret_key, ticket_buf, sizeof(ticket_buf)) < 0) {
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
"sso: could not decrypt ticket from request");
return -1;
}
params->t = apr_pstrdup(r->pool, ticket_buf);
} else {
if (!params->t || !params->t[0]) {
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
"sso: request is missing the 't' query parameter");
return -1;
}
}
return 0;
}
/**
* Apache method handler for mod_sso.
*
......@@ -452,8 +537,8 @@ static int mod_sso_method_handler(request_rec *r) {
return HTTP_BAD_REQUEST;
}
// Parse query params
if (modsso_parse_query_string(r->pool, r->args, &params) < 0) {
// Parse query params, including decrypting the ticket if necessary.
if (modsso_get_params_from_request(s_cfg, r, &params) < 0) {
ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
"sso: invalid parameters for sso_login: %s", r->args);
return HTTP_BAD_REQUEST;
......@@ -530,6 +615,9 @@ static int redirect_to_login_server(request_rec *r, modsso_config *s_cfg,
unique_id, sso_login_path);
}
}
if (modsso_config_ticket_encryption_enabled(s_cfg)) {
login_url = apr_pstrcat(r->pool, login_url, "&k=", s_cfg->local_encryption_public_key_base64, NULL);
}
if (groups) {
login_url =
apr_pstrcat(r->pool, login_url,
......
......@@ -25,6 +25,7 @@
#include "ap_config.h"
#include "apr_strings.h"
#include <sso/sso.h>
#include <sso/sso_encrypt.h>
#ifdef APLOG_USE_MODULE
APLOG_USE_MODULE(sso);
......@@ -44,6 +45,7 @@ struct request_rec;
struct modsso_params {
char *t;
char *et;
char *d;
};
......@@ -63,6 +65,9 @@ void modsso_set_cookie(request_rec *r, const char *cookie_name,
void modsso_del_cookie(request_rec *r, const char *cookie_name);
int modsso_read_fixed_size_file(apr_pool_t *pool, const char *path,
size_t size, const unsigned char **out);
// session.c
int modsso_session_read_key_from_file(apr_pool_t *pool, const char *path,
unsigned char *out, size_t *outsz);
......
......@@ -22,16 +22,18 @@
* OTHER DEALINGS IN THE SOFTWARE.
*/
#include <ctype.h>
#include "ap_config.h"
#include "apr_strings.h"
#include "httpd.h"
#include "http_config.h"
#include "http_core.h"
#include "http_log.h"
#include "apr_strings.h"
#include "http_protocol.h"
#include "http_main.h"
#include "ap_config.h"
#include "http_protocol.h"
#include "mod_sso.h"
#include <ctype.h>
// Ugly implementation of url decoding. Returns a newly allocated
// string (using malloc).
......@@ -40,48 +42,48 @@ char *modsso_url_decode(apr_pool_t *p, const char *i) {
char state = 'N';
char buf[3], obuf[2];
for (o = output; *i; i++) {
switch(state) {
switch (state) {
case 'N':
if (*i == '%') state = 1;
else *o++ = *i;
if (*i == '%')
state = 1;
else
*o++ = *i;
break;
case 1: {
char c = tolower(*i);
buf[0] = *i;
if ((c >= 'a' && c <= 'f') || (c >= '0' && c <= '9')) {
state = 2;
} else {
*o++ = '%';
*o++ = *i;
state = 'N';
}
break;
}
case 2: {
char c = tolower(*i);
if ((c >= 'a' && c <= 'f') || (c >= '0' && c <= '9')) {
buf[1] = *i;
buf[2] = 0;
obuf[0] = strtol(buf, 0, 16);
obuf[1] = 0;
if (obuf[0])
*o++ = obuf[0];
} else {
*o++ = '%';
*o++ = buf[0];
*o++ = *i;
}
state = 'N';
break;
case 1:
{
char c = tolower(*i);
buf[0] = *i;
if ((c >= 'a' && c <= 'f') || (c >= '0' && c <= '9')) {
state = 2;
} else {
*o++ = '%';
*o++ = *i;
state = 'N';
}
break;
}
case 2:
{
char c = tolower(*i);
if ((c >= 'a' && c <= 'f') || (c >= '0' && c <= '9')) {
buf[1] = *i;
buf[2] = 0;
obuf[0] = strtol(buf, 0, 16);
obuf[1] = 0;
if (obuf[0])
*o++ = obuf[0];
} else {
*o++ = '%';
*o++ = buf[0];
*o++ = *i;
}
state = 'N';
break;
}
}
}
}
if (state != 'N') {
*o++ = buf[0];
if (state > 1)
*o++ = buf[1];
*o++ = buf[1];
}
*o = '\0';
return output;
......@@ -105,11 +107,13 @@ char *modsso_url_encode(apr_pool_t *p, const char *s) {
return out;
}
int modsso_parse_query_string(apr_pool_t *p, const char *str, modsso_params_t params) {
int modsso_parse_query_string(apr_pool_t *p, const char *str,
modsso_params_t params) {
char *tmp = apr_pstrdup(p, str), *strptr = NULL;
params->d = NULL;
params->t = NULL;
params->et = NULL;
if (str == NULL) {
return 0;
......@@ -136,31 +140,30 @@ int modsso_parse_query_string(apr_pool_t *p, const char *str, modsso_params_t pa
params->d = value;
} else if (!strcmp(token, "t")) {
params->t = value;
} else if (!strcmp(token, "et")) {
params->et = value;
}
}
if (!params->d || !params->d[0]
|| !params->t || !params->t[0]) {
// Both 'd' and one of 't' or 'et' must be set.
if (!params->d || !params->d[0] ||
((!params->t || !params->t[0]) && (!params->et || !params->et[0]))) {
return -1;
}
return 0;
}
static char *make_cookie_value(request_rec *r,
const char *name,
const char *session_id,
const char *path,
int cookie_lifespan)
{
static char *make_cookie_value(request_rec *r, const char *name,
const char *session_id, const char *path,
int cookie_lifespan) {
size_t sz = 1024;
char *cookie_value = (char *)apr_palloc(r->pool, sz);
if (cookie_lifespan == 0) {
snprintf(cookie_value, sz,
"%s=%s; path=%s; secure; httpOnly;",
name, session_id, path);
snprintf(cookie_value, sz, "%s=%s; path=%s; secure; httpOnly;", name,
session_id, path);
} else {
char expires[200];
time_t t;
......@@ -168,8 +171,7 @@ static char *make_cookie_value(request_rec *r,
t = time(NULL) + cookie_lifespan;
tmp = gmtime(&t);
strftime(expires, sizeof(expires), "%a, %d-%b-%Y %H:%M:%S GMT", tmp);
snprintf(cookie_value, sz,
"%s=%s; expires=%s; path=%s; secure; httpOnly;",
snprintf(cookie_value, sz, "%s=%s; expires=%s; path=%s; secure; httpOnly;",
name, session_id, expires, path);
}
return cookie_value;
......@@ -185,7 +187,7 @@ static char *make_cookie_value(request_rec *r,
char *modsso_get_cookie(request_rec *r, const char *cookie_name) {
char *rv = NULL, *cookies, *cookie, *tokenizerCtx = NULL;
int cookie_name_len = strlen(cookie_name);
const char *cookies_c = apr_table_get(r->headers_in, "Cookie");
if (cookies_c == NULL) {
return NULL;
......@@ -198,7 +200,8 @@ char *modsso_get_cookie(request_rec *r, const char *cookie_name) {
while (cookie) {
while (*cookie == ' ')
cookie++;
if (strncmp(cookie, cookie_name, cookie_name_len) == 0 && cookie[cookie_name_len] == '=') {
if (strncmp(cookie, cookie_name, cookie_name_len) == 0 &&
cookie[cookie_name_len] == '=') {
cookie += (cookie_name_len + 1);
rv = apr_pstrdup(r->pool, cookie);
break;
......@@ -208,20 +211,43 @@ char *modsso_get_cookie(request_rec *r, const char *cookie_name) {
return rv;
}
void modsso_set_cookie(request_rec *r, const char *cookie_name,
const char *value, const char *path)
{
void modsso_set_cookie(request_rec *r, const char *cookie_name,
const char *value, const char *path) {
char *cookie_value;
cookie_value = make_cookie_value(r, cookie_name, value, path, 0);
apr_table_setn(r->err_headers_out, "Set-Cookie",
apr_table_setn(r->err_headers_out, "Set-Cookie",
apr_pstrdup(r->pool, cookie_value));
}
void modsso_del_cookie(request_rec *r, const char *cookie_name)
{
void modsso_del_cookie(request_rec *r, const char *cookie_name) {
char cookie_value[512];
sprintf(cookie_value, "%s=; expires=Mar, 01-01-1971 00:00:00 GMT",
cookie_name);
apr_table_setn(r->err_headers_out, "Set-Cookie",
apr_pstrdup(r->pool, cookie_value));
}
int modsso_read_fixed_size_file(apr_pool_t *pool, const char *path, size_t size,
const unsigned char **out) {
char *m = NULL;
int status;
apr_file_t *file;
apr_size_t n;
if (apr_file_open(&file, path, APR_FOPEN_READ, 0, pool) != APR_SUCCESS)
goto fail;
n = size;
m = apr_palloc(pool, n);
status = apr_file_read(file, m, &n);
apr_file_close(file);
if (status != APR_SUCCESS || n != size)
goto fail;
*out = (unsigned char *)m;
return 0;
fail:
// apr_pfree(pool, m);
return -1;
}
......@@ -8,6 +8,7 @@ ssotool_LDADD = libsso.la
libsso_la_SOURCES = \
sso.c sso.h \
sso_encrypt.c sso_encrypt.h \
base64.c \
randombytes.c \
tweetnacl.c tweetnacl.h
......
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "sso.h"
#include "tweetnacl.h"
extern void randombytes(unsigned char *, unsigned long long);
int sso_generate_encryption_keys(unsigned char *publicp,
unsigned char *secretp) {
return crypto_box_keypair(publicp, secretp);
}
int sso_serialize_ticket_encryption_public_key(const unsigned char *public_key,
char *out, size_t out_sz) {
size_t sz = out_sz;
return sso_base64_encode((unsigned char *)out, &sz, public_key,
crypto_box_PUBLICKEYBYTES);
}
int sso_ticket_encrypt(const char *serialized_ticket,
const char *serialized_pub_key,
const unsigned char *secret_key, char *out,
size_t out_sz) {
unsigned char pub_key[crypto_box_PUBLICKEYBYTES];
unsigned char n[crypto_box_NONCEBYTES];
unsigned char *m = NULL, *c = NULL;
int serialized_ticket_len;
int mlen;
int ret = -1;
size_t sz, enc_sz, nonce_sz, pub_sz;
// Decode the public key (base64-encoded).
pub_sz = crypto_box_PUBLICKEYBYTES;
ret = sso_base64_decode(pub_key, &pub_sz, (unsigned char *)serialized_pub_key,
strlen(serialized_pub_key));
if (ret < 0)
return ret;
// Encrypt the serialized ticket string.
serialized_ticket_len = strlen(serialized_ticket);
mlen = serialized_ticket_len + crypto_box_ZEROBYTES;
c = malloc(mlen);
m = malloc(mlen);
memset(m, 0, crypto_box_ZEROBYTES);
memcpy(m + crypto_box_ZEROBYTES, serialized_ticket, serialized_ticket_len);
randombytes(n, crypto_box_NONCEBYTES);
crypto_box(c, m, mlen, n, pub_key, secret_key);
// Build the output string by concatenating the nonce and the
// encrypted message, each base64-encoded, separated by a dot.
nonce_sz = out_sz;
ret = sso_base64_encode((unsigned char *)out, &nonce_sz, n,
crypto_box_NONCEBYTES);
if (ret < 0)
goto fail;
out[nonce_sz++] = '.';
sz = out_sz - nonce_sz;
enc_sz = mlen - crypto_box_BOXZEROBYTES;
ret = sso_base64_encode((unsigned char *)(out + nonce_sz), &sz,
c + crypto_box_BOXZEROBYTES, enc_sz);
fail:
free(m);
free(c);
return ret;
}
int sso_ticket_decrypt(const char *encrypted, const unsigned char *public_key,
const unsigned char *secret_key, char *out,
size_t out_sz) {
const char *nonce, *enc_msg;
unsigned char n[crypto_box_NONCEBYTES];
unsigned char *c = NULL, *m = NULL;
int nonce_b64len, enc_msg_b64len, clen;
size_t nonce_sz, enc_msg_sz;
int ret;
nonce = encrypted;
enc_msg = strchr(encrypted, '.');
if (!enc_msg)
return -1;
nonce_b64len = enc_msg - nonce;
enc_msg++;
enc_msg_b64len = strlen(enc_msg);
nonce_sz = crypto_box_NONCEBYTES;
ret = sso_base64_decode(n, &nonce_sz, (unsigned char *)nonce, nonce_b64len);
if (ret < 0)
goto fail;
if (nonce_sz != crypto_box_NONCEBYTES) {
ret = -1;
goto fail;
}
c = malloc(crypto_box_BOXZEROBYTES + enc_msg_b64len);
memset(c, 0, crypto_box_BOXZEROBYTES);
enc_msg_sz = enc_msg_b64len;
ret = sso_base64_decode(c + crypto_box_BOXZEROBYTES, &enc_msg_sz,
(unsigned char *)enc_msg, enc_msg_b64len);
if (ret < 0)
goto fail;
clen = crypto_box_BOXZEROBYTES + enc_msg_sz;
m = malloc(clen);
ret = crypto_box_open(m, c, clen, n, public_key, secret_key);
if (ret < 0)
goto fail;