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