Duo Auth
This commit is contained in:
719
lib/DuoAuthLibrary/src/DuoAuthLib.cpp
Normal file
719
lib/DuoAuthLibrary/src/DuoAuthLib.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
306
lib/DuoAuthLibrary/src/DuoAuthLib.h
Normal file
306
lib/DuoAuthLibrary/src/DuoAuthLib.h
Normal 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
|
||||
Reference in New Issue
Block a user