This commit is contained in:
iranl
2025-01-18 20:59:58 +01:00
parent 5cf8231500
commit c4404e3baf
24 changed files with 3408 additions and 190 deletions

View File

@@ -0,0 +1,719 @@
/**
*@license
*
*Copyright 2020 Cisco Systems, Inc. or its affiliates
*
*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 DuoAuthLib.cpp
* @brief An Arduino Library to simplify the operations of performing Duo Multi-Factor Authentication within your ESP32 Micro Controller project.
*
* @url https://github.com/CiscoDevNet/Arduino-DuoAuthLibrary-ESP32
* @version 1.0.0
* @author Gary Oppel <gaoppel@cisco.com>
*/
//Include DuoAuthLib Library Header
#include "DuoAuthLib.h"
// Include ESP32 MDEDTLS Library
#include "mbedtls/md.h"
// Load CA CRT BUNDLE
extern const uint8_t x509_crt_imported_bundle_bin_start[] asm("_binary_x509_crt_bundle_start");
extern const uint8_t x509_crt_imported_bundle_bin_end[] asm("_binary_x509_crt_bundle_end");
// Include ESP NetworkClientSecure Library
#include <NetworkClientSecure.h>
// Include ESP HTTPClient Library
#include <HTTPClient.h>
// Include ArduinoJSON Library
#include <ArduinoJson.h>
//----------------------------------------------------------------
//DuoAuthLib Constructor
//----------------------------------------------------------------
//Function that handles the creation and setup of instance
DuoAuthLib::DuoAuthLib()
{
//Initialize Return Variables
_duoApiStat = "";
_duoAuthTxId = "";
_duoApiAuthResponseResult = "";
_duoApiAuthResponseStatus = "";
_duoApiAuthResponseStatusMessage = "";
_duoApiFailureCode = 0;
_duoApiFailureMessage = "";
_duoPushType[0] = '\0';
_init = false;
_async = false;
}
//Function that handles the deletion/removals of instance
DuoAuthLib::~DuoAuthLib()
{
}
//----------------------------------------------------------------
//'begin(...)' - used to initialize and prepare for an Auth request.
void DuoAuthLib::begin(const char* duoApiHost, const char* duoApiIKey, const char* duoApiSKey, struct tm* timeInfo)
{
_timeinfo = timeInfo;
_duoHost = (char *)duoApiHost;
_duoIkey = (char *)duoApiIKey;
_duoSkey = (char *)duoApiSKey;
//Set Default HTTP Timeout to 30 Seconds
_httpTimeout = _defaultTimeout;
//Set Default IP Address to 0.0.0.0
strcpy(_ipAddress, "0.0.0.0");
//Set the library initialized to True
_init = true;
}
//----------------------------------------------------------------
//DuoAuthLib Public Methods
//----------------------------------------------------------------
//----------------------------------------------------------------
// Duo Ping API
// https://duo.com/docs/authapi#/ping
bool DuoAuthLib::ping()
{
if(!_init){
return false;
}
bool pingSuccess = false;
bool apiResponse = submitApiRequest(0, (char *)DUO_PING_API_PATH, "", (char *)"", (char *)"");
if(apiResponse == true){
if(_httpCode == 200){
pingSuccess = true;
}
}
return pingSuccess;
}
//----------------------------------------------------------------
// Duo Check API
// https://duo.com/docs/authapi#/check
bool DuoAuthLib::checkApiKey()
{
if(!_init){
return false;
}
bool duoAuthRequestResult = false;
if(getLocalTime(_timeinfo)){
char hmac_password[41];
char timeStringBuffer[TIME_BUF_STR_SIZE];
//Get Current time from timeinfo
strftime(timeStringBuffer, sizeof(timeStringBuffer), "%a, %d %b %Y %T %z", _timeinfo);
// Create empty char array to hold our string
char hmacPayload[SIGNATURE_PAYLOAD_BUFFER_SIZE + CHECK_AUTH_BUFFER_SIZE];
// Generate the required URL Request Contents for the DUO API Call
generateHmacPayload(hmacPayload, timeStringBuffer, (char *)"GET", _duoHost, DUO_CHECK_API_PATH, (char *)"");
// Generate the required URL Request Contents for the DUO API Call
calculateHmacSha1((char *)_duoSkey, hmacPayload, hmac_password);
bool apiResponse = submitApiRequest(0, timeStringBuffer, DUO_CHECK_API_PATH, hmac_password, (char *)"");
if(apiResponse == true){
bool processResult = processResponse(&_lastHttpResponse);
if(processResult == true){
duoAuthRequestResult = true;
}
}
}
return duoAuthRequestResult;
}
//----------------------------------------------------------------
// Duo Auth Status API
// https://duo.com/docs/authapi#/auth_status
bool DuoAuthLib::authStatus(String transactionId)
{
if(!_init){
return false;
}
bool duoAuthRequestResult = false;
if(transactionId.length() == 36){
char txId[37];
//Convert String to Character Array
transactionId.toCharArray(txId, 37);
if(getLocalTime(_timeinfo)){
char hmac_password[41];
char timeStringBuffer[TIME_BUF_STR_SIZE];
char authRequestContents[CHECK_AUTH_BUFFER_SIZE];
authRequestContents[0] = '\0';
//Get Current time from timeinfo
strftime(timeStringBuffer, sizeof(timeStringBuffer), "%a, %d %b %Y %T %z", _timeinfo);
addRequestParameter(authRequestContents, (char *)TRANSACTION_PARAM, txId, true);
// Create empty char array to hold our string
char hmacPayload[SIGNATURE_PAYLOAD_BUFFER_SIZE + CHECK_AUTH_BUFFER_SIZE];
// Generate the required URL Request Contents for the DUO API Call
generateHmacPayload(hmacPayload, timeStringBuffer, (char *)"GET", _duoHost, DUO_AUTHSTATUS_API_PATH, authRequestContents);
// Generate the required URL Request Contents for the DUO API Call
calculateHmacSha1((char *)_duoSkey, hmacPayload, hmac_password);
bool apiResponse = submitApiRequest(0, timeStringBuffer, DUO_AUTHSTATUS_API_PATH, hmac_password, authRequestContents);
if(apiResponse == true){
bool processResult = processResponse(&_lastHttpResponse);
if(processResult == true){
duoAuthRequestResult = true;
}
}
}
}
return duoAuthRequestResult;
}
//----------------------------------------------------------------
// Duo Auth API
// https://duo.com/docs/authapi#/auth
// https://duo.com/docs/authapi#authentication
bool DuoAuthLib::pushAuth(char* userName, bool async)
{
if(!_init){
return false;
}
bool authSuccess = false;
_async = async;
//Create new Buffer using the method below
int userStrLen = encodeUrlBufferSize(userName);
char encodedUsername[userStrLen];
encodeUrl(encodedUsername, userName);
return performAuth(encodedUsername, (char*)"", PUSH);
}
//----------------------------------------------------------------
// Duo Auth API
// https://duo.com/docs/authapi#/auth
// https://duo.com/docs/authapi#authentication
bool DuoAuthLib::passcodeAuth(char* userName, char* userPasscode)
{
if(!_init){
return false;
}
//Create new Buffer using the method below
int userStrLen = encodeUrlBufferSize(userName);
char encodedUsername[userStrLen];
encodeUrl(encodedUsername, userName);
return performAuth(encodedUsername, userPasscode, PASSCODE);
}
//----------------------------------------------------------------
//Duo - Set Public IP Address of Device for Auth API Request
void DuoAuthLib::setIPAddress(char* ipAddress)
{
if(strlen(ipAddress) < MAX_IP_LENGTH){
strcpy(_ipAddress, ipAddress);
}
}
//----------------------------------------------------------------
//Duo - Set the Push Type String that is displayed to the end user
//See the Parameter 'Type' within the 'Duo Push' Section of the
//Duo API Documentation: https://duo.com/docs/authapi#/auth
void DuoAuthLib::setPushType(char* pushType)
{
int typeLength = strlen(pushType);
if(typeLength > 0 && typeLength < MAX_TYPE_LENGTH){
//Create new Buffer using the method below
int userStrLen = encodeUrlBufferSize(pushType);
char encodedUsername[userStrLen];
encodeUrl(encodedUsername, pushType);
strcpy(_duoPushType, encodedUsername);
}
}
//----------------------------------------------------------------
//Duo - Set the HTTP Timeout from the default of 30 Seconds
void DuoAuthLib::setHttpTimeout(int httpTimeout)
{
_httpTimeout = httpTimeout;
}
//----------------------------------------------------------------
//Duo Authentication API Result
//https://duo.com/docs/authapi#/auth
//https://duo.com/docs/authapi#authentication
//Returns True if the Duo Authentication Result is 'allow'
bool DuoAuthLib::authSuccessful()
{
if(_duoApiAuthResponseResult == "allow"){
return true;
}else{
return false;
}
}
//----------------------------------------------------------------
//Duo Authentication API Result
//https://duo.com/docs/authapi#/auth
//https://duo.com/docs/authapi#authentication
//Returns True if the Duo Authentication Result is 'waiting'
bool DuoAuthLib::pushWaiting()
{
if(_duoApiAuthResponseResult == "waiting"){
return true;
}else{
return false;
}
}
//----------------------------------------------------------------
String DuoAuthLib::getHttpResponse()
{
return _lastHttpResponse;
}
String DuoAuthLib::getApiStat()
{
return _duoApiStat;
}
String DuoAuthLib::getAuthResponseResult()
{
return _duoApiAuthResponseResult;
}
String DuoAuthLib::getAuthResponseStatus()
{
return _duoApiAuthResponseStatus;
}
String DuoAuthLib::getAuthResponseStatusMessage()
{
return _duoApiAuthResponseStatusMessage;
}
String DuoAuthLib::getApiFailureCode()
{
return String(_duoApiFailureCode);
}
String DuoAuthLib::getApiFailureMessage()
{
return _duoApiFailureMessage;
}
String DuoAuthLib::getAuthTxId()
{
return _duoAuthTxId;
}
int DuoAuthLib::getHttpCode() {
return _httpCode;
}
//----------------------------------------------------------------
//----------------------------------------------------------------
//DuoAuthLib Private Methods
//----------------------------------------------------------------
//https://duo.com/docs/authapi#/auth
//https://duo.com/docs/authapi#authentication
bool DuoAuthLib::performAuth(char* userName, char* userPasscode, enum DUO_AUTH_METHOD authMethod)
{
bool duoAuthRequestResult = false;
if(getLocalTime(_timeinfo)){
char hmac_password[41];
char timeStringBuffer[TIME_BUF_STR_SIZE];
char authRequestContents[AUTH_REQUEST_BUFFER_SIZE];
authRequestContents[0] = '\0';
//Get Current time from timeinfo
strftime(timeStringBuffer, sizeof(timeStringBuffer), "%a, %d %b %Y %T %z", _timeinfo);
//Check the authentication method and build the request accordingly.
if(authMethod == PUSH){
if(_async){
addRequestParameter(authRequestContents, (char *)ASYNC_PARAM, (char *)"1");
}
addRequestParameter(authRequestContents, (char *)DEVICE_PARAM, (char *)AUTO_PARAM);
addRequestParameter(authRequestContents, (char *)FACTOR_PARAM, (char *)PUSH_PARAM);
addRequestParameter(authRequestContents, (char *)IPADDR_PARAM, _ipAddress);
if(strlen(_duoPushType) > 0){
addRequestParameter(authRequestContents, (char *)TYPE_PARAM, _duoPushType);
}
addRequestParameter(authRequestContents, (char *)USERNAME_PARAM, userName, true);
}else if(authMethod == PASSCODE){
addRequestParameter(authRequestContents, (char *)FACTOR_PARAM, (char *)PASSCODE_PARAM);
addRequestParameter(authRequestContents, (char *)IPADDR_PARAM, _ipAddress);
addRequestParameter(authRequestContents, (char *)PASSCODE_PARAM, userPasscode);
if(strlen(_duoPushType) > 0){
addRequestParameter(authRequestContents, (char *)TYPE_PARAM, _duoPushType);
}
addRequestParameter(authRequestContents, (char *)USERNAME_PARAM, userName, true);
}else{
return duoAuthRequestResult;
}
// Create empty char array to hold our string
char hmacPayload[SIGNATURE_PAYLOAD_BUFFER_SIZE + AUTH_REQUEST_BUFFER_SIZE];
// Generate the required URL Request Contents for the DUO API Call
generateHmacPayload(hmacPayload, timeStringBuffer, (char *)"POST", _duoHost, DUO_AUTH_API_PATH, authRequestContents);
// Generate the required URL Request Contents for the DUO API Call
calculateHmacSha1((char *)_duoSkey, hmacPayload, hmac_password);
bool apiResponse = submitApiRequest(1, timeStringBuffer, DUO_AUTH_API_PATH, hmac_password, authRequestContents);
if(apiResponse == true){
bool processResult = processResponse(&_lastHttpResponse);
if(processResult == true){
duoAuthRequestResult = true;
}
}
}
return duoAuthRequestResult;
}
//----------------------------------------------------------------
//Create and Submit API Request to Duo API Server
bool DuoAuthLib::submitApiRequest(uint8_t apiMethod, char *timeString, const char* apiPath, char *hmacPassword, char* requestContents)
{
NetworkClientSecure *clientDuoAuth = new NetworkClientSecure;
if (clientDuoAuth)
{
clientDuoAuth->setCACertBundle(x509_crt_imported_bundle_bin_start, x509_crt_imported_bundle_bin_end - x509_crt_imported_bundle_bin_start);
{
//Create our HTTPClient Instance
HTTPClient http;
//Build the Request URL based on the Method.
//Append the requestContents to the end of
//the URL for an HTTP GET request
String requestUrl = "https://";
requestUrl += _duoHost;
requestUrl += apiPath;
if(apiMethod == 0 && strlen(requestContents) > 0){
requestUrl += '?';
requestUrl += requestContents;
}
http.begin(requestUrl); //Specify the URL
// HTTP Connection Timeout
http.setTimeout(_httpTimeout);
//Set User Agent Header
http.setUserAgent(_duoUserAgent);
//Set Host Header
http.addHeader("Host", _duoHost);
//Add the required Date Header for DUO API Calls
if(timeString){
http.addHeader(F("Date"), String(timeString));
}
//Add Content Type Header for POST requests
if(apiMethod == 1){
http.addHeader(F("Content-Type"), F("application/x-www-form-urlencoded"));
}
//Add the required HTTP Authorization Header for the DUO API Call
if(hmacPassword){
http.setAuthorization(_duoIkey, hmacPassword);
}
if(apiMethod == 1){
_httpCode = http.POST(requestContents);
}else if(apiMethod == 0){
_httpCode = http.GET();
}else{
http.end();
return false;
}
//----------------------------------------------------------------------------------------
//Valid Duo API Endpoints HTTP(S) Response codes. Only respond with a valid request for
//these values:
// 200 - Success
// 400 - Invalid or missing parameters.
// 401 - The "Authorization" and/or "Date" headers were missing or invalid.
//NOTE: Other HTTP Codes exist; however, only those for the API endpoints are noted above,
// Please refer to the Duo Auth API Documentation @ https://duo.com/docs/authapi
//----------------------------------------------------------------------------------------
if ((_httpCode == 200) || (_httpCode == 400) || (_httpCode == 401)) { //Check for the returning code
_lastHttpResponse = http.getString();
http.end();
return true;
}else{
_lastHttpResponse = "";
http.end();
return false;
}
}
delete clientDuoAuth;
}
return false;
}
bool DuoAuthLib::processResponse(String* serializedJsonData)
{
JsonDocument doc;
_duoApiStat = "";
_duoAuthTxId = "";
_duoApiAuthResponseResult = "";
_duoApiAuthResponseStatus = "";
_duoApiAuthResponseStatusMessage = "";
_duoApiFailureCode = 0;
_duoApiFailureMessage = "";
//Deserialize the json response from the Duo API Endpoints
DeserializationError error = deserializeJson(doc, *serializedJsonData);
//Check if have an error in our Deserialization before proceeding.
if(!error){
const char* apiStat = doc["stat"];
if(apiStat){
_duoApiStat = String(apiStat);
if(strcmp(apiStat,"OK") == 0){
JsonObject response = doc["response"];
if(_async){
const char* duoTxId = response["txid"];
if(duoTxId){
_duoAuthTxId = String(duoTxId);
}else{
_duoAuthTxId = "";
return false;
}
}else{
const char* duoResult = response["result"];
const char* duoStatus = response["status"];
const char* duoStatusMsg = response["status_msg"];
if(duoResult && duoStatus && duoStatusMsg){
_duoApiAuthResponseResult = String(duoResult);
_duoApiAuthResponseStatus = String(duoStatus);
_duoApiAuthResponseStatusMessage = String(duoStatusMsg);
}else{
_duoApiAuthResponseResult = "";
_duoApiAuthResponseStatus = "";
_duoApiAuthResponseStatusMessage = "";
}
}
return true;
}else if(strcmp(apiStat,"FAIL") == 0){
_duoApiFailureCode = doc["code"];
const char* failureMessage = doc["message"];
if(failureMessage){
_duoApiFailureMessage = String(failureMessage);
}
return false;
}else{
return false;
}
}else{
return false;
}
}else{
_duoApiFailureCode = -1;
_duoApiFailureMessage = "DuoAuthLib: Error processing received response";
return false;
}
}
void DuoAuthLib::addRequestParameter(char *requestBuffer, char* field, char* value, bool lastEntry)
{
if(lastEntry == false){
strcat(requestBuffer, field);
strcat(requestBuffer, EQUALS_PAYLOAD_PARAM);
strcat(requestBuffer, value);
strcat(requestBuffer, AMPERSAND_PAYLOAD_PARAM);
}else{
strcat(requestBuffer, field);
strcat(requestBuffer, EQUALS_PAYLOAD_PARAM);
strcat(requestBuffer, value);
}
}
void DuoAuthLib::generateHmacPayload(char *hmacPayload, char* timeBuffer, char* httpMethod, char* duoHost, const char* duoApiPath, char* postContents)
{
hmacPayload[0] = 0;
strcat(hmacPayload, timeBuffer);
strcat(hmacPayload, NEWLINE_PAYLOAD_PARAM);
strcat(hmacPayload, httpMethod);
strcat(hmacPayload, NEWLINE_PAYLOAD_PARAM);
strcat(hmacPayload, duoHost);
strcat(hmacPayload, NEWLINE_PAYLOAD_PARAM);
strcat(hmacPayload, duoApiPath);
strcat(hmacPayload, NEWLINE_PAYLOAD_PARAM);
strcat(hmacPayload, postContents);
}
void DuoAuthLib::calculateHmacSha1(char *signingKey, char *dataPayload, char *hmacSignatureChar)
{
byte hmacSignature[20];
hmacSignatureChar[0] = 0;
mbedtls_md_context_t mbedTlsContext;
//Select the SHA1 Hashtype
mbedtls_md_type_t mbedTlsHashType = MBEDTLS_MD_SHA1;
const size_t payloadLength = strlen(dataPayload);
const size_t keyLength = strlen(signingKey);
mbedtls_md_init(&mbedTlsContext);
mbedtls_md_setup(&mbedTlsContext, mbedtls_md_info_from_type(mbedTlsHashType), 1);
mbedtls_md_hmac_starts(&mbedTlsContext, (const unsigned char *) signingKey, keyLength);
mbedtls_md_hmac_update(&mbedTlsContext, (const unsigned char *) dataPayload, payloadLength);
mbedtls_md_hmac_finish(&mbedTlsContext, hmacSignature);
mbedtls_md_free(&mbedTlsContext);
for(int i= 0; i< sizeof(hmacSignature); i++){
char str[3];
sprintf(str, "%02x", (int)hmacSignature[i]);
strcat(hmacSignatureChar, str);
}
}
//----------------------------------------------------------------
//Functions to read in a character array and output the calculated
//buffer size, and Encode the input excluding the below
//characters:
// 48-57 = Numbers ( 0123456789 )
// 65-90 = UPPPERCASE LETTERS ( ABCDEF )
// 97-122 = lowercase Letters ( abcdef )
// 45 = Dash ( - )
// 46 = Period ( . )
// 95 = Underscore ( _ )
// 126 = Tilde ( ~ )
//----------------------------------------------------------------
//----------------------------------------------------------------
//Calculate the length of the Character Array based on Input
//This function also takes into account Terminating Null
int DuoAuthLib::encodeUrlBufferSize(char *stringToEncode)
{
//Start the count at 1 to account for the terminating null character
int newStrLen = 1;
//Loop Through Char Array t
for(int i= 0; i< strlen(stringToEncode); i++){
int charAscii = (int)stringToEncode[i];
if((charAscii > 47 && charAscii < 58) || (charAscii > 64 && charAscii < 91) || (charAscii > 96 && charAscii < 123) || (charAscii == 45) || (charAscii == 46) || (charAscii == 95) || (charAscii == 126)){
//Found Regular Character
//Increment by 1
newStrLen++;
}else{
//Found Character that requires URL encoding
//Increment by 3
newStrLen += 3;
}
}
return newStrLen;
}
//----------------------------------------------------------------
//Function to read in a character array and output a URL Encoded
//and write the new variable to the destination variable
void DuoAuthLib::encodeUrl(char *destination, char *stringToEncode)
{
//Empty our Character Array before proceeding
destination[0] = '\0';
//Loop Through Char Array to perform urlEncode as required
for(int i= 0; i< strlen(stringToEncode); i++){
int charAscii = (int)stringToEncode[i];
if((charAscii > 47 && charAscii < 58) || (charAscii > 64 && charAscii < 91) || (charAscii > 96 && charAscii < 123) || (charAscii == 45) || (charAscii == 95) || (charAscii == 126) || (charAscii == 46)){
char str[2];
//Output only the Single Character as it does not need to be encoded
sprintf(str, "%c", (int)stringToEncode[i]);
strcat(destination, str);
}else{
char str[4];
//Output the URL Encoded Format '%XX'
sprintf(str, "%%%02X", (int)stringToEncode[i]);
strcat(destination, str);
}
}
}

