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,4 @@
# Duo Authentication Library for ESP32 Micro Controllers
# List of Authors
Gary Oppel <gaoppel@cisco.com>

View File

@@ -0,0 +1,7 @@
Duo Authentication Library for ESP32 Micro Controllers - Change Log
=======================
v1.0.0 (08/26/2020)
------
* Initial Library Release

View File

@@ -0,0 +1,24 @@
# Guidance on how to contribute
Contributions to this code are welcome and appreciated.
> All contributions to this code will be released under the terms of the [LICENSE](./LICENSE) of this code. By submitting a pull request or filing a bug, issue, or feature request, you are agreeing to comply with this waiver of copyright interest. Details can be found in our [LICENSE](./LICENSE).
There are two primary ways to contribute:
1. Using the issue tracker
2. Changing the codebase
## Using the issue tracker
Use the issue tracker to suggest feature requests, report bugs, and ask questions. This is also a great way to connect with the developers of the project as well as others who are interested in this solution.
Use the issue tracker to find ways to contribute. Find a bug or a feature, mention in the issue that you will take on that effort, then follow the _Changing the codebase_ guidance below.
## Changing the codebase
Generally speaking, you should fork this repository, make changes in your own fork, and then submit a pull request. All new code should have associated unit tests (if applicable) that validate implemented features and the presence or lack of defects.
Additionally, the code should follow any stylistic and architectural guidelines prescribed by the project. In the absence of such guidelines, mimic the styles and patterns in the existing codebase.

176
lib/DuoAuthLibrary/LICENSE Normal file
View File

@@ -0,0 +1,176 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS

View File

@@ -0,0 +1,5 @@
Duo Authentication Library for ESP32 Micro Controllers
Copyright (c) 2020 Cisco Systems, Inc. or its affiliates
This project includes software developed at Cisco Systems, Inc. or its affiliates.

View File

