commit af6dcf2629edf126c91a12146af07f8c02e6ca74 Author: Lech Date: Sun Dec 17 12:31:13 2017 +0100 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a9135f9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +*.config +*.creator +*.creator.* +*.files +*.includes diff --git a/ArduinoGpio.cpp b/ArduinoGpio.cpp new file mode 100644 index 0000000..63190e4 --- /dev/null +++ b/ArduinoGpio.cpp @@ -0,0 +1,52 @@ +#include "ArduinoGpio.h" +#include + +ArduinoGpio::ArduinoGpio(int pin, bool initialValue, IGpio::Mode initialMode): + pin(pin) +{ + /* There is no protection to prevent from creation of multiple instances + * of single pin, as Arduino does not support C++ exceptions. + * Only one parent object may own instance of a pin. + */ + write(initialValue); + setMode((initialMode)); +} + +ArduinoGpio::~ArduinoGpio() +{ + setMode(Mode::Input); + write(false); +} + +bool ArduinoGpio::read() +{ + return (digitalRead(pin) != 0); +} + +void ArduinoGpio::write(bool newValue) +{ + digitalWrite(pin, newValue ? HIGH : LOW); +} + +IGpio::Mode ArduinoGpio::getMode() +{ + return mode; +} + +void ArduinoGpio::setMode(IGpio::Mode newMode) +{ + mode = newMode; + pinMode(pin, toAdruinoMode(newMode)); +} + +int ArduinoGpio::toAdruinoMode(IGpio::Mode mode) +{ + switch(mode) { + case Mode::Input: + return INPUT; + case Mode::InputPullup: + return INPUT_PULLUP; + case Mode::Output: + return OUTPUT; + } +} diff --git a/ArduinoGpio.h b/ArduinoGpio.h new file mode 100644 index 0000000..ef31fb6 --- /dev/null +++ b/ArduinoGpio.h @@ -0,0 +1,22 @@ +#ifndef ARDUINOGPIO_H +#define ARDUINOGPIO_H + +#include "IGpio.h" + +class ArduinoGpio : public IGpio +{ +public: + ArduinoGpio(int pin, bool initialValue, Mode initialMode); + virtual ~ArduinoGpio(); + virtual bool read(); + virtual void write(bool newValue); + virtual Mode getMode(); + virtual void setMode(Mode newMode); + +private: + int pin; + Mode mode; + int toAdruinoMode(Mode mode); +}; + +#endif // ARDUINOGPIO_H diff --git a/ArduinoSerialLogger.cpp b/ArduinoSerialLogger.cpp new file mode 100644 index 0000000..24f47a4 --- /dev/null +++ b/ArduinoSerialLogger.cpp @@ -0,0 +1,63 @@ +#include "ArduinoSerialLogger.h" +#include + +ArduinoSerialLogger::ArduinoSerialLogger(Serial_ &hardwareSerial): + hardwareSerial(hardwareSerial) +{ + hardwareSerial.begin(DEBUG_CONSOLE_BAUD); +} + +ArduinoSerialLogger::~ArduinoSerialLogger() +{ + hardwareSerial.end(); +} + +void ArduinoSerialLogger::debug(const char *format, ...) const +{ + va_list args; + va_start(args, format); + formatLog("DBG/", format, args); + va_end(args); +} + +void ArduinoSerialLogger::info(const char *format, ...) const +{ + va_list args; + va_start(args, format); + formatLog("INF/", format, args); + va_end(args); +} + +void ArduinoSerialLogger::warning(const char *format, ...) const +{ + va_list args; + va_start(args, format); + formatLog("WRN/", format, args); + va_end(args); +} + +void ArduinoSerialLogger::error(const char *format, ...) const +{ + va_list args; + va_start(args, format); + formatLog("ERR/", format, args); + va_end(args); +} + +void ArduinoSerialLogger::flush() const +{ + hardwareSerial.flush(); +} + +void ArduinoSerialLogger::formatLog(const char *level, const char *format, va_list args) const +{ + char newFormat[128]; + /* This is the least crappy solution I came up with at 23:40 after a beer. + * sstream is not available on this platform. + * CAN YOU SPOT THE BUG? */ + snprintf(newFormat, sizeof(newFormat), "%s%s\n", level, format); + + char buffer[256]; + vsnprintf(buffer, sizeof(buffer), newFormat, args); + hardwareSerial.print(buffer); +} diff --git a/ArduinoSerialLogger.h b/ArduinoSerialLogger.h new file mode 100644 index 0000000..6082d62 --- /dev/null +++ b/ArduinoSerialLogger.h @@ -0,0 +1,31 @@ +#ifndef ARDUINOSERIALLOGGER_H +#define ARDUINOSERIALLOGGER_H + +#include "ILogger.h" +#include + +#define Serial_ HardwareSerial +class ArduinoSerialLogger : public ILogger +{ +public: + explicit ArduinoSerialLogger(Serial_& hardwareSerial); + virtual ~ArduinoSerialLogger(); + + virtual void debug(const char *format, ...) const; + virtual void info(const char *format, ...) const; + virtual void warning(const char* format, ...) const; + virtual void error(const char* format, ...) const; + + virtual void flush() const; + +private: + enum { + DEBUG_CONSOLE_BAUD = 9600 + }; + + void formatLog(const char* level, const char* format, va_list args) const; + + Serial_& hardwareSerial; +}; + +#endif // ARDUINOSERIALLOGGER_H diff --git a/DoorLock.cpp b/DoorLock.cpp new file mode 100644 index 0000000..22f70b8 --- /dev/null +++ b/DoorLock.cpp @@ -0,0 +1,29 @@ +#include "DoorLock.h" + +#include + +DoorLock::DoorLock(IGpio &gpio, DoorLock::ActiveState activeState, DoorLock::TMilliseconds openTime): + gpio(gpio), + activeState(activeState), + openTime(openTime) +{ + deactivate(); + gpio.setMode(IGpio::Mode::Output); +} + +void DoorLock::open() +{ + activate(); + delay(openTime); + deactivate(); +} + +void DoorLock::activate() +{ + gpio.write(activeState == ActiveState::High); +} + +void DoorLock::deactivate() +{ + gpio.write(activeState != ActiveState::High); +} diff --git a/DoorLock.h b/DoorLock.h new file mode 100644 index 0000000..8b7f7e2 --- /dev/null +++ b/DoorLock.h @@ -0,0 +1,27 @@ +#ifndef DOORLOCK_H +#define DOORLOCK_H + +#include "IGpio.h" + +class DoorLock +{ +public: + enum class ActiveState { + Low, + High, + }; + typedef int TMilliseconds; + + DoorLock(IGpio& gpio, ActiveState activeState, TMilliseconds openTime); + void open(); + +private: + void activate(); + void deactivate(); + + IGpio& gpio; + ActiveState activeState; + TMilliseconds openTime; +}; + +#endif // DOORLOCK_H diff --git a/DoorLockController.cpp b/DoorLockController.cpp new file mode 100644 index 0000000..bea602d --- /dev/null +++ b/DoorLockController.cpp @@ -0,0 +1,61 @@ +#include "DoorLockController.h" + +DoorLockController::DoorLockController() : + logger(Serial), + greenLedGpio(LED_GREEN_PIN, false, IGpio::Mode::Output), + redLedGpio(LED_RED_PIN, false, IGpio::Mode::Output), + doorLockGpio(DOOR_PIN, false, IGpio::Mode::Output), + statusLed(redLedGpio, greenLedGpio), + doorLock(doorLockGpio, DoorLock::ActiveState::Low, DOOR_OPEN_TIME_MS), + nfcAuthenticator(NFC_SLAVE_SELECT_PIN, NFC_RESET_PIN, logger), + oneWireAuthenticator(ONEWIRE_PIN, logger), + authenticators{&nfcAuthenticator, &oneWireAuthenticator}, + unauthorizedAccess(false), + heartbeatCounter(0) +{ + logger.info("Ready. Waiting for keys."); +} + +void DoorLockController::heartbeat() +{ + heartbeatCounter = (heartbeatCounter == BLINK_INTERVAL_CYCLES - 1) ? 0 : heartbeatCounter+1; + if(heartbeatCounter == 0) + statusLed.setState(unauthorizedAccess ? DualColorLed::State::Red : DualColorLed::State::Green); +} + +void DoorLockController::checkForKeys() +{ + for(IAuthenticator* authenticator : authenticators) { + Key key = authenticator->getKey(); + if(!key.isValid()) + continue; + + if(keyDatabase.contains(key)) + { + statusLed.setState(DualColorLed::State::Green); + logger.info("Access granted."); + unauthorizedAccess = false; + doorLock.open(); + continue; + } + else + { + statusLed.setState(DualColorLed::State::Red); + logger.error("ACCESS DENIED!"); + unauthorizedAccess = true; + delay(DOOR_OPEN_TIME_MS); + continue; + } + } +} + +void DoorLockController::run() +{ + for(;;) + { + heartbeat(); + checkForKeys(); + statusLed.setState(DualColorLed::State::Off); + delay(POLLING_INTERVAL_MS); + } +} diff --git a/DoorLockController.h b/DoorLockController.h new file mode 100644 index 0000000..484b79b --- /dev/null +++ b/DoorLockController.h @@ -0,0 +1,50 @@ +#ifndef CDOORLOCKCONTROLLER_H +#define CDOORLOCKCONTROLLER_H + +#include "ArduinoGpio.h" +#include "ArduinoSerialLogger.h" + +#include "DoorLock.h" +#include "NfcAuthenticator.h" +#include "OneWireAuthenticator.h" +#include "HardcodedKeyStorage.h" +#include "DualColorLed.h" + +class DoorLockController +{ +public: + DoorLockController(); + void run(); + +private: + enum { + NFC_SLAVE_SELECT_PIN = 10, + NFC_RESET_PIN = 9, + ONEWIRE_PIN = 8, + DOOR_PIN = 4, + LED_RED_PIN = 3, + LED_GREEN_PIN = 2, + DOOR_OPEN_TIME_MS = 3000, + NUM_AUTHENTICATORS=2, + POLLING_INTERVAL_MS = 250, + BLINK_INTERVAL_CYCLES = 20 + }; + + void heartbeat(); + void checkForKeys(); + + ArduinoSerialLogger logger; + ArduinoGpio greenLedGpio, redLedGpio, doorLockGpio; + + DualColorLed statusLed; + DoorLock doorLock; + NfcAuthenticator nfcAuthenticator; + OneWireAuthenticator oneWireAuthenticator; + IAuthenticator* authenticators[NUM_AUTHENTICATORS]; + HardcodedKeyStorage keyDatabase; + + bool unauthorizedAccess; + short heartbeatCounter; +}; + +#endif // CDOORLOCKCONTROLLER_H diff --git a/DualColorLed.cpp b/DualColorLed.cpp new file mode 100644 index 0000000..6772805 --- /dev/null +++ b/DualColorLed.cpp @@ -0,0 +1,37 @@ +#include "DualColorLed.h" + +#include + +DualColorLed::DualColorLed(IGpio& redGpio, IGpio& greenGpio): + redGpio(redGpio), + greenGpio(greenGpio), + currentState(State::Off) +{ + redGpio.write(false); + greenGpio.write(false); + redGpio.setMode(IGpio::Mode::Output); + greenGpio.setMode(IGpio::Mode::Output); +} + +void DualColorLed::setState(DualColorLed::State newState) +{ + if(currentState == newState) + return; + + currentState = newState; + + switch(newState) { + case State::Off: + redGpio.write(false); + greenGpio.write(false); + return; + case State::Red: + redGpio.write(true); + greenGpio.write(false); + return; + case State::Green: + redGpio.write(false); + greenGpio.write(true); + return; + } +} diff --git a/DualColorLed.h b/DualColorLed.h new file mode 100644 index 0000000..129e285 --- /dev/null +++ b/DualColorLed.h @@ -0,0 +1,24 @@ +#ifndef DUALCOLORLED_H +#define DUALCOLORLED_H + +#include "IGpio.h" + +class DualColorLed +{ +public: + enum class State { + Off, + Red, + Green + }; + DualColorLed(IGpio& redGpio, IGpio& greenGpio); + + void setState(State newState); + +private: + IGpio& redGpio; + IGpio& greenGpio; + State currentState; +}; + +#endif // DUALCOLORLED_H diff --git a/HardcodedKeyStorage.cpp b/HardcodedKeyStorage.cpp new file mode 100644 index 0000000..927128f --- /dev/null +++ b/HardcodedKeyStorage.cpp @@ -0,0 +1,36 @@ +#include "HardcodedKeyStorage.h" + +HardcodedKeyStorage::HardcodedKeyStorage(): + keyTable( { + MifareClassicKey((const uint8_t[]) { 0x01, 0x02, 0x03, 0x04 }), // SAMPLE. DO NOT USE! + }) +{} + +HardcodedKeyStorage::~HardcodedKeyStorage() {} + +bool HardcodedKeyStorage::contains(const Key& inputKey) +{ + if(!inputKey.isValid()) + return false; + + for(const Key& keyFromTable: keyTable) + { + if(inputKey == keyFromTable) + return true; + } + + return false; +} + +bool HardcodedKeyStorage::insert(const Key &existingKey, const Key &newKey) +{ + (void) existingKey; + (void) newKey; + return false; +} + +bool HardcodedKeyStorage::remove(const Key &key) +{ + (void) key; + return false; +} diff --git a/HardcodedKeyStorage.h b/HardcodedKeyStorage.h new file mode 100644 index 0000000..e677fc2 --- /dev/null +++ b/HardcodedKeyStorage.h @@ -0,0 +1,24 @@ +#ifndef HARDCODEDKEYSTORAGE_H +#define HARDCODEDKEYSTORAGE_H + +#include "IKeyStorage.h" + +class HardcodedKeyStorage: public IKeyStorage +{ +public: + HardcodedKeyStorage(); + + virtual ~HardcodedKeyStorage(); + virtual bool contains(const Key &inputKey); + virtual bool insert(const Key &existingKey, const Key &newKey); + virtual bool remove(const Key &key); + +private: + enum { + NUMBER_OF_ENTRIES = 1, + }; + + Key keyTable[NUMBER_OF_ENTRIES]; +}; + +#endif // HARDCODEDKEYSTORAGE_H diff --git a/IAuthenticator.h b/IAuthenticator.h new file mode 100644 index 0000000..4d4c56a --- /dev/null +++ b/IAuthenticator.h @@ -0,0 +1,14 @@ +#ifndef IAUTHENTICATOR_H +#define IAUTHENTICATOR_H + +#include "Key.h" + +class IAuthenticator +{ +public: + virtual ~IAuthenticator() {}; + + virtual Key getKey() = 0; +}; + +#endif // IAUTHENTICATOR_H diff --git a/IGpio.h b/IGpio.h new file mode 100644 index 0000000..a26990b --- /dev/null +++ b/IGpio.h @@ -0,0 +1,20 @@ +#ifndef IGPIO_H +#define IGPIO_H + +class IGpio +{ +public: + enum class Mode { + Input, + InputPullup, + Output, + }; + + virtual ~IGpio() {} + virtual bool read() = 0; + virtual void write(bool value) = 0; + virtual Mode getMode() = 0; + virtual void setMode(Mode mode) = 0; +}; + +#endif // IGPIO_H diff --git a/IKeyStorage.h b/IKeyStorage.h new file mode 100644 index 0000000..e488ffd --- /dev/null +++ b/IKeyStorage.h @@ -0,0 +1,16 @@ +#ifndef IKEYSTORAGE_H +#define IKEYSTORAGE_H + +#include "Key.h" + +class IKeyStorage +{ +public: + virtual ~IKeyStorage() {} + + virtual bool contains(const Key& key) = 0; + virtual bool insert(const Key& existingKey, const Key& newKey) = 0; + virtual bool remove(const Key& key) = 0; +}; + +#endif // IKEYSTORAGE_H diff --git a/ILogger.h b/ILogger.h new file mode 100644 index 0000000..ed7cbd0 --- /dev/null +++ b/ILogger.h @@ -0,0 +1,17 @@ +#ifndef ILOGGER_H +#define ILOGGER_H + +class ILogger +{ +public: + virtual ~ILogger() {} + + virtual void debug(const char *format, ...) const = 0; + virtual void info(const char *format, ...) const = 0; + virtual void warning(const char* format, ...) const = 0; + virtual void error(const char* format, ...) const = 0; + + virtual void flush() const; +}; + +#endif // ILOGGER_H diff --git a/Key.cpp b/Key.cpp new file mode 100644 index 0000000..463d616 --- /dev/null +++ b/Key.cpp @@ -0,0 +1,48 @@ +#include "Key.h" + +#include + +Key::Key() : + type(KeyType::Invalid), + size(0), + value() +{ +} + +Key::Key(KeyType type, size_t size, const void *data): + type(type), + size(size), + value() +{ + memcpy(value, data, size); +} + +bool Key::isValid() const +{ + return type != KeyType::Invalid; +} + +bool Key::operator==(const Key &rhs) const +{ + if(type == KeyType::Invalid) + return type == rhs.type; + + return type == rhs.type && size == rhs.size && isBufferEqual(value, rhs.value, size); +} + +bool Key::isBufferEqual(const unsigned char *lhs, const unsigned char *rhs, size_t size) const +{ + unsigned char maskOfDifferences = 0; + + /** This comparison by design checks whole buffers to harden timing attacks */ + for(size_t i = 0; i < size; ++i) + maskOfDifferences |= lhs[i] ^ rhs[i]; + + return (maskOfDifferences == 0); +} + +DallasIButtonKey::DallasIButtonKey(const uint8_t uid[]): + Key(KeyType::iButton, UID_SIZE, uid) {} + +MifareClassicKey::MifareClassicKey(const uint8_t nuid[]): + Key(KeyType::MifareClassic, NUID_SIZE, nuid) {} diff --git a/Key.h b/Key.h new file mode 100644 index 0000000..78f599b --- /dev/null +++ b/Key.h @@ -0,0 +1,48 @@ +#ifndef KEY_H +#define KEY_H + +#include +#include + +class Key { +public: + enum { + MAX_KEY_SIZE = 8 + }; + + enum class KeyType + { + Invalid = 0, + MifareClassic = 1, + iButton = 2, + }; + + Key(); + + bool isValid() const; + bool operator==(const Key& rhs) const; + +protected: + Key(KeyType type, size_t size, const void* data); + +private: + bool isBufferEqual(const unsigned char *lhs, const unsigned char* rhs, size_t size) const; + + KeyType type; + size_t size; + unsigned char value[MAX_KEY_SIZE]; +}; + +class DallasIButtonKey: public Key { +public: + enum {UID_SIZE = 8}; + explicit DallasIButtonKey(const uint8_t uid[UID_SIZE]); +}; + +class MifareClassicKey: public Key { +public: + enum {NUID_SIZE = 4}; + explicit MifareClassicKey(const uint8_t nuid[NUID_SIZE]); +}; + +#endif // KEY_H diff --git a/NfcAuthenticator.cpp b/NfcAuthenticator.cpp new file mode 100644 index 0000000..872edb7 --- /dev/null +++ b/NfcAuthenticator.cpp @@ -0,0 +1,54 @@ +#include "NfcAuthenticator.h" + +#include "ILogger.h" +#include + +NfcAuthenticator::NfcAuthenticator(int nfcSlaveSelectPin, int nfcResetPin, const ILogger &logger): + rfid(nfcSlaveSelectPin, nfcResetPin), + logger(logger) +{ + SPI.begin(); + rfid.PCD_Init(); +} + +NfcAuthenticator::~NfcAuthenticator() +{} + +Key NfcAuthenticator::getKey() +{ + if(!initializeCard()) + return Key(); + + uint8_t *nuid = rfid.uid.uidByte; + logger.debug("Mifare Classic tag detected, NUID: %02hhX %02hhX %02hhX %02hhX", nuid[0], nuid[1], nuid[2], nuid[3]); + + releaseCard(); + + return MifareClassicKey(rfid.uid.uidByte); +} + +bool NfcAuthenticator::initializeCard() +{ + if (!rfid.PICC_IsNewCardPresent()) + return false; + + if (!rfid.PICC_ReadCardSerial()) + return false; + + auto piccType = rfid.PICC_GetType(rfid.uid.sak); + if (piccType != MFRC522::PICC_TYPE_MIFARE_MINI && + piccType != MFRC522::PICC_TYPE_MIFARE_1K && + piccType != MFRC522::PICC_TYPE_MIFARE_4K) + { + logger.warning("Invalid Mifare tag type: %s", rfid.PICC_GetTypeName(piccType)); + return false; + } + + return true; +} + +void NfcAuthenticator::releaseCard() +{ + rfid.PICC_HaltA(); + rfid.PCD_StopCrypto1(); +} diff --git a/NfcAuthenticator.h b/NfcAuthenticator.h new file mode 100644 index 0000000..a0148d8 --- /dev/null +++ b/NfcAuthenticator.h @@ -0,0 +1,25 @@ +#ifndef NFCAUTHENTICATOR_H +#define NFCAUTHENTICATOR_H + +#include "IAuthenticator.h" + +#include + +class ILogger; + +class NfcAuthenticator: public IAuthenticator +{ +public: + NfcAuthenticator(int nfcSlaveSelectPin, int nfcResetPin, const ILogger& logger); + virtual ~NfcAuthenticator(); + virtual Key getKey(); + + +private: + bool initializeCard(); + void releaseCard(); + MFRC522 rfid; + const ILogger& logger; +}; + +#endif // NFCAUTHENTICATOR_H diff --git a/OneWireAuthenticator.cpp b/OneWireAuthenticator.cpp new file mode 100644 index 0000000..03acae2 --- /dev/null +++ b/OneWireAuthenticator.cpp @@ -0,0 +1,27 @@ +#include "OneWireAuthenticator.h" + +#include "ILogger.h" +#include + +OneWireAuthenticator::OneWireAuthenticator(int interfacePin, const ILogger& logger): + oneWire(interfacePin), + logger(logger) +{ + pinMode(interfacePin, INPUT_PULLUP); +} + +Key OneWireAuthenticator::getKey() +{ + uint8_t key[ONEWIRE_KEY_SIZE]; + + if (!oneWire.search(key)) { + oneWire.reset_search(); + return Key(); + } + + logger.debug("1-Wire device detected, S/N: %02hhX %02hhX %02hhX %02hhX %02hhX %02hhX %02hhX %02hhX", + key[0], key[1], key[2], key[3], key[4], key[5], key[6], key[7]); + + oneWire.reset(); + return DallasIButtonKey(key); +} diff --git a/OneWireAuthenticator.h b/OneWireAuthenticator.h new file mode 100644 index 0000000..e4fdc27 --- /dev/null +++ b/OneWireAuthenticator.h @@ -0,0 +1,24 @@ +#ifndef ONEWIREAUTHENTICATOR_H +#define ONEWIREAUTHENTICATOR_H + +#include "IAuthenticator.h" + +#include + +class ILogger; + +class OneWireAuthenticator: public IAuthenticator +{ +public: + OneWireAuthenticator(int interfacePin, const ILogger& logger); + virtual Key getKey(); +private: + enum { + ONEWIRE_KEY_SIZE = 8, + }; + + OneWire oneWire; + const ILogger& logger; +}; + +#endif // ONEWIREAUTHENTICATOR_H diff --git a/zamek_hswro.ino b/zamek_hswro.ino new file mode 100644 index 0000000..46cff27 --- /dev/null +++ b/zamek_hswro.ino @@ -0,0 +1,8 @@ +#include "DoorLockController.h" + +void setup() {} + +void loop() +{ + DoorLockController().run(); +}