mirror of
https://github.com/outbackdingo/parodus.git
synced 2026-01-27 18:20:04 +00:00
549 lines
14 KiB
C
549 lines
14 KiB
C
/**
|
|
* 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 <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <stdbool.h>
|
|
#include <unistd.h>
|
|
#include <netinet/in.h>
|
|
#ifdef FEATURE_DNS_QUERY
|
|
#include <ucresolv.h>
|
|
#endif
|
|
//#include <res_update.h>
|
|
#include <netdb.h>
|
|
#include <strings.h>
|
|
#include <string.h>
|
|
|
|
#include <assert.h>
|
|
#include <sys/types.h>
|
|
#include <sys/param.h>
|
|
#include <arpa/inet.h>
|
|
#include <arpa/nameser.h>
|
|
#include <ctype.h>
|
|
#include <errno.h>
|
|
#include <time.h>
|
|
|
|
#include <cjwt/cjwt.h>
|
|
#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<<alg;
|
|
if ((alg_mask & cfg->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<rrlen; i++)
|
|
{
|
|
c = rr_ptr[i];
|
|
if (c == ':') {
|
|
if (digit_ct >= 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<SEQ_TABLE_SIZE; i++)
|
|
{
|
|
seq_table[i].rr_ptr = NULL;
|
|
seq_table[i].rr_len = 0;
|
|
}
|
|
|
|
// extract and concatenate all the records in rr
|
|
for (i=0; i<num_rr_recs; i++) {
|
|
ret = ns_parserr(msg_handle, ns_s_an, i, &rr);
|
|
if (ret != 0) {
|
|
ParodusError ("query_dns: ns_parserr failed: %s\n", strerror (errno));
|
|
return ret;
|
|
}
|
|
if (ns_rr_type(rr) != ns_t_txt)
|
|
continue;
|
|
++num_txt_recs;
|
|
rr_ptr = (const char *)ns_rr_rdata(rr);
|
|
rrlen = ns_rr_rdlen(rr);
|
|
ParodusPrint ("Found rr rec type %d: %s\n", ns_t_txt, rr_ptr);
|
|
rr_ptr = strip_rr_data (rr_ptr, &rrlen);
|
|
seq_pos = find_seq_num (rr_ptr, rrlen);
|
|
if (seq_pos == -2) {
|
|
ParodusError ("Invalid seq number in rr record %d\n", i);
|
|
return -1;
|
|
}
|
|
if (seq_pos < 0) {
|
|
seq_num = 0;
|
|
} else {
|
|
rr_ptr += seq_pos;
|
|
rrlen -= seq_pos;
|
|
seq_num = get_rr_seq_num (rr_ptr, rrlen);
|
|
}
|
|
ParodusPrint ("Found seq num %d in rr rec %d\n", seq_num, i);
|
|
|
|
if (seq_num < 0) {
|
|
ParodusError ("Seq number not found in rr record %d\n", i);
|
|
return -1;
|
|
}
|
|
if (seq_num > 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; i<num_txt_recs; i++) {
|
|
if (NULL == seq_table[i].rr_ptr) {
|
|
ParodusError ("Missing rr record number %d\n", i+1);
|
|
return -1;
|
|
}
|
|
}
|
|
return num_txt_recs;
|
|
}
|
|
|
|
int assemble_jwt_from_dns (ns_msg *msg_handle, int num_rr_recs, char *jwt_ans)
|
|
{
|
|
// slot 0 in the seq table is for the sequence-less record that
|
|
// you get when there is only one record.
|
|
rr_rec_t seq_table[SEQ_TABLE_SIZE];
|
|
int i;
|
|
int num_txt_recs = get_rr_seq_table (msg_handle, num_rr_recs, seq_table);
|
|
|
|
if (num_txt_recs < 0)
|
|
return num_txt_recs;
|
|
ParodusPrint ("Found %d TXT records\n", num_txt_recs);
|
|
jwt_ans[0] = 0;
|
|
for (i=1; i<=num_txt_recs; i++)
|
|
strncat (jwt_ans, seq_table[i].rr_ptr, seq_table[i].rr_len);
|
|
return 0;
|
|
}
|
|
|
|
int query_dns(const char* dns_txt_record_id,char *jwt_ans)
|
|
{
|
|
u_char *nsbuf;
|
|
ns_msg msg_handle;
|
|
int ret;
|
|
int l = -1;
|
|
|
|
if( !dns_txt_record_id || !jwt_ans )
|
|
return l;
|
|
|
|
nsbuf = (u_char *) malloc (NS_MAXBUF);
|
|
ParodusInfo ("nsbuf (1) 0x%lx\n", (unsigned long) nsbuf);
|
|
if (NULL == nsbuf) {
|
|
ParodusError ("Unable to allocate nsbuf in query_dns\n");
|
|
return TOKEN_ERR_MEMORY_FAIL;
|
|
}
|
|
|
|
l = nquery(dns_txt_record_id,nsbuf);
|
|
if (l < 0) {
|
|
ParodusError("nquery returns error: l value is %d\n", l);
|
|
free (nsbuf);
|
|
return l;
|
|
}
|
|
ParodusInfo ("nsbuf (2) 0x%lx\n", (unsigned long) nsbuf);
|
|
|
|
/*--
|
|
memset((void *) &msg_handle, 0x5e, sizeof (ns_msg));
|
|
ParodusInfo ("nsbuf (3) 0x%lx\n", (unsigned long) nsbuf);
|
|
msg_handle._msg = nsbuf;
|
|
*/
|
|
ParodusInfo ("ns_initparse, msglen %d, nsbuf 0x%lx\n",
|
|
l, (unsigned long) nsbuf);
|
|
ret = ns_initparse((const u_char *) nsbuf, l, &msg_handle);
|
|
if (ret != 0) {
|
|
ParodusError ("ns_initparse failed\n");
|
|
free (nsbuf);
|
|
return ret;
|
|
}
|
|
|
|
ParodusInfo ("ns_msg_count\n");
|
|
l = ns_msg_count(msg_handle, ns_s_an);
|
|
ParodusInfo ("query_dns: ns_msg_count : %d\n",l);
|
|
jwt_ans[0] = 0;
|
|
|
|
ret = assemble_jwt_from_dns (&msg_handle, l, jwt_ans);
|
|
free (nsbuf);
|
|
if (ret == 0)
|
|
ParodusInfo ("query_dns JWT: %s\n", jwt_ans);
|
|
return ret;
|
|
}
|
|
|
|
static void get_dns_txt_record_id (char *buf)
|
|
{
|
|
ParodusCfg *cfg = get_parodus_cfg();
|
|
buf[0] = 0;
|
|
|
|
sprintf (buf, "%s.%s", cfg->hw_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;
|
|
}
|