Table of Contents

REST token generator

During REST authentication the user is authenticated by a third part that provides a REST token to wrtc_kit that it can use to prove to IXNX unit its user has already been authenticated.

For REST authentication to work the token creator and the token verifier (the IXNX unit) must share a shared secret that the unit in between (the wrtc_kit) does not know. wrtc_kit can pass through the token from its creator to IXNX for verification without being able to read its contents.

The instance authenticating a user shall create a REST token that prove client has been authenticated. The crytography which is used is the AEAD function (Authenticated Encryption with Associated Data) AES-256-GCM, because it allows for authentication, encryption and for the inclusion of unencrypted data in one single step. Binary parts of the result is URL-friendly base64 encoded (-_ instead of +/).

Token fields

The REST authentication token contains a flexible number of data fields in JSON format that contain information given by the token creator. That information can be used by either the wrtc client or the IXNX unit – it is however only the IXNX unit that can verify the validity of the token, thus avoid using information extracted from the token before it has been validated (event onLoggedIn()|wrtc.onLoggedIn() has occurred).

Token fields are in JSON format:

You can also add your own token fields that the client can read using wrtc.adv.GetTokenVal().

Use correct format

To be multi platform compatible please make sure all keys and values are surrounded by quotes, separated by commas, and no spaces between.

Correct:

Incorrect:

Example

{"e":"1657109524","u":"martinga@intertex.se","T":"2","dname":"MartinGa64","n":"mg_rauth","t":"_WFDBinqrOFdx0B95MOIkw","i":"ZePzeeWMdVvkdn5Z","c":"hx2KbwXjcUlHKmkL1TicF8RZi2hBE5b0ctd9CaGl9Sd_68gzeuPQ4ZkRgD6iOa_L-iaQMB8goz6h4XoYswnUJZw9wlV5cU3hH_PdTSZQ9olt17UWNgsUI8pOvcEaeLEzTMSInb4lnNiZ"}

mktoken

mktoken is a utility to generate REST authentication tokens compatible with IXNX.

You compile mktoken using:

>gcc mktoken.c -L/usr/lib -lssl -o mktoken

mktoken.c is old and must be replaced by new code

Then you can call it like this:

>mktoken mysecret PLAIN-START n=httpname u=username e=1427896580 CRYPT-START secpar=hidden

which generates the following token:

n=httpname\nu=username\ne=1427896580\nt=rGwt1-tpDM9Q4LuliyDoBg\ni=q358cTBCZFYMCp_Z\nc=JHb04PrN0cLoxjLPSw
mktoken.c
#include <stdio.h>
#include <string.h>
#include <openssl/evp.h>
 
char *base64_encode(const unsigned char *data, size_t input_length, size_t *output_length);
 
int IX_aes_256_gcm_encrypt(unsigned char *plaintext, int plaintext_len, unsigned char *aad,
    int aad_len, unsigned char *key, unsigned char *iv,
    unsigned char *ciphertext, unsigned char *tag)
{
    EVP_CIPHER_CTX *ctx = NULL;
 
    int len;
 
    int ciphertext_len;
 
 
    /* Create and initialise the context */
    if(!(ctx = EVP_CIPHER_CTX_new()))
        goto err_exit;
 
    /* Initialise the encryption operation. */
    if(1 != EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, NULL, NULL))
        goto err_exit;
 
    /* Set IV length if default 12 bytes (96 bits) is not appropriate */
//    if(1 != EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, 16, NULL))
//        goto err_exit;
 
    /* Initialise key and IV */
    if(1 != EVP_EncryptInit_ex(ctx, NULL, NULL, key, iv))
        goto err_exit;
 
    /* Provide any AAD data. This can be called zero or more times as
     * required
     */
    if(1 != EVP_EncryptUpdate(ctx, NULL, &len, aad, aad_len))
        goto err_exit;
 
    /* Provide the message to be encrypted, and obtain the encrypted output.
     * EVP_EncryptUpdate can be called multiple times if necessary
     */
    if(1 != EVP_EncryptUpdate(ctx, ciphertext, &len, plaintext, plaintext_len))
        goto err_exit;
 
    ciphertext_len = len;
 
    /* Finalise the encryption. Normally ciphertext bytes may be written at
     * this stage, but this does not occur in GCM mode
     */
    if (1 != EVP_EncryptFinal_ex(ctx, ciphertext + len, &len))
        goto err_exit;
 
    ciphertext_len += len;
 
    /* Get the tag */
    if(1 != EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, 16, tag))
        goto err_exit;
 
    /* Clean up */
    EVP_CIPHER_CTX_free(ctx);
    ctx = NULL;
    return ciphertext_len;
