spnego.c revision 0c27b3fe77ac1d5094ba3521e8142d9e7973133f
/*
* Copyright (C) 2006-2016 Internet Systems Consortium, Inc. ("ISC")
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/*! \file
* \brief
* Portable SPNEGO implementation.
*
* This is part of a portable implementation of the SPNEGO protocol
* (RFCs 2478 and 4178). This implementation uses the RFC 4178 ASN.1
* module but is not a full implementation of the RFC 4178 protocol;
* at the moment, we only support GSS-TSIG with Kerberos
* authentication, so we only need enough of the SPNEGO protocol to
* support that.
*
* The files that make up this portable SPNEGO implementation are:
* \li spnego.c (this file)
* \li spnego.asn1 (SPNEGO ASN.1 module)
* \li spnego_asn1.c (routines generated from spngo.asn1)
* \li spnego_asn1.pl (perl script to generate spnego_asn1.c)
*
* Everything but the functions exported in spnego.h is static, to
* avoid possible conflicts with other libraries (particularly Heimdal,
* since much of this code comes from Heimdal by way of mod_auth_kerb).
*
* spnego_asn1.c is shipped as part of lib/dns because generating it
* requires both Perl and the Heimdal ASN.1 compiler. See
* spnego_asn1.pl for further details. We've tried to eliminate all
* compiler warnings from the generated code, but you may see a few
* when using a compiler version we haven't tested yet.
*/
/*
* Portions of this code were derived from mod_auth_kerb and Heimdal.
* These packages are available from:
*
*
* and were released under the following licenses:
*
* ----------------------------------------------------------------
*
* Copyright (c) 2004 Masarykova universita
* (Masaryk University, Brno, Czech Republic)
* All rights reserved.
*
* 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
*
* 3. Neither the name of the University 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 THE COPYRIGHT OWNER OR 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.
*
* ----------------------------------------------------------------
*
* Copyright (c) 1997 - 2003 Kungliga Tekniska H�gskolan
* (Royal Institute of Technology, Stockholm, Sweden).
* All rights reserved.
*
* 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
*
* 3. Neither the name of the Institute 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 INSTITUTE 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 THE INSTITUTE OR 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.
*/
/*
* XXXSRA We should omit this file entirely in Makefile.in via autoconf,
* but this will keep it from generating errors until that's written.
*/
#ifdef GSSAPI
/*
* XXXSRA Some of the following files are almost certainly unnecessary,
* but using this list (borrowed from gssapictx.c) gets rid of some
* whacky compilation errors when building with MSVC and should be
* harmless in any case.
*/
#include <config.h>
#include <stdlib.h>
#include <errno.h>
#include <dns/fixedname.h>
#include <dns/rdataclass.h>
#include <dns/keyvalues.h>
#include "dst_internal.h"
/*
* The API we export
*/
#include "spnego.h"
/* asn1_err.h */
/* Generated from ../../../lib/asn1/asn1_err.et */
#ifndef ERROR_TABLE_BASE_asn1
/* these may be brought in already via gssapi_krb5.h */
typedef enum asn1_error_number {
ASN1_BAD_TIMEFORMAT = 1859794432,
ASN1_MISSING_FIELD = 1859794433,
ASN1_MISPLACED_FIELD = 1859794434,
ASN1_TYPE_MISMATCH = 1859794435,
ASN1_OVERFLOW = 1859794436,
ASN1_OVERRUN = 1859794437,
ASN1_BAD_ID = 1859794438,
ASN1_BAD_LENGTH = 1859794439,
ASN1_BAD_FORMAT = 1859794440,
ASN1_PARSE_ERROR = 1859794441
#define ERROR_TABLE_BASE_asn1 1859794432
#endif
#define __asn1_common_definitions__
typedef struct octet_string {
void *data;
} octet_string;
typedef char *general_string;
typedef char *utf8_string;
typedef struct oid {
unsigned *components;
} oid;
/* der.h */
typedef enum {
} Der_class;
typedef enum {
} Der_type;
/* Universal tags */
enum {
UT_Boolean = 1,
UT_Integer = 2,
UT_BitString = 3,
UT_OctetString = 4,
UT_Null = 5,
UT_OID = 6,
UT_Enumerated = 10,
UT_Sequence = 16,
UT_Set = 17,
UT_PrintableString = 19,
UT_IA5String = 22,
UT_UTCTime = 23,
UT_GeneralizedTime = 24,
UT_VisibleString = 26,
UT_GeneralString = 27
};
#define ASN1_INDEFINITE 0xdce0deed
static int
static int
static int
static int
static int
static int
static int
static int
static int
static int
static int
static int
static int
static int
static int
static int
static int
const octet_string * k, size_t *);
static int
static void
static void
static size_t
static int
/*
* Include stuff generated by the ASN.1 compiler.
*/
#include "spnego_asn1.c"
static unsigned char gss_krb5_mech_oid_bytes[] = {
0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x01, 0x02, 0x02
};
static gss_OID_desc gss_krb5_mech_oid_desc = {
sizeof(gss_krb5_mech_oid_bytes),
};
static unsigned char gss_mskrb5_mech_oid_bytes[] = {
0x2a, 0x86, 0x48, 0x82, 0xf7, 0x12, 0x01, 0x02, 0x02
};
static gss_OID_desc gss_mskrb5_mech_oid_desc = {
sizeof(gss_mskrb5_mech_oid_bytes),
};
static unsigned char gss_spnego_mech_oid_bytes[] = {
0x2b, 0x06, 0x01, 0x05, 0x05, 0x02
};
static gss_OID_desc gss_spnego_mech_oid_desc = {
sizeof(gss_spnego_mech_oid_bytes),
};
/* spnegokrb5_locl.h */
static OM_uint32
unsigned char *,
const gss_OID);
static OM_uint32
unsigned char **,
size_t *,
const gss_OID);
/* mod_auth_kerb.c */
static int
{
unsigned char *p;
return (GSS_S_DEFECTIVE_TOKEN);
if (*p++ != 0x60)
return (GSS_S_DEFECTIVE_TOKEN);
len = *p++;
if (len & 0x80) {
return (GSS_S_DEFECTIVE_TOKEN);
p += len & 0x7f;
}
if (*p++ != 0x06)
return (GSS_S_DEFECTIVE_TOKEN);
return (GSS_S_DEFECTIVE_TOKEN);
}
/* accept_sec_context.c */
/*
* SPNEGO wrapper for Kerberos5 GSS-API kouril@ics.muni.cz, 2003 (mostly
* based on Heimdal code)
*/
static OM_uint32
const NegTokenResp * resp,
unsigned char **outbuf,
{
buf_size = 1024;
*minor_status = ENOMEM;
return (GSS_S_FAILURE);
}
do {
if (ret == 0) {
CONS,
1,
&tmp);
if (ret == 0)
}
if (ret) {
if (ret == ASN1_OVERFLOW) {
buf_size *= 2;
*minor_status = ENOMEM;
return (GSS_S_FAILURE);
}
} else {
*minor_status = ret;
return (GSS_S_FAILURE);
}
}
} while (ret == ASN1_OVERFLOW);
*minor_status = ENOMEM;
return (GSS_S_FAILURE);
}
*outbuf_size = buf_len;
return (GSS_S_COMPLETE);
}
static OM_uint32
{
*minor_status = ENOMEM;
return (GSS_S_FAILURE);
}
(unsigned char **)&output_token->value,
&output_token->length);
if (ret)
return (ret);
return (GSS_S_BAD_MECH);
}
static OM_uint32
{
*minor_status = ENOMEM;
return (GSS_S_FAILURE);
}
*minor_status = ENOMEM;
return (GSS_S_FAILURE);
}
NULL);
if (ret) {
*minor_status = ENOMEM;
return (GSS_S_FAILURE);
}
*minor_status = ENOMEM;
return (GSS_S_FAILURE);
}
}
(unsigned char **)&output_token->value,
&output_token->length);
}
if (ret)
return (ret);
return (GSS_S_COMPLETE);
}
const gss_buffer_t input_token_buffer,
{
unsigned char *buf;
int found = 0;
int ret;
unsigned i;
/*
* Before doing anything else, see whether this is a SPNEGO
* PDU. If not, dispatch to the GSSAPI library and get out.
*/
return (gss_accept_sec_context(minor_status,
/*
* If we get here, it's SPNEGO.
*/
if (ret)
return (ret);
if (ret)
return (ret);
if (ret) {
return (GSS_S_DEFECTIVE_TOKEN);
}
unsigned char mechbuf[17];
sizeof(mechbuf),
&mech_len);
if (ret) {
return (GSS_S_DEFECTIVE_TOKEN);
}
mech_len))
{
found = 1;
break;
}
mech_len))
{
found = 1;
if (i == 0)
break;
}
}
if (!found) {
}
&ibuf,
&obuf,
if (GSS_ERROR(major_status)) {
return (major_status);
}
}
return (ret);
}
/* decapsulate.c */
static OM_uint32
{
int e;
if (total_len < 1U)
return (GSS_S_DEFECTIVE_TOKEN);
if (*p++ != 0x60)
return (GSS_S_DEFECTIVE_TOKEN);
return (GSS_S_DEFECTIVE_TOKEN);
p += len_len;
if (*p++ != 0x06)
return (GSS_S_DEFECTIVE_TOKEN);
if (e)
return (GSS_S_DEFECTIVE_TOKEN);
p += foo;
return (GSS_S_BAD_MECH);
return (GSS_S_BAD_MECH);
p += mech_len;
*str = p;
return (GSS_S_COMPLETE);
}
/*
* Remove the GSS-API wrapping from `in_token' giving `buf and buf_size' Does
* not copy data, so just free `in_token'.
*/
static OM_uint32
unsigned char **buf,
{
u_char *p;
p = input_token_buffer->value;
ret = gssapi_verify_mech_header(&p,
mech);
if (ret) {
*minor_status = ret;
return (GSS_S_FAILURE);
}
*buf = p;
return (GSS_S_COMPLETE);
}
/* der_free.c */
static void
{
}
static void
{
free(k->components);
k->components = NULL;
}
/* der_get.c */
/*
* All decoding functions take a pointer `p' to first position in which to
* read, from the left, `len' which means the maximum number of characters we
* are able to read, `ret' were the value will be returned and `size' where
* the number of used bytes is stored. Either 0 or an error code is returned.
*/
static int
{
unsigned val = 0;
while (len--)
if (size)
return (0);
}
static int
{
int val = 0;
if (len > 0U) {
val = (signed char)*p++;
while (--len)
}
if (size)
return (0);
}
static int
{
size_t v;
if (len <= 0U)
return (ASN1_OVERRUN);
--len;
v = *p++;
if (v < 128U) {
*val = v;
if (size)
*size = 1;
} else {
int e;
size_t l;
unsigned tmp;
if (v == 0x80U) {
*val = ASN1_INDEFINITE;
if (size)
*size = 1;
return (0);
}
v &= 0x7F;
if (len < v)
return (ASN1_OVERRUN);
e = der_get_unsigned(p, v, &tmp, &l);
if (e)
return (e);
if (size)
*size = l + 1;
}
return (0);
}
static int
{
if (len != 0U) {
return (ENOMEM);
} else
if (size)
return (0);
}
static int
{
int n;
if (len < 1U)
return (ASN1_OVERRUN);
return (ENOMEM);
--len;
++p;
for (n = 2; len > 0U; ++n) {
unsigned u = 0;
do {
--len;
u = u * 128 + (*p++ % 128);
data->components[n] = u;
}
if (p[-1] & 0x80) {
return (ASN1_OVERRUN);
}
if (size)
return (0);
}
static int
{
if (len < 1U)
return (ASN1_OVERRUN);
*tag = (*p) & 0x1F;
if (size)
*size = 1;
return (0);
}
static int
{
size_t l;
int thistag;
int e;
if (e)
return (e);
return (ASN1_BAD_ID);
return (ASN1_MISPLACED_FIELD);
return (ASN1_MISSING_FIELD);
if (size)
*size = l;
return (0);
}
static int
{
int e;
if (e)
return (e);
p += l;
len -= l;
ret += l;
if (e)
return (e);
/* p += l; */
len -= l;
ret += l;
if (size)
return (0);
}
static int
{
int e;
if (e)
return (e);
p += l;
len -= l;
ret += l;
if (e)
return (e);
p += l;
len -= l;
ret += l;
if (e)
return (e);
p += l;
len -= l;
ret += l;
if (size)
return (0);
}
static int
{
size_t l;
int e;
k->length = 0;
if (e)
return (e);
p += l;
len -= l;
ret += l;
if (e)
return (e);
p += l;
len -= l;
ret += l;
return (ASN1_OVERRUN);
e = der_get_octet_string(p, slen, k, &l);
if (e)
return (e);
p += l;
len -= l;
ret += l;
if (size)
return (0);
}
static int
{
size_t l;
int e;
if (e)
return (e);
p += l;
len -= l;
ret += l;
if (e)
return (e);
p += l;
len -= l;
ret += l;
return (ASN1_OVERRUN);
e = der_get_oid(p, slen, k, &l);
if (e)
return (e);
p += l;
len -= l;
ret += l;
if (size)
return (0);
}
static int
{
if (reallen == ASN1_INDEFINITE)
return (1);
return (-1);
return (0);
}
/* der_length.c */
static size_t
len_unsigned(unsigned val)
{
do {
++ret;
val /= 256;
} while (val);
return (ret);
}
static size_t
{
if (len < 128U)
return (1);
else
}
/* der_put.c */
/*
* All encoding functions take a pointer `p' to first position in which to
* write, from the right, `len' which means the maximum number of characters
* we are able to write. The function returns the number of characters
* written in `size' (if non-NULL). The return value is 0 or an error.
*/
static int
{
unsigned char *base = p;
if (val) {
*p-- = val % 256;
val /= 256;
--len;
}
if (val != 0)
return (ASN1_OVERFLOW);
else {
return (0);
}
} else if (len < 1U)
return (ASN1_OVERFLOW);
else {
*p = 0;
*size = 1;
return (0);
}
}
static int
{
unsigned char *base = p;
if (val >= 0) {
do {
if (len < 1U)
return (ASN1_OVERFLOW);
*p-- = val % 256;
len--;
val /= 256;
} while (val);
if (p[1] >= 128) {
if (len < 1U)
return (ASN1_OVERFLOW);
*p-- = 0;
len--;
}
} else {
do {
if (len < 1U)
return (ASN1_OVERFLOW);
*p-- = ~(val % 256);
len--;
val /= 256;
} while (val);
if (p[1] < 128) {
if (len < 1U)
return (ASN1_OVERFLOW);
*p-- = 0xff;
len--;
}
}
return (0);
}
static int
{
if (len < 1U)
return (ASN1_OVERFLOW);
if (val < 128U) {
*p = (unsigned char)val;
*size = 1;
return (0);
} else {
size_t l;
int e;
if (e)
return (e);
p -= l;
*p = 0x80 | (unsigned char)l;
*size = l + 1;
return (0);
}
}
static int
{
return (ASN1_OVERFLOW);
return (0);
}
static int
{
unsigned char *base = p;
size_t n;
if (len < 1U)
return (ASN1_OVERFLOW);
*p-- = u % 128;
u /= 128;
--len;
while (u > 0) {
if (len < 1U)
return (ASN1_OVERFLOW);
*p-- = 128 + u % 128;
u /= 128;
--len;
}
}
if (len < 1U)
return (ASN1_OVERFLOW);
return (0);
}
static int
{
if (len < 1U)
return (ASN1_OVERFLOW);
*size = 1;
return (0);
}
static int
{
size_t l;
int e;
if (e)
return (e);
p -= l;
len -= l;
ret += l;
if (e)
return (e);
p -= l;
len -= l;
ret += l;
return (0);
}
static int
{
size_t l;
int e;
if (e)
return (e);
p -= l;
len -= l;
ret += l;
if (e)
return (e);
p -= l;
len -= l;
ret += l;
return (0);
}
static int
{
size_t l;
int e;
e = der_put_octet_string(p, len, k, &l);
if (e)
return (e);
p -= l;
len -= l;
ret += l;
if (e)
return (e);
p -= l;
len -= l;
ret += l;
return (0);
}
static int
{
size_t l;
int e;
e = der_put_oid(p, len, k, &l);
if (e)
return (e);
p -= l;
len -= l;
ret += l;
if (e)
return (e);
p -= l;
len -= l;
ret += l;
return (0);
}
/* encapsulate.c */
static void
{
}
static u_char *
{
int e;
*p++ = 0x60;
return (NULL);
p += len_len;
*p++ = 0x06;
return (p);
}
/*
* Give it a krb5_data and it will encapsulate with extra GSS-API wrappings.
*/
static OM_uint32
unsigned char *buf,
{
u_char *p;
*minor_status = ENOMEM;
return (GSS_S_FAILURE);
}
if (p == NULL) {
if (output_token->length != 0U)
return (GSS_S_FAILURE);
}
return (GSS_S_COMPLETE);
}
/* init_sec_context.c */
/*
* SPNEGO wrapper for Kerberos5 GSS-API kouril@ics.muni.cz, 2003 (mostly
* based on Heimdal code)
*/
static int
{
int ret;
return (ENOMEM);
if (ret)
return (ret);
return (0);
}
/*
* return the length of the mechanism in token or -1
* (which implies that the token was bad - GSS_S_DEFECTIVE_TOKEN
*/
static ssize_t
{
int e;
if (total_len < 1U)
return (-1);
if (*p++ != 0x60)
return (-1);
return (-1);
p += len_len;
if (*p++ != 0x06)
return (-1);
if (e)
return (-1);
p += foo;
*mech_ret = p;
return (mech_len);
}
static OM_uint32
const gss_name_t target_name,
const gss_buffer_t input_token,
{
int ret;
(void)mech_type;
if (ret) {
*minor_status = ret;
ret = GSS_S_FAILURE;
goto end;
}
time_rec);
if (GSS_ERROR(major_status)) {
ret = major_status;
goto end;
}
if (krb5_output_token.length > 0U) {
*minor_status = ENOMEM;
ret = GSS_S_FAILURE;
goto end;
}
}
/*
* The MS implementation of SPNEGO seems to not like the mechListMIC
* field, so we omit it (it's optional anyway)
*/
buf_size = 1024;
*minor_status = ENOMEM;
ret = GSS_S_FAILURE;
goto end;
}
do {
&token_init, &len);
if (ret == 0) {
len,
CONS,
0,
&tmp);
if (ret == 0)
}
if (ret) {
if (ret == ASN1_OVERFLOW) {
buf_size *= 2;
*minor_status = ENOMEM;
ret = GSS_S_FAILURE;
goto end;
}
} else {
*minor_status = ret;
ret = GSS_S_FAILURE;
goto end;
}
}
} while (ret == ASN1_OVERFLOW);
if (ret == GSS_S_COMPLETE)
ret = major_status;
end:
}
if (krb5_output_token.length != 0U)
if (buf)
return (ret);
}
static OM_uint32
const gss_name_t target_name,
const gss_buffer_t input_token,
{
unsigned char *buf;
const u_char *p;
(void)mech_type;
output_token->length = 0;
/*
* SPNEGO doesn't include gss wrapping on SubsequentContextToken
* like the Kerberos 5 mech does. But lets check for it anyway.
*/
&p);
if (mech_len < 0) {
return (gss_init_sec_context(minor_status,
time_rec));
&buf,
&buf_size,
if (ret)
return (ret);
} else
return (GSS_S_BAD_MECH);
if (ret)
return (ret);
return (ASN1_OVERRUN);
if (ret) {
*minor_status = ENOMEM;
return (GSS_S_FAILURE);
}
return (GSS_S_BAD_MECH);
}
sizeof(oidbuf),
&oidlen);
{
return GSS_S_BAD_MECH;
}
} else {
}
time_rec);
if (ret) {
return (ret);
}
/*
* XXXSRA I don't think this limited implementation ever needs
* to check the MIC -- our preferred mechanism (Kerberos)
* authenticates its own messages and is the only mechanism
* we'll accept, so if the mechanism negotiation completes
* successfully, we don't need the MIC. See RFC 4178.
*/
return (ret);
}
const gss_name_t target_name,
const gss_buffer_t input_token,
{
/* Dirty trick to suppress compiler warnings */
/* Figure out whether we're starting over or processing a reply */
return (spnego_initial(minor_status,
time_rec));
else
return (spnego_reply(minor_status,
time_rec));
}
#endif /* GSSAPI */