Table of Contents
A. Client-Side | goto |
1. Locating TInyAuth Keys | goto |
2. Extracting Credentials | goto |
3. Prompting for 2FA | goto |
4. Serializing for Transfer | goto |
5. Client-Side Library | goto |
B. Server-Side | goto |
1. De-serializing Credentials | goto |
2. Constructing the POST Request | goto |
3. Processing the Response | goto |
3. Server-Side Library | goto |
API Documentation
Utilizing TInyAuth requires two layers of handling--client-side and server-side. The client-side deals with finding TInyAuth keyfiles, decoding the data within, performing decryption if the file is encrypted, and serializing that data into something that the server-side can process. The server-side deals with abstracting the data sent by the client and issuing a properly-formatted request to TInyAuth for authentication using that information.
It is also highly recommended that developers encrypt the transfer of credentials between the client and their own service. The API for TInyAuth is served over SSL. The workflow for processing user credentials should look something like this:
- Search for TInyAuth keyfiles by prefix string.
- Load keyfile selected by user.
- Decode ASN.1-encoded key data.
- Determine if keyfile uses encryption, if so decrypt.
- Serialize credentials into packet for sending to bridge1.
- Bridge1 should relay packet over secure socket (SSL) to target server.
- Target server extracts serialized credentials, constructs an API request, and forwards that to TInyAuth.
- Target server checks response json for the "success" and optionally "error" keys. Handle user authorization accordingly.
1The use of a bridge is required because there currently exists no way to connect the client device directly to a router. The device can, however, open a usb/serial connection with a computer running a bridge that can convert between serial packets and TCP packets to facilitate a network.
Client-Side
-
Locating TInyAuth Keys top
TInyAuth keyfiles are always prefixed by the 6-byte string "TIAUTH" which can be paired with the use of the ti_Detect function from the fileioc toolchain standard library to get a list of all Application Variables on the device with that prefix. The user can then navigate a GUI to select the appropriate keyfile from that list. -
Extracting Credentials top
Once selected, the file can be opened using the ti_Open function from the fileioc toolchain standard library or using the fopen API from the C standard also implemented within the toolchain. The file can be read in-place but it is recommended to make a copy and perform decoding on that both to ensure scope and to allow for decryption without altering the file.
The ASN.1-encoded structure begins at the end of the 6-byte prefix string meaning you will need to begin decoding at that point. The ASN.1 structure of the keyfile is as follows:
KeyNormal :: SEQUENCE { Encrypted BOOLEAN, Credentials :: SEQUENCE { UserID INTEGER, Token OCTET STRING }, Hash OCTET STRING } KeyEncrypted :: SEQUENCE { Encrypted BOOLEAN Salt OCTET STRING Credentials OCTET STRING Tag OCTET STRING } where: Credentials, Tag = Cipher(AES-256-GCM, key = PBKDF2(password = user-input, salt = Salt, rounds = 100)[:32] iv = PBKDF2(password = user-input, salt = Salt, rounds = 100)[32:48] data = Credentials :: SEQUENCE {UserId INTEGER, Token OCTET STRING}) // Note that of the 48-byte PBKDF2 returned, the first 32 bytes are the key, // and the last 16 are the iv/nonce // the user should supply the password as input during key load, or the password // can be read from some arbitrary on-device password manager
-
Prompting for 2FA top
The TInyAuth API currently does not communicate the necessity of 2FA to the client prior to the authentication attempt. This means that the client application developer will need to handle prompting the user for a TOTP optionally and handling the user returning an empty string (handling that case as NULL). This also means the user will need to know if their account is configured to use 2FA for keyfile authentication. Prompt for a six-digit TOTP code and pass the pointer to that string (or NULL) to the serialization. Be aware (as both developer and end-user) that failing to provide a TOTP code during authentication with 2FA enabled will simply fail for invalid credentials. -
Serializing for Transfer top
Once the keyfile data has been processed, the client then needs to transfer it to the server in an understandable format. The recommended means of doing so involves encoding the credentials as length-prepended strings. Zero-termination is not reliable since you cannot guarantee that the token string will not contain a zero byte somewhere. You are not restricted to this serialization format, just make sure that whatever serialization method you use, you de-serialize using an appropriate algorithm on the server. -
Client-Side Library top
We have made available a static library to handle all of the aforementioned keyfile decoding and serialization. This leaves the application developer to handle only keyfile selection and actual transmission of data. The library is available at the TInyAuth Github. NOTE: Requires Cryptx library. Use of the library API is quite simple:#include <fileioc.h> #include "tinyauth.h" char *var_name; void *vat_ptr = NULL; char keyfiles[128][9]; // support up to 128 keyfiles size_t keyfile_count = 0; while ((var_name = ti_Detect(&vat_ptr, "TIAUTH"))){ strcpy(keyfiles[keyfile_count], var_name); keyfile_count++; } char* key_selected = user_select_key(keyfiles, keyfile_count); // ^ this would be an API that lets the user select the key char* password = prompt_for_user_password(); // ^ this would be an API that prompts for password // return NULL if no password entered struct tinyauth_key k; tinyauth_open(&k, key_selected, password); // ^ Note that should the integrity checks included in the key fail, // an error will be returned and if the key is encrypted, the key will // not decrypt. if(!k.error){ uint8_t buf[k.credentials_len + 9]; // this field can approximate needed output size char otp[7]; prompt_for_otp(otp); // ^ allow user to supply OTP for 2FA // return NULL if no OTP entered size_t olen = tinyauth_serialize_for_transfer(&k, otp, buf); network_send(buf, olen); memset(buf, 0, olen); // wipe this after use } tinyauth_close(&k); // ^ This frees allocated memory and destroys the copy of the key // Do not forget this or you may leave decrypted keyfile in memory.
Server-Side
Use of TInyAuth on the server-side is even simpler.
- De-serializing Credentials top
The first step to this part of the process is to reverse the serialization applied on the client side. This guide assumes you are length-prepending each field of the credentials payload. The first two fields--user id and signature--will always exists. The first field, the TOTP code, may not always be present. The following code written in Python should properly de-serialize the payload.# assume received data is in 'data' and serialization method is length-prepended # deserialize credentials packet (length-prepended) # get user id if len(data) > 3: # data len should be > size word len segment_len = data[0:3] data = data[3:] # trim size word if len(data) > segment_len: # data len should be > segment len userid = data[:segment_len] data = data[segment_len:] # trim segment else: raise Exception("serialization error") else raise Exception("serialization error") # get token if len(data) > 3: # data len should be > size word len segment_len = data[0:3] data = data[3:] # trim size word if len(data) >= segment_len: data len should be > segment len token = data[:segment_len] data = data[segment_len:] # trim segment else: raise Exception("serialization error") else: raise Exception("serialization error") # conditional get otp otp = "" if len(data): # this segment is optional if len(data) > 3: # data len should be > size word len segment_len = data[0:3] if segment_len != 6: raise Exception("serialization error") # ^ TOTP code should be 6 digits data = data[3:] # trim size word if len(data) == segment_len: # data len should be == segment len otp = data[:segment_len] # no need to trim, we's done with data else: raise Exception("serialization error") else: raise Exception("serialization error")
- Constructing the POST Request top
Once you deserialize the credentials you can send a POST request to TInyAuth with the credentials supplied as parameters. There are four valid parameters:- user (required): should contain the numerical ID of the user who owns the token to follow
- token (required): should contain the raw data of the user's authentication token
- otp (conditional): should contain a one-time passcode provided by a configured TOTP application
- origin (conditional): should contain the IP address of the host supplying the credentials
NOTE: OTP: This field is required if 2FA for keyfile authentication is turned on in your account 2FA configuration. If it is disabled, this field can be omitted without consequence.
NOTE: ORIGIN: As an alternative to the "origin" field specified above you may set the "X-Forwarded-For" header in your request. If this is specified, it will override the origin parameter of the request (which may in this case be omitted). Note that if you want to allow our Service to perform rate limiting against clients, the X-Forwarded-For header is required.
Note that both the user and token fields, as well as either the X-Forwarded-For header or origin field are required for the query to be accepted. Also be advised that all queries are sanitized and validated via appropriate input filters. Here is some code demonstrating how to pack user credentials into a valid POST request, in Python.
import requests # send POST request uri = "https://tinyauth.cagstech.com/auth.php" response = requests.post( uri, headers={"X-Forwarded-For":self.addr[0]}, params={'user': userid, 'token': token, 'otp': otp}, # self.addr is assumed to be the addr tuple belonging to the client's socket )
- Processing the Response top
The response is an HTTP status code as well as a JSON object. To make matters simple you can interpret a 200 status code as success, a 4XX as an authentication failure, and a 5XX as an error. Alternatively, you can interpret the JSON response to determine exactly what error took place. This is recommended. See the Python code excerpt below.# check response if response.json["success"] == True: // user authenticated successfully else: // user did not authenticate // error string in response.json["error"] // if error string unset, credentials were incorrect // else, internal error most likely
- Server-Side Library top
A Python module exists to allow users to bypass much of this using a class which takes the origin IP and the data bytearray received from the client as initialization parameters. Simply create an instance of the TInyAuth class with the required parameters, then call the query method as shown below.# assume received data is in 'data' and origin IP is in self.addr[0] import tinyauth me = TInyAuth(self.addr[0], data) response = me.query() if response.json["success"]: // user is authenticated else: // user is not authenticated
The module can be downloaded from the TInyAuth Github, placed into your project directory (or an include path) and then imported for use.
It's really that simple.