/* provide a way to set the encrypted data directly without base64 encoding. * This allows direct AFL fuzzing against decryption routines. */ int fko_afl_set_spa_data(fko_ctx_t ctx, const char * const enc_msg, const int enc_msg_len) { /* Must be initialized */ if(!CTX_INITIALIZED(ctx)) return FKO_ERROR_CTX_NOT_INITIALIZED; if(enc_msg == NULL) return(FKO_ERROR_INVALID_DATA_FUNCS_SET_MSGLEN_VALIDFAIL); if(! is_valid_encoded_msg_len(enc_msg_len)) return(FKO_ERROR_INVALID_DATA_FUNCS_SET_MSGLEN_VALIDFAIL); if(ctx->encrypted_msg != NULL) free(ctx->encrypted_msg); /* Copy the raw encrypted data into the context */ ctx->encrypted_msg = calloc(1, enc_msg_len); if(ctx->encrypted_msg == NULL) return(FKO_ERROR_MEMORY_ALLOCATION); memcpy(ctx->encrypted_msg, enc_msg, enc_msg_len); ctx->encrypted_msg_len = enc_msg_len; return(FKO_SUCCESS); }
/* Set the fko SPA encrypted data. */ int fko_set_spa_data(fko_ctx_t ctx, const char * const enc_msg) { int enc_msg_len; /* Must be initialized */ if(!CTX_INITIALIZED(ctx)) return FKO_ERROR_CTX_NOT_INITIALIZED; if(enc_msg == NULL) return(FKO_ERROR_INVALID_DATA_FUNCS_SET_MSGLEN_VALIDFAIL); enc_msg_len = strnlen(enc_msg, MAX_SPA_ENCODED_MSG_SIZE); if(! is_valid_encoded_msg_len(enc_msg_len)) return(FKO_ERROR_INVALID_DATA_FUNCS_SET_MSGLEN_VALIDFAIL); if(ctx->encrypted_msg != NULL) free(ctx->encrypted_msg); /* First, add the data to the context. */ ctx->encrypted_msg = strdup(enc_msg); ctx->encrypted_msg_len = enc_msg_len; if(ctx->encrypted_msg == NULL) return(FKO_ERROR_MEMORY_ALLOCATION); return(FKO_SUCCESS); }
/* Encrypt the encoded SPA data. */ int fko_encrypt_spa_data(fko_ctx_t ctx, const char * const enc_key, const int enc_key_len) { int res = 0; /* Must be initialized */ if(!CTX_INITIALIZED(ctx)) return(FKO_ERROR_CTX_NOT_INITIALIZED); if(enc_key_len < 0) return(FKO_ERROR_INVALID_KEY_LEN); /* If there is no encoded data or the SPA data has been modified, * go ahead and re-encode here. */ if(ctx->encoded_msg == NULL || FKO_IS_SPA_DATA_MODIFIED(ctx)) res = fko_encode_spa_data(ctx); if(res != FKO_SUCCESS) return(res); /* Croak on invalid encoded message as well. At present this is a * check for a somewhat arbitrary minimum length for the encoded * data. */ if (! is_valid_encoded_msg_len(ctx->encoded_msg_len)) return(FKO_ERROR_MISSING_ENCODED_DATA); /* Encrypt according to type and return... */ if(ctx->encryption_type == FKO_ENCRYPTION_RIJNDAEL) { if(enc_key == NULL) return(FKO_ERROR_INVALID_KEY_LEN); res = _rijndael_encrypt(ctx, enc_key, enc_key_len); } else if(ctx->encryption_type == FKO_ENCRYPTION_GPG) #if HAVE_LIBGPGME res = gpg_encrypt(ctx, enc_key); #else res = FKO_ERROR_UNSUPPORTED_FEATURE; #endif else
/* Return the fko SPA encrypted data. */ int fko_get_spa_data(fko_ctx_t ctx, char **spa_data) { #if HAVE_LIBFIU fiu_return_on("fko_get_spa_data_init", FKO_ERROR_CTX_NOT_INITIALIZED); #endif /* Must be initialized */ if(!CTX_INITIALIZED(ctx)) return(FKO_ERROR_CTX_NOT_INITIALIZED); if(spa_data == NULL) return(FKO_ERROR_INVALID_DATA); #if HAVE_LIBFIU fiu_return_on("fko_get_spa_data_val", FKO_ERROR_INVALID_DATA); #endif /* We expect to have encrypted data to process. If not, we bail. */ if(ctx->encrypted_msg == NULL || ! is_valid_encoded_msg_len( strnlen(ctx->encrypted_msg, MAX_SPA_ENCODED_MSG_SIZE))) return(FKO_ERROR_MISSING_ENCODED_DATA); #if HAVE_LIBFIU fiu_return_on("fko_get_spa_data_encoded", FKO_ERROR_MISSING_ENCODED_DATA); #endif *spa_data = ctx->encrypted_msg; /* Notice we omit the first 10 bytes if Rijndael encryption is * used (to eliminate the consistent 'Salted__' string), and * in GnuPG mode we eliminate the consistent 'hQ' base64 encoded * prefix */ if(ctx->encryption_type == FKO_ENCRYPTION_RIJNDAEL) *spa_data += B64_RIJNDAEL_SALT_STR_LEN; else if(ctx->encryption_type == FKO_ENCRYPTION_GPG) *spa_data += B64_GPG_PREFIX_STR_LEN; return(FKO_SUCCESS); }
int fko_verify_hmac(fko_ctx_t ctx, const char * const hmac_key, const int hmac_key_len) { char *hmac_digest_from_data = NULL; char *tbuf = NULL; int res = FKO_SUCCESS; int hmac_b64_digest_len = 0, zero_free_rv = FKO_SUCCESS; /* Must be initialized */ if(!CTX_INITIALIZED(ctx)) return(FKO_ERROR_CTX_NOT_INITIALIZED); if (! is_valid_encoded_msg_len(ctx->encrypted_msg_len)) return(FKO_ERROR_INVALID_DATA_HMAC_MSGLEN_VALIDFAIL); if(hmac_key_len > MAX_DIGEST_BLOCK_LEN) return(FKO_ERROR_INVALID_HMAC_KEY_LEN); if(ctx->hmac_type == FKO_HMAC_MD5) hmac_b64_digest_len = MD5_B64_LEN; else if(ctx->hmac_type == FKO_HMAC_SHA1) hmac_b64_digest_len = SHA1_B64_LEN; else if(ctx->hmac_type == FKO_HMAC_SHA256) hmac_b64_digest_len = SHA256_B64_LEN; else if(ctx->hmac_type == FKO_HMAC_SHA384) hmac_b64_digest_len = SHA384_B64_LEN; else if(ctx->hmac_type == FKO_HMAC_SHA512) hmac_b64_digest_len = SHA512_B64_LEN; else return(FKO_ERROR_UNSUPPORTED_HMAC_MODE); if((ctx->encrypted_msg_len - hmac_b64_digest_len) < MIN_SPA_ENCODED_MSG_SIZE) return(FKO_ERROR_INVALID_DATA_HMAC_ENCMSGLEN_VALIDFAIL); /* Get digest value */ hmac_digest_from_data = strndup((ctx->encrypted_msg + ctx->encrypted_msg_len - hmac_b64_digest_len), hmac_b64_digest_len); if(hmac_digest_from_data == NULL) return(FKO_ERROR_MEMORY_ALLOCATION); /* Now we chop the HMAC digest off of the encrypted msg */ tbuf = strndup(ctx->encrypted_msg, ctx->encrypted_msg_len - hmac_b64_digest_len); if(tbuf == NULL) { if(zero_free(hmac_digest_from_data, strnlen(hmac_digest_from_data, MAX_SPA_ENCODED_MSG_SIZE)) == FKO_SUCCESS) return(FKO_ERROR_MEMORY_ALLOCATION); else return(FKO_ERROR_ZERO_OUT_DATA); } if(zero_free(ctx->encrypted_msg, ctx->encrypted_msg_len) != FKO_SUCCESS) zero_free_rv = FKO_ERROR_ZERO_OUT_DATA; ctx->encrypted_msg = tbuf; ctx->encrypted_msg_len -= hmac_b64_digest_len; if(ctx->encryption_mode == FKO_ENC_MODE_ASYMMETRIC) { /* See if we need to add the "hQ" string to the front of the * encrypted data. */ if(! ctx->added_gpg_prefix) { res = add_gpg_prefix(ctx); } } else { /* See if we need to add the "Salted__" string to the front of the * encrypted data. */ if(! ctx->added_salted_str) { res = add_salted_str(ctx); } } if (res != FKO_SUCCESS) { if(zero_free(hmac_digest_from_data, strnlen(hmac_digest_from_data, MAX_SPA_ENCODED_MSG_SIZE)) != FKO_SUCCESS) zero_free_rv = FKO_ERROR_ZERO_OUT_DATA; if(zero_free_rv == FKO_SUCCESS) return(res); else return(zero_free_rv); } /* Calculate the HMAC from the encrypted data and then * compare */ res = fko_set_spa_hmac_type(ctx, ctx->hmac_type); if(res == FKO_SUCCESS) { res = fko_set_spa_hmac(ctx, hmac_key, hmac_key_len); if(res == FKO_SUCCESS) { if(constant_runtime_cmp(hmac_digest_from_data, ctx->msg_hmac, hmac_b64_digest_len) != 0) { res = FKO_ERROR_INVALID_DATA_HMAC_COMPAREFAIL; } } } if(zero_free(hmac_digest_from_data, strnlen(hmac_digest_from_data, MAX_SPA_ENCODED_MSG_SIZE)) != FKO_SUCCESS) zero_free_rv = FKO_ERROR_ZERO_OUT_DATA; if(res == FKO_SUCCESS) return(zero_free_rv); else return(res); }
/* 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((int)(unsigned char)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); }
/* Decode the encoded SPA data. */ int fko_decode_spa_data(fko_ctx_t ctx) { char *tbuf, *ndx, *tmp; int t_size, i, is_err; 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; for (i=0; i < MAX_SPA_FIELDS; i++) { if ((tmp = strchr(ndx, ':')) == NULL) break; ndx = tmp; ndx++; } if (i < MIN_SPA_FIELDS) return(FKO_ERROR_INVALID_DATA_DECODE_LT_MIN_FIELDS); t_size = strnlen(ndx, SHA512_B64_LEN+1); 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); 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); /* Zero out the rest 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 = malloc(FKO_ENCODE_TMP_BUF_SIZE); if(tbuf == NULL) return(FKO_ERROR_MEMORY_ALLOCATION); /* Can now verify the digest. */ 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; } /* 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) { 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; /* The rand val data */ 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); /* Jump to the next field (username). We need to use the temp buffer * for the base64 decode step. */ ndx += t_size + 1; if((t_size = strcspn(ndx, ":")) < 1) { free(tbuf); return(FKO_ERROR_INVALID_DATA_DECODE_USERNAME_MISSING); } if (t_size > MAX_SPA_USERNAME_SIZE) { free(tbuf); return(FKO_ERROR_INVALID_DATA_DECODE_USERNAME_TOOBIG); } strlcpy(tbuf, ndx, t_size+1); if(ctx->username != NULL) free(ctx->username); ctx->username = malloc(t_size+1); /* Yes, more than we need */ if(ctx->username == NULL) { free(tbuf); return(FKO_ERROR_MEMORY_ALLOCATION); } if(b64_decode(tbuf, (unsigned char*)ctx->username) < 0) { free(tbuf); return(FKO_ERROR_INVALID_DATA_DECODE_USERNAME_DECODEFAIL); } if(validate_username(ctx->username) != FKO_SUCCESS) { free(tbuf); return(FKO_ERROR_INVALID_DATA_DECODE_USERNAME_VALIDFAIL); } /* Extract the timestamp value. */ ndx += t_size + 1; if((t_size = strcspn(ndx, ":")) < 1) { free(tbuf); return(FKO_ERROR_INVALID_DATA_DECODE_TIMESTAMP_MISSING); } if (t_size > MAX_SPA_TIMESTAMP_SIZE) { free(tbuf); 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) { free(tbuf); return(FKO_ERROR_INVALID_DATA_DECODE_TIMESTAMP_DECODEFAIL); } /* Extract the version string. */ ndx += t_size + 1; if((t_size = strcspn(ndx, ":")) < 1) { free(tbuf); return(FKO_ERROR_INVALID_DATA_DECODE_VERSION_MISSING); } if (t_size > MAX_SPA_VERSION_SIZE) { free(tbuf); return(FKO_ERROR_INVALID_DATA_DECODE_VERSION_TOOBIG); } if(ctx->version != NULL) free(ctx->version); ctx->version = malloc(t_size+1); if(ctx->version == NULL) { free(tbuf); return(FKO_ERROR_MEMORY_ALLOCATION); } strlcpy(ctx->version, ndx, t_size+1); /* Extract the message type value. */ ndx += t_size + 1; if((t_size = strcspn(ndx, ":")) < 1) { free(tbuf); return(FKO_ERROR_INVALID_DATA_DECODE_MSGTYPE_MISSING); } if (t_size > MAX_SPA_MESSAGE_TYPE_SIZE) { free(tbuf); 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, NO_EXIT_UPON_ERR, &is_err); if(is_err != FKO_SUCCESS) { free(tbuf); return(FKO_ERROR_INVALID_DATA_DECODE_MSGTYPE_DECODEFAIL); } /* Extract the SPA message string. */ ndx += t_size + 1; if((t_size = strcspn(ndx, ":")) < 1) { free(tbuf); return(FKO_ERROR_INVALID_DATA_DECODE_MESSAGE_MISSING); } if (t_size > MAX_SPA_MESSAGE_SIZE) { free(tbuf); return(FKO_ERROR_INVALID_DATA_DECODE_MESSAGE_TOOBIG); } strlcpy(tbuf, ndx, t_size+1); if(ctx->message != NULL) free(ctx->message); ctx->message = malloc(t_size+1); /* Yes, more than we need */ if(ctx->message == NULL) { free(tbuf); return(FKO_ERROR_MEMORY_ALLOCATION); } if(b64_decode(tbuf, (unsigned char*)ctx->message) < 0) { free(tbuf); 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) { free(tbuf); 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) { free(tbuf); return(FKO_ERROR_INVALID_DATA_DECODE_ACCESS_VALIDFAIL); } } /* Extract nat_access string if the message_type indicates so. */ 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) { ndx += t_size + 1; if((t_size = strcspn(ndx, ":")) < 1) { free(tbuf); return(FKO_ERROR_INVALID_DATA_DECODE_NATACCESS_MISSING); } if (t_size > MAX_SPA_MESSAGE_SIZE) { free(tbuf); 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 = malloc(t_size+1); /* Yes, more than we need */ if(ctx->nat_access == NULL) { free(tbuf); return(FKO_ERROR_MEMORY_ALLOCATION); } if(b64_decode(tbuf, (unsigned char*)ctx->nat_access) < 0) { free(tbuf); return(FKO_ERROR_INVALID_DATA_DECODE_NATACCESS_DECODEFAIL); } if(validate_nat_access_msg(ctx->nat_access) != FKO_SUCCESS) { free(tbuf); return(FKO_ERROR_INVALID_DATA_DECODE_NATACCESS_VALIDFAIL); } } /* Now look for a server_auth string. */ ndx += t_size + 1; if((t_size = strlen(ndx)) > 0) { if (t_size > MAX_SPA_MESSAGE_SIZE) { free(tbuf); return(FKO_ERROR_INVALID_DATA_DECODE_SRVAUTH_MISSING); } /* There is data, but what is it? * If the message_type does not have a timeout, assume it is a * server_auth field. */ 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) { strlcpy(tbuf, ndx, t_size+1); if(ctx->server_auth != NULL) free(ctx->server_auth); ctx->server_auth = malloc(t_size+1); /* Yes, more than we need */ if(ctx->server_auth == NULL) { free(tbuf); return(FKO_ERROR_MEMORY_ALLOCATION); } if(b64_decode(tbuf, (unsigned char*)ctx->server_auth) < 0) { free(tbuf); return(FKO_ERROR_INVALID_DATA_DECODE_SRVAUTH_DECODEFAIL); } /* At this point we should be done. */ free(tbuf); /* Call the context initialized. */ ctx->initval = FKO_CTX_INITIALIZED; FKO_SET_CTX_INITIALIZED(ctx); return(FKO_SUCCESS); } /* 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) { free(tbuf); return(FKO_ERROR_INVALID_DATA_DECODE_EXTRA_TOOBIG); } /* Looks like we have both, so assume this is the */ strlcpy(tbuf, ndx, t_size+1); if(ctx->server_auth != NULL) free(ctx->server_auth); ctx->server_auth = malloc(t_size+1); /* Yes, more than we need */ if(ctx->server_auth == NULL) { free(tbuf); return(FKO_ERROR_MEMORY_ALLOCATION); } if(b64_decode(tbuf, (unsigned char*)ctx->server_auth) < 0) { free(tbuf); return(FKO_ERROR_INVALID_DATA_DECODE_EXTRA_DECODEFAIL); } ndx += t_size + 1; } /* Now we look for a timeout value if one is supposed to be there. */ 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) { free(tbuf); return(FKO_ERROR_INVALID_DATA_DECODE_TIMEOUT_MISSING); } if (t_size > MAX_SPA_MESSAGE_SIZE) { free(tbuf); return(FKO_ERROR_INVALID_DATA_DECODE_TIMEOUT_TOOBIG); } /* Should be a number only. */ if(strspn(ndx, "0123456789") != t_size) { free(tbuf); 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) { free(tbuf); return(FKO_ERROR_INVALID_DATA_DECODE_TIMEOUT_DECODEFAIL); } } } /* Done with the tmp buffer. */ free(tbuf); /* Call the context initialized. */ ctx->initval = FKO_CTX_INITIALIZED; FKO_SET_CTX_INITIALIZED(ctx); return(FKO_SUCCESS); }
/* Prep and encrypt using Rijndael */ static int _rijndael_encrypt(fko_ctx_t ctx, const char *enc_key, const int enc_key_len) { char *plaintext; char *b64ciphertext; unsigned char *ciphertext; int cipher_len; int pt_len; int zero_free_rv = FKO_SUCCESS; if(enc_key_len > RIJNDAEL_MAX_KEYSIZE) return(FKO_ERROR_INVALID_KEY_LEN); if (! is_valid_encoded_msg_len(ctx->encoded_msg_len)) return(FKO_ERROR_INVALID_DATA_ENCRYPT_MSGLEN_VALIDFAIL); switch(ctx->digest_len) { case MD5_B64_LEN: break; case SHA1_B64_LEN: break; case SHA256_B64_LEN: break; case SHA384_B64_LEN: break; case SHA512_B64_LEN: break; default: return(FKO_ERROR_INVALID_DATA_ENCRYPT_DIGESTLEN_VALIDFAIL); } pt_len = ctx->encoded_msg_len + ctx->digest_len + RIJNDAEL_BLOCKSIZE + 2; /* Make a bucket big enough to hold the enc msg + digest (plaintext) * and populate it appropriately. */ plaintext = calloc(1, pt_len); if(plaintext == NULL) return(FKO_ERROR_MEMORY_ALLOCATION); pt_len = snprintf(plaintext, pt_len, "%s:%s", ctx->encoded_msg, ctx->digest); if(! is_valid_pt_msg_len(pt_len)) { if(zero_free(plaintext, pt_len) == FKO_SUCCESS) return(FKO_ERROR_INVALID_DATA_ENCRYPT_PTLEN_VALIDFAIL); else return(FKO_ERROR_ZERO_OUT_DATA); } /* Make a bucket for the encrypted version and populate it. */ ciphertext = calloc(1, pt_len + 32); /* Plus padding for salt and Block */ if(ciphertext == NULL) { if(zero_free(plaintext, pt_len) == FKO_SUCCESS) return(FKO_ERROR_MEMORY_ALLOCATION); else return(FKO_ERROR_ZERO_OUT_DATA); } cipher_len = rij_encrypt( (unsigned char*)plaintext, pt_len, (char*)enc_key, enc_key_len, ciphertext, ctx->encryption_mode ); /* Now make a bucket for the base64-encoded version and populate it. */ b64ciphertext = malloc(((cipher_len / 3) * 4) + 8); if(b64ciphertext == NULL) { if(zero_free((char *) ciphertext, pt_len+32) == FKO_SUCCESS && zero_free(plaintext, pt_len) == FKO_SUCCESS) return(FKO_ERROR_MEMORY_ALLOCATION); else return(FKO_ERROR_ZERO_OUT_DATA); } b64_encode(ciphertext, b64ciphertext, cipher_len); strip_b64_eq(b64ciphertext); if(ctx->encrypted_msg != NULL) zero_free_rv = zero_free(ctx->encrypted_msg, strnlen(ctx->encrypted_msg, MAX_SPA_ENCODED_MSG_SIZE)); ctx->encrypted_msg = strdup(b64ciphertext); ctx->encrypted_msg_len = strnlen(ctx->encrypted_msg, MAX_SPA_ENCODED_MSG_SIZE); /* Clean-up */ if(zero_free(plaintext, pt_len) != FKO_SUCCESS) zero_free_rv = FKO_ERROR_ZERO_OUT_DATA; if(zero_free((char *) ciphertext, pt_len+32) != FKO_SUCCESS) zero_free_rv = FKO_ERROR_ZERO_OUT_DATA; if(zero_free(b64ciphertext, strnlen(b64ciphertext, MAX_SPA_ENCODED_MSG_SIZE)) != FKO_SUCCESS) zero_free_rv = FKO_ERROR_ZERO_OUT_DATA; if(ctx->encrypted_msg == NULL) return(FKO_ERROR_MEMORY_ALLOCATION); if(! is_valid_encoded_msg_len(ctx->encrypted_msg_len)) return(FKO_ERROR_INVALID_DATA_ENCRYPT_RESULT_MSGLEN_VALIDFAIL); return(zero_free_rv); }
/* Prep and decrypt using gpgme */ static int gpg_decrypt(fko_ctx_t ctx, const char *dec_key) { unsigned char *cipher; size_t cipher_len; int res, pt_len, b64_decode_len; /* Now see if we need to add the "hQ" string to the front of the * base64-encoded-GPG-encrypted data. */ if(! ctx->added_gpg_prefix) add_gpg_prefix(ctx); /* Create a bucket for the (base64) decoded encrypted data and get the * raw cipher data. */ cipher = malloc(ctx->encrypted_msg_len); if(cipher == NULL) return(FKO_ERROR_MEMORY_ALLOCATION); if((b64_decode_len = b64_decode(ctx->encrypted_msg, cipher)) < 0) { if(zero_free((char *) cipher, ctx->encrypted_msg_len) == FKO_SUCCESS) return(FKO_ERROR_INVALID_DATA_ENCRYPT_GPG_CIPHER_DECODEFAIL); else return(FKO_ERROR_ZERO_OUT_DATA); } cipher_len = b64_decode_len; /* Create a bucket for the plaintext data and decrypt the message * data into it. */ /* --DSS Actually, the needed memory will be malloced in the gpgme_decrypt // function. Just leaving this here for reference (for now). //ctx->encoded_msg = malloc(cipher_len); //if(ctx->encoded_msg == NULL) // return(FKO_ERROR_MEMORY_ALLOCATION); */ res = gpgme_decrypt(ctx, cipher, cipher_len, dec_key, (unsigned char**)&ctx->encoded_msg, &cipher_len ); /* Done with cipher... */ if(res != FKO_SUCCESS) { if(zero_free((char *) cipher, ctx->encrypted_msg_len) != FKO_SUCCESS) return(FKO_ERROR_ZERO_OUT_DATA); else return(res); } pt_len = strnlen(ctx->encoded_msg, MAX_SPA_ENCODED_MSG_SIZE); if(ctx->encoded_msg == NULL) return(FKO_ERROR_INVALID_DATA_ENCRYPT_DECRYPTED_MESSAGE_MISSING); if(! is_valid_encoded_msg_len(pt_len)) return(FKO_ERROR_INVALID_DATA_ENCRYPT_DECRYPTED_MSGLEN_VALIDFAIL); ctx->encoded_msg_len = pt_len; /* Call fko_decode and return the results. */ return(fko_decode_spa_data(ctx)); }
/* Prep and encrypt using gpgme */ static int gpg_encrypt(fko_ctx_t ctx, const char *enc_key) { int res; char *plain; int pt_len, zero_free_rv = FKO_SUCCESS; char *b64cipher; unsigned char *cipher = NULL; size_t cipher_len; char *empty_key = ""; if (! is_valid_encoded_msg_len(ctx->encoded_msg_len)) return(FKO_ERROR_INVALID_DATA_ENCRYPT_GPG_MESSAGE_VALIDFAIL); switch(ctx->digest_len) { case MD5_B64_LEN: break; case SHA1_B64_LEN: break; case SHA256_B64_LEN: break; case SHA384_B64_LEN: break; case SHA512_B64_LEN: break; default: return(FKO_ERROR_INVALID_DATA_ENCRYPT_GPG_DIGEST_VALIDFAIL); } /* First make sure we have a recipient key set. */ if(ctx->gpg_recipient == NULL) return(FKO_ERROR_MISSING_GPG_KEY_DATA); pt_len = ctx->encoded_msg_len + ctx->digest_len + 2; /* Make a bucket big enough to hold the enc msg + digest (plaintext) * and populate it appropriately. */ plain = malloc(ctx->encoded_msg_len + ctx->digest_len + 2); if(plain == NULL) return(FKO_ERROR_MEMORY_ALLOCATION); pt_len = snprintf(plain, pt_len+1, "%s:%s", ctx->encoded_msg, ctx->digest); if(! is_valid_pt_msg_len(pt_len)) { if(zero_free(plain, pt_len) == FKO_SUCCESS) return(FKO_ERROR_INVALID_DATA_ENCRYPT_GPG_MSGLEN_VALIDFAIL); else return(FKO_ERROR_ZERO_OUT_DATA); } if (enc_key != NULL) { res = gpgme_encrypt(ctx, (unsigned char*)plain, pt_len, enc_key, &cipher, &cipher_len ); } else { res = gpgme_encrypt(ctx, (unsigned char*)plain, pt_len, empty_key, &cipher, &cipher_len ); } /* --DSS XXX: Better parsing of what went wrong would be nice :) */ if(res != FKO_SUCCESS) { zero_free_rv = zero_free(plain, pt_len); if(cipher != NULL) if(zero_free((char *) cipher, cipher_len) != FKO_SUCCESS) zero_free_rv = FKO_ERROR_ZERO_OUT_DATA; if(zero_free_rv == FKO_SUCCESS) return(res); else return(zero_free_rv); } /* Now make a bucket for the base64-encoded version and populate it. */ b64cipher = malloc(((cipher_len / 3) * 4) + 8); if(b64cipher == NULL) { if(zero_free(plain, pt_len) != FKO_SUCCESS) zero_free_rv = FKO_ERROR_ZERO_OUT_DATA; if(cipher != NULL) if(zero_free((char *) cipher, cipher_len) != FKO_SUCCESS) zero_free_rv = FKO_ERROR_ZERO_OUT_DATA; if(zero_free_rv == FKO_SUCCESS) return(FKO_ERROR_MEMORY_ALLOCATION); else return(zero_free_rv); } b64_encode(cipher, b64cipher, cipher_len); strip_b64_eq(b64cipher); if(ctx->encrypted_msg != NULL) zero_free_rv = zero_free(ctx->encrypted_msg, strnlen(ctx->encrypted_msg, MAX_SPA_ENCODED_MSG_SIZE)); ctx->encrypted_msg = strdup(b64cipher); ctx->encrypted_msg_len = strnlen(ctx->encrypted_msg, MAX_SPA_ENCODED_MSG_SIZE); /* Clean-up */ if(zero_free(plain, pt_len) != FKO_SUCCESS) zero_free_rv = FKO_ERROR_ZERO_OUT_DATA; if(zero_free((char *) cipher, cipher_len) != FKO_SUCCESS) zero_free_rv = FKO_ERROR_ZERO_OUT_DATA; if(zero_free(b64cipher, strnlen(b64cipher, MAX_SPA_ENCODED_MSG_SIZE)) != FKO_SUCCESS) zero_free_rv = FKO_ERROR_ZERO_OUT_DATA; if(ctx->encrypted_msg == NULL) return(FKO_ERROR_MEMORY_ALLOCATION); if(! is_valid_encoded_msg_len(ctx->encrypted_msg_len)) return(FKO_ERROR_INVALID_DATA_ENCRYPT_GPG_RESULT_MSGLEN_VALIDFAIL); return(zero_free_rv); }
/* Decode, decrypt, and parse SPA data into the context. */ static int _rijndael_decrypt(fko_ctx_t ctx, const char *dec_key, const int key_len, int encryption_mode) { unsigned char *ndx; unsigned char *cipher; int cipher_len, pt_len, i, err = 0, res = FKO_SUCCESS; int zero_free_rv = FKO_SUCCESS; if(key_len > RIJNDAEL_MAX_KEYSIZE) return(FKO_ERROR_INVALID_KEY_LEN); /* Now see if we need to add the "Salted__" string to the front of the * encrypted data. */ if(! ctx->added_salted_str) { res = add_salted_str(ctx); if(res != FKO_SUCCESS) return res; } /* Create a bucket for the (base64) decoded encrypted data and get the * raw cipher data. */ cipher = malloc(ctx->encrypted_msg_len); if(cipher == NULL) return(FKO_ERROR_MEMORY_ALLOCATION); if((cipher_len = b64_decode(ctx->encrypted_msg, cipher)) < 0) { if(zero_free((char *)cipher, ctx->encrypted_msg_len) == FKO_SUCCESS) return(FKO_ERROR_INVALID_DATA_ENCRYPT_CIPHERLEN_DECODEFAIL); else return(FKO_ERROR_ZERO_OUT_DATA); } /* Since we're using AES, make sure the incoming data is a multiple of * the blocksize */ if((cipher_len % RIJNDAEL_BLOCKSIZE) != 0) { if(zero_free((char *)cipher, ctx->encrypted_msg_len) == FKO_SUCCESS) return(FKO_ERROR_INVALID_DATA_ENCRYPT_CIPHERLEN_VALIDFAIL); else return(FKO_ERROR_ZERO_OUT_DATA); } if(ctx->encoded_msg != NULL) zero_free_rv = zero_free(ctx->encoded_msg, strnlen(ctx->encoded_msg, MAX_SPA_ENCODED_MSG_SIZE)); /* Create a bucket for the plaintext data and decrypt the message * data into it. */ ctx->encoded_msg = malloc(cipher_len); if(ctx->encoded_msg == NULL) { if(zero_free((char *)cipher, ctx->encrypted_msg_len) == FKO_SUCCESS) return(FKO_ERROR_MEMORY_ALLOCATION); else return(FKO_ERROR_ZERO_OUT_DATA); } pt_len = rij_decrypt(cipher, cipher_len, dec_key, key_len, (unsigned char*)ctx->encoded_msg, encryption_mode); /* Done with cipher... */ if(zero_free((char *)cipher, ctx->encrypted_msg_len) != FKO_SUCCESS) zero_free_rv = FKO_ERROR_ZERO_OUT_DATA; /* The length of the decrypted data should be within 32 bytes of the * length of the encrypted version. */ if(pt_len < (cipher_len - 32) || pt_len <= 0) return(FKO_ERROR_DECRYPTION_SIZE); if(ctx->encoded_msg == NULL) return(FKO_ERROR_INVALID_DATA_ENCRYPT_GPG_ENCODEDMSG_NULL); if(! is_valid_encoded_msg_len(pt_len)) return(FKO_ERROR_INVALID_DATA_ENCRYPT_GPG_ENCODEDMSGLEN_VALIDFAIL); if(zero_free_rv != FKO_SUCCESS) return(zero_free_rv); ctx->encoded_msg_len = pt_len; /* At this point we can check the data to see if we have a good * decryption by ensuring the first field (16-digit random decimal * value) is valid and is followed by a colon. Additional checks * are made in fko_decode_spa_data(). */ ndx = (unsigned char *)ctx->encoded_msg; for(i=0; i<FKO_RAND_VAL_SIZE; i++) if(!isdigit(*(ndx++))) err++; if(err > 0 || *ndx != ':') return(FKO_ERROR_DECRYPTION_FAILURE); /* Call fko_decode and return the results. */ return(fko_decode_spa_data(ctx)); }
/* Initialize an fko context with external (encrypted/encoded) data. * This is used to create a context with the purpose of decoding * and parsing the provided data into the context data. */ int fko_new_with_data(fko_ctx_t *r_ctx, const char * const enc_msg, const char * const dec_key, const int dec_key_len, int encryption_mode, const char * const hmac_key, const int hmac_key_len, const int hmac_type) { fko_ctx_t ctx = NULL; int res = FKO_SUCCESS; /* Are we optimistic or what? */ int enc_msg_len; #if HAVE_LIBFIU fiu_return_on("fko_new_with_data_msg", FKO_ERROR_INVALID_DATA_FUNCS_NEW_ENCMSG_MISSING); #endif if(enc_msg == NULL) return(FKO_ERROR_INVALID_DATA_FUNCS_NEW_ENCMSG_MISSING); #if HAVE_LIBFIU fiu_return_on("fko_new_with_data_keylen", FKO_ERROR_INVALID_KEY_LEN); #endif if(dec_key_len < 0 || hmac_key_len < 0) return(FKO_ERROR_INVALID_KEY_LEN); ctx = calloc(1, sizeof *ctx); if(ctx == NULL) return(FKO_ERROR_MEMORY_ALLOCATION); enc_msg_len = strnlen(enc_msg, MAX_SPA_ENCODED_MSG_SIZE); if(! is_valid_encoded_msg_len(enc_msg_len)) { free(ctx); return(FKO_ERROR_INVALID_DATA_FUNCS_NEW_MSGLEN_VALIDFAIL); } /* First, add the data to the context. */ ctx->encrypted_msg = strdup(enc_msg); ctx->encrypted_msg_len = enc_msg_len; if(ctx->encrypted_msg == NULL) { free(ctx); return(FKO_ERROR_MEMORY_ALLOCATION); } /* Default Encryption Mode (Rijndael in CBC mode) */ ctx->initval = FKO_CTX_INITIALIZED; res = fko_set_spa_encryption_mode(ctx, encryption_mode); if(res != FKO_SUCCESS) { fko_destroy(ctx); ctx = NULL; return res; } /* HMAC digest type */ res = fko_set_spa_hmac_type(ctx, hmac_type); if(res != FKO_SUCCESS) { fko_destroy(ctx); ctx = NULL; return res; } /* Check HMAC if the access stanza had an HMAC key */ if(hmac_key_len > 0 && hmac_key != NULL) res = fko_verify_hmac(ctx, hmac_key, hmac_key_len); if(res != FKO_SUCCESS) { fko_destroy(ctx); ctx = NULL; return res; } /* Consider it initialized here. */ FKO_SET_CTX_INITIALIZED(ctx); /* If a decryption key is provided, go ahead and decrypt and decode. */ if(dec_key != NULL) { res = fko_decrypt_spa_data(ctx, dec_key, dec_key_len); if(res != FKO_SUCCESS) { fko_destroy(ctx); ctx = NULL; *r_ctx = NULL; /* Make sure the caller ctx is null just in case */ return(res); } } #if HAVE_LIBGPGME /* Set gpg signature verify on. */ ctx->verify_gpg_sigs = 1; #endif /* HAVE_LIBGPGME */ *r_ctx = ctx; return(res); }