err_exit:
    if (ctx)
        EVP_CIPHER_CTX_free(ctx);
    return -1;
}
 
 
enum mkt_state {MKTS_PLAIN_LINE, MKTS_ENC_LINE, MKTS_INVALID};
static const char* mkt_ctrl[] = {"PLAIN-START", "CRYPT-START", NULL};
static enum mkt_state mkt_state_get(const char* ctrl)
{
    enum mkt_state state;
    if (ctrl==NULL)
        return MKTS_INVALID;
 
    for (state = MKTS_PLAIN_LINE; state < MKTS_INVALID; state++)
    {
        if (strcmp(ctrl, mkt_ctrl[state])==0)
            return state;
    }
    return MKTS_INVALID;
}
// how to call: mktoken mysecretkey "PLAIN-START" "unencrypted-line" "unencrypted-line" ... "CRYPT-START" "encrypted-line""encrypted-line"
// e.g. mktoken mysecret PLAIN-START "n=httpname" "u=username" "e=1427896580" CRYPT-START "secpar=hidden"
 
static int ifc_do_mktoken(int argc, char **argv)
{
    char* textu = malloc(1); //will hold unencrypted lines terminated with \n and concetenated
    char* texte = malloc(1); //will hold encrypted lines terminated with \n and concetenated
    int writeEncrypted = 0;
    int ctext_len = 0;
 
    static enum mkt_state mkts = MKTS_INVALID;
    if (!textu || !texte)
    {
        fprintf(stdout, "ERROR:mem_alloc_error1\n");
        return 255;
    }
    textu[0]=0;
    texte[0]=0;
    int argidx=1;
    char* secret = (argidx<argc)?argv[argidx]:NULL;
    if (!secret)
    {
        fprintf(stdout, "ERROR:secret missing\n");
        return 255;
    }
    char key[64] = {0};
    strncpy(key, secret, 32);
    argidx++;
    char* control = (argidx<argc)?argv[argidx]:NULL;
    if ((mkts=mkt_state_get(control))==MKTS_INVALID)
    {
        fprintf(stdout, "ERROR:control (expected as argv[%d]) not found\n", argidx);
        return 255;
    }
    while (++argidx<argc)
    {
        const char* arg = argv[argidx];
        //check first for control tag
        enum mkt_state mkt_state_new = mkt_state_get(arg);
        if (mkt_state_new != MKTS_INVALID)
        {
            mkts = mkt_state_new;
            if (mkts==MKTS_PLAIN_LINE)
                writeEncrypted=0;
            else if (mkts==MKTS_ENC_LINE)
                writeEncrypted=1;
            continue;
        }
        switch (mkts)
        {
            case MKTS_PLAIN_LINE:
            case MKTS_ENC_LINE:
                {
                    char* buf = writeEncrypted?texte:textu;
                    char* newbuf = realloc(buf, strlen(buf) + strlen(arg)+2); //+2 = /n + term zero
                    if (!newbuf)
                    {
                        fprintf(stdout, "ERROR:mem_alloc_error3\n");
                        return 255;
                    }
                    sprintf(newbuf+strlen(newbuf), "%s\n", arg);
                    if (writeEncrypted)
                        texte=newbuf;
                    else
                        textu=newbuf;
                }
                break;
            default:
                fprintf(stdout, "ERROR:invalid_parsing_state\n");
                return 255;
        } //switch end
    } //while end (argument parsing)
 
    int texte_len = strlen(texte);
    if (texte[texte_len-1]=='\n')
        texte_len--; //remove \n at the end of encrpted block
    char* ctext = (char*)malloc(texte_len+64);//+64 for sanity
    if (!ctext)
    {
        fprintf(stdout, "ERROR:mem_alloc_error4\n");
        return 255;
    }
    char tag[16];
    char iv[12] = {0};
 
    srand(time(NULL));
    int i;
    for (i=0;i<sizeof(iv);i++)
        iv[i] = rand() & 0xff;
 
    ctext_len = IX_aes_256_gcm_encrypt((unsigned char*)texte, texte_len, (unsigned char*)textu, strlen(textu), (unsigned char*)key, (unsigned char*)iv, (unsigned char*)ctext, (unsigned char*)tag);
    if (ctext_len!=texte_len)
    {
        fprintf(stdout, "ERROR:aes_256_gcm_encrypt_failed\n");
        return 255;
    }
    size_t ctext_b64_len = 0;
    char *ctext_b64 = base64_encode((unsigned char*)ctext, ctext_len, &ctext_b64_len);
    size_t iv_b64_len = 0;
    char* iv_b64 = base64_encode((unsigned char*)iv,sizeof(iv),&iv_b64_len);
    size_t tag_b64_len = 0;
    char* tag_b64 = base64_encode((unsigned char*)tag,sizeof(tag),&tag_b64_len);
    if (iv_b64 && tag_b64)
    {
        char* newbuf = NULL;
        /* first append the tag (==hash) to the unencrypted buffer */
        newbuf = realloc(textu, strlen(textu) + 2 + strlen(tag_b64)+2); //+2 = "e=", +2 = /n + term zero
        if (newbuf)
        {
            textu = newbuf;
            sprintf(textu+strlen(textu), "t=%s\n", tag_b64);
        }
        else
        {
            fprintf(stdout, "ERROR:mem_alloc_error7\n");
            return 255;
        }
        /* second append the initial vector to the unencrypted buffer */
        newbuf = realloc(textu, strlen(textu) + 2 + strlen(iv_b64)+2); //+2 = "e=", +2 = /n + term zero
        if (newbuf)
        {
            textu = newbuf;
            sprintf(textu+strlen(textu), "i=%s\n", iv_b64);
        }
        else
        {
            fprintf(stdout, "ERROR:mem_alloc_error6\n");
            return 255;
        }
        /* last, append the encrypted data to the unencrypted buffer */
        if (ctext_b64 && strlen(ctext_b64))
        {
            newbuf = realloc(textu, strlen(textu) + 2 + strlen(ctext_b64)+2); //+2 = "e=", +2 = /n + term zero
            if (newbuf)
            {
                textu = newbuf;
                sprintf(textu+strlen(textu), "c=%s\n", ctext_b64);
            }
            else
            {
                fprintf(stdout, "ERROR:mem_alloc_error5\n");
                return 255;
            }
        }
    }
    else
    {
        fprintf(stdout, "ERROR:mem_alloc_error8\n");
        return 255;
    }
    fprintf(stdout, "%s", textu);
    return 0;
}
 
