From 6700610d35e076cbb9037a6ab3709607bc2b9e79 Mon Sep 17 00:00:00 2001 From: Rick Watson Date: Mon, 6 May 2019 15:50:19 +0100 Subject: [PATCH] messing around with JWT implementation --- src/ArduinoJsonJWT.cpp | 96 ++++++++++++++++++++++++++++++++++++++ src/ArduinoJsonJWT.h | 34 ++++++++++++++ src/SecurityManager.cpp | 31 ++++++++++-- src/SecurityManager.h | 5 +- src/{jwt => }/base64.cpp | 2 + src/{jwt => }/base64.h | 0 src/jwt/ArduinoJsonJWT.cpp | 50 -------------------- src/jwt/ArduinoJsonJWT.h | 34 -------------- src/{jwt => }/sha256.cpp | 3 +- src/{jwt => }/sha256.h | 0 10 files changed, 165 insertions(+), 90 deletions(-) create mode 100644 src/ArduinoJsonJWT.cpp create mode 100644 src/ArduinoJsonJWT.h rename src/{jwt => }/base64.cpp (99%) rename src/{jwt => }/base64.h (100%) delete mode 100644 src/jwt/ArduinoJsonJWT.cpp delete mode 100644 src/jwt/ArduinoJsonJWT.h rename src/{jwt => }/sha256.cpp (99%) rename src/{jwt => }/sha256.h (100%) diff --git a/src/ArduinoJsonJWT.cpp b/src/ArduinoJsonJWT.cpp new file mode 100644 index 0000000..e0a7355 --- /dev/null +++ b/src/ArduinoJsonJWT.cpp @@ -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()){ + jsonDocument.clear(); + } +} diff --git a/src/ArduinoJsonJWT.h b/src/ArduinoJsonJWT.h new file mode 100644 index 0000000..d76b5c6 --- /dev/null +++ b/src/ArduinoJsonJWT.h @@ -0,0 +1,34 @@ +#ifndef ArduinoJsonJWT_H +#define ArduinoJsonJWT_H + +#include "sha256.h" +#include "base64.h" + +#include +#include + +#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 diff --git a/src/SecurityManager.cpp b/src/SecurityManager.cpp index 3a00d9a..f164d12 100644 --- a/src/SecurityManager.cpp +++ b/src/SecurityManager.cpp @@ -10,6 +10,14 @@ SecurityManager::SecurityManager(AsyncWebServer* server, FS* fs) : SettingsPersi _signInRequestHandler.setMaxContentLength(MAX_SECURITY_MANAGER_SETTINGS_SIZE); _signInRequestHandler.onRequest(std::bind(&SecurityManager::signIn, this, std::placeholders::_1, std::placeholders::_2)); 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() {} @@ -72,18 +80,33 @@ void SecurityManager::signIn(AsyncWebServerRequest *request, JsonDocument &jsonD // create JWT DynamicJsonDocument _jsonDocument(MAX_JWT_SIZE); JsonObject jwt = _jsonDocument.to(); - jwt["user"] = user.getUsername(); + jwt["username"] = user.getUsername(); jwt["role"] = user.getRole(); // send JWT response AsyncJsonResponse * response = new AsyncJsonResponse(MAX_USERS_SIZE); JsonObject jsonObject = response->getRoot(); - jsonObject["access_token"] = jwtHandler.encodeJWT(jwt); + jsonObject["access_token"] = jwtHandler.buildJWT(jwt); response->setLength(); request->send(response); + return; + } + } + + // authentication failed + AsyncWebServerResponse *response = request->beginResponse(401); + request->send(response); +} + +void SecurityManager::testVerification(AsyncWebServerRequest *request, JsonDocument &jsonDocument){ + if (jsonDocument.is()) { + String accessToken = jsonDocument["access_token"]; + DynamicJsonDocument parsedJwt(MAX_JWT_SIZE); + if (jwtHandler.parseJWT(accessToken, parsedJwt)){ + String username = parsedJwt["username"]; + } } - // authentication failed AsyncWebServerResponse *response = request->beginResponse(401); request->send(response); @@ -102,7 +125,7 @@ void SecurityManager::begin() { readFromFS(); // configure secret - jwtHandler.setPSK(_jwtSecret); + jwtHandler.setSecret(_jwtSecret); } User SecurityManager::verifyUser(String jwt) { diff --git a/src/SecurityManager.h b/src/SecurityManager.h index a58e24e..2dd0352 100644 --- a/src/SecurityManager.h +++ b/src/SecurityManager.h @@ -6,7 +6,7 @@ #include #include #include -#include +#include #define DEFAULT_JWT_SECRET "esp8266-react" @@ -15,6 +15,7 @@ #define USERS_PATH "/rest/users" #define AUTHENTICATE_PATH "/rest/authenticate" #define SIGN_IN_PATH "/rest/signIn" +#define TEST_VERIFICATION_PATH "/rest/verification" #define MAX_JWT_SIZE 128 #define MAX_SECURITY_MANAGER_SETTINGS_SIZE 512 @@ -85,6 +86,7 @@ class SecurityManager : public SettingsPersistence { // server instance AsyncWebServer* _server; AsyncJsonRequestWebHandler _signInRequestHandler; + AsyncJsonRequestWebHandler _testVerifiction; // access point settings String _jwtSecret; @@ -94,6 +96,7 @@ class SecurityManager : public SettingsPersistence { // endpoint functions void fetchUsers(AsyncWebServerRequest *request); void signIn(AsyncWebServerRequest *request, JsonDocument &jsonDocument); + void testVerification(AsyncWebServerRequest *request, JsonDocument &jsonDocument); }; #endif // end SecurityManager_h \ No newline at end of file diff --git a/src/jwt/base64.cpp b/src/base64.cpp similarity index 99% rename from src/jwt/base64.cpp rename to src/base64.cpp index 0ad89b1..2ebaf27 100644 --- a/src/jwt/base64.cpp +++ b/src/base64.cpp @@ -1,3 +1,5 @@ +#include "base64.h" + unsigned char binary_to_base64(unsigned char v) { // Capital letters - 'A' is ascii 65 and base64 0 if(v < 26) return v + 'A'; diff --git a/src/jwt/base64.h b/src/base64.h similarity index 100% rename from src/jwt/base64.h rename to src/base64.h diff --git a/src/jwt/ArduinoJsonJWT.cpp b/src/jwt/ArduinoJsonJWT.cpp deleted file mode 100644 index ac384b2..0000000 --- a/src/jwt/ArduinoJsonJWT.cpp +++ /dev/null @@ -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; -} - diff --git a/src/jwt/ArduinoJsonJWT.h b/src/jwt/ArduinoJsonJWT.h deleted file mode 100644 index 1be6c90..0000000 --- a/src/jwt/ArduinoJsonJWT.h +++ /dev/null @@ -1,34 +0,0 @@ -#ifndef ArduinoJsonJWT_H -#define ArduinoJsonJWT_H - -#include "jwt/base64.h" -#include "jwt/sha256.h" -#include "jwt/ArduinoJsonJWT.h" - -#include -#include - -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 diff --git a/src/jwt/sha256.cpp b/src/sha256.cpp similarity index 99% rename from src/jwt/sha256.cpp rename to src/sha256.cpp index 7bdb2df..6ff1193 100644 --- a/src/jwt/sha256.cpp +++ b/src/sha256.cpp @@ -1,6 +1,7 @@ -#include #include "sha256.h" +#include + uint32_t sha256K[] PROGMEM = { 0x428a2f98,0x71374491,0xb5c0fbcf,0xe9b5dba5,0x3956c25b,0x59f111f1,0x923f82a4,0xab1c5ed5, 0xd807aa98,0x12835b01,0x243185be,0x550c7dc3,0x72be5d74,0x80deb1fe,0x9bdc06a7,0xc19bf174, diff --git a/src/jwt/sha256.h b/src/sha256.h similarity index 100% rename from src/jwt/sha256.h rename to src/sha256.h