blob: 93db35dffc40334e1ccb0b6f279aa1864175eb6f [file] [log] [blame]
/*******************************************************************************
* Copyright (C) 2008 Centrify Corp. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* - Neither the name of Intel Corp. nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL Intel Corp. OR THE CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*******************************************************************************/
#ifdef SHTTPD_GSS
#include "shttpd_defs.h"
#include <gssapi/gssapi_generic.h>
#define THE_END "\tContent-Type: application/octet-stream"
#define FAKE_CT "Content-Type: application/soap+xml\r\n"
#define FAKE_CL "Content-Length: "
#define ENCRYPTED_BOUNDARY "-- Encrypted Boundary\r\n"
#define KERB_CONTENT_TYPE "\tContent-Type: application/HTTP-Kerberos-session-encrypted\r\n"
#define ORIGINAL_CONTENT "\tOriginalContent: type=application/soap+xml;charset=UTF-16;Length="
#define STREAM_CONTENT_TYPE "\tContent-Type: application/octet-stream\r\n"
void displayError(char *msg, OM_uint32 maj_stat, OM_uint32 min_stat);
int getCreds(char *service_name, gss_cred_id_t *server_creds);
int do_gss(struct conn *c);
int gss_encrypt(struct shttpd_arg *arg, char *input, int inlen, char **output, int *outlen);
char* gss_decrypt(struct shttpd_arg *arg, char *data, int len);
void getGssName(struct conn *c, char **user);
static void doDisplay(char *m,OM_uint32 code,int type)
{
OM_uint32 maj_stat, min_stat;
gss_buffer_desc msg;
OM_uint32 msg_ctx;
msg_ctx = 0;
while (1)
{
maj_stat = gss_display_status(&min_stat, code,
type, GSS_C_NULL_OID,
&msg_ctx, &msg);
DBG(("GSS-API error %s: %s\n", m,
(char *)msg.value));
(void) gss_release_buffer(&min_stat, &msg);
if (!msg_ctx)
break;
}
}
void displayError(char *msg, OM_uint32 maj_stat, OM_uint32 min_stat)
{
doDisplay(msg, maj_stat, GSS_C_GSS_CODE);
doDisplay(msg, min_stat, GSS_C_MECH_CODE);
}
int getCreds(char *service_name, gss_cred_id_t *server_creds)
{
gss_buffer_desc name_buf;
gss_name_t server_name;
OM_uint32 maj_stat, min_stat;
name_buf.value = service_name;
name_buf.length = strlen(name_buf.value) + 1;
maj_stat = gss_import_name(&min_stat, &name_buf,
(gss_OID) GSS_C_NT_HOSTBASED_SERVICE, &server_name);
if (maj_stat != GSS_S_COMPLETE)
{
displayError("importing name", maj_stat, min_stat);
return -1;
}
maj_stat = gss_acquire_cred(&min_stat, server_name, 0,
GSS_C_NULL_OID_SET, GSS_C_ACCEPT,
server_creds, NULL, NULL);
if (maj_stat != GSS_S_COMPLETE)
{
displayError("acquiring credentials", maj_stat, min_stat);
return -1;
}
(void) gss_release_name(&min_stat, &server_name);
return 0;
}
static int connectContext(
void *buff, int len,
gss_cred_id_t server_creds,
gss_ctx_id_t *context,
gss_buffer_t client_name,
gss_buffer_t reply,
OM_uint32 *ret_flags)
{
gss_buffer_desc recv_tok;
gss_name_t client;
gss_OID doid;
OM_uint32 maj_stat, min_stat, acc_sec_min_stat;
recv_tok.length = len;
recv_tok.value = buff;
maj_stat = gss_accept_sec_context(&acc_sec_min_stat,
context,
server_creds,
&recv_tok,
GSS_C_NO_CHANNEL_BINDINGS,
&client,
&doid,
reply,
ret_flags,
0,
0);
if (maj_stat != GSS_S_COMPLETE)
{
displayError("accept",maj_stat,min_stat);
return maj_stat;
}
maj_stat = gss_display_name(&min_stat, client, client_name, &doid);
if(maj_stat == 0)
{
DBG(("Accept principal = %.*s", client_name->length, client_name->value));
maj_stat = gss_release_name(&min_stat, &client);
}
return maj_stat;
}
int do_gss(struct conn *c)
{
gss_cred_id_t gsscreds;
gss_buffer_desc client_name;
gss_buffer_desc reply;
OM_uint32 retflags;
if(getCreds("host@localhost", &gsscreds) != 0)
return 0;
// now find the beginning and end of the base64 encoded stuff
struct vec *auth_vec = &c->ch.auth.v_vec;
char *p = (char *) auth_vec->ptr + 9;
while ((*p == ' ') || (*p == '\t'))
{
p++;
}
char *pp = p;
while ((*p != ' ') && (*p != '\t') && (*p != '\r')
&& (*p != '\n') && (*p != 0))
{
p++;
}
if (pp == p)
{
return 0;
}
*p = 0;
// pp = start, p = end. Now decode
char decbuf[p - pp]; // this is too long but we dont care
int l = ws_base64_decode(pp, p - pp, decbuf, 4095);
int ret = connectContext(decbuf, l, gsscreds, &c->gss_ctx, &client_name, &reply, &retflags);
if (ret != 0)
{
send_server_error(c, 400, "Bad request");
return 0;
}
// encode the reply
char repbuf[reply.length * 2]; // again, too large but we dont care
ws_base64_encode(reply.value, reply.length, repbuf);
io_clear(&c->loc.io);
c->loc.headers_len = c->loc.io.head = snprintf(c->loc.io.buf,
c->loc.io.size, "HTTP/1.1 200 \r\nWWW-Authenticate: Kerberos %s\r\n\r\n",
repbuf);
c->status = 200;
OM_uint32 min_stat;
gss_release_buffer(&min_stat, &reply);
c->loc.flags |= FLAG_RESPONSE_COMPLETE;
return 2;
}
int gss_encrypt(struct shttpd_arg *arg, char *input, int inlen, char **output, int *outlen)
{
struct conn *c = (struct conn *)arg->priv;
OM_uint32 min_stat;
gss_buffer_desc in_buf;
gss_buffer_desc out_buf;
int state;
// convert to UTF-16
char utf16[inlen * 2 + 2];
char *u = utf16;
*u++ = '\xff';
*u++ = '\xfe';
int i;
for (i = 0; i < inlen; i++)
{
*u++ = input[i];
*u++ = 0;
}
int msglen = inlen * 2 + 2; // the data converted to UTF16
msglen += sizeof(ENCRYPTED_BOUNDARY);
msglen += sizeof(KERB_CONTENT_TYPE);
msglen += sizeof(ORIGINAL_CONTENT);
msglen += 10; // computed length
msglen += sizeof(STREAM_CONTENT_TYPE);
msglen += sizeof(ENCRYPTED_BOUNDARY);
msglen += 200; // encryption overhead (checksums, flags, confounders etc
char *payload = (char*)malloc(msglen);
if(!payload)
return -1;
char *p = payload;
memcpy(p, ENCRYPTED_BOUNDARY, sizeof(ENCRYPTED_BOUNDARY) - 1);
p += sizeof(ENCRYPTED_BOUNDARY) - 1;
memcpy(p, KERB_CONTENT_TYPE, sizeof(KERB_CONTENT_TYPE) -1 );
p += sizeof(KERB_CONTENT_TYPE) - 1;
memcpy(p, ORIGINAL_CONTENT, sizeof(ORIGINAL_CONTENT) - 1);
p += sizeof(ORIGINAL_CONTENT) - 1 ;
p += snprintf(p, 30, "%d\r\n", inlen * 2 + 2 + 1); // windows wants this to be the size of the encrypted data
// and gss adds an extra pad byte
memcpy(p, ENCRYPTED_BOUNDARY, sizeof(ENCRYPTED_BOUNDARY) - 1);
p += sizeof(ENCRYPTED_BOUNDARY) - 1;
memcpy(p, STREAM_CONTENT_TYPE, sizeof(STREAM_CONTENT_TYPE) -1 );
p += sizeof(STREAM_CONTENT_TYPE) - 1;
in_buf.value = utf16;
in_buf.length = inlen * 2 + 2;
OM_uint32 maj_stat = gss_wrap(&min_stat, c->gss_ctx, 1, GSS_C_QOP_DEFAULT,
&in_buf, &state, &out_buf);
if (maj_stat != GSS_S_COMPLETE)
{
displayError("wrapping message", maj_stat, min_stat);
return -1;
}
// thsi is the encryption space overhead
*p++ = out_buf.length - in_buf.length - 1; // not quite sure why I need -1 but I do. Its all to do with the gss pad byte
*p++ = 0;
*p++ = 0;
*p++ = 0;
memcpy(p, out_buf.value, out_buf.length);
p += out_buf.length;
gss_release_buffer(&min_stat, &out_buf);
memcpy(p, ENCRYPTED_BOUNDARY, sizeof(ENCRYPTED_BOUNDARY) - 1);
p += sizeof(ENCRYPTED_BOUNDARY) - 1;
*output = payload;
*outlen = p - payload;
return 1; // OK
}
void getGssName(struct conn *c, char **user)
{
OM_uint32 majStat, minStat;
gss_name_t client;
gss_OID doid;
gss_buffer_desc client_name;
*user = 0;
majStat = gss_inquire_context(&minStat, c->gss_ctx, &client, 0,0,0,0,0,0);
if (majStat != GSS_S_COMPLETE)
{
displayError("inq context", majStat, minStat);
return;
}
majStat = gss_display_name(&minStat, client, &client_name, &doid);
if (majStat != GSS_S_COMPLETE)
{
displayError("get display name", majStat, minStat);
return;
}
*user = u_strndup(client_name.value, client_name.length);
majStat = gss_release_name(&minStat, &client);
}
char* gss_decrypt(struct shttpd_arg *arg, char *data, int len)
{
struct conn *c = (struct conn *)arg->priv;
OM_uint32 maj_stat, min_stat;
int conf_state;
gss_buffer_desc enc;
gss_buffer_desc plain;
DBG(("Decypting message %d", len));
char line[1000];
char *in = data;
int found = 0;
while ((in - data) < len)
{
char *p = line;
while ((*p++ = *in++) != '\n' && (p - line < sizeof(line) - 1));
*p = 0;
//DBG(("->%s", line));
if (!memcmp(line, THE_END, sizeof(THE_END) - 1 ))
{
found = 1;
break;
}
}
if(!found)
{
DBG(("Bad enc message format"));
return strdup("");
}
in += 4; // skip 2f000000, this is the GSS coding overhead
char *p = in;
found = 0;
while (p - data < len)
{
if (!memcmp(p,"-- Enc", 6))
{
found = 1;
break;
}
p++;
}
if(!found)
{
return strdup("");
}
enc.length= p - in;
enc.value = in;
maj_stat = gss_unwrap(&min_stat, c->gss_ctx, &enc, &plain,
&conf_state, (gss_qop_t *) NULL);
if (maj_stat != GSS_S_COMPLETE)
{
displayError("unsealing message", maj_stat, min_stat);
return strdup("");
}
char * pl = plain.value;
// naive UTF-16 to ascii conversion
char utf8[plain.length / 2 + 1];
p = utf8;
int j;
pl += 2; // fffe utf-16 lead in
for (j = 0; j < (plain.length - 2) / 2; j++)
{
*p++ = *pl++;
pl++;
}
*p = 0;
gss_release_buffer(&min_stat, &plain);
return( strdup(utf8));
}
#endif