int main (int argc, char *argv[])
{
    if (0==ifc_do_mktoken(argc, argv))
        return 0;
    else
        return -1;
 
}
 
 
 
/* In case you do not have base64_encode() available here is some code that you can use for this: */
 
static char encoding_table[] = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
                                'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
                                'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
                                'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
                                'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
                                'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
                                'w', 'x', 'y', 'z', '0', '1', '2', '3',
                                '4', '5', '6', '7', '8', '9', '-', '_'};//- and _ instead of + and / --> URL-friendly !
static char *decoding_table = NULL;
static size_t mod_table[] = {0, 2, 1};
 
char *base64_encode(const unsigned char *data,
                    size_t input_length,
                    size_t *output_length) {
 
    *output_length = 4 * ((input_length + 2) / 3);
 
    char *encoded_data = (char*)malloc(*output_length+1);
    if (encoded_data == NULL) return NULL;
 
    size_t i,j;
    for (i = 0, j = 0; i < input_length;) {
 
        unsigned int octet_a = i < input_length ? data[i++] : 0;
        unsigned int octet_b = i < input_length ? data[i++] : 0;
        unsigned int octet_c = i < input_length ? data[i++] : 0;
 
        unsigned int triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c;
 
        encoded_data[j++] = encoding_table[(triple >> 3 * 6) & 0x3F];
        encoded_data[j++] = encoding_table[(triple >> 2 * 6) & 0x3F];
        encoded_data[j++] = encoding_table[(triple >> 1 * 6) & 0x3F];
        encoded_data[j++] = encoding_table[(triple >> 0 * 6) & 0x3F];
    }
    for (i = 0; i < mod_table[input_length % 3]; i++)
        encoded_data[*output_length - 1 - i] = 0;
 
    encoded_data[*output_length]=0;
 
    return encoded_data;
}