View File

@@ -0,0 +1,306 @@
/**
*@license
*
*Copyright 2020 Cisco Systems, Inc. or its affiliates
*
*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 DuoAuthLib.h
* @brief An Arduino Library to simplify the operations of performing Duo Multi-Factor Authentication within your ESP32 Micro Controller project.
*
* @url https://github.com/CiscoDevNet/Arduino-DuoAuthLibrary-ESP32
* @version 1.0.0
* @author Gary Oppel <gaoppel@cisco.com>
*/
//Verify that the Duo Auth Library descriptor is only included once
#ifndef DuoAuthLib_H
#define DuoAuthLib_H
#include <memory>
#include <Arduino.h>
enum DUO_AUTH_METHOD
{
PUSH,
PASSCODE
};
/*!
* @brief Main Duo Authentication Library class
*/
class DuoAuthLib
{
public:
//Public Class Functions
//-------------------------
//External Functions which abstract Duo API Authentication Requests
DuoAuthLib();
~DuoAuthLib();
/**
* @brief Initializes the Duo Auth Library
*
* @param duoApiHost Contains the Duo API Hostname
* @param duoApiIKey Contains the Duo Integration Key (IKey)
* @param duoApiSKey Contains the Duo Secret Key (SKey)
* @param timeInfo Contains the Memory Pointer to the timeinfo Structure Declaration
*/
void begin(const char* duoApiHost, const char* duoApiIKey, const char* duoApiSKey, struct tm* timeInfo);
/**
* @brief Performs a Duo 'ping' API Query to check if alive
* @return Returns `true` if Ping API Call is Successful
*/
bool ping();
/**
* @brief Performs a Duo 'check' API Query to check if provided API-Host, Integration Key (IKey), or Signing Key (SKey) are valid
* @return Returns `true` if provided details for the Duo API are correct
*/
bool checkApiKey();
/**
* @brief Performs a Duo 'auth_status' API query to check if alivethe status of provided transaction Id
* @param transactionId Asynchronous Duo Push Transaction Id to get the status
* @return Returns `true` API call was successful
*/
bool authStatus(String transactionId);
/**
* @brief Performs a Duo Push Request
* @param userName Username of the user to send a Push Request
* @param async Set this to 'true' to enable Asynchronous mode. Duo will return a Transaction ID for checking status updates later.
* @return Returns `true` API call was successful
*/
bool pushAuth(char* userName, bool async = false);
/**
* @brief Performs a Duo Passcode Authentication Request
* @param userName Username of the user who is verifying their Duo passcode
* @param userPasscode 6 Digit Duo Passcode of the user
* @return Returns `true` API call was successful
*/
bool passcodeAuth(char* userName, char* userPasscode);
/**
* @brief Checks if the last Duo Push/Passcode Request's Authentication was Successful
* @return Returns `true` if Duo Authentication was successful (E.g. Duo Auth API Result = 'allow')
*/
bool authSuccessful();
/**
* @brief Checks if the status returned from the last Duo Request is in the Waiting State (Only Applies to Asynchronous Pushes)
* @return Returns `true` if Duo Push is in waiting state (E.g. Duo Auth API Result = 'waiting')
*/
bool pushWaiting();
/**
* @brief Sets the HTTP Timeout for the Duo API Request (Default: 30000ms [30 seconds])
* @param httpTimeout Value in milliseconds for Duo's HTTP API Requests
*/
void setHttpTimeout(int httpTimeout);
/**
* @brief Sets the IP Address of the device for Duo API Auth Requests (Default: 0.0.0.0)
* @param ipAddress Value contains the Public IP Address of the Device for additonal Duo Policy Controls
*/
void setIPAddress(char* ipAddress);
/**
* @brief Sets the Duo Mobile Application's Push Value. This string is displayed in the Duo Mobile app before the word "request".
* @param pushType Value contains the text to be displayed on the Push Notification Screen
*/
void setPushType(char* pushType);
/**
* @brief Sets the Push Type Notification Text that is displayed to the Enduser's Mobile Device. (Only supported with Duo Push functionality)
* @param Character Array of the Duo Push Type Text
*/
int getHttpCode();
/**
* @brief Gets the RAW HTTP Response from the last Duo Request
* @return Returns the RAW HTTP Response
*/
String getHttpResponse();
/**
* @brief Gets the API Stat Response from the last Duo Request
* @return Returns the API Stat Response ('OK', 'FAIL')
*/
String getApiStat();
/**
* @brief Gets the Auth Response Result from the last Duo Request
* @return Returns the Auth Response Result
*/
String getAuthResponseResult();
/**
* @brief Gets the Auth Response Status from the last Duo Request
* @return Returns the Auth Response Status
*/
String getAuthResponseStatus();
/**
* @brief Gets the Auth Response Status Message from the last Duo Request
* @return Returns the Auth Response Status Message
*/
String getAuthResponseStatusMessage();
/**
* @brief Sets the Duo Mobile Application's Push Value. This string is displayed in the Duo Mobile app before the word "request".
* @param pushType Value contains the text to be displayed on the Push Notification Screen
*/
String getAuthTxId();
//Duo API Error Code Reference Table
//https://help.duo.com/s/article/1338
/**
* @brief Gets the API Failure Code from the last Duo Request
* @return Returns the API Failure Code
*/
String getApiFailureCode();
/**
* @brief Gets the API Failure Message from the last Duo Request
* @return Returns the API Failure Message
*/
String getApiFailureMessage();
protected:
//Protected Class Functions
//-------------------------
//Internal Functions to handle abstracting core and common Library Functions
bool performAuth(char* userId, char* userPasscode, enum DUO_AUTH_METHOD authMethod);
bool submitApiRequest(uint8_t apiMethod, char *timeString, const char* apiPath, char *hmacPassword, char* requestContents);
bool processResponse(String* serializedJsonData);
//Functions to generate the API Request to the required format
void generateHmacPayload(char *hmacPayload, char* timeBuffer, char* httpMethod, char* duoHost, const char* duoApiPath, char* postContents);
void addRequestParameter(char *requestBuffer, char* field, char* value, bool lastEntry = false);
//Function that Calculates the HMAC-SHA1 Signature for the Duo API Call
void calculateHmacSha1(char *signingKey, char *dataPayload, char *hmacSignatureChar);
//URL Encoding Functions
int encodeUrlBufferSize(char *stringToEncode);
void encodeUrl(char *destination, char *stringToEncode);
//Duo Auth Library Initialized Flag
bool _init;
//Buffer size for Date/Time Output Character Array
const int TIME_BUF_STR_SIZE = 36;
//Maximum IPv4 Length is 16 including null termination
static const int MAX_IP_LENGTH = 16;
//Maximum Length of the 'Push Type' Notification String
static const int MAX_TYPE_LENGTH = 21;
//Maximum Length of various miscellaneous variables
static const int MAX_METHOD_LENGTH = 5;
static const int MAX_HOSTNAME_LENGTH = 64;
static const int MAX_API_ENDPOINT_LENGTH = 24;
//Maximum Username Length
//NOTE: Usernames can contain e-mail addresses.
// Tested & validated with 44 character length Email Address (username).
// Maximum username length selected is 50, with 49 being the usable maximum.
static const int MAX_USER_LENGTH = 50;
static const int MAX_PARAM_LENGTH = 10;
static const int MAX_PAYLOAD_LENGTH = 20;
const int SIGNATURE_PAYLOAD_BUFFER_SIZE = TIME_BUF_STR_SIZE + MAX_METHOD_LENGTH + MAX_HOSTNAME_LENGTH + MAX_API_ENDPOINT_LENGTH;
//Required Parameters for Duo API Auth Requests
const char* ASYNC_PARAM = "async";
const char* AUTO_PARAM = "auto";
const char* DEVICE_PARAM = "device";
const char* FACTOR_PARAM = "factor";
const char* IPADDR_PARAM = "ipaddr";
const char* PASSCODE_PARAM = "passcode";
const char* PUSH_PARAM = "push";
const char* TRANSACTION_PARAM = "txid";
const char* TYPE_PARAM = "type";
const char* USERNAME_PARAM = "username";
//Common variables for Duo API Auth Requests
const char* NEWLINE_PAYLOAD_PARAM = "\n";
const char* AMPERSAND_PAYLOAD_PARAM = "&";
const char* EQUALS_PAYLOAD_PARAM = "=";
//Duo Auth API URL Paths
const char* DUO_PING_API_PATH = "/auth/v2/ping";
const char* DUO_CHECK_API_PATH = "/auth/v2/check";
const char* DUO_AUTH_API_PATH = "/auth/v2/auth";
const char* DUO_AUTHSTATUS_API_PATH = "/auth/v2/auth_status";
//Duo Auth Library HTTP User Agent
String _duoUserAgent = "DuoAuthLib/1.0 (ESP32HTTPClient)";
//Variable to hold IP Address for Duo Authentication Requests
char _ipAddress[MAX_IP_LENGTH];
//Variable to hold Push Type string, which is displayed on Push notifications
char _duoPushType[MAX_TYPE_LENGTH];
//Variable holds if the Push request is Asynchronous
bool _async;
//Duo Auth Library Required Variables for API interface
char* _duoHost;
char* _duoIkey;
char* _duoSkey;
//Asynchronous Request ID Variable
char* _asyncRequestId;
//Duo Auth Library HTTP Timeout value (Default: 30000 [30 seconds])
int _defaultTimeout = 30000;
int _httpTimeout;
//HTTP code returned from the Duo API Request
int _httpCode;
//Return Variables for Public Functions
String _duoApiStat;
String _duoAuthTxId;
String _duoApiAuthResponseResult;
String _duoApiAuthResponseStatus;
String _duoApiAuthResponseStatusMessage;
int _duoApiFailureCode;
String _duoApiFailureMessage;
String _lastHttpResponse;
//Buffer sizes for Check Auth API User provided variables
const int CHECK_AUTH_BUFFER_SIZE = 42;
//Buffer size for Authentication Request API.
//This buffer multiplies the max user length by 3 as the assumption would be all characters would require URL Encoding
const int AUTH_REQUEST_BUFFER_SIZE = 64 + (MAX_USER_LENGTH * 3);
//Empty Pointer for the `timeinfo` Variable passed in by the end user from the 'begin(...)' function
struct tm* _timeinfo = nullptr;
};
#endif