| /******************************************************************************* |
| * 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 |