Pull to refresh

Ввод пароля или похитители времени

Reading time5 min
Views52K


Не знаю, как вам, но мне в течении дня приходится часто отходить от рабочего места и блокировать мак. Чтобы не совершать несколько кликов мышкой, блокировку своего мака я «повесил» на клавиши «shift + cmd + l», но по приходу к рабочему месту опять же приходилось вводить пароль (который в силу моей параноидальности не так-то прост). И вот, ошибившись в спешке в очередной раз при его вводе, задумался автоматизировать процесс блокировки/разблокировки. Так как все двери нашего офиса открываются по карте, решил повесить на RFID-метку (всё равно всё время болтается на шее) и эту функцию. Итак, задача на словах выглядела так: авторизовавшись единожды в начале рабочего дня иметь возможность блокировки/разблокировки мака по RFID-метке, при этом все функции проверки валидности метки и т.п. должны происходить на стороне мака.

Начало — уже половина дела, да и как раз под рукой освободился стенд на базе Arduino UNO.

В процессе работы решил дополнить функционал: считывание метки будет происходить только при нажатой кнопке жёлтого цвета на фото выше (уж не знаю, зачем такие усложнения — видимо, опять параноя сказывается). Итак, общий процесс должен будет выглядеть следующим образом:

  • Функциональная часть вся будет на стороне мака, а Arduino будет только передавать код метки и «мигать светодиодами»;
  • Зажимаем кнопку — загорается жёлтый светодиод готовности;
  • Если прикладываем некорректную метку — загорается красный светодиод;
  • Прикладываем правильную метку — загорается зелёный светодиод и происходит блокировка/разблокировка мака.

Докупив модуль RFID на 125 кГц, собрал на макетной плате прототип устройства.

Скетч и код для Arduino


#include <SoftwareSerial.h>

// "Распиновка"
int buttonPin = 2;
int ledGreenPin = 13;
int ledYellowPin = 12;
int ledRedPin = 11;

// Модуль RFID и переменны для него
SoftwareSerial RFID(6, 7);

String inputString = "";
int rfidData;
String rfidNumber = "";
String rfidNumberLast = "";
boolean startPressButton = false;

void setup() {
  Serial.begin(115200);
  RFID.begin(9600);

  pinMode(buttonPin, INPUT);

  pinMode(ledGreenPin, OUTPUT);
  pinMode(ledYellowPin, OUTPUT);
  pinMode(ledRedPin, OUTPUT);

  digitalWrite(ledGreenPin, LOW);
  digitalWrite(ledYellowPin, LOW);
  digitalWrite(ledRedPin, LOW);
}

void loop() {
  listenButton();
}

/* Слушаем кнопку. Если нажата - слушаем RFID */
void listenButton() {
  if (digitalRead(buttonPin) == HIGH) {
    if (!startPressButton) {
      startPressButton = true;
      clearRFID();
    }
    digitalWrite(ledYellowPin, HIGH);
    listenRFID();
  } else {
    startPressButton = false;
    digitalWrite(ledYellowPin, LOW);
  }
}

/* Слушаем RFID. Если получен номер метки - кидаем его в поток */
void listenRFID() {
  if (RFID.available()) {
    delay(100);
    rfidNumber = "";
    for (int i = 0; i < 14; i++) {
      rfidData = RFID.read();
      if (rfidData < 16) rfidNumber += '0';
      rfidNumber += rfidData;
    }
    RFID.flush();
    sendRDIFNumber();
  }
}

/* Кидаем номер метки в поток */
void sendRDIFNumber() {
  if (rfidNumber != "" and rfidNumberLast != rfidNumber) {
    Serial.print("S");
    Serial.print(rfidNumber);
    Serial.print("E");
    rfidNumberLast = rfidNumber;
    rfidNumber = "";
  }
}

/* Слушаем поток на предмет комманд для Arduino */
void serialEvent() {
  while (Serial.available()) {
    char inChar = (char)Serial.read();
    inputString += inChar;
    if (inputString == "M1F") {
      Serial.flush();
      inputString = "";
      logInOutProcess();
    }
    if (inputString == "M0F") {
      Serial.flush();
      inputString = "";
      logInOutFail();
    }
  }
}

/* Проверка на стороне мака прошла успешно - зелёный цвет */
void logInOutProcess() {
  clearRFID();
  digitalWrite(ledGreenPin, HIGH);
  digitalWrite(ledYellowPin, LOW);
  digitalWrite(ledRedPin, LOW);
  delay(1000);
  digitalWrite(ledGreenPin, LOW);
  digitalWrite(ledYellowPin, LOW);
  digitalWrite(ledRedPin, LOW);
}