@@ -0,0 +1,217 @@
# Duo Authentication Library for ESP32 Micro Controllers
*An Arduino Library to simplify the operations of performing Duo Multi-Factor Authentication within your ESP32 Micro Controller project*
---
## Overview
The Duo Authentication Library was created to abstract the [Duo Auth API Authentication Requirements](https://duo.com/docs/authapi#authentication). These requirements, which utilizes the HMAC_SHA1 algorithm, NTP Time Source, and Duo Application details (API Host, Application/Integration Key, and Secret Key) to sign the properly formatted requests before being sent to Duo's Auth API. The library has been targeted for the Espressif ESP32 Micro Controllers, due to the built-in crypto hardware support for both the HTTPS and HMAC Signature requirements.
Duo Security extends various API Endpoints for use. In particular, this Library's focus is on Duo's [Authentication API ](https://duo.com/docs/authapi).
## Features
The Duo Authentication Library enables the following features/functionality:
- Duo Mobile Push Authentication
- Duo Asynchronous Mobile Push Authentication
- Duo Passcode Authentication
- Ability to set device's IP Address for Duo Auth API Requests
- Ability to set Duo's Mobile Push Notification Type (This string is displayed before the word "Request")
- Various Duo Authentication API Response fields exposed with easy to use functions
- **Includes 4 example sketches** to demonstrate the use of the library
## Key Design Requirements
The library was created with the following Key Duo API Requirements in Mind:
- Accurate Time Source for Signing the Authorization Signature
- *Note: This variable can be declared as Global, Private 'setup();' or 'loop();', or within your own Method/Function that will interface with the Duo Auth Library! It is strongly suggested that you leverage a Global variable if you are using a primary authentication source as well!*
- Simple to use functions for anyone to get up and quickly as well as incorporating into your existing projects
- Built-in URL Encoding as required for certain usernames or other API properties
- Functions that enable granular 'Authentication' decision making based on Duo's Auth API Responses
- Initial Library scope primarily targeted mainly Duo Push and Passcode functionality
## Technologies & Frameworks Used
The library was built to provide a simple and easy to use interface for the Duo Auth API on Espressif's ESP32 Micro Controllers. The library is written for the use within the Arduino IDE.
**Cisco Products & Services:**
- Duo Security
**Duo Authentication API Endpoints in use:**
This library abstracts most of the [Duo Auth API](https://duo.com/docs/authapi) functionality. The specific API Endpoints leveraged by this library are as follows:
- GET ```/auth/v2/ping```
- GET ```/auth/v2/check```
- POST ```/auth/v2/auth```
- GET ```/auth/v2/auth_status```
**Tools & Frameworks:**
- Arduino IDE (*Tested on v1.8.12*) or PlatformIO for VSCode IDE
- [Arduino Core for the ESP32](https://github.com/espressif/arduino-esp32)
- [ArduinoJson Library for Arduino and IoT Devices](https://github.com/bblanchon/ArduinoJson)
**I would like to thank all the authors & contributers on the previously mentioned Tools & Frameworks!**
The Documentation and Guides available from these tools, contained the details needed to bring this Library to life. Below are links to references used for the above Tools & Frameworks:
- Arduino - Writing a Library for Arduino [https://www.arduino.cc/en/Hacking/LibraryTutorial](https://www.arduino.cc/en/Hacking/LibraryTutorial)
- ArduinoJson - Deserialization Tutorial [https://arduinojson.org/v6/doc/deserialization/](https://arduinojson.org/v6/doc/deserialization/)
**Hardware Required:**
- Espressif ESP32 WiFi Micro-controller
- User Input Device
- 10 Digit Keypad
- LCD Touch Screen
- Serial/RS-232 Device
- RFID Reader/NFC Reader
- Etc..
## Installation
#### Arduino IDE
*Note: This document assumes that your Arduino IDE is already setup and configured for Espressif's ESP32 Micro Controllers (Arduino Core for the ESP32)*
- To install the Duo Authentication Library in your Arduino IDE, follow the instructions below:
- Browse to the Duo Authentication Library Releases GitHub Page
- Download Latest Duo Authentication Library package version (Choose ZIP Package)
- Follow the **"Installing Additional Arduino Libraries"** documentation at [Arduino's Library Documentation Site](https://www.arduino.cc/en/guide/libraries)
#### PlatformIO for VSCode IDE
- To install the Duo Authentication Library in your PlatformIO for VSCode IDE, follow the instructions below:
- Open VSCode PlatformIO Core CLI
- Execute the following installation command
- `pio lib install https://github.com/CiscoDevNet/Arduino-DuoAuthLibrary-ESP32/archive/v1.0.0.zip`
## Library Usage
### Example Sketches
Included as part of the Duo Authentication Library are 4 Arduino example sketches, which outline and demonstrate how to use the library. The examples included are:
- **Push Authentication** - Provides example on a Duo Library Push Authentication
- **Passcode Authentication** - Provides example on a Duo Library Passcode Authentication
- **Asynchronous Push Authentication** - Provides example on an Asynchronous Duo Push Authentication
- **Common Functions** - Provides examples on the Libraries Common Functions
### Configuration Requirements
The following parameters are required inputs into the Duo Authentication Library:
- NTP Server (Intranet or Internet) and other Time Variables
```cpp
const char* ntpServer = "pool.ntp.org";
//Adjusting these values from '0' are not necessary
const long gmtOffset_sec = 0;
const int daylightOffset_sec = 0;
```
- WiFi SSID & Pre-Shared Key
```cpp
const char* ssid = "MyWirelessAP";
const char* wirelessPsk = "MySecretWiFiPassword";
```
- Time Info Variable Declaration & Configuration
The Time Info structure provides the necessary variable for the NTP server to update the time, which is used by the Duo Auth Library.
```cpp
//Create timeinfo variable. Note: This can be declared globally or within the function using the Duo Auth Library
struct tm timeinfo;
//Configure the Time settings
configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
```
- Root Certificate Authority (CA) Public Certificate for the Duo API Host
See [Root Certificate Authority Section](/README.md#root-certificate-authority)
- Duo API Hostname
```cpp
const char* duo_host = "";
```
- Duo Application/Integration Key
```cpp
const char* duo_akey = "";
```
- Duo Secret Key
```cpp
const char* duo_skey = "";
```
- Username
- Passcode of User, if using Duo Passcode Authentication
*For a more comprehensive view into the Configuration Requirements and Library use, please view the included example sketches.*
### Root Certificate Authority
Duo Authentication API calls are performed over HTTPS, and as such we require a trustpoint to know that we are communicating with our trusted Duo API Host.
You will need to export the Base64 Encoded X.509 Public Key of the Root Certificate Authority for your Duo API Host (```duo_host```).
Once exported, you will need to format the Base64 Output similar to the example below:
```cpp
const char* root_ca= \
"-----BEGIN CERTIFICATE-----\n" \
"XxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXx\n" \
"XxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXx\n" \
"XxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXx\n" \
"XxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXx\n" \
"XxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXx\n" \
"XxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXx\n" \
"XxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXx\n" \
"XxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXx\n" \
"XxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXx\n" \
"XxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXx\n" \
"XxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXx\n" \
"XxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXx\n" \
"XxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXx\n" \
"XxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXx\n" \
"XxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXx\n" \
"XxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXx\n" \
"XxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXx\n" \
"XxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXx\n" \
"XxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXx\n" \
"XxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXx\n" \
"XxXxXxXxXxXx\n" \
"-----END CERTIFICATE-----\n";
```
## Authors & Maintainers
The following people are responsible for the creation and/or maintenance of this project:
- Gary Oppel <gaoppel@cisco.com>
## License
This project is licensed to you under the terms of the [Apache License, Version 2.0](./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.

View File

@@ -0,0 +1,146 @@
/**
*@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.
*/
//-------------------------------------------------------------------------------------------
//Duo Authentication Library (Common Library Functions Example)
//GitHub: https://github.com/CiscoDevNet/Arduino-DuoAuthLibrary-ESP32
//-------------------------------------------------------------------------------------------
//Include WiFi Header File to enable Network Connectivity
#include <WiFi.h>
//Include DuoAuthLibrary Header File enabling the functions for Duo Authentication Requests
#include <DuoAuthLib.h>
//UPDATE: Update the below values with your Wireless SSID and Pre-Shared Key settings
const char* ssid = "MyWirelessAP";
const char* wirelessPsk = "MySecretWiFiPassword";
//-------------------------------------------------------------------------------------------
//START: Duo Authentication Library Required Variables
//Initialize the Time Variable Globally since it is needed for Duo Auth and potentially the
//primary Authentication Provider
struct tm timeinfo;
//UPDATE: Provide an accurate Local or Internet Based Time-source (NTP) (IP or FQDN)
//If using a FQDN, please ensure that DNS Servers are configured or provided through DHCP
const char* ntpServer = "pool.ntp.org";
//Duo Auth API uses UTC time, which is queried against the above NTP Server.
//Adjusting these values from '0' are not necessary
const long gmtOffset_sec = 0;
const int daylightOffset_sec = 0;
//UPDATE: Update these variables with the appropriate Duo Application API values
const char* duo_host = "";
const char* duo_akey = "";
const char* duo_skey = "";
//END: Duo Authentication Library Required Variables
//-------------------------------------------------------------------------------------------
void setup(){
//Start Serial Connection at 115200 Baud
Serial.begin(115200);
delay(1000);
//Start WiFi with the provided SSID and PSK
WiFi.begin(ssid, wirelessPsk);
//Wait for a valid WiFi Connection
while (WiFi.status() != WL_CONNECTED){
delay(1000);
Serial.println("Connecting to WiFi..");
}
Serial.println("Connected to the WiFi network");
Serial.println("Configuring NTP Client");
//Configure SNTP Settings for an Accurate Time-source
configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
Serial.println("Duo Authentication Library (Common Functions Example)");
//Declare Duo Authentication Instance
DuoAuthLib duoAuth;
//Call the begin(...) function to initialize the Duo Auth Library
duoAuth.begin(duo_host, duo_akey, duo_skey, &timeinfo);
//This function allows you to check if the Duo API AKey, SKey, and Host are correct and Valid.
//This is good for validating or testing that the Duo variables correctly are correctly set
bool checkAPIKey = duoAuth.checkApiKey();
//Let's see the response from the above command
//If it is 'true' we have successfully authenticated against the Duo Auth API
if(checkAPIKey){
Serial.println("Duo API Credentials are VALID");
}else{
//The Credential validation has failed for the Duo Auth API, please check the AKey, SKey, and Duo Hostname
Serial.println("Duo API Credentials are INVALID");
}
//Example providing the HTTP Response code from DuoAuthLib's last API request
Serial.print("DuoAuthLib:HTTP Code : ");
Serial.println(String(duoAuth.getHttpCode()));
//Example providing the Duo Auth API Status from the DuoAuthLib's last API request
Serial.print("DuoAuthLib:getApiStat : ");
Serial.println(duoAuth.getApiStat());
//Example providing the Duo Authentication Result from the DuoAuthLib's last API request
//NOTE: This value is the decision on the Authentication Request. This must match "allow"
//for a successful Auth Request. https://duo.com/docs/authapi#/auth under Response Formats
//
//To simplify the ease of use, leverage the 'authSuccessful(...)' function instead of the
//below function, unless advanced control is required.
Serial.print("DuoAuthLib:getAuthResponseResult : ");
Serial.println(duoAuth.getAuthResponseResult());
//Example providing Duo's Authentication Status from DuoAuthLib's last API request
Serial.print("DuoAuthLib:getAuthResponseStatus : ");
Serial.println(duoAuth.getAuthResponseStatus());
//Example providing the HTTP Response code from DuoAuthLib's last API request
Serial.print("DuoAuthLib:getAuthResponseStatusMessage : ");
Serial.println(duoAuth.getAuthResponseStatusMessage());
//Example providing Duo's API Failure code from DuoAuthLib's last API request
//See https://help.duo.com/s/article/1338 for more details
Serial.print("DuoAuthLib:getApiFailureCode : ");
Serial.println(duoAuth.getApiFailureCode());
//Example providing Duo's API Failure Message from DuoAuthLib's last API request
//See https://help.duo.com/s/article/1338 for more details
Serial.print("DuoAuthLib:getApiFailureMessage : ");
Serial.println(duoAuth.getApiFailureMessage());
//Example providing the RAW HTTP Response from DuoAuthLib's last API request
Serial.print("DuoAuthLib:HTTP Raw Response : \n");
Serial.println(duoAuth.getHttpResponse());
}
void loop(){
if ((WiFi.status() == WL_CONNECTED)){
//-------------------------------------------------------------------------------------------
//Loop Function Code Block - Intentionally Left Blank (Example in 'setup()' function above)
//-------------------------------------------------------------------------------------------
}
}

View File

@@ -0,0 +1,258 @@
/**
*@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.
*/
//-------------------------------------------------------------------------------------------
//Duo Authentication Library (Passcode Authentication Example)
//GitHub: https://github.com/CiscoDevNet/Arduino-DuoAuthLibrary-ESP32
//-------------------------------------------------------------------------------------------
//Include WiFi Header File to enable Network Connectivity
#include <WiFi.h>
//Include DuoAuthLibrary Header File enabling the functions for Duo Authentication Requests
#include <DuoAuthLib.h>
//UPDATE: Update the below values with your Wireless SSID and Pre-Shared Key settings
const char* ssid = "MyWirelessAP";
const char* wirelessPsk = "MySecretWiFiPassword";
//Global variables used for example
char duoUsername[25];
char duoPasscode[7];
bool duoUser;
const byte numChars = 25;
char userInputChars[numChars];
bool newUserInput = false;
bool readyToAuth = false;
//-------------------------------------------------------------------------------------------
//START: Duo Authentication Library Required Variables
//Initialize the Time Variable Globally since it is needed for Duo Auth and potentially the
//primary Authentication Provider
struct tm timeinfo;
//UPDATE: Provide an accurate Local or Internet Based Time-source (NTP) (IP or FQDN)
//If using a FQDN, please ensure that DNS Servers are configured or provided through DHCP
const char* ntpServer = "pool.ntp.org";
//Duo Auth API uses UTC time, which is queried against the above NTP Server.
//Adjusting these values from '0' are not necessary
const long gmtOffset_sec = 0;
const int daylightOffset_sec = 0;
//UPDATE: Update these variables with the appropriate Duo Application API values
const char* duo_host = "";
const char* duo_akey = "";
const char* duo_skey = "";
//END: Duo Authentication Library Required Variables
//-------------------------------------------------------------------------------------------
void setup(){
//Start Serial Connection at 115200 Baud
Serial.begin(115200);
delay(1000);
//Start WiFi with the provided SSID and PSK
WiFi.begin(ssid, wirelessPsk);
//Wait for a valid WiFi Connection
while (WiFi.status() != WL_CONNECTED){
delay(1000);
Serial.println("Connecting to WiFi..");
}
Serial.println("Connected to the WiFi network");
Serial.println("Configuring NTP Client");
//Configure SNTP Settings for an Accurate Time-source
configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
Serial.println("Duo Authentication Library (Passcode Example)");
Serial.print("Please enter your Username :");
}
void loop(){
//Check if Wifi is connected before proceeding
if ((WiFi.status() == WL_CONNECTED)){
//Declare Duo Authentication Instance
DuoAuthLib duoAuth;
//Check for New user input from Serial Interface
//NOTE: See checkSerialInput(...) function below for more details
checkSerialInput();
//Check if new data is available to be processed by the application
if (newUserInput == true){
newUserInput = false;
Serial.println(userInputChars);
if(!duoUser){
//New Username being entered, empty the existing contents of both variables
duoUsername[0] = '\0';
duoPasscode[0] = '\0';
//Copy the user input into the duoUsername Variable
strcat(duoUsername, userInputChars);
userInputChars[0] = '\0';
Serial.print("Please enter your Duo Passcode :");
duoUser = true;
}else{
//Copy the user input into the duoUsername Variable
strcat(duoPasscode, userInputChars);
userInputChars[0] = '\0';
duoUser = false;
readyToAuth = true;
}
}
//-------------------------------------------------------------------------------------------
//Primary Identity Authentication will need to be inserted where appropriate
//If we have received both a Username and Passcode, we can proceed to requesting
//Duo Passcode Authentication
//-------------------------------------------------------------------------------------------
if(readyToAuth){
readyToAuth = false;
//-------------------------------------------------------------------------------------------
//BEGIN : Duo Authentication
//-------------------------------------------------------------------------------------------
//Declare Variable to hold our Duo Push Request Status
bool duoRequestResult;
//Reset the new data flag as its being processed
newUserInput = false;
//Call the begin(...) function to initialize the Duo Auth Library
duoAuth.begin(duo_host, duo_akey, duo_skey, &timeinfo);
//-------------------------------------------------------------------------------------------
//Optional configuration functions available to control some intermediate features
//
//--------
//Set HTTP Timeout for the Duo Auth Library HTTP Request. Adjust to your requirements
// Default Setting - 30000ms (30 Seconds)
//
//duoAuth.setHttpTimeout(10000);
//--------
//
//--------
//Sets the IP Address of the device for Duo API Auth Requests
// Default: 0.0.0.0
//
//duoAuth.setIPAddress("255.255.255.255");
//--------
//
//-------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------
//Call passcodeAuth(...) function for user entered
//This is a stopping function and will wait until a response is received, an error occurs,
//or the session times out. NOTE: The default HTTP Timeout for the Duo Library is 30 Seconds
//See 'Asynchronous Push Authentication' example for an alternative Push Method
//-------------------------------------------------------------------------------------------
duoRequestResult = duoAuth.passcodeAuth(duoUsername, duoPasscode);
//-------------------------------------------------------------------------------------------
//Check if Duo Auth Request was Successful
//This returns the True/False if the Duo Auth Library API
//Request was successful and received the appropriate "OK" response
//If the result returned is 'true'
//-------------------------------------------------------------------------------------------
//https://duo.com/docs/authapi#/auth
//-------------------------------------------------------------------------------------------
if(duoRequestResult == true){
//Check if Duo Authentication for the user was Allowed
if(duoAuth.authSuccessful()){
//Duo Authentication was Successful as the "allow" response was received from the API
Serial.println("Successful Auth!");
//-------------------------------------------------------------------------------------------
//INSERT Your PROTECTED Code HERE
//-------------------------------------------------------------------------------------------
}else{
Serial.println("Failed Auth!");
//Duo Authentication Failed as the "allow" response was NOT received from the API
//-------------------------------------------------------------------------------------------
//INSERT Your Authentication FAILURE Code HERE
//-------------------------------------------------------------------------------------------
}
}else{
Serial.println("Failed Auth!");
//Duo Authentication Failed as the "allow" response was NOT received from the API
//-------------------------------------------------------------------------------------------
//INSERT Your Duo API Request FAILURE Code HERE
//-------------------------------------------------------------------------------------------
}
//-------------------------------------------------------------------------------------------
//END : Duo Authentication
//-------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------
//Reset our Variables for the next User Input/Authentication Request
//-------------------------------------------------------------------------------------------
userInputChars[0] = '\0';
Serial.println("Duo Authentication Library (Passcode Example)");
Serial.print("Please enter your Username :");
}
}
}
//-------------------------------------------------------------------------------------------
//Check Serial for Input and wait for the "Enter" key to be pressed
//-------------------------------------------------------------------------------------------
void checkSerialInput(){
static byte index = 0;
//Define Terminating Character
char endingCharacter = '\n';
char receivedChar;
//-------------------------------------------------------------------------------------------
//Only check for new Serial Data when new data is available from the Serial Interface and we
//have not seen the "Enter" key press yet.
//-------------------------------------------------------------------------------------------
while (Serial.available() > 0 && newUserInput == false){
//We have new input
receivedChar = Serial.read();
if(receivedChar != endingCharacter){
//Detected that other characters were entered
userInputChars[index] = receivedChar;
index++;
if (index >= numChars) {
index = numChars - 1;
}
}else{
//Detected that the 'Enter' button was pressed
userInputChars[index] = '\0';
index = 0;
//Update the flag telling the main loop we now have User Input for processing
newUserInput = true;
}
}
}

View File

@@ -0,0 +1,296 @@
/**
*@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.
*/
//-------------------------------------------------------------------------------------------
//Duo Authentication Library (Asynchronous Push Authentication Example)
//GitHub: https://github.com/CiscoDevNet/Arduino-DuoAuthLibrary-ESP32
//-------------------------------------------------------------------------------------------
//Include WiFi Header File to enable Network Connectivity
#include <WiFi.h>
//Include DuoAuthLibrary Header File enabling the functions for Duo Authentication Requests
#include <DuoAuthLib.h>
//UPDATE: Update the below values with your Wireless SSID and Pre-Shared Key settings
const char* ssid = "MyWirelessAP";
const char* wirelessPsk = "MySecretWiFiPassword";
//Global variables used for example
const byte numChars = 25;
char userInputChars[numChars];
boolean newUserInput = false;
//-------------------------------------------------------------------------------------------
//START: Duo Authentication Library Example Dependant Variables
String transactionId;
bool activeRequest;
int asyncPollTimeout = 10000;
unsigned long asyncBeginMarker;
//END: Duo Authentication Library Example Dependant Variables
//-------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------
//START: Duo Authentication Library Required Variables
//Initialize the Time Variable Globally since it is needed for Duo Auth and potentially the
//primary Authentication Provider
struct tm timeinfo;
//UPDATE: Provide an accurate Local or Internet Based Time-source (NTP) (IP or FQDN)
//If using a FQDN, please ensure that DNS Servers are configured or provided through DHCP
const char* ntpServer = "pool.ntp.org";
//Duo Auth API uses UTC time, which is queried against the above NTP Server.
//Adjusting these values from '0' are not necessary
const long gmtOffset_sec = 0;
const int daylightOffset_sec = 0;
//UPDATE: Update these variables with the appropriate Duo Application API values
const char* duo_host = "";
const char* duo_akey = "";
const char* duo_skey = "";
//END: Duo Authentication Library Required Variables
//-------------------------------------------------------------------------------------------
void setup(){
//Start Serial Connection at 115200 Baud
Serial.begin(115200);
delay(1000);
//Start WiFi with the provided SSID and PSK
WiFi.begin(ssid, wirelessPsk);
//Wait for a valid WiFi Connection
while (WiFi.status() != WL_CONNECTED){
delay(1000);
Serial.println("Connecting to WiFi..");
}
Serial.println("Connected to the WiFi network");
Serial.println("Configuring NTP Client");
//Configure SNTP Settings for an Accurate Time-source
configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
Serial.println("Duo Authentication Library (Asynchronous Push Example)");
Serial.print("Please enter your Username :");
}
void loop(){
//Check if Wifi is connected before proceeding
if ((WiFi.status() == WL_CONNECTED)){
//Declare Duo Authentication Instance
DuoAuthLib duoAuth;
//Check for New user input from Serial Interface
//NOTE: See checkSerialInput(...) function below for more details
checkSerialInput();
//Check if new data is available to be processed by the application
if (newUserInput == true){
Serial.println(userInputChars);
//-------------------------------------------------------------------------------------------
//PERFORM Primary Identity Authentication Here before proceeding to Duo Authentication
//-------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------
//Begin Duo Authentication
//Declare Variable to hold our Duo Push Request Status
bool duoRequestResult;
//Reset the new data flag as its being processed
newUserInput = false;
//Call the 'begin(...)' function to initialize the Duo Auth Library
duoAuth.begin(duo_host, duo_akey, duo_skey, &timeinfo);
//-------------------------------------------------------------------------------------------
//Optional configuration functions available to control some intermediate features
//
//--------
//Set HTTP Timeout for the Duo Auth Library HTTP Request. Adjust to your requirements
// Default Setting - 30000ms (30 Seconds)
//
//duoAuth.setHttpTimeout(10000);
//--------
//
//--------
//Sets the IP Address of the device for Duo API Auth Requests
// Default: 0.0.0.0
//
//duoAuth.setIPAddress("255.255.255.255");
//--------
//
//--------
//Sets the Push Type Notification Text that is displayed to the Enduser's Mobile Device.
// Note: Only supported with Duo Push functionality
//
//duoAuth.setPushType(10000);
//--------
//
//-------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------
//Call 'pushAuth(...)' function for user defined in 'userInputChars'
//This is a stopping function and will wait until a response is received, an error occurs,
//or the session times out. NOTE: The default HTTP Timeout for the Duo Library is 30 Seconds
//See 'Asynchronous Push Authentication' example for an alternative Push Method
//-------------------------------------------------------------------------------------------
duoRequestResult = duoAuth.pushAuth(userInputChars, true);
//-------------------------------------------------------------------------------------------
//Check if Duo Auth Request was Successful
//This returns the True/False if the Duo Auth Library API
//Request was successful and received the approriate "OK" response
//If the result returned is 'true'
//-------------------------------------------------------------------------------------------
//https://duo.com/docs/authapi#/auth
//-------------------------------------------------------------------------------------------
if(duoRequestResult == true){
//-------------------------------------------------------------------------------------------
//We have a successful Asynchronous Request, Let's Grab the Transaction ID and save it so we
//can continue processing code within our main loop() function. We can check the status of
//Duo Push Authentication Request with the 'authStatus(...)' function
//-------------------------------------------------------------------------------------------
transactionId = duoAuth.getAuthTxId();
activeRequest = true;
asyncBeginMarker = millis();
Serial.println(String("Processing other tasks for ") + String(asyncPollTimeout/1000) + String(" seconds"));
}else{
Serial.println("Failed Auth!");
//Duo Authentication Failed as the "allow" response was NOT received from the API
//-------------------------------------------------------------------------------------------
//INSERT Your Duo API Request FAILURE Code HERE
//-------------------------------------------------------------------------------------------
//Run the Duo Prompt Loop Again
Serial.println("Duo Authentication Library (Asynchronous Push Example)");
Serial.print("Please enter your Username :");
}
//Reset Variables
userInputChars[0] = '\0';
}
//Note: According to the Duo API, the 'auth_status' endpoint will perform a long Poll
if(activeRequest && (millis() > (asyncBeginMarker + asyncPollTimeout))){
//Call the 'begin(...)' function to initialize the Duo Auth Library
duoAuth.begin(duo_host, duo_akey, duo_skey, &timeinfo);
Serial.println("Checking Duo Push Status...");
//-------------------------------------------------------------------------------------------
//Call 'authStatus(...)' function to check on the status of our Asynchronous Push Request
//-------------------------------------------------------------------------------------------
duoAuth.authStatus(transactionId);
//-------------------------------------------------------------------------------------------
//Call 'authStatus(...)' function to check on the status of our Asynchronous Push Request
//-------------------------------------------------------------------------------------------
if(duoAuth.pushWaiting()){
//-------------------------------------------------------------------------------------------
//Duo Authentication was sucessfully pushed to the user, we are still waiting for user response
//-------------------------------------------------------------------------------------------
Serial.println("Duo Push Waiting...");
//Reset the asyncBeginMarker to check for auth again
asyncBeginMarker = millis();
Serial.println(String("Processing other tasks for ") + String(asyncPollTimeout/1000) + String(" seconds"));
}else{
if(duoAuth.authSuccessful()){
//Duo Authentication was Successful as the "allow" response was received from the API
Serial.println("Successful Auth!");
//-------------------------------------------------------------------------------------------
//INSERT Your PROTECTED Code HERE
//-------------------------------------------------------------------------------------------
activeRequest = false;
transactionId = "";
//Run the Duo Prompt Loop Again
Serial.println("Duo Authentication Library (Asynchronous Push Example)");
Serial.print("Please enter your Username :");
}else{
//Duo Authentication Failed as the "allow" response was NOT received from the API
Serial.println("Failed Auth!");
activeRequest = false;
transactionId = "";
//-------------------------------------------------------------------------------------------
//INSERT Your Authentication FAILURE Code HERE
//-------------------------------------------------------------------------------------------
//Run the Duo Prompt Loop Again
Serial.println("Duo Authentication Library (Asynchronous Push Example)");
Serial.print("Please enter your Username :");
}
}
}
//-------------------------------------------------------------------------------------------
//Insert your normal process code here to process while we wait to check on the Auth Status
//-------------------------------------------------------------------------------------------
}
//-------------------------------------------------------------------------------------------
//Insert your normal process code here to process while we wait to check on the Auth Status
//-------------------------------------------------------------------------------------------
}
//-------------------------------------------------------------------------------------------
//Check Serial for Input and wait for the "Enter" key to be pressed
//-------------------------------------------------------------------------------------------
void checkSerialInput(){
static byte index = 0;
//Define Terminating Character
char endingCharacter = '\n';
char receivedChar;
//-------------------------------------------------------------------------------------------
//Only check for new Serial Data when new data is available from the Serial Interface and we
//have not seen the "Enter" key press yet.
//-------------------------------------------------------------------------------------------
while (Serial.available() > 0 && newUserInput == false){
//We have new input
receivedChar = Serial.read();
if(receivedChar != endingCharacter){
//Detected that other characters were entered
userInputChars[index] = receivedChar;
index++;
if (index >= numChars) {
index = numChars - 1;
}
}else{
//Detected that the 'Enter' button was pressed
userInputChars[index] = '\0';
index = 0;
//Update the flag telling the main loop we now have User Input for processing
newUserInput = true;
}
}
}

View File

@@ -0,0 +1,234 @@
/**
*@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.
*/
//-------------------------------------------------------------------------------------------
//Duo Authentication Library (Push Authentication Example)
//GitHub: https://github.com/CiscoDevNet/Arduino-DuoAuthLibrary-ESP32
//-------------------------------------------------------------------------------------------
//Include WiFi Header File to enable Network Connectivity
#include <WiFi.h>
//Include DuoAuthLibrary Header File enabling the functions for Duo Authentication Requests
#include <DuoAuthLib.h>
//UPDATE: Update the below values with your Wireless SSID and Pre-Shared Key settings
const char* ssid = "MyWirelessAP";
const char* wirelessPsk = "MySecretWiFiPassword";
//Global variables used for example
const byte numChars = 25;
char userInputChars[numChars];
bool newUserInput = false;
//-------------------------------------------------------------------------------------------
//START: Duo Authentication Library Required Variables
//Initialize the Time Variable Globally since it is needed for Duo Auth and potentially the
//primary Authentication Provider
struct tm timeinfo;
//UPDATE: Provide an accurate Local or Internet Based Time-source (NTP) (IP or FQDN)
//If using a FQDN, please ensure that DNS Servers are configured or provided through DHCP
const char* ntpServer = "pool.ntp.org";
//Duo Auth API uses UTC time, which is queried against the above NTP Server.
//Adjusting these values from '0' are not necessary
const long gmtOffset_sec = 0;
const int daylightOffset_sec = 0;
//UPDATE: Update these variables with the appropriate Duo Application API values
const char* duo_host = "";
const char* duo_akey = "";
const char* duo_skey = "";
//END: Duo Authentication Library Required Variables
//-------------------------------------------------------------------------------------------
void setup(){
//Start Serial Connection at 115200 Baud
Serial.begin(115200);
delay(1000);
//Start WiFi with the provided SSID and PSK
WiFi.begin(ssid, wirelessPsk);
//Wait for a valid WiFi Connection
while (WiFi.status() != WL_CONNECTED){
delay(1000);
Serial.println("Connecting to WiFi..");
}
Serial.println("Connected to the WiFi network");
Serial.println("Configuring NTP Client");
//Configure SNTP Settings for an Accurate Time-source
configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
Serial.println("Duo Authentication Library (Push Example)");
Serial.print("Please enter your Username :");
}
void loop(){
//Check if Wifi is connected before proceeding
if ((WiFi.status() == WL_CONNECTED)){
//Declare Duo Authentication Instance
DuoAuthLib duoAuth;
//Check for New user input from Serial Interface
//NOTE: See checkSerialInput(...) function below for more details
checkSerialInput();
//Check if new data is available to be processed by the application
if (newUserInput == true){
Serial.println(userInputChars);
//-------------------------------------------------------------------------------------------
//PERFORM Primary Identity Authentication Here before proceeding to Duo Authentication
//-------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------
//BEGIN : Duo Authentication
//-------------------------------------------------------------------------------------------
//Declare Variable to hold our Duo Push Request Status
bool duoRequestResult;
//Reset the new data flag as its being processed
newUserInput = false;
//Call the begin(...) function to initialize the Duo Auth Library
duoAuth.begin(duo_host, duo_akey, duo_skey, &timeinfo);
//-------------------------------------------------------------------------------------------
//Optional configuration functions available to control some intermediate features
//
//--------
//Set HTTP Timeout for the Duo Auth Library HTTP Request. Adjust to your requirements
// Default Setting - 30000ms (30 Seconds)
//
//duoAuth.setHttpTimeout(10000);
//--------
//
//--------
//Sets the IP Address of the device for Duo API Auth Requests
// Default: 0.0.0.0
//
//duoAuth.setIPAddress("255.255.255.255");
//--------
//
//--------
//Sets the Push Type Notification Text that is displayed to the Enduser's Mobile Device.
// Note: Only supported with Duo Push functionality
//
//duoAuth.setPushType(10000);
//--------
//
//-------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------
//Call pushAuth(...) function for user defined in 'userInputChars'
//This is a stopping function and will wait until a response is received, an error occurs,
//or the session times out. NOTE: The default HTTP Timeout for the Duo Library is 30 Seconds
//See 'Asynchronous Push Authentication' example for an alternative Push Method
//-------------------------------------------------------------------------------------------
duoRequestResult = duoAuth.pushAuth(userInputChars);
//-------------------------------------------------------------------------------------------
//Check if Duo Auth Request was Successful
//This returns the True/False if the Duo Auth Library API
//Request was successful and received the approriate "OK" response
//If the result returned is 'true'
//-------------------------------------------------------------------------------------------
//https://duo.com/docs/authapi#/auth
//-------------------------------------------------------------------------------------------
if(duoRequestResult == true){
//Check if Duo Authentication for the user was Allowed
if(duoAuth.authSuccessful()){
//Duo Authentication was Successful as the "allow" response was received from the API
Serial.println("Successful Auth!");
//-------------------------------------------------------------------------------------------
//INSERT Your PROTECTED Code HERE
//-------------------------------------------------------------------------------------------
}else{
Serial.println("Failed Auth!");
//Duo Authentication Failed as the "allow" response was NOT received from the API
//-------------------------------------------------------------------------------------------
//INSERT Your Authentication FAILURE Code HERE
//-------------------------------------------------------------------------------------------
}
}else{
Serial.println("Failed Auth!");
//Duo Authentication Failed as the "allow" response was NOT received from the API
//-------------------------------------------------------------------------------------------
//INSERT Your Duo API Request FAILURE Code HERE
//-------------------------------------------------------------------------------------------
}
//-------------------------------------------------------------------------------------------
//END : Duo Authentication
//-------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------
//Reset our Variables for the next User Input/Authentication Request
//-------------------------------------------------------------------------------------------
userInputChars[0] = '\0';
Serial.println("Duo Authentication Library (Push Example)");
Serial.print("Please enter your [Username] :");
}
}
}
//-------------------------------------------------------------------------------------------
//Check Serial for Input and wait for the "Enter" key to be pressed
//-------------------------------------------------------------------------------------------
void checkSerialInput(){
static byte index = 0;
//Define Terminating Character
char endingCharacter = '\n';
char receivedChar;
//-------------------------------------------------------------------------------------------
//Only check for new Serial Data when new data is available from the Serial Interface and we
//have not seen the "Enter" key press yet.
//-------------------------------------------------------------------------------------------
while (Serial.available() > 0 && newUserInput == false){
//We have new input
receivedChar = Serial.read();
if(receivedChar != endingCharacter){
//Detected that other characters were entered
userInputChars[index] = receivedChar;
index++;
if (index >= numChars) {
index = numChars - 1;
}
}else{
//Detected that the 'Enter' button was pressed
userInputChars[index] = '\0';
index = 0;
//Update the flag telling the main loop we now have User Input for processing
newUserInput = true;
}
}
}

View File

@@ -0,0 +1,50 @@
#######################################
# Syntax Coloring Map For Test
#######################################
#######################################
# Datatypes (KEYWORD1)
#######################################
DuoAuthLib KEYWORD1
#######################################
# Methods and Functions (KEYWORD2)
#######################################
begin KEYWORD2
ping KEYWORD2
checkApiKey KEYWORD2
authStatus KEYWORD2
pushAuth KEYWORD2
passcodeAuth KEYWORD2
authSuccessful KEYWORD2
pushWaiting KEYWORD2
setHttpTimeout KEYWORD2
setIPAddress KEYWORD2
setPushType KEYWORD2
getHttpCode KEYWORD2
getHttpResponse KEYWORD2
getApiStat KEYWORD2
getAuthResponseResult KEYWORD2
getAuthResponseStatus KEYWORD2
getAuthResponseStatusMessage KEYWORD2
getAuthTxId KEYWORD2
getApiFailureCode KEYWORD2
getApiFailureMessage KEYWORD2
#######################################
# Instances (KEYWORD2)
#######################################
#######################################
# Constants (LITERAL1)
#######################################

View File

@@ -0,0 +1,9 @@
name=Duo Auth Library
version=1.0.0
author=Gary Oppel
maintainer=Gary Oppel
sentence=Enables Duo Authentication within your ESP32 Wi-Fi Projects
paragraph=Extends Duo Authentication API's for Push, Passcode, and Asynchronous Push Authentication requests.
category=Other
url=https://github.com/CiscoDevNet/Arduino-DuoAuthLibrary-ESP32
architectures=esp32

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