This commit is contained in:
Simon 2021-10-02 01:43:52 +01:00
parent 1b4c4add09
commit a6f0f8c72a
16 changed files with 496 additions and 35 deletions

View File

@ -5,4 +5,5 @@ go 1.15
require (
github.com/gin-gonic/gin v1.7.4
go.mongodb.org/mongo-driver v1.7.2
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
)

View File

@ -19,6 +19,10 @@ func main() {
func initializeRoutes(r *gin.Engine) {
r.POST("/data", src.CreateData)
r.PUT("/data/:id", src.CreateEnvironmentData)
r.Use(src.AEADHandler)
{
r.PUT("/data/authed/:id", src.CreateEnvironmentData)
}
}
func getPort() string {

View File

@ -2,15 +2,22 @@ package src
import (
"context"
"fmt"
"encoding/base64"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"go.mongodb.org/mongo-driver/mongo/readpref"
"golang.org/x/crypto/blake2s"
)
const uri = "mongodb://192.168.0.159:27017"
const salt = "ENVIRONMENT"
var dbCollection *mongo.Collection
var dbDevices *mongo.Collection
var mongoClient *mongo.Client
func DbConnect() {
@ -23,8 +30,38 @@ func DbConnect() {
if err := mongoClient.Ping(context.TODO(), readpref.Primary()); err != nil {
panic(err)
}
db := mongoClient.Database("Environment")
dbCollection = db.Collection("Main")
dbDevices = db.Collection("Devices")
}
dbCollection = mongoClient.Database("Environment").Collection("Main")
func GetDeviceKey(api uint64) ([]byte, error) {
apiSigned := int64(api)
filter := bson.D{{"ApiID", apiSigned}}
var result bson.M
err := dbDevices.FindOne(context.TODO(), filter).Decode(&result)
if err != nil {
return nil, err
}
key, err := DeriveKey(result["Passcode"].(string))
fmt.Printf(base64.StdEncoding.EncodeToString(key))
// We should cache this!
return key, err
}
func DeriveKey(passcode string) ([]byte, error) {
hash, err := blake2s.New256(nil)
if err != nil {
return nil, err
}
hash.Write([]byte(salt))
hash.Write([]byte(passcode))
fmt.Printf("SALT %s PASS %s\n", salt, passcode)
return hash.Sum(nil), nil
}
func DbDisconnect() {

58
Api/src/middleware.go Normal file
View File

@ -0,0 +1,58 @@
package src
import (
"bytes"
"encoding/base64"
"fmt"
"io/ioutil"
"net/http"
"strconv"
"github.com/gin-gonic/gin"
"golang.org/x/crypto/chacha20poly1305"
)
const ivSize = 12
const DecryptedData = "DecryptedData"
func AEADHandler(c *gin.Context) {
// get id
uintID, err := strconv.ParseUint(c.Param("id"), 10, 64)
if err != nil {
fmt.Printf("ERR %s", err.Error())
c.AbortWithStatus(http.StatusUnauthorized)
} else {
// get key
passcode, err := GetDeviceKey(uintID)
if err != nil {
fmt.Printf("ERR %s", err.Error())
c.AbortWithStatus(http.StatusNotFound)
} else {
// get content
data, err := c.GetRawData()
if err != nil {
fmt.Printf("ERR %s", err.Error())
c.AbortWithStatus(http.StatusBadRequest)
} else {
// decrypt
iv, ciphertext := data[:ivSize], data[ivSize:]
aead, err := chacha20poly1305.New(passcode)
if err != nil {
fmt.Printf("ERR %s", err.Error())
c.AbortWithStatus(http.StatusInternalServerError)
} else {
fmt.Printf("iv: %s cypher: %s", base64.StdEncoding.EncodeToString(iv), base64.StdEncoding.EncodeToString(ciphertext))
plaintext, err := aead.Open(nil, iv, ciphertext, nil)
if err != nil {
fmt.Printf("ERR %s", err.Error())
c.AbortWithStatus(http.StatusBadRequest)
} else {
c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(plaintext))
}
}
}
}
}
}

View File

@ -3,6 +3,7 @@
#include <Ticker.h>
#include <U8g2lib.h>
#include <ESP8266HTTPClient.h>
#include "byte_buffer.h"
//scl D1
//sda D2
@ -26,9 +27,16 @@ float humidity = 0;
#define TEMP "Temperature: "
#define HUMID "Humidity: "
#define DERIVATION_HASH_SIZE 32
static constexpr const char *DERIVATION_SALT = "ENVIRONMENT";
void UpdateEnvironment();
void PostEnvironment();
void InitializeWifi();
utils::byte_buffer Encrypt(std::string data);
void DeriveKey();

View File

@ -0,0 +1,19 @@
#pragma once
#include <cstdint>
#include <string>
namespace Environment
{
class Settings
{
private:
/* data */
public:
uint64_t apiID;
std::string endpoint;
std::string passcode;
};
}

View File

@ -26,7 +26,7 @@ class setup_server
bool serverConnection;
uint8_t connect_to_ap(const std::string ssid, const std::string password);
uint8_t connect_to_ap(const std::string &ssid, const std::string &password);
void root_callback();

View File

@ -0,0 +1,200 @@
#include <byte_buffer.h>
#include <cstdlib>
#include <algorithm>
#include <cstring>
#include <osapi.h>
#include <libb64/cdecode.h>
#include <libb64/cencode.h>
namespace utils
{
byte_buffer::byte_buffer(uint_fast16_t length)
: _length(length)
{
// +1 to allow this to be safely printed as a char
_buffer = reinterpret_cast<unsigned char *>(std::calloc(length + 1, sizeof(unsigned char)));
}
byte_buffer::~byte_buffer()
{
clear_buffer();
std::free(_buffer);
}
const bool byte_buffer::is_valid()
{
return _buffer != nullptr;
}
unsigned char *byte_buffer::get_ptr()
{
if (is_valid())
{
return _buffer;
}
else
{
return nullptr;
}
}
/////////////////////////////////////////////////////////////////////////////////////////
// Get the length of the buffer.
//
// Returns: The length of the buffer, or 0 if the buffer is invalid.
/////////////////////////////////////////////////////////////////////////////////////////
const uint_fast16_t byte_buffer::get_length()
{
if (is_valid())
{
return _length;
}
else
{
return 0;
}
}
/////////////////////////////////////////////////////////////////////////////////////////
// Get a pointer to a location in the buffer, length is updated to show the length
// available in that buffer
//
// index: The location to get a pointer to.
// length: The length of the data you expect to access from this pointer.
//
// Returns: A ptr to the buffer, or nullptr if reading that much data would cause
// an overflow
/////////////////////////////////////////////////////////////////////////////////////////
unsigned char *byte_buffer::get_ptr(const uint_fast16_t index, uint_fast16_t &length)
{
unsigned char *ret = nullptr;
if (is_valid() && _length > index)
{
length = _length - index;
ret = &_buffer[index];
}
else
{
length = 0;
}
return ret;
}
// Copy data into this buffer. The index value is updated to the index of the next point in the buffer
const bool byte_buffer::copy_from(const void *const sourceBuffer, uint_fast16_t &index, const uint_fast16_t &length)
{
auto ret = false;
if (is_valid() &&
sourceBuffer != nullptr &&
(index + length) <= _length)
{
std::memcpy(_buffer + index, sourceBuffer, length);
index += length;
ret = true;
}
return ret;
}
// Copy data out of this buffer. The index value is updated to the index of the next point in the buffer
const bool byte_buffer::copy_to(void *destinationBuffer, uint_fast16_t &index, const uint_fast16_t &length)
{
auto ret = false;
if (is_valid() &&
destinationBuffer != nullptr &&
(index + length) < _length)
{
std::memcpy(destinationBuffer, _buffer + index, length);
index += length;
ret = true;
}
return ret;
}
const bool byte_buffer::clone(byte_buffer &bufferToClone)
{
auto ret = false;
if (is_valid() &&
bufferToClone.is_valid() &&
bufferToClone.get_length() <= _length)
{
std::memcpy(bufferToClone.get_ptr(), 0, bufferToClone.get_length());
ret = true;
}
return ret;
}
const void byte_buffer::clear_buffer()
{
if (is_valid())
{
std::fill(_buffer, _buffer + _length, 0);
}
}
const void byte_buffer::fill_random()
{
if (is_valid())
{
os_get_random(_buffer, _length);
}
}
// Return the size of the data stored
const uint_fast16_t byte_buffer::load_base64(std::string base64Data)
{
auto read = 0u;
if (is_valid() && _length >= base64_decode_expected_len(base64Data.length()))
{
base64_decodestate state;
base64_init_decodestate(&state);
read = base64_decode_block(base64Data.c_str(), base64Data.length(), reinterpret_cast<char *>(_buffer), &state);
}
return read;
}
//// Get a base64 encoded string of this buffer.
const std::string byte_buffer::get_base64()
{
if (!is_valid())
{
return "";
}
auto b64String = std::string("");
auto size = base64_encode_expected_len_nonewlines(_length) + 1;
auto buffer = reinterpret_cast<char *>(std::calloc(size, 1));
if (buffer != nullptr)
{
base64_encodestate state;
base64_init_encodestate_nonewlines(&state);
auto encoded = base64_encode_block(reinterpret_cast<const char *>(_buffer), _length, buffer, &state);
encoded = base64_encode_blockend(buffer + encoded, &state);
b64String = std::string(buffer);
std::free(buffer);
}
return b64String;
}
const std::string byte_buffer::to_string()
{
if (!is_valid())
{
return "";
}
// Make sure that extra byte really is null!
_buffer[_length] = '\0';
return std::string(reinterpret_cast<char *>(_buffer));
}
} // namespace Control_System

View File

@ -0,0 +1,38 @@
#pragma once
#include <stdint.h>
#include <string>
namespace utils
{
class byte_buffer
{
public:
byte_buffer(uint_fast16_t length);
~byte_buffer();
const bool is_valid();
unsigned char *get_ptr();
const uint_fast16_t get_length();
unsigned char *get_ptr(const uint_fast16_t index, uint_fast16_t &length);
const bool copy_to(void *destinationBuffer, uint_fast16_t &index, const uint_fast16_t &length);
const bool copy_from(const void *const sourceBuffer, uint_fast16_t &index, const uint_fast16_t &length);
const bool clone(byte_buffer &bufferToClone);
const void clear_buffer();
const void fill_random();
const uint_fast16_t load_base64(std::string base64Data);
const std::string get_base64();
const std::string to_string();
private:
unsigned char *_buffer;
const uint_fast16_t _length;
};
} // namespace Control_System

View File

@ -15,3 +15,4 @@ framework = arduino
lib_deps =
olikraus/U8g2@^2.28.8
finitespace/BME280@^3.0.0
rweather/Crypto@^0.2.0

View File

@ -4,37 +4,58 @@
#include "setup_server.h"
#include <ESP8266WiFi.h>
#include <string>
#include "settings.h"
void setup()
#include <ChaChaPoly.h>
#include <BLAKE2s.h>
Environment::Settings settings;
utils::byte_buffer passcode(DERIVATION_HASH_SIZE);
void setup()
{
settings.apiID = 11085093266951290551U;
settings.endpoint = std::string("http://192.168.64.244:8080/data/authed/");
settings.passcode = std::string("password");
Serial.begin(9600);
Serial.println("\nSTART");
Serial.print("Last Shutdown: ");
Serial.println(ESP.getResetReason());
Serial.print("Derive Key: ");
DeriveKey();
// put your setup code here, to run once:
screen.begin();
Wire.begin();
bme.begin();
environmentUpdate.attach_scheduled(1, UpdateEnvironment);
environmentPost.attach_scheduled(60, PostEnvironment);
screen.firstPage();
do
{
screen.setFont(FONT);
screen.drawStr(2, LINE_1, "Connecting...");
} while (screen.nextPage());
InitializeWifi();
}
void loop()
void loop()
{
// put your main code here, to run repeatedly:
screen.firstPage();
do{
do
{
screen.setFont(FONT);
screen.drawStr(2, LINE_1, "Hello World!");
screen.drawStr(2, LINE_2, "Hi Butlersaurus!");
}while(screen.nextPage());
} while (screen.nextPage());
delay(1000);
screen.firstPage();
do{
do
{
screen.setFont(FONT);
screen.drawStr(2, LINE_1, TEMP);
screen.setCursor(screen.getStrWidth(TEMP), LINE_1);
@ -42,35 +63,68 @@ void loop()
screen.drawStr(2, LINE_2, HUMID);
screen.setCursor(screen.getStrWidth(HUMID), LINE_2);
screen.print(humidity);
}while(screen.nextPage());
} while (screen.nextPage());
delay(1000);
}
void UpdateEnvironment()
{
bme.read(pressure, temp, humidity,tempUnit, presUnit);
bme.read(pressure, temp, humidity, tempUnit, presUnit);
}
void PostEnvironment()
{
utils::debug_print("Post");
requests.begin(client, "http://192.168.64.239/api/environment");
//requests.begin(client, "http://192.168.64.239/api/environment");
auto endpoint = std::string(settings.endpoint);
endpoint.append(std::to_string(settings.apiID));
requests.begin(client, endpoint.c_str());
requests.addHeader("Content-Type", "application/json");
char dataBuffer[128];
char formatString[] = "{\"h\":\"%f\",\"t\":\"%f\"}";
char dataBuffer[128] = {0};
char formatString[] = "{\"h\":%f,\"t\":%f}";
snprintf(&dataBuffer[0], 128, &formatString[0], humidity, temp);
auto code = requests.POST(dataBuffer);
if (code > 0){
utils::debug_print(code);
}else{
utils::debug_print(requests.errorToString(code));
auto data = Encrypt(std::string(dataBuffer));
auto code = requests.PUT(data.get_ptr(), data.get_length());
if (code > 0)
{
utils::debug_print(code);
}
else
{
utils::debug_print(requests.errorToString(code));
}
requests.end();
utils::debug_print("Posted");
}
utils::byte_buffer Encrypt(std::string data)
{
// struct used by go, with basically no docs :|
// iv (12) | cipher (x) | tag(16)
const uint_fast16_t ivSize = 12;
auto size = ivSize + data.length() + 16;
auto dataOut = utils::byte_buffer(size);
// for the iv
dataOut.fill_random();
// first 12 bytes
auto cipher = ChaChaPoly();
cipher.setKey(passcode.get_ptr(), passcode.get_length());
cipher.setIV(dataOut.get_ptr(), ivSize);
uint_fast16_t buffLen = 0;
auto encryptBuffer = dataOut.get_ptr(ivSize, buffLen);
cipher.encrypt(encryptBuffer, (const uint8_t *)data.c_str(), data.length());
auto tagBuffer = dataOut.get_ptr(ivSize + data.length(), buffLen);
cipher.computeTag(tagBuffer, buffLen);
return dataOut;
}
void InitializeWifi()
{
delay(1);
WiFi.mode(WIFI_STA);
WiFi.begin();
auto ret = WiFi.waitForConnectResult();
@ -83,6 +137,16 @@ void InitializeWifi()
std::string password = std::string();
setup.get_connection(ssid, password);
}
WiFi.setAutoConnect(true);
utils::debug_print("Wifi Connected: " + WiFi.SSID());
}
void DeriveKey()
{
BLAKE2s hash = BLAKE2s();
hash.reset();
hash.update(DERIVATION_SALT, strlen(DERIVATION_SALT));
hash.update(settings.passcode.c_str(), settings.passcode.length());
hash.finalize(passcode.get_ptr(), passcode.get_length());
utils::debug_print(passcode.get_base64());
}

View File

@ -1,15 +1,15 @@
#include <setup_server.h>
#include <string>
#include <html/first_connection_page.h>
#ifdef _DEBUG
#define _DEBUG_PRINT(x) utils::debug_print(x)
#define _DEBUG_PRINT(x) utils::debug_print(x)
#else
#define _DEBUG_PRINT(x)
#define _DEBUG_PRINT(x)
#endif
setup_server::setup_server()
: server(new ESP8266WebServer(80))
setup_server::setup_server()
: server(new ESP8266WebServer(80))
{
server->on("/", [this] { this->root_callback(); });
server->on("/submit", HTTP_POST, [this] { this->ap_submission_callback(); });
@ -53,7 +53,7 @@ setup_server::SetupErrorCodes setup_server::get_connection(const std::string &ss
return ret;
}
uint8_t setup_server::connect_to_ap(const std::string ssid, const std::string password)
uint8_t setup_server::connect_to_ap(const std::string &ssid, const std::string &password)
{
_DEBUG_PRINT("Connecting " + ssid + ":" + password);
WiFi.begin(ssid.c_str(), password.c_str());
@ -65,10 +65,11 @@ void setup_server::root_callback()
{
_DEBUG_PRINT("Request \"/\" from " + server->client().remoteIP().toString());
auto pageSize = strlen_P(first_connection_page);
auto outBuffer = new char(pageSize);
auto outBuffer = new char[pageSize + 1];
strncpy_P(outBuffer, first_connection_page, pageSize);
outBuffer[pageSize] = '\0';
server->send(200, "text/html", outBuffer);
delete outBuffer;
delete[] outBuffer;
}
void setup_server::ap_submission_callback()

View File

@ -5,6 +5,7 @@ using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using MongoDB.Bson;
using MongoDB.Driver;
using System.Security.Claims;
namespace ManagementPage.Pages
{
@ -44,16 +45,34 @@ namespace ManagementPage.Pages
return NotFound();
}
var filter = new BsonDocument("ApiID", deviceId);
// get the logged in user
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
var filter = new BsonDocument("OpenId", userId);
var claims = await _dbClient.AccountsCollection.Find(filter).SingleOrDefaultAsync();
// No user?
if (claims == null)
{
return Forbid();
}
// Get the device
filter = new BsonDocument("ApiID", deviceId);
device = await _dbClient.DeviceCollection.Find(filter).FirstOrDefaultAsync();
FindOptions<EnvironmentData> options = new FindOptions<EnvironmentData>
// No device?
if (device == null)
{
Limit = 100,
NoCursorTimeout = false
};
return NotFound();
}
using var cursor = await _dbClient.Collection.FindAsync(filter, options);
// Device owned by someone else?
if (!claims.Devices.Contains(device._id))
{
return Forbid();
}
using var cursor = await _dbClient.Collection.Find(filter).SortByDescending(x => x.Time).Limit(100).ToCursorAsync();
data.AddRange(await cursor.ToListAsync());
foreach (var item in data)
{
@ -61,6 +80,9 @@ namespace ManagementPage.Pages
temperature.Add(new DataSet() { x = item.Time, y = item.Temperature });
}
humidity.Reverse();
temperature.Reverse();
return Page();
}
}

View File

@ -51,7 +51,7 @@ namespace ManagementPage.Pages
foreach (var item in data)
{
var filter = new BsonDocument("ApiID", item.ApiID);
var envData = await _dbClient.Collection.Find(filter).FirstOrDefaultAsync();
var envData = await _dbClient.Collection.Find(filter).SortByDescending(x => x.Time).FirstOrDefaultAsync();
envData ??= new EnvironmentData(item.ApiID, 0, 0, DateTime.UtcNow);
currentEnvironment.Add(item.ApiID, envData);
}
@ -65,9 +65,15 @@ namespace ManagementPage.Pages
}
var id = BitConverter.ToInt64(Guid.NewGuid().ToByteArray(),4);
var device = new DeviceData(NewDevice.Name, NewDevice.Passcode, id);
device._id = ObjectId.GenerateNewId();
await _dbClient.DeviceCollection.InsertOneAsync(device);
return Page();
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
var filter = new BsonDocument("OpenId", userId);
var update = Builders<UserData>.Update.AddToSet("Devices", device._id);
await _dbClient.AccountsCollection.UpdateOneAsync(filter, update);
return new RedirectToPageResult("/UserHome");
}
}
}

View File

@ -12,7 +12,7 @@
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "https://localhost:8800;http://+:80",
"applicationUrl": "https://environment.51m0n.com;http://+:80",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}

View File

@ -8,5 +8,7 @@
},
"oidc": {
"region": "openid-connect",
"clientid": "51m0n-temperature",
"clientsecret": "577b32ae-d36e-42ec-b6a2-92025dc16619"
}
}