/* Проверка на стороне мака не прошла - красный цвет */
void logInOutFail() {
  clearRFID();
  digitalWrite(ledGreenPin, LOW);
  digitalWrite(ledYellowPin, LOW);
  digitalWrite(ledRedPin, HIGH);
  delay(1000);
  digitalWrite(ledGreenPin, LOW);
  digitalWrite(ledYellowPin, LOW);
  digitalWrite(ledRedPin, LOW);
}

/* Чистка выдачи RFID-модуля */
void clearRFID() {
  RFID.flush();
  rfidNumberLast = "";
  rfidNumber = "";
}


Самое интересное, на мой взгляд, происходит не на стороне Adruino, а на стороне мака. Итак, общаться со стендом будет Node.js с модулем SerialPort. Но для начала хотелось бы решить вопрос с хранением пароля разблокировки (очень уж не хотелось держать его открытым в теле скрипта, хоть и FileVault по-умолчанию включён). Для этого решил воспользоваться стандартной «ключницей» OS X — Keychain Access.

Как добавить пароль в ключницу?
Вызываем Keychain Access (Spotlight Search Вам в помощь)


Добавляем новый пароль...


В поле Account Name прописываем адекватное имя — позже к нему будем обращаться из скрипта

Не забываем получить доступ к ключу:
security find-generic-password -ga my password


image
Подтверждаем доступ к ключу для консольной программы security

Ну вот, можно приступить к самому скрипту на Node.js. Для этого на рабочем столе создаём папку «RFIDUnLock», сам скрипт будет именоваться как «rfid.js»:

var inputString = "";
var serialport = require('serialport');
var SerialPort = serialport.SerialPort;
var sp = new SerialPort('/dev/tty.usbmodem20331', { // подсмотреть "путь" девайса можно в "Tools/Serial Port" программы Arduino
	baudrate: 115200
});
var exec = require('child_process').exec;
sp.on('open', function() {
	/* Читаем поток */
	sp.on('data', function(data) {
		inputString += data.toString("utf8");
		/* Берём из потока нужные данные по "маркерам" */
		var cardCode = inputString.match(/S([0-9]+)E/i);
		if (cardCode && cardCode[1] != 'undefined') {
			checkCardNumber(cardCode[1]);
			inputString = '';
		}
	});
});
function checkCardNumber(cardCode) {
	sp.flush(function() {
		/* Если метка та, что нужно... */
		if (cardCode == '0211111111111111111111111103') {
			/* ...отправляем команду Arduino "мигнуть зелёным" */
			sp.write('M1F');
			/* проверяем: запущен ли "скрин сейвер"? */
			exec('ps aux | grep -c ScreenSaverEngine.app | grep -v grep', function (error, stdout, stderr) {
				/* если запущен - берём пароль из Kaychain и "печатаем" в поле ввода пароля */
				if (parseInt(stdout) > 2) {
					exec("security 2>&1 >/dev/null find-generic-password -ga mypassword | ruby -e 'print $1 if STDIN.gets =~ /^password: \"(.*)\"$/'", function (error, stdout, stderr) {
					if (error !== null) return;
						var appleScript = 'osascript -e \'tell application "System Events"\' -e \'key code 56\' -e \'delay 0.5\' -e \'keystroke "' + stdout + '"\' -e \'key code 36\' -e \'end tell\'';
						exec(appleScript);
					});
				/* ...если "скрин север" не запущен - запускаем */
				} else {
					exec('open -a /System/Library/Frameworks/ScreenSaver.framework/Versions/Current/Resources/ScreenSaverEngine.app');
				}
			});
		/* Метка не корректна - отправляем команду Arduino "мигнуть красным" */
		} else {
			sp.write('M0F');
		}
	});
}


Далее сохраняем как программу (с помощью Script Editor) код вызова Node.js скрипта:

do shell script "/usr/local/bin/node ~/Desktop/RFIDUnLock/rfid.js"

Подробнее, если можно...
Вызываем Script Editor (Spotlight Search Вам в помощь)


Прописываем код...


Экспортируем...


Сохраняем как Application

Можно так же добавить ключ, сообщающий о запуске программы в background-режиме. Для этого в файле «info.plist» (доступен при просмоте содержимого папки программы: ctrl + click на файле и выбор «Show Package Contents») необходимо дописать перед закрывающими тегами "</dict></plist>":

<key>LSBackgroundOnly</key>
</true>


...И добавляем запуск нашей программы при загрузке системы
image

P.S.: Поигравшись пару дней с прототипом, решил, что он «имеет право жить» — осталось спаять его в корпус, с использованием меньшего брата Arduino Nano.

N.B.: В XXI веке будет много завещаний, содержащих пароли.
Tags:
Hubs:
+43
Comments51

Articles

Change theme settings