/** * Copyright 2015 Comcast Cable Communications Management, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ /** * @file token.c * * @description This file contains operations for using jwt token. * */ #include #include #include #include #include #ifdef FEATURE_DNS_QUERY #include #endif //#include #include #include #include #include #include #include #include #include #include #include #include #include #include "token.h" #include "config.h" #include "parodus_log.h" #include "ParodusInternal.h" #define JWT_MAXBUF 8192 #ifdef NS_MAXMSG #if NS_MAXMSG > JWT_MAXBUF #define NS_MAXBUF JWT_MAXBUF #else #define NS_MAXBUF NS_MAXMSG #endif #else #define NS_MAXBUF JWT_MAXBUF #endif #define TXT_REC_ID_MAXSIZE 128 #define MAX_RR_RECS 10 #define SEQ_TABLE_SIZE (MAX_RR_RECS + 1) /*----------------------------------------------------------------------------*/ /* Macros */ /*----------------------------------------------------------------------------*/ #define ENDPOINT_NAME "endpoint" /*----------------------------------------------------------------------------*/ /* File Scoped Variables */ /*----------------------------------------------------------------------------*/ /* none */ /*----------------------------------------------------------------------------*/ /* Function Prototypes */ /*----------------------------------------------------------------------------*/ /* none */ /*----------------------------------------------------------------------------*/ /* External Functions */ /*----------------------------------------------------------------------------*/ #ifdef FEATURE_DNS_QUERY extern int __res_ninit(res_state statp); extern void __res_nclose(res_state statp); extern int __res_nquery(res_state statp, const char *name, /* domain name */ int class, int type, /* class and type of query */ u_char *answer, /* buffer to put answer */ int anslen); /* size of answer buffer */ /*----------------------------------------------------------------------------*/ /* Internal functions */ /*----------------------------------------------------------------------------*/ static void show_times (time_t exp_time, time_t cur_time) { char exp_buf[30]; char cur_buf[30]; ctime_r (&exp_time, exp_buf); exp_buf[strlen(exp_buf)-1] = 0; ctime_r (&cur_time, cur_buf); cur_buf[strlen(cur_buf)-1] = 0; ParodusInfo ("Exp: %d %s, Current: %d %s\n", (int)exp_time, exp_buf+4, (int)cur_time, cur_buf+4); } // returns 1 if insecure, 0 if secure, < 0 if error int analyze_jwt (const cjwt_t *jwt, char **url_buf, unsigned int *port) { cJSON *claims = jwt->private_claims; cJSON *endpoint = NULL; time_t exp_time, cur_time; int http_match; if (!claims) { ParodusError ("Private claims not found in jwt\n"); return TOKEN_ERR_INVALID_JWT_CONTENT; } endpoint = cJSON_GetObjectItem(claims, ENDPOINT_NAME); if (!endpoint) { ParodusError ("Endpoint claim not found in jwt\n"); return TOKEN_ERR_INVALID_JWT_CONTENT; } ParodusInfo ("JWT endpoint: %s\n", endpoint->valuestring); exp_time = jwt->exp.tv_sec; if (0 == exp_time) { ParodusError ("exp not found in JWT payload\n"); return TOKEN_ERR_NO_EXPIRATION; } else { cur_time = time(NULL); show_times (exp_time, cur_time); if (exp_time < cur_time) { ParodusError ("JWT has expired\n"); OnboardLog ("JWT has expired\n"); return TOKEN_ERR_JWT_EXPIRED; } } http_match = parse_webpa_url (endpoint->valuestring, url_buf, port); if (http_match < 0) { ParodusError ("Invalid endpoint claim in JWT\n"); OnboardLog("Invalid endpoint claim in JWT\n"); return TOKEN_ERR_BAD_ENDPOINT; } ParodusInfo ("JWT is_http strncmp: %d\n", http_match); return http_match; } bool validate_algo(const cjwt_t *jwt) { // return true if jwt->header.alg is included in the set // of allowed algorithms specified by cfg->jwt_algo ParodusCfg *cfg = get_parodus_cfg(); int alg = jwt->header.alg; int alg_mask; if ((alg < 0) || (alg >= num_algorithms)) return false; alg_mask = 1<jwt_algo) == 0) { ParodusError ("Algorithm %d not allowed (mask %d)\n", alg, alg_mask); return false; } return true; } int nquery(const char* dns_txt_record_id, u_char *nsbuf) { int len; struct __res_state statp; /* Initialize resolver */ memset (&statp, 0, sizeof(__res_state)); if (NULL == nsbuf) { ParodusError ("nquery: nsbuf is NULL\n"); return (-1); } statp.options = RES_DEBUG; if (__res_ninit(&statp) < 0) { ParodusError ("res_ninit error: can't initialize statp.\n"); return (-1); } ParodusInfo ("nquery: domain : %s\n", dns_txt_record_id); memset (nsbuf, 0, NS_MAXBUF); len = __res_nquery(&statp, dns_txt_record_id, ns_c_in, ns_t_txt, nsbuf, NS_MAXBUF); if (len < 0) { if (0 != statp.res_h_errno) { const char *msg = hstrerror (statp.res_h_errno); ParodusError ("Error in res_nquery: %s\n", msg); } return len; } __res_nclose (&statp); ParodusInfo ("nquery: nsbuf (1) 0x%lx\n", (unsigned long) nsbuf); if (len >= NS_MAXBUF) { ParodusError ("res_nquery error: ns buffer too small.\n"); return -1; } return len; } bool valid_b64_char (char c) { if ((c>='A') && (c<='Z')) return true; if ((c>='a') && (c<='z')) return true; if ((c>='0') && (c<='9')) return true; if ((c=='/') || (c=='+') || (c=='-') || (c=='_')) return true; return false; } static bool is_digit (char c) { return (bool) ((c>='0') && (c<='9')); } // strip quotes and newlines from rr rec const char *strip_rr_data (const char *rr_ptr, int *rrlen) { int len = *rrlen; const char *optr = rr_ptr; char c; if (len > 0) { c = optr[0]; if (!is_digit(c)) { optr++; len--; } } if (len > 0) { if (!valid_b64_char (optr[len-1])) len--; } if (len > 0) { if (!valid_b64_char (optr[len-1])) len--; } *rrlen = len; return optr; } // return offset to seq number in record // return -1 if not found, -2 if invalid fmt int find_seq_num (const char *rr_ptr, int rrlen) { char c; int i; int digit_ct = 0; for (i=0; i= 2) return i - 2; else return -2; } if (is_digit (c)) digit_ct++; else digit_ct = 0; } return -1; } // get seq num in rr rec // return -1 if not formatted correctly int get_rr_seq_num (const char *rr_ptr, int rrlen) { char c; int lo, hi; if (rrlen < 3) return -1; if (rr_ptr[2] != ':') return -1; c = rr_ptr[0]; if (is_digit (c)) hi = c - '0'; else return -1; c = rr_ptr[1]; if (is_digit (c)) lo = c - '0'; else return -1; return (10*hi) + lo; } // scan rr recs and build seq table using seq numbers in the recs // return num_txt_recs int get_rr_seq_table (ns_msg *msg_handle, int num_rr_recs, rr_rec_t *seq_table) { ns_rr rr; const char *rr_ptr; int seq_pos; int rrlen; int i, ret, seq_num; int num_txt_recs = 0; if (num_rr_recs > MAX_RR_RECS) { ParodusError ("num rr recs (%d) to big, > %d\n", num_rr_recs, MAX_RR_RECS); return -1; } // clear seq table for (i=0; i num_rr_recs) { ParodusError ("Invalid seq number (too big) in rr record %d\n", i); return -1; } if (NULL != seq_table[seq_num].rr_ptr) { ParodusError ("Duplicate rr record number %d\n", seq_num); return -1; } if (seq_num != 0) { rr_ptr += 3; // skip the seq number rrlen -= 3; } seq_table[seq_num].rr_ptr = rr_ptr; seq_table[seq_num].rr_len = rrlen; } if (NULL != seq_table[0].rr_ptr) { // sequence-less record should not be used when there // are multiple records if (num_txt_recs > 1) { ParodusError ("Seq number not found in rr record\n"); return -1; } // when there is only one record, use the sequence-less record seq_table[1].rr_ptr = seq_table[0].rr_ptr; seq_table[1].rr_len = seq_table[0].rr_len; } // check if we got them all for (i=1; ihw_mac, cfg->dns_txt_url); ParodusInfo("dns_txt_record_id %s\n", buf); } #endif int allow_insecure_conn(char **server_addr, unsigned int *port) { #ifdef FEATURE_DNS_QUERY int insecure=0, ret = -1; char *jwt_token, *key; cjwt_t *jwt = NULL; char dns_txt_record_id[TXT_REC_ID_MAXSIZE]; jwt_token = malloc (NS_MAXBUF); if (NULL == jwt_token) { ParodusError ("Unable to allocate jwt_token in allow_insecure_conn\n"); insecure = TOKEN_ERR_MEMORY_FAIL; goto end; } get_dns_txt_record_id (dns_txt_record_id); ret = query_dns(dns_txt_record_id, jwt_token); ParodusPrint("query_dns returns %d\n", ret); if(ret){ ParodusError("Failed in DNS query\n"); if (ret == TOKEN_ERR_MEMORY_FAIL){ insecure = ret; } else{ insecure = TOKEN_ERR_QUERY_DNS_FAIL; } goto end; } //Decoding the jwt token key = get_parodus_cfg()->jwt_key; ret = cjwt_decode( jwt_token, 0, &jwt, ( const uint8_t * )key,strlen(key) ); if(ret) { if (ret == ENOMEM) { ParodusError ("Memory allocation failed in JWT decode\n"); } else { ParodusError ("CJWT decode error\n"); } insecure = TOKEN_ERR_JWT_DECODE_FAIL; goto end; } ParodusPrint("Decoded CJWT successfully\n"); //validate algo from --jwt_algo if( validate_algo(jwt) ) { insecure = analyze_jwt (jwt, server_addr, port); } else { insecure = TOKEN_ERR_ALGO_NOT_ALLOWED; } if (insecure >= 0) { char *claim_str = cJSON_Print (jwt->private_claims); ParodusInfo ("JWT claims: %s\n", claim_str); free (claim_str); } cjwt_destroy(&jwt); end: if (NULL != jwt_token) free (jwt_token); #else (void) server_addr; (void) port; int insecure = TOKEN_NO_DNS_QUERY; #endif ParodusPrint ("Allow Insecure %d\n", insecure); return insecure; }