Win32-OpenSSH/kerberos-sspi.c

864 lines
20 KiB
C
Raw Normal View History

/*
* Author: NoMachine <developers@nomachine.com>
*
* Copyright (c) 2009, 2011 NoMachine
* All rights reserved
*
* Support functions and system calls' replacements needed to let the
* software run on Win32 based operating systems.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. 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.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 THE AUTHOR 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.
*/
#include "includes.h"
#ifdef WIN32_FIXME
/*
* Includes.
*/
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <netdb.h>
#include <pwd.h>
#include <signal.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#if defined(HAVE_STRNVIS) && defined(HAVE_VIS_H)
#include <vis.h>
#endif
#include "openbsd-compat/sys-queue.h"
#include "xmalloc.h"
#include "ssh.h"
#include "ssh2.h"
#include "buffer.h"
#include "packet.h"
#include "compat.h"
#include "cipher.h"
#include "key.h"
#include "kex.h"
#include "myproposal.h"
#include "sshconnect.h"
#include "authfile.h"
#include "dh.h"
#include "authfd.h"
#include "log.h"
#include "readconf.h"
#include "misc.h"
#include "match.h"
#include "dispatch.h"
#include "canohost.h"
#include "msg.h"
#include "pathnames.h"
#include "uidswap.h"
#include "hostfile.h"
#include "schnorr.h"
#include "jpake.h"
#include "ssh-gss.h"
#include "kerberos-sspi.h"
/*
* Defines.
*/
#define FAIL(X) if (X) goto fail
#define FAILEX(X, ...) if (X) {error(__VA_ARGS__); goto fail;}
#define SSPI_FAIL(X) if ((sspiCode = (X)) != SEC_E_OK) goto fail
/*
* Structs.
*/
typedef struct Authctxt Authctxt;
typedef struct Authmethod Authmethod;
struct Authmethod
{
char *name;
void *userauth;
void *cleanup;
int *enabled;
int *batch_flag;
};
struct Authctxt
{
const char *server_user;
const char *local_user;
const char *host;
const char *service;
Authmethod *method;
sig_atomic_t success;
char *authlist;
void *keys;
void *agent;
void *sensitive;
int info_req_seen;
void *methoddata;
};
/*
* Hardcoded, kerberos5 OID in <type><len><OID> format.
*/
static unsigned char KRB5_OID[] =
{
SSH_GSS_OIDTYPE,
9,
0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x01, 0x02, 0x02
};
void input_sspi_kerberos_token(int type, u_int32_t plen, void *ctxt);
void input_sspi_kerberos_error(int type, u_int32_t plen, void *ctxt);
void input_sspi_kerberos_errtok(int type, u_int32_t plen, void *ctxt);
int SspiProcessToken(void *input, int inputSize, Authctxt *auth);
void input_sspi_kerberos_response(int type, u_int32_t plen, void *ctxt);
/*
* Convert SECURITY_STATUS code into human readable string.
*
* RETURNS: Human readable string or "UNKNOWN" if unknown code.
*/
const char *SspiGetCodeName(DWORD code)
{
struct
{
DWORD code_;
const char *name_;
}
map[] =
{
{SEC_E_OK, "SEC_E_OK"},
{SEC_E_CERT_EXPIRED, "SEC_E_CERT_EXPIRED"},
{SEC_E_INCOMPLETE_MESSAGE, "SEC_E_INCOMPLETE_MESSAGE"},
{SEC_E_INSUFFICIENT_MEMORY, "SEC_E_INSUFFICIENT_MEMORY"},
{SEC_E_INTERNAL_ERROR, "SEC_E_INTERNAL_ERROR"},
{SEC_E_INVALID_HANDLE, "SEC_E_INTERNAL_ERROR"},
{SEC_E_INVALID_TOKEN, "SEC_E_INTERNAL_ERROR"},
{SEC_E_LOGON_DENIED, "SEC_E_INTERNAL_ERROR"},
{SEC_E_NO_AUTHENTICATING_AUTHORITY, "SEC_E_INTERNAL_ERROR"},
{SEC_E_NO_CREDENTIALS, "SEC_E_INTERNAL_ERROR"},
{SEC_E_TARGET_UNKNOWN, "SEC_E_TARGET_UNKNOWN"},
{SEC_E_UNSUPPORTED_FUNCTION, "SEC_E_UNSUPPORTED_FUNCTION"},
{SEC_E_UNTRUSTED_ROOT, "SEC_E_UNTRUSTED_ROOT"},
{SEC_E_WRONG_PRINCIPAL, "SEC_E_WRONG_PRINCIPAL"},
{SEC_E_SECPKG_NOT_FOUND, "SEC_E_SECPKG_NOT_FOUND"},
{SEC_E_QOP_NOT_SUPPORTED, "SEC_E_QOP_NOT_SUPPORTED"},
{SEC_E_UNKNOWN_CREDENTIALS, "SEC_E_UNKNOWN_CREDENTIALS"},
{SEC_E_NOT_OWNER, "SEC_E_NOT_OWNER"},
{SEC_I_RENEGOTIATE, "SEC_I_RENEGOTIATE"},
{SEC_I_COMPLETE_AND_CONTINUE, "SEC_I_COMPLETE_AND_CONTINUE"},
{SEC_I_COMPLETE_NEEDED, "SEC_I_COMPLETE_NEEDED"},
{SEC_I_CONTINUE_NEEDED, "SEC_I_CONTINUE_NEEDED"},
{SEC_I_INCOMPLETE_CREDENTIALS, "SEC_I_INCOMPLETE_CREDENTIALS"},
{0, NULL}
};
int i = 0;
for (i = 0; map[i].name_ != NULL; i++)
{
if (map[i].code_ == code)
{
return map[i].name_;
}
}
return "UNKNOWN";
}
/*
* Free SSPI context allocated in userauth_sspi_kerberos().
* This struct is stored inside AuthCtx as 'methoddata'.
*/
void userauth_sspi_kerberos_cleanup(Authctxt *authctxt)
{
debug3("-> userauth_sspi_kerberos_cleanup()...");
if (authctxt != NULL)
{
SspiContext *sspi = authctxt -> methoddata;
if (sspi != NULL)
{
if (FreeCredentialsHandle(&sspi -> credHandle) != SEC_E_OK)
{
error("WARNING: Cannot free SSPI credentials.");
}
if (DeleteSecurityContext(&sspi -> context) != SEC_E_OK)
{
error("WARNING: Cannot delete SSPI context.");
}
if (sspi -> targetName != NULL)
{
free(sspi -> targetName);
}
if (sspi -> oidOut != NULL)
{
free(sspi -> oidOut);
}
free(sspi);
authctxt -> methoddata = NULL;
}
}
debug3("<- userauth_sspi_kerberos_cleanup()...");
}
/*
* Perform Kerberos authentication via native SSPI.
*/
int userauth_sspi_kerberos(Authctxt *authctxt)
{
static int alreadyCalled = 0;
/*
* If this auth was tried before, it means
* one of futher step fails.
* Don't try once again.
*/
if (alreadyCalled == 1)
{
return 0;
}
debug3("-> userauth_sspi_kerberos()...");
int exitCode = 0;
SspiContext *sspi = NULL;
alreadyCalled = 1;
/*
* Allocate new SSPI context.
*/
debug3("Allocating new SSPI auth context...");
sspi = calloc(sizeof(SspiContext), 1);
FAILEX(sspi == NULL, "ERROR: Out of memory.");
authctxt -> methoddata = sspi;
debug3("Set auth context to [%p].", sspi);
/*
* Add 'host/' prefix to server name.
*/
sspi -> targetName = malloc(sizeof("host/") + strlen(authctxt -> host));
FAILEX(sspi -> targetName == NULL, "ERROR: Out of memory");
strcpy(sspi -> targetName, "host/");
strcat(sspi -> targetName, authctxt -> host);
/*
* Set kerberos5 as outgoing OID.
*/
debug3("Setting up KRB5 mechanism as outgoing OID...");
sspi -> oidOutLen = sizeof(KRB5_OID);
sspi -> oidOut = malloc(sizeof(KRB5_OID));
FAILEX(sspi -> oidOut == NULL, "ERROR: Out of memory.");
memcpy(sspi -> oidOut, KRB5_OID, sizeof(KRB5_OID));
/*
* Send SSH2_MSG_USERAUTH_REQUEST packet to server.
* We declare that we want kerberos authentication here.
*/
debug3("Sending SSH2_MSG_USERAUTH_REQUEST:");
debug3(" Server user : [%s].", authctxt -> server_user);
debug3(" Service : [%s].", authctxt -> service);
debug3(" Method : [%s].", authctxt -> method -> name);
packet_start(SSH2_MSG_USERAUTH_REQUEST);
packet_put_cstring(authctxt -> server_user);
packet_put_cstring(authctxt -> service);
packet_put_cstring(authctxt -> method -> name);
/*
* Declare 1 Kerberos5 mechanism.
*
* 0 4 number of OIDs (hardcoded to 1)
* 4 4 total len in bytes
* 8 ... OID's data
*/
packet_put_int(1);
packet_put_int(sspi -> oidOutLen);
packet_put_raw(sspi -> oidOut, sspi -> oidOutLen);
packet_send();
/*
* Set callbacks to handle auth specific packets.
*/
dispatch_set(SSH2_MSG_USERAUTH_GSSAPI_RESPONSE, &input_sspi_kerberos_response);
dispatch_set(SSH2_MSG_USERAUTH_GSSAPI_TOKEN, &input_sspi_kerberos_token);
dispatch_set(SSH2_MSG_USERAUTH_GSSAPI_ERROR, &input_sspi_kerberos_error);
dispatch_set(SSH2_MSG_USERAUTH_GSSAPI_ERRTOK, &input_sspi_kerberos_errtok);
exitCode = 1;
/*
* Error handler.
*/
fail:
if (exitCode == 0)
{
error("ERROR: Cannot perform kerberos SSPI authentication.\n"
"WINAPI error code is : %u.", GetLastError());
}
debug3("<- userauth_sspi_kerberos()...");
return exitCode;
}
/*
* Parse incoming SSH2_MSG_USERAUTH_GSSAPI_TOKEN packet.
* Called as long as handshake process finished.
*
* One incoming SSH2_MSG_USERAUTH_GSSAPI_TOKEN means:
*
* - one outcoming SSH2_MSG_USERAUTH_GSSAPI_TOKEN sent if handshake not
* finished.
*
* - one outcoming SSH2_MSG_USERAUTH_GSSAPI_MIC if handshake finished.
*
* - one outcoming SSH2_MSG_USERAUTH_GSSAPI_ERRTOK if error.
*
* type - UNUSED.
* plen - UNUSED.
* ctxt - User auth context (IN/OUT).
*/
void input_sspi_kerberos_token(int type, u_int32_t plen, void *ctxt)
{
debug3("-> input_sspi_kerberos_token()...");
Authctxt *auth = ctxt;
SspiContext *sspi = NULL;
int exitCode = -1;
char *buf = NULL;
int bufLen = 0;
SECURITY_STATUS sspiCode = SEC_E_OK;
debug3("Received [SSH2_MSG_USERAUTH_GSSAPI_TOKEN] packet.");
/*
* Get back SSPI context created in userauth_sspi_kerberos() call.
*/
FAILEX(auth == NULL, "ERROR: Auth context cannot be NULL in '%s'.", __FUNCTION__);
sspi = auth -> methoddata;
FAILEX(sspi == NULL, "ERROR: SSPI context cannot be NULL in '%s'.", __FUNCTION__);
/*
* Receive token from server.
*/
buf = packet_get_string(&bufLen);
debug3("Received [%d] bytes token.", bufLen);
/*
* Eat remaining packet's data if any.
* Must called to save integrity on incoming network data.
*/
packet_check_eom();
/*
* Process token received from server.
*/
FAIL(SspiProcessToken(buf, bufLen, auth));
/*
* Clean up.
*/
exitCode = 0;
fail:
if (exitCode)
{
error("ERROR: Cannot process SSH2_MSG_USERAUTH_GSSAPI_TOKEN packet.");
}
free(buf);
debug3("<- input_sspi_kerberos_token()...");
}
/*
* Process server side fault.
*
* type - UNUSED.
* plen - UNUSED.
* ctxt - UNUSED.
*/
void input_sspi_kerberos_error(int type, u_int32_t plen, void *ctxt)
{
debug3("-> input_sspi_kerberos_error()...");
OM_uint32 maj = 0;
OM_uint32 min = 0;
char *msg = NULL;
char *lang = NULL;
maj = packet_get_int();
min = packet_get_int();
msg = packet_get_string(NULL);
lang = packet_get_string(NULL);
error("Server GSSAPI Error:\n%s", msg);
packet_check_eom();
/*
* Eat remaining packet's data if any.
* Must called to save integrity on incoming network data.
*/
packet_check_eom();
free(msg);
free(lang);
debug3("<- input_sspi_kerberos_error()...");
}
void input_sspi_kerberos_errtok(int type, u_int32_t plen, void *ctxt)
{
debug3("-> input_sspi_kerberos_errtok()...");
input_sspi_kerberos_token(type, plen, ctxt);
debug3("<- input_sspi_kerberos_errtok()...");
}
/*
* Process input token (i.e. message, being part of handshake protocol)
* received from server and send answer (outgoing token) back to server
* if needed.
*
* input - input token received from server or NULL if first time
* called (IN).
*
* inputSize - size of input buffer in bytes (IN).
* auth - pointer to authenticate context (IN).
*
* RETURNS: 0 if OK.
*/
int SspiProcessToken(void *input, int inputSize, Authctxt *auth)
{
debug3("-> SspiProcessToken()...");
int exitCode = -1;
/*
* Input (received from server) and outgoing
* (going be to send) tokens.
*/
SecBuffer inpBuf = {inputSize, SECBUFFER_TOKEN, input};
SecBuffer outBuf = {0, SECBUFFER_TOKEN, NULL};
SecBufferDesc inpBufDesc = {SECBUFFER_VERSION, 1, &inpBuf};
SecBufferDesc outBufDesc = {SECBUFFER_VERSION, 1, &outBuf};
/*
* Plain message to sign at the last hanshake step.
* This message is generated on client side and send
* to server after sign.
*/
Buffer mic;
/*
* Buffers to sign 'mic' into 'hash'.
*
* hash[0] = input, plain mic.
* hash[1] = output, signed mic.
*/
SecPkgContext_Sizes contextSizes = {0};
SecBuffer hash[2] = {0};
SecBufferDesc hashDesc = {SECBUFFER_VERSION, 2, &hash};
unsigned long outFlags = 0;
unsigned long inpFlags = ISC_REQ_MUTUAL_AUTH
| ISC_REQ_REPLAY_DETECT
| ISC_REQ_CONFIDENTIALITY
| ISC_REQ_ALLOCATE_MEMORY
| ISC_REQ_DELEGATE;
SECURITY_STATUS sspiCode = SEC_E_OK;
SspiContext *sspi = NULL;
/*
* Get back SSPI context created in userauth_sspi_kerberos() call.
*/
FAILEX(auth == NULL, "ERROR: Auth context cannot be NULL in '%s'.", __FUNCTION__);
sspi = auth -> methoddata;
FAILEX(sspi == NULL, "ERROR: SSPI context cannot be NULL in '%s'.", __FUNCTION__);
/*
* Parse input token received from server.
* This function generates output token needed to send back to server.
*/
debug3("InitializeSecurityContext:");
debug3(" Credentials Handle : [%p]", &sspi -> credHandle);
debug3(" Security Context : [%p]", sspi -> contextHandle);
debug3(" Target name : [%s]", sspi -> targetName);
debug3(" ContextReq : [%x]", inpFlags);
debug3(" Target Data Repr. : [%x]", SECURITY_NATIVE_DREP);
debug3(" Input buffer len : [%d]", inpBuf.cbBuffer);
debug3(" Input buffer ptr : [%p]", inpBuf.pvBuffer);
debug3(" Output buffer len : [%d]", outBuf.cbBuffer);
debug3(" Output buffer ptr : [%p]", outBuf.pvBuffer);
sspiCode = InitializeSecurityContextA(&sspi -> credHandle, sspi -> contextHandle,
sspi -> targetName, inpFlags,
0, SECURITY_NATIVE_DREP,
&inpBufDesc, 0,
&sspi -> context,
&outBufDesc,
&outFlags,
&sspi -> expiry);
sspi -> contextHandle = &sspi -> context;
debug3("InitializeSecurityContext finished with code [0x%x][%s].",
sspiCode, SspiGetCodeName(sspiCode));
switch(sspiCode)
{
/*
* Handshake completed.
* Prepare MIC, sign it and send to server.
* After server will accept our hash authentication is completed.
*/
case SEC_E_OK:
{
debug3("[SEC_E_OK]");
SSPI_FAIL(QueryContextAttributesA(&sspi -> context,
SECPKG_ATTR_SIZES, &contextSizes));
/*
* Build plain message.
*/
debug3("Building mic...");
ssh_gssapi_buildmic(&mic, auth -> server_user,
auth -> service, "gssapi-with-mic");
/*
* Sign message into hash.
*/
debug3("Signing [%d] bytes mic...", buffer_len(&mic));
hash[0].BufferType = SECBUFFER_DATA;
hash[0].cbBuffer = buffer_len(&mic);
hash[0].pvBuffer = buffer_ptr(&mic);;
hash[1].BufferType = SECBUFFER_TOKEN;
hash[1].cbBuffer = contextSizes.cbMaxSignature;
hash[1].pvBuffer = calloc(contextSizes.cbMaxSignature, 1);
SSPI_FAIL(MakeSignature(&sspi -> context, 0, &hashDesc, 0));
/*
* Send signed message (hash) to server.
*/
debug3("Sending [%d] bytes hash...", hash[1].cbBuffer);
packet_start(SSH2_MSG_USERAUTH_GSSAPI_MIC);
packet_put_string(hash[1].pvBuffer, hash[1].cbBuffer);
packet_send();
buffer_free(&mic);
break;
}
/*
* Handshake is in progress.
* Send next partial packet to server.
*/
case SEC_I_CONTINUE_NEEDED:
{
debug3("[SEC_I_CONTINUE_NEEDED]");
packet_start(SSH2_MSG_USERAUTH_GSSAPI_TOKEN);
debug3("Sending [%d] bytes token...", outBuf.cbBuffer);
packet_put_string(outBuf.pvBuffer, outBuf.cbBuffer);
packet_send();
break;
}
/*
* Unexpected code. Treat as error.
* Tell server that something fail.
*/
default:
{
error("Unhandled code [%x].", sspiCode);
packet_start(SSH2_MSG_USERAUTH_GSSAPI_ERRTOK);
packet_send();
goto fail;
}
}
/*
* Clean up.
*/
exitCode = 0;
fail:
if (exitCode)
{
error("ERROR: Cannot process SSH2_MSG_USERAUTH_GSSAPI_TOKEN packet.\n"
"SSPI code is : 0x%x / [%s].\nWINAPI code is : %d.",
sspiCode, SspiGetCodeName(sspiCode), GetLastError());
}
buffer_free(&mic);
if (hash[1].pvBuffer)
{
free(hash[1].pvBuffer);
}
FreeContextBuffer(outBuf.pvBuffer);
debug3("<- SspiProcessToken()...");
return exitCode;
}
/*
* Process SSH2_MSG_USERAUTH_GSSAPI_RESPONSE packet sent by server
* as response for SSH2_MSG_USERAUTH_REQUEST.
* Shoud called one time.
*
* type - UNUSED.
* plen - UNUSED.
* ctxt - User auth context (IN/OUT).
*/
void input_sspi_kerberos_response(int type, u_int32_t plen, void *ctxt)
{
debug3("-> input_sspi_kerberos_response()...");
debug3("SSH2_MSG_USERAUTH_REQUEST packet received.");
Authctxt *auth = ctxt;
SspiContext *sspi = NULL;
int oidlen = 0;
char *oid = NULL;
int exitCode = -1;
SECURITY_STATUS sspiCode = SEC_E_OK;
/*
* Get back SSPI context created in userauth_sspi_kerberos() call.
*/
sspi = auth -> methoddata;
FAILEX(sspi == NULL,
"ERROR: SSPI context cannot"" be NULL in '%s'.",
__FUNCTION__);
/*
* Read OID from server.
*/
oid = packet_get_string(&oidlen);
debug3("Received [%d] bytes OID.", oidlen);
/*
* Verify is OID correct.
* If all ok, server should response the same OID, which
* we sent in userauth_sspi_kerberos() call.
*/
FAILEX(oidlen <= 2, "ERROR: OID too short.");
FAILEX(oid[0] != SSH_GSS_OIDTYPE, "ERROR: Wrong OID's type.");
FAILEX(oid[1] != oidlen - 2, "ERROR: Wrong OID's len field.");
FAILEX(oidlen != sspi -> oidOutLen, "ERROR: OID's len mismatch.");
FAILEX(memcmp(oid, sspi -> oidOut, oidlen), "ERROR: OID's data mismatch.");
/*
* Eat remaining packet's data if any.
* Must called to save integrity on incoming network data.
*/
packet_check_eom();
/*
* Here, we know server knows and accepted request to
* perform kerberos5 auth.
*/
/*
* Get creadentials ticket from local SSPI/Kerberos cache.
*/
debug3("Acquiring SSPI/Kerberos credentials...");
SSPI_FAIL(AcquireCredentialsHandleA(NULL, "Kerberos",
SECPKG_CRED_OUTBOUND,
NULL, NULL, NULL, NULL,
&sspi -> credHandle,
&sspi -> expiry));
debug3("Acquired SSPI/Kerberos creentials [%p].", sspi -> credHandle);
/*
* Start auth negotiation.
* Get first outgoing packet to set to server from SSPI.
*/
FAIL(SspiProcessToken(NULL, 0, auth));
/*
* Clean up.
*/
exitCode = 0;
fail:
if (exitCode)
{
error("ERROR: Cannot process SSH2_MSG_USERAUTH_GSSAPI_RESPONSE packet.\n"
"SSPI code is : 0x%x / [%s].\nWINAPI code is : %d.",
sspiCode, SspiGetCodeName(sspiCode), GetLastError());
/*
* If current method fails, try next one.
*/
userauth(auth, NULL);
}
free(oid);
debug3("<- input_sspi_kerberos_response()...");
}
#endif /* WIN32_FIXME */