fwknop/lib/fko_decode.c
Michael Rash 7e290245b2 [libfko] per-SPA message type field count validation
This commit implements more rigorous SPA packet field count validation
that takes into account expected field counts for each SPA message type.
Two new libfko error codes have been added in support of this, and the
corresponding changes made in the perl and python modules.
2014-03-16 21:47:19 -04:00

589 lines
17 KiB
C

/*
*****************************************************************************
*
* File: fko_decode.c
*
* Purpose: Decode an FKO SPA message after decryption.
*
* Fwknop is developed primarily by the people listed in the file 'AUTHORS'.
* Copyright (C) 2009-2014 fwknop developers and contributors. For a full
* list of contributors, see the file 'CREDITS'.
*
* License (GNU General Public License):
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
* USA
*
*****************************************************************************
*/
#include "fko_common.h"
#include "fko.h"
#include "cipher_funcs.h"
#include "base64.h"
#include "digest.h"
#define FIELD_PARSERS 9
static int
num_fields(char *str)
{
int i=0;
char *tmp = NULL;
/* Count the number of remaining SPA packet fields
*/
for (i=0; i <= MAX_SPA_FIELDS+1; i++)
{
if ((tmp = strchr(str, ':')) == NULL)
break;
str = tmp + 1;
}
return i;
}
static int
last_field(char *str)
{
int i=0, pos_last=0;
char *tmp = NULL;
/* Count the number of bytes to the last ':' char
*/
for (i=0; i <= MAX_SPA_FIELDS+1; i++)
{
if ((tmp = strchr(str, ':')) == NULL)
break;
pos_last += (tmp - str) + 1;
str = tmp + 1;
}
return pos_last;
}
static int
verify_digest(char *tbuf, int t_size, fko_ctx_t ctx)
{
switch(ctx->digest_type)
{
case FKO_DIGEST_MD5:
md5_base64(tbuf, (unsigned char*)ctx->encoded_msg, ctx->encoded_msg_len);
break;
case FKO_DIGEST_SHA1:
sha1_base64(tbuf, (unsigned char*)ctx->encoded_msg, ctx->encoded_msg_len);
break;
case FKO_DIGEST_SHA256:
sha256_base64(tbuf, (unsigned char*)ctx->encoded_msg, ctx->encoded_msg_len);
break;
case FKO_DIGEST_SHA384:
sha384_base64(tbuf, (unsigned char*)ctx->encoded_msg, ctx->encoded_msg_len);
break;
case FKO_DIGEST_SHA512:
sha512_base64(tbuf, (unsigned char*)ctx->encoded_msg, ctx->encoded_msg_len);
break;
default: /* Invalid or unsupported digest */
return(FKO_ERROR_INVALID_DIGEST_TYPE);
}
/* We give up here if the computed digest does not match the
* digest in the message data.
*/
if(constant_runtime_cmp(ctx->digest, tbuf, t_size) != 0)
return(FKO_ERROR_DIGEST_VERIFICATION_FAILED);
return FKO_SUCCESS;
}
static int
is_valid_digest_len(int t_size, fko_ctx_t ctx)
{
switch(t_size)
{
case MD5_B64_LEN:
ctx->digest_type = FKO_DIGEST_MD5;
ctx->digest_len = MD5_B64_LEN;
break;
case SHA1_B64_LEN:
ctx->digest_type = FKO_DIGEST_SHA1;
ctx->digest_len = SHA1_B64_LEN;
break;
case SHA256_B64_LEN:
ctx->digest_type = FKO_DIGEST_SHA256;
ctx->digest_len = SHA256_B64_LEN;
break;
case SHA384_B64_LEN:
ctx->digest_type = FKO_DIGEST_SHA384;
ctx->digest_len = SHA384_B64_LEN;
break;
case SHA512_B64_LEN:
ctx->digest_type = FKO_DIGEST_SHA512;
ctx->digest_len = SHA512_B64_LEN;
break;
default: /* Invalid or unsupported digest */
return(FKO_ERROR_INVALID_DIGEST_TYPE);
}
if (ctx->encoded_msg_len - t_size < 0)
return(FKO_ERROR_INVALID_DATA_DECODE_ENC_MSG_LEN_MT_T_SIZE);
return FKO_SUCCESS;
}
static int
parse_msg(char *tbuf, char **ndx, int *t_size, fko_ctx_t ctx)
{
if((*t_size = strcspn(*ndx, ":")) < 1)
return(FKO_ERROR_INVALID_DATA_DECODE_MESSAGE_MISSING);
if (*t_size > MAX_SPA_MESSAGE_SIZE)
return(FKO_ERROR_INVALID_DATA_DECODE_MESSAGE_TOOBIG);
strlcpy(tbuf, *ndx, *t_size+1);
if(ctx->message != NULL)
free(ctx->message);
ctx->message = calloc(1, *t_size+1); /* Yes, more than we need */
if(ctx->message == NULL)
return(FKO_ERROR_MEMORY_ALLOCATION);
if(b64_decode(tbuf, (unsigned char*)ctx->message) < 0)
return(FKO_ERROR_INVALID_DATA_DECODE_MESSAGE_DECODEFAIL);
if(ctx->message_type == FKO_COMMAND_MSG)
{
/* Require a message similar to: 1.2.3.4,<command>
*/
if(validate_cmd_msg(ctx->message) != FKO_SUCCESS)
{
return(FKO_ERROR_INVALID_DATA_DECODE_MESSAGE_VALIDFAIL);
}
}
else
{
/* Require a message similar to: 1.2.3.4,tcp/22
*/
if(validate_access_msg(ctx->message) != FKO_SUCCESS)
{
return(FKO_ERROR_INVALID_DATA_DECODE_ACCESS_VALIDFAIL);
}
}
*ndx += *t_size + 1;
return FKO_SUCCESS;
}
static int
parse_nat_msg(char *tbuf, char **ndx, int *t_size, fko_ctx_t ctx)
{
if( ctx->message_type == FKO_NAT_ACCESS_MSG
|| ctx->message_type == FKO_LOCAL_NAT_ACCESS_MSG
|| ctx->message_type == FKO_CLIENT_TIMEOUT_NAT_ACCESS_MSG
|| ctx->message_type == FKO_CLIENT_TIMEOUT_LOCAL_NAT_ACCESS_MSG)
{
if((*t_size = strcspn(*ndx, ":")) < 1)
return(FKO_ERROR_INVALID_DATA_DECODE_NATACCESS_MISSING);
if (*t_size > MAX_SPA_MESSAGE_SIZE)
return(FKO_ERROR_INVALID_DATA_DECODE_NATACCESS_TOOBIG);
strlcpy(tbuf, *ndx, *t_size+1);
if(ctx->nat_access != NULL)
free(ctx->nat_access);
ctx->nat_access = calloc(1, *t_size+1); /* Yes, more than we need */
if(ctx->nat_access == NULL)
return(FKO_ERROR_MEMORY_ALLOCATION);
if(b64_decode(tbuf, (unsigned char*)ctx->nat_access) < 0)
return(FKO_ERROR_INVALID_DATA_DECODE_NATACCESS_DECODEFAIL);
if(validate_nat_access_msg(ctx->nat_access) != FKO_SUCCESS)
return(FKO_ERROR_INVALID_DATA_DECODE_NATACCESS_VALIDFAIL);
*ndx += *t_size + 1;
}
return FKO_SUCCESS;
}
static int
parse_server_auth(char *tbuf, char **ndx, int *t_size, fko_ctx_t ctx)
{
if((*t_size = strlen(*ndx)) > 0)
{
if (*t_size > MAX_SPA_MESSAGE_SIZE)
{
return(FKO_ERROR_INVALID_DATA_DECODE_SRVAUTH_MISSING);
}
}
else
return FKO_SUCCESS;
if( ctx->message_type == FKO_CLIENT_TIMEOUT_ACCESS_MSG
|| ctx->message_type == FKO_CLIENT_TIMEOUT_NAT_ACCESS_MSG
|| ctx->message_type == FKO_CLIENT_TIMEOUT_LOCAL_NAT_ACCESS_MSG)
{
/* If we are here then we may still have a server_auth string,
* or a timeout, or both. So we look for a ':' delimiter. If
* it is there we have both, if not we check the message_type
* again.
*/
if(strchr(*ndx, ':'))
{
*t_size = strcspn(*ndx, ":");
if (*t_size > MAX_SPA_MESSAGE_SIZE)
return(FKO_ERROR_INVALID_DATA_DECODE_EXTRA_TOOBIG);
strlcpy(tbuf, *ndx, *t_size+1);
if(ctx->server_auth != NULL)
free(ctx->server_auth);
ctx->server_auth = calloc(1, *t_size+1); /* Yes, more than we need */
if(ctx->server_auth == NULL)
return(FKO_ERROR_MEMORY_ALLOCATION);
if(b64_decode(tbuf, (unsigned char*)ctx->server_auth) < 0)
return(FKO_ERROR_INVALID_DATA_DECODE_EXTRA_DECODEFAIL);
*ndx += *t_size + 1;
}
}
else
{
strlcpy(tbuf, *ndx, *t_size+1);
if(ctx->server_auth != NULL)
free(ctx->server_auth);
ctx->server_auth = calloc(1, *t_size+1); /* Yes, more than we need */
if(ctx->server_auth == NULL)
return(FKO_ERROR_MEMORY_ALLOCATION);
if(b64_decode(tbuf, (unsigned char*)ctx->server_auth) < 0)
return(FKO_ERROR_INVALID_DATA_DECODE_SRVAUTH_DECODEFAIL);
}
return FKO_SUCCESS;
}
static int
parse_client_timeout(char *tbuf, char **ndx, int *t_size, fko_ctx_t ctx)
{
int is_err;
if( ctx->message_type == FKO_CLIENT_TIMEOUT_ACCESS_MSG
|| ctx->message_type == FKO_CLIENT_TIMEOUT_NAT_ACCESS_MSG
|| ctx->message_type == FKO_CLIENT_TIMEOUT_LOCAL_NAT_ACCESS_MSG)
{
if((*t_size = strlen(*ndx)) < 1)
return(FKO_ERROR_INVALID_DATA_DECODE_TIMEOUT_MISSING);
if (*t_size > MAX_SPA_MESSAGE_SIZE)
return(FKO_ERROR_INVALID_DATA_DECODE_TIMEOUT_TOOBIG);
/* Should be a number only.
*/
if(strspn(*ndx, "0123456789") != *t_size)
return(FKO_ERROR_INVALID_DATA_DECODE_TIMEOUT_VALIDFAIL);
ctx->client_timeout = (unsigned int) strtol_wrapper(*ndx, 0,
(2 << 15), NO_EXIT_UPON_ERR, &is_err);
if(is_err != FKO_SUCCESS)
return(FKO_ERROR_INVALID_DATA_DECODE_TIMEOUT_DECODEFAIL);
}
return FKO_SUCCESS;
}
static int
parse_msg_type(char *tbuf, char **ndx, int *t_size, fko_ctx_t ctx)
{
int is_err, remaining_fields;
if((*t_size = strcspn(*ndx, ":")) < 1)
return(FKO_ERROR_INVALID_DATA_DECODE_MSGTYPE_MISSING);
if(*t_size > MAX_SPA_MESSAGE_TYPE_SIZE)
return(FKO_ERROR_INVALID_DATA_DECODE_MSGTYPE_TOOBIG);
strlcpy(tbuf, *ndx, *t_size+1);
ctx->message_type = strtol_wrapper(tbuf, 0,
FKO_LAST_MSG_TYPE-1, NO_EXIT_UPON_ERR, &is_err);
if(is_err != FKO_SUCCESS)
return(FKO_ERROR_INVALID_DATA_DECODE_MSGTYPE_DECODEFAIL);
/* Now that we have a valid type, ensure that the total
* number of SPA fields is also valid for the type
*/
remaining_fields = num_fields(*ndx);
switch(ctx->message_type)
{
/* optional server_auth + digest */
case FKO_COMMAND_MSG:
case FKO_ACCESS_MSG:
if(remaining_fields > 2)
return FKO_ERROR_INVALID_DATA_DECODE_WRONG_NUM_FIELDS;
break;
/* nat or client timeout + optional server_auth + digest */
case FKO_NAT_ACCESS_MSG:
case FKO_LOCAL_NAT_ACCESS_MSG:
case FKO_CLIENT_TIMEOUT_ACCESS_MSG:
if(remaining_fields > 3)
return FKO_ERROR_INVALID_DATA_DECODE_WRONG_NUM_FIELDS;
break;
/* client timeout + nat + optional server_auth + digest */
case FKO_CLIENT_TIMEOUT_NAT_ACCESS_MSG:
case FKO_CLIENT_TIMEOUT_LOCAL_NAT_ACCESS_MSG:
if(remaining_fields > 4)
return FKO_ERROR_INVALID_DATA_DECODE_WRONG_NUM_FIELDS;
break;
default: /* Should not reach here */
return(FKO_ERROR_INVALID_DATA_DECODE_MSGTYPE_DECODEFAIL);
}
*ndx += *t_size + 1;
return FKO_SUCCESS;
}
static int
parse_version(char *tbuf, char **ndx, int *t_size, fko_ctx_t ctx)
{
if((*t_size = strcspn(*ndx, ":")) < 1)
return(FKO_ERROR_INVALID_DATA_DECODE_VERSION_MISSING);
if (*t_size > MAX_SPA_VERSION_SIZE)
return(FKO_ERROR_INVALID_DATA_DECODE_VERSION_TOOBIG);
if(ctx->version != NULL)
free(ctx->version);
ctx->version = calloc(1, *t_size+1);
if(ctx->version == NULL)
return(FKO_ERROR_MEMORY_ALLOCATION);
strlcpy(ctx->version, *ndx, *t_size+1);
*ndx += *t_size + 1;
return FKO_SUCCESS;
}
static int
parse_timestamp(char *tbuf, char **ndx, int *t_size, fko_ctx_t ctx)
{
int is_err;
if((*t_size = strcspn(*ndx, ":")) < 1)
return(FKO_ERROR_INVALID_DATA_DECODE_TIMESTAMP_MISSING);
if (*t_size > MAX_SPA_TIMESTAMP_SIZE)
return(FKO_ERROR_INVALID_DATA_DECODE_TIMESTAMP_TOOBIG);
strlcpy(tbuf, *ndx, *t_size+1);
ctx->timestamp = (unsigned int) strtol_wrapper(tbuf,
0, -1, NO_EXIT_UPON_ERR, &is_err);
if(is_err != FKO_SUCCESS)
return(FKO_ERROR_INVALID_DATA_DECODE_TIMESTAMP_DECODEFAIL);
*ndx += *t_size + 1;
return FKO_SUCCESS;
}
static int
parse_username(char *tbuf, char **ndx, int *t_size, fko_ctx_t ctx)
{
if((*t_size = strcspn(*ndx, ":")) < 1)
return(FKO_ERROR_INVALID_DATA_DECODE_USERNAME_MISSING);
if (*t_size > MAX_SPA_USERNAME_SIZE)
return(FKO_ERROR_INVALID_DATA_DECODE_USERNAME_TOOBIG);
strlcpy(tbuf, *ndx, *t_size+1);
if(ctx->username != NULL)
free(ctx->username);
ctx->username = calloc(1, *t_size+1); /* Yes, more than we need */
if(ctx->username == NULL)
return(FKO_ERROR_MEMORY_ALLOCATION);
if(b64_decode(tbuf, (unsigned char*)ctx->username) < 0)
return(FKO_ERROR_INVALID_DATA_DECODE_USERNAME_DECODEFAIL);
if(validate_username(ctx->username) != FKO_SUCCESS)
return(FKO_ERROR_INVALID_DATA_DECODE_USERNAME_VALIDFAIL);
*ndx += *t_size + 1;
return FKO_SUCCESS;
}
static int
parse_rand_val(char *tbuf, char **ndx, int *t_size, fko_ctx_t ctx)
{
if((*t_size = strcspn(*ndx, ":")) < FKO_RAND_VAL_SIZE)
{
free(tbuf);
return(FKO_ERROR_INVALID_DATA_DECODE_RAND_MISSING);
}
if(ctx->rand_val != NULL)
free(ctx->rand_val);
ctx->rand_val = calloc(1, FKO_RAND_VAL_SIZE+1);
if(ctx->rand_val == NULL)
{
free(tbuf);
return(FKO_ERROR_MEMORY_ALLOCATION);
}
ctx->rand_val = strncpy(ctx->rand_val, *ndx, FKO_RAND_VAL_SIZE);
*ndx += *t_size + 1;
return FKO_SUCCESS;
}
/* Decode the encoded SPA data.
*/
int
fko_decode_spa_data(fko_ctx_t ctx)
{
char *tbuf, *ndx;
int t_size, i, res;
/* Array of function pointers to SPA field parsing functions
*/
int (*field_parser[FIELD_PARSERS])(char *tbuf, char **ndx, int *t_size, fko_ctx_t ctx)
= { parse_rand_val, /* Extract random value */
parse_username, /* Extract username */
parse_timestamp, /* Client timestamp */
parse_version, /* SPA version */
parse_msg_type, /* SPA msg type */
parse_msg, /* SPA msg string */
parse_nat_msg, /* SPA NAT msg string */
parse_server_auth, /* optional server authentication method */
parse_client_timeout /* client defined timeout */
};
if (! is_valid_encoded_msg_len(ctx->encoded_msg_len))
return(FKO_ERROR_INVALID_DATA_DECODE_MSGLEN_VALIDFAIL);
/* Make sure there are no non-ascii printable chars
*/
for (i=0; i < (int)strnlen(ctx->encoded_msg, MAX_SPA_ENCODED_MSG_SIZE); i++)
if(isprint(ctx->encoded_msg[i]) == 0)
return(FKO_ERROR_INVALID_DATA_DECODE_NON_ASCII);
/* Make sure there are enough fields in the SPA packet
* delimited with ':' chars
*/
ndx = ctx->encoded_msg;
if (num_fields(ndx) < MIN_SPA_FIELDS)
return(FKO_ERROR_INVALID_DATA_DECODE_LT_MIN_FIELDS);
ndx += last_field(ndx);
t_size = strnlen(ndx, SHA512_B64_LEN+1);
/* Validate digest length
*/
res = is_valid_digest_len(t_size, ctx);
if(res != FKO_SUCCESS)
return res;
if(ctx->digest != NULL)
free(ctx->digest);
/* Copy the digest into the context and terminate the encoded data
* at that point so the original digest is not part of the
* encoded string.
*/
ctx->digest = strdup(ndx);
if(ctx->digest == NULL)
return(FKO_ERROR_MEMORY_ALLOCATION);
/* Chop the digest off of the encoded_msg bucket...
*/
bzero((ndx-1), t_size);
ctx->encoded_msg_len -= t_size+1;
/* Make a tmp bucket for processing base64 encoded data and
* other general use.
*/
tbuf = calloc(1, FKO_ENCODE_TMP_BUF_SIZE);
if(tbuf == NULL)
return(FKO_ERROR_MEMORY_ALLOCATION);
/* Can now verify the digest.
*/
res = verify_digest(tbuf, t_size, ctx);
if(res != FKO_SUCCESS)
{
free(tbuf);
return(FKO_ERROR_DIGEST_VERIFICATION_FAILED);
}
/* Now we will work through the encoded data and extract (and base64-
* decode where necessary), the SPA data fields and populate the context.
*/
ndx = ctx->encoded_msg;
for (i=0; i < FIELD_PARSERS; i++)
{
res = (*field_parser[i])(tbuf, &ndx, &t_size, ctx);
if(res != FKO_SUCCESS)
{
free(tbuf);
return res;
}
}
/* Done with the tmp buffer.
*/
free(tbuf);
/* Call the context initialized.
*/
ctx->initval = FKO_CTX_INITIALIZED;
FKO_SET_CTX_INITIALIZED(ctx);
return(FKO_SUCCESS);
}
/***EOF***/