messing around with JWT implementation
This commit is contained in:
parent
41ace49d5c
commit
6700610d35
96
src/ArduinoJsonJWT.cpp
Normal file
96
src/ArduinoJsonJWT.cpp
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
#include "ArduinoJsonJWT.h"
|
||||||
|
|
||||||
|
ArduinoJsonJWT::ArduinoJsonJWT(String secret) : _secret(secret) { }
|
||||||
|
|
||||||
|
void ArduinoJsonJWT::setSecret(String secret){
|
||||||
|
_secret = secret;
|
||||||
|
}
|
||||||
|
|
||||||
|
String ArduinoJsonJWT::sign(String &value) {
|
||||||
|
// create signature
|
||||||
|
Sha256.initHmac((uint8_t*) _secret.c_str(), _secret.length());
|
||||||
|
Sha256.print(value);
|
||||||
|
|
||||||
|
// trim and return
|
||||||
|
return encode(Sha256.resultHmac(), 32);
|
||||||
|
}
|
||||||
|
|
||||||
|
String ArduinoJsonJWT::decode(unsigned char * value) {
|
||||||
|
// create buffer of approperate length
|
||||||
|
size_t decodedLength = decode_base64_length(value) + 1;
|
||||||
|
char decoded[decodedLength];
|
||||||
|
|
||||||
|
// decode
|
||||||
|
decode_base64(value, (unsigned char *) decoded);
|
||||||
|
decoded[decodedLength-1] = 0;
|
||||||
|
|
||||||
|
// return as arduino string
|
||||||
|
return String(decoded);
|
||||||
|
}
|
||||||
|
|
||||||
|
String ArduinoJsonJWT::encode(unsigned char * value , int length) {
|
||||||
|
int encodedIndex = encode_base64_length(length);
|
||||||
|
|
||||||
|
// encode
|
||||||
|
char encoded[encodedIndex];
|
||||||
|
encode_base64(value, length, (unsigned char *) encoded);
|
||||||
|
|
||||||
|
// trim padding
|
||||||
|
while (encoded[--encodedIndex] == '=') {
|
||||||
|
encoded[encodedIndex] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// return as string
|
||||||
|
return String(encoded);
|
||||||
|
}
|
||||||
|
|
||||||
|
String ArduinoJsonJWT::buildJWT(JsonObject &payload) {
|
||||||
|
// serialize, then encode payload
|
||||||
|
String jwt;
|
||||||
|
serializeJson(payload, jwt);
|
||||||
|
jwt = encode((unsigned char *) jwt.c_str(), jwt.length());
|
||||||
|
|
||||||
|
// add the header to payload
|
||||||
|
jwt = JWT_HEADER + '.' + jwt;
|
||||||
|
|
||||||
|
// add signature
|
||||||
|
jwt += '.' + sign(jwt);
|
||||||
|
|
||||||
|
return jwt;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ArduinoJsonJWT::parseJWT(String jwt, JsonDocument &jsonDocument) {
|
||||||
|
// clear json document before we begin, jsonDocument wil be null on failure
|
||||||
|
jsonDocument.clear();
|
||||||
|
|
||||||
|
// must be of minimum length or greater
|
||||||
|
if (jwt.length() <= JWT_SIG_SIZE + JWT_HEADER_SIZE + 2) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// must have the correct header and delimiter
|
||||||
|
if (!jwt.startsWith(JWT_HEADER) || jwt.indexOf('.') != JWT_HEADER_SIZE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// must have signature of correct length
|
||||||
|
int signatureDelimieterIndex = jwt.length() - JWT_SIG_SIZE - 1;
|
||||||
|
if (jwt.lastIndexOf('.') != signatureDelimieterIndex) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// signature must be correct
|
||||||
|
String signature = jwt.substring(signatureDelimieterIndex + 1);
|
||||||
|
jwt = jwt.substring(0, signatureDelimieterIndex);
|
||||||
|
if (sign(jwt) != signature){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// decode payload
|
||||||
|
jwt = jwt.substring(JWT_HEADER_SIZE + 1);
|
||||||
|
jwt = decode((unsigned char *) jwt.c_str());
|
||||||
|
|
||||||
|
// parse payload, clearing json document after failure
|
||||||
|
DeserializationError error = deserializeJson(jsonDocument, jwt);
|
||||||
|
if (error != DeserializationError::Ok || !jsonDocument.is<JsonObject>()){
|
||||||
|
jsonDocument.clear();
|
||||||
|
}
|
||||||
|
}
|
34
src/ArduinoJsonJWT.h
Normal file
34
src/ArduinoJsonJWT.h
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
#ifndef ArduinoJsonJWT_H
|
||||||
|
#define ArduinoJsonJWT_H
|
||||||
|
|
||||||
|
#include "sha256.h"
|
||||||
|
#include "base64.h"
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <ArduinoJson.h>
|
||||||
|
|
||||||
|
#define JWT_HEADER_SIZE 36
|
||||||
|
#define JWT_SIG_SIZE 43
|
||||||
|
|
||||||
|
class ArduinoJsonJWT {
|
||||||
|
|
||||||
|
private:
|
||||||
|
String _secret;
|
||||||
|
|
||||||
|
// {"alg": "HS256", "typ": "JWT"}
|
||||||
|
const String JWT_HEADER = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9";
|
||||||
|
|
||||||
|
String sign(String &value);
|
||||||
|
String encode(unsigned char * value, int length);
|
||||||
|
String decode(unsigned char * value);
|
||||||
|
|
||||||
|
public:
|
||||||
|
ArduinoJsonJWT(String secret);
|
||||||
|
|
||||||
|
void setSecret(String secret);
|
||||||
|
String buildJWT(JsonObject &payload);
|
||||||
|
void parseJWT(String jwt, JsonDocument &jsonDocument);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
@ -10,6 +10,14 @@ SecurityManager::SecurityManager(AsyncWebServer* server, FS* fs) : SettingsPersi
|
|||||||
_signInRequestHandler.setMaxContentLength(MAX_SECURITY_MANAGER_SETTINGS_SIZE);
|
_signInRequestHandler.setMaxContentLength(MAX_SECURITY_MANAGER_SETTINGS_SIZE);
|
||||||
_signInRequestHandler.onRequest(std::bind(&SecurityManager::signIn, this, std::placeholders::_1, std::placeholders::_2));
|
_signInRequestHandler.onRequest(std::bind(&SecurityManager::signIn, this, std::placeholders::_1, std::placeholders::_2));
|
||||||
server->addHandler(&_signInRequestHandler);
|
server->addHandler(&_signInRequestHandler);
|
||||||
|
|
||||||
|
|
||||||
|
// sign in request
|
||||||
|
_testVerifiction.setUri(TEST_VERIFICATION_PATH);
|
||||||
|
_testVerifiction.setMethod(HTTP_POST);
|
||||||
|
_testVerifiction.setMaxContentLength(MAX_SECURITY_MANAGER_SETTINGS_SIZE);
|
||||||
|
_testVerifiction.onRequest(std::bind(&SecurityManager::testVerification, this, std::placeholders::_1, std::placeholders::_2));
|
||||||
|
server->addHandler(&_testVerifiction);
|
||||||
}
|
}
|
||||||
|
|
||||||
SecurityManager::~SecurityManager() {}
|
SecurityManager::~SecurityManager() {}
|
||||||
@ -72,18 +80,33 @@ void SecurityManager::signIn(AsyncWebServerRequest *request, JsonDocument &jsonD
|
|||||||
// create JWT
|
// create JWT
|
||||||
DynamicJsonDocument _jsonDocument(MAX_JWT_SIZE);
|
DynamicJsonDocument _jsonDocument(MAX_JWT_SIZE);
|
||||||
JsonObject jwt = _jsonDocument.to<JsonObject>();
|
JsonObject jwt = _jsonDocument.to<JsonObject>();
|
||||||
jwt["user"] = user.getUsername();
|
jwt["username"] = user.getUsername();
|
||||||
jwt["role"] = user.getRole();
|
jwt["role"] = user.getRole();
|
||||||
|
|
||||||
// send JWT response
|
// send JWT response
|
||||||
AsyncJsonResponse * response = new AsyncJsonResponse(MAX_USERS_SIZE);
|
AsyncJsonResponse * response = new AsyncJsonResponse(MAX_USERS_SIZE);
|
||||||
JsonObject jsonObject = response->getRoot();
|
JsonObject jsonObject = response->getRoot();
|
||||||
jsonObject["access_token"] = jwtHandler.encodeJWT(jwt);
|
jsonObject["access_token"] = jwtHandler.buildJWT(jwt);
|
||||||
response->setLength();
|
response->setLength();
|
||||||
request->send(response);
|
request->send(response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// authentication failed
|
||||||
|
AsyncWebServerResponse *response = request->beginResponse(401);
|
||||||
|
request->send(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SecurityManager::testVerification(AsyncWebServerRequest *request, JsonDocument &jsonDocument){
|
||||||
|
if (jsonDocument.is<JsonObject>()) {
|
||||||
|
String accessToken = jsonDocument["access_token"];
|
||||||
|
DynamicJsonDocument parsedJwt(MAX_JWT_SIZE);
|
||||||
|
if (jwtHandler.parseJWT(accessToken, parsedJwt)){
|
||||||
|
String username = parsedJwt["username"];
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// authentication failed
|
// authentication failed
|
||||||
AsyncWebServerResponse *response = request->beginResponse(401);
|
AsyncWebServerResponse *response = request->beginResponse(401);
|
||||||
request->send(response);
|
request->send(response);
|
||||||
@ -102,7 +125,7 @@ void SecurityManager::begin() {
|
|||||||
readFromFS();
|
readFromFS();
|
||||||
|
|
||||||
// configure secret
|
// configure secret
|
||||||
jwtHandler.setPSK(_jwtSecret);
|
jwtHandler.setSecret(_jwtSecret);
|
||||||
}
|
}
|
||||||
|
|
||||||
User SecurityManager::verifyUser(String jwt) {
|
User SecurityManager::verifyUser(String jwt) {
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
#include <SettingsService.h>
|
#include <SettingsService.h>
|
||||||
#include <DNSServer.h>
|
#include <DNSServer.h>
|
||||||
#include <IPAddress.h>
|
#include <IPAddress.h>
|
||||||
#include <jwt/ArduinoJsonJWT.h>
|
#include <ArduinoJsonJWT.h>
|
||||||
|
|
||||||
#define DEFAULT_JWT_SECRET "esp8266-react"
|
#define DEFAULT_JWT_SECRET "esp8266-react"
|
||||||
|
|
||||||
@ -15,6 +15,7 @@
|
|||||||
#define USERS_PATH "/rest/users"
|
#define USERS_PATH "/rest/users"
|
||||||
#define AUTHENTICATE_PATH "/rest/authenticate"
|
#define AUTHENTICATE_PATH "/rest/authenticate"
|
||||||
#define SIGN_IN_PATH "/rest/signIn"
|
#define SIGN_IN_PATH "/rest/signIn"
|
||||||
|
#define TEST_VERIFICATION_PATH "/rest/verification"
|
||||||
|
|
||||||
#define MAX_JWT_SIZE 128
|
#define MAX_JWT_SIZE 128
|
||||||
#define MAX_SECURITY_MANAGER_SETTINGS_SIZE 512
|
#define MAX_SECURITY_MANAGER_SETTINGS_SIZE 512
|
||||||
@ -85,6 +86,7 @@ class SecurityManager : public SettingsPersistence {
|
|||||||
// server instance
|
// server instance
|
||||||
AsyncWebServer* _server;
|
AsyncWebServer* _server;
|
||||||
AsyncJsonRequestWebHandler _signInRequestHandler;
|
AsyncJsonRequestWebHandler _signInRequestHandler;
|
||||||
|
AsyncJsonRequestWebHandler _testVerifiction;
|
||||||
|
|
||||||
// access point settings
|
// access point settings
|
||||||
String _jwtSecret;
|
String _jwtSecret;
|
||||||
@ -94,6 +96,7 @@ class SecurityManager : public SettingsPersistence {
|
|||||||
// endpoint functions
|
// endpoint functions
|
||||||
void fetchUsers(AsyncWebServerRequest *request);
|
void fetchUsers(AsyncWebServerRequest *request);
|
||||||
void signIn(AsyncWebServerRequest *request, JsonDocument &jsonDocument);
|
void signIn(AsyncWebServerRequest *request, JsonDocument &jsonDocument);
|
||||||
|
void testVerification(AsyncWebServerRequest *request, JsonDocument &jsonDocument);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // end SecurityManager_h
|
#endif // end SecurityManager_h
|
@ -1,3 +1,5 @@
|
|||||||
|
#include "base64.h"
|
||||||
|
|
||||||
unsigned char binary_to_base64(unsigned char v) {
|
unsigned char binary_to_base64(unsigned char v) {
|
||||||
// Capital letters - 'A' is ascii 65 and base64 0
|
// Capital letters - 'A' is ascii 65 and base64 0
|
||||||
if(v < 26) return v + 'A';
|
if(v < 26) return v + 'A';
|
@ -1,50 +0,0 @@
|
|||||||
#include "jwt/ArduinoJsonJWT.h"
|
|
||||||
|
|
||||||
ArduinoJsonJWT::ArduinoJsonJWT(String psk) : _psk(psk) { }
|
|
||||||
|
|
||||||
void ArduinoJsonJWT::setPSK(String psk){
|
|
||||||
_psk = psk;
|
|
||||||
}
|
|
||||||
|
|
||||||
String ArduinoJsonJWT::encodeJWT(JsonObject payload) {
|
|
||||||
// serialize payload
|
|
||||||
String serializedPayload;
|
|
||||||
serializeJson(payload, serializedPayload);
|
|
||||||
|
|
||||||
// calculate length of string
|
|
||||||
uint16_t encodedPayloadLength = encode_base64_length(serializedPayload.length());
|
|
||||||
|
|
||||||
// create JWT char array
|
|
||||||
char encodedJWT[BASE_JWT_LENGTH + encodedPayloadLength];
|
|
||||||
unsigned char* ptr = (unsigned char*) encodedJWT;
|
|
||||||
|
|
||||||
// 1 - add the header
|
|
||||||
memcpy(ptr, JWT_HEADER, JWT_HEADER_LENGTH);
|
|
||||||
ptr += JWT_HEADER_LENGTH;
|
|
||||||
|
|
||||||
// 2 - add payload, trim and null terminate
|
|
||||||
*ptr++ = '.';
|
|
||||||
encode_base64((unsigned char*) serializedPayload.c_str(), serializedPayload.length(), ptr);
|
|
||||||
ptr += encodedPayloadLength;
|
|
||||||
while(*(ptr - 1) == '=') {
|
|
||||||
ptr--;
|
|
||||||
}
|
|
||||||
*(ptr) = 0;
|
|
||||||
|
|
||||||
// ... calculate ...
|
|
||||||
Sha256.initHmac((const unsigned char*)_psk.c_str(), _psk.length());
|
|
||||||
Sha256.print(encodedJWT);
|
|
||||||
|
|
||||||
// 3 - add signature
|
|
||||||
*ptr++ = '.';
|
|
||||||
encode_base64(Sha256.resultHmac(), 32, ptr);
|
|
||||||
ptr += SIGNATURE_LENGTH;
|
|
||||||
while(*(ptr - 1) == '=') {
|
|
||||||
ptr--;
|
|
||||||
}
|
|
||||||
*(ptr) = 0;
|
|
||||||
|
|
||||||
Serial.println(BASE_JWT_LENGTH + encodedPayloadLength);
|
|
||||||
return encodedJWT;
|
|
||||||
}
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
|||||||
#ifndef ArduinoJsonJWT_H
|
|
||||||
#define ArduinoJsonJWT_H
|
|
||||||
|
|
||||||
#include "jwt/base64.h"
|
|
||||||
#include "jwt/sha256.h"
|
|
||||||
#include "jwt/ArduinoJsonJWT.h"
|
|
||||||
|
|
||||||
#include <Arduino.h>
|
|
||||||
#include <ArduinoJson.h>
|
|
||||||
|
|
||||||
class ArduinoJsonJWT {
|
|
||||||
|
|
||||||
private:
|
|
||||||
String _psk;
|
|
||||||
|
|
||||||
// {"alg": "HS256", "typ": "JWT"}
|
|
||||||
const char* JWT_HEADER = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9";
|
|
||||||
const uint16_t JWT_HEADER_LENGTH = strlen(JWT_HEADER);
|
|
||||||
const uint16_t SIGNATURE_LENGTH = encode_base64_length(32);
|
|
||||||
|
|
||||||
// static JWT length is made of:
|
|
||||||
// - the header length
|
|
||||||
// - the signature length
|
|
||||||
// - 2 delimiters, 1 terminator
|
|
||||||
const uint16_t BASE_JWT_LENGTH = JWT_HEADER_LENGTH + SIGNATURE_LENGTH + 3;
|
|
||||||
|
|
||||||
public:
|
|
||||||
ArduinoJsonJWT(String psk);
|
|
||||||
void setPSK(String psk);
|
|
||||||
String encodeJWT(JsonObject payload);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
#endif
|
|
@ -1,6 +1,7 @@
|
|||||||
#include <string.h>
|
|
||||||
#include "sha256.h"
|
#include "sha256.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
uint32_t sha256K[] PROGMEM = {
|
uint32_t sha256K[] PROGMEM = {
|
||||||
0x428a2f98,0x71374491,0xb5c0fbcf,0xe9b5dba5,0x3956c25b,0x59f111f1,0x923f82a4,0xab1c5ed5,
|
0x428a2f98,0x71374491,0xb5c0fbcf,0xe9b5dba5,0x3956c25b,0x59f111f1,0x923f82a4,0xab1c5ed5,
|
||||||
0xd807aa98,0x12835b01,0x243185be,0x550c7dc3,0x72be5d74,0x80deb1fe,0x9bdc06a7,0xc19bf174,
|
0xd807aa98,0x12835b01,0x243185be,0x550c7dc3,0x72be5d74,0x80deb1fe,0x9bdc06a7,0xc19bf174,
|
Loading…
Reference in New Issue
